Core changes: - Add @defgroup/@ingroup to 1791 C2+ contracts (555 files) for HCA 128× pre-training DSA grouping - Add §0.1 Pre-Training Frequency matrix to semantics-core - Add §VIII Attention Architecture rules (ATTN_1-4) with MLA/CSA/HCA/DSA mechanics - Add @defgroup/@ingroup to canonical syntax (§II) and all contract examples Agent prompts (5 files): - Add ZERO-STATE RATIONALE with MLA/CSA/HCA/DSA compression mechanics - Add pre-training note: @RATIONALE/@REJECTED are in-context learned tags - svelte-coder: add missing #region contract, fix Svelte rule violations - python-coder/fullstack-coder: honor function contracts from speckit plan - qa-tester: add attention compliance audit (P3 ATTN_1-4 checks) Skills (6 files): - Translate all axiom_config descriptions to English - Fix doc_dirs to index .opencode/ and .specify/ - Deduplicate 5× complexity_rules → single global_tags catalog - Reduce semantics-svelte 591→485 lines (remove duplicate code blocks) - Fix semantics-testing: 'Short IDs' → 'Short hierarchical IDs' - Fix all examples: flat IDs → hierarchical Domain.Name format - Fix Svelte examples: replace raw Tailwind + <button> with semantic tokens + /ui Speckit workflow (commands + templates): - speckit.plan: add Function-Level Contracts for C3+ with @PRE/@POST/@TEST_EDGE - speckit.plan: add Attention Compliance Gate (ATTN_1-4 before contract generation) - speckit.tasks: add function contract inlining format (constraints in task description) - speckit.specify: load semantics-core for spec density rules - spec-template: add #region contract, @SEMANTICS grouping, hierarchical IDs - ux-reference-template: add #region wrapper - plan-template: add attention gate, @defgroup/@ingroup guidance - tasks-template: add attention audit + rebuild + orphan check tasks - constitution.md: translate to English, add Principle VIII (attention-optimized contracts) Reference modules rewritten (hierarchical IDs + full contracts): - Auth.Jwt: 6 child contracts with @RATIONALE/@REJECTED/@TEST_EDGE - Api.Auth: 5 endpoints with @TEST_EDGE + molecular CoT markers - Migration.Model: @defgroup Migration with 18 @ACTION + 6 @INVARIANT Scripts: - add_defgroup_ingroup.py: zero-risk additive @ingroup migration (1791 insertions) - migrate_hierarchical.py: flat→hierarchical ID dry-run analysis (792 contracts) - merge_prompts.py: merge all prompts/skills/commands into one review file Config: - axiom_config.yaml: 749→395 lines (-47%), English, doc_dirs include prompts - Fix test_datasets.py import collision (rename → test_datasets_routes.py) - Fix test_preview.py: SupersetClient→get_superset_client, AsyncMock, logger f-string
217 lines
12 KiB
Python
217 lines
12 KiB
Python
# #region AssistantDispatch [C:4] [TYPE Module] [SEMANTICS assistant, dispatch, confirm, execution, orchestration]
|
||
# @defgroup AssistantApi Module group.
|
||
# @BRIEF Intent dispatch engine and backward-compat wrapper around the central tool registry.
|
||
# @LAYER API
|
||
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
|
||
# @RELATION DEPENDS_ON -> [AssistantSchemas]
|
||
# @INVARIANT Unsupported operations are rejected via HTTPException(400).
|
||
# @INVARIANT All tool handlers live in _tool_registry — _dispatch is a thin wrapper.
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from fastapi import HTTPException
|
||
from sqlalchemy.orm import Session
|
||
|
||
from src.core.config_manager import ConfigManager
|
||
from src.core.logger import belief_scope, logger
|
||
from src.core.task_manager import TaskManager
|
||
from src.schemas.auth import User
|
||
from src.services.git_service import GitService
|
||
|
||
from ._history import _coerce_query_bool
|
||
from ._resolvers import (
|
||
_get_environment_name_by_id,
|
||
_resolve_dashboard_id_entity,
|
||
_resolve_env_id,
|
||
)
|
||
from ._schemas import AssistantAction
|
||
from ._tool_registry import dispatch as _registry_dispatch
|
||
|
||
# #region _get_git_service [TYPE Function]
|
||
# @BRIEF Lazy-init GitService singleton to avoid crash at module import time when /app/storage/ is unavailable.
|
||
# @POST Returns GitService instance (created once, cached).
|
||
# @SIDE_EFFECT May attempt to create /app/storage/repositories on first call.
|
||
_git_service_instance: GitService | None = None
|
||
|
||
|
||
def _get_git_service() -> GitService:
|
||
global _git_service_instance
|
||
if _git_service_instance is None:
|
||
_git_service_instance = GitService()
|
||
return _git_service_instance
|
||
|
||
|
||
# #endregion _get_git_service
|
||
|
||
|
||
# #region _clarification_text_for_intent [C:2] [TYPE Function]
|
||
# @BRIEF Convert technical missing-parameter errors into user-facing clarification prompts.
|
||
# @PRE state was classified as needs_clarification for current intent/error combination.
|
||
# @POST Returned text is human-readable and actionable for target operation.
|
||
def _clarification_text_for_intent(
|
||
intent: dict[str, Any] | None, detail_text: str
|
||
) -> str:
|
||
operation = (intent or {}).get("operation")
|
||
guidance_by_operation: dict[str, str] = {
|
||
"run_llm_validation": (
|
||
"Нужно уточнение для запуска LLM-валидации: Укажите дашборд (id или slug), окружение и провайдер LLM."
|
||
),
|
||
"run_llm_documentation": (
|
||
"Нужно уточнение для генерации документации: Укажите dataset_id, окружение и провайдер LLM."
|
||
),
|
||
"create_branch": "Нужно уточнение: укажите дашборд (id/slug/title) и имя ветки.",
|
||
"commit_changes": "Нужно уточнение: укажите дашборд (id/slug/title) для коммита.",
|
||
"deploy_dashboard": "Нужно уточнение: укажите дашборд (id/slug/title) и целевое окружение.",
|
||
"execute_migration": "Нужно уточнение: укажите дашборд (id/slug/title), source_env и target_env.",
|
||
"run_backup": "Нужно уточнение: укажите окружение и при необходимости дашборд (id/slug/title).",
|
||
}
|
||
return guidance_by_operation.get(operation, detail_text)
|
||
|
||
|
||
# #endregion _clarification_text_for_intent
|
||
|
||
|
||
# #region _async_confirmation_summary [C:4] [TYPE Function]
|
||
# @BRIEF Build human-readable confirmation prompt for an intent before execution.
|
||
# @PRE actions is a non-empty list of planned review actions.
|
||
# @POST Returns a formatted summary string suitable for display to the user.
|
||
# @SIDE_EFFECT None - pure formatting function.
|
||
async def _async_confirmation_summary(intent: dict[str, Any], config_manager: ConfigManager, db: Session) -> str:
|
||
with belief_scope('_confirmation_summary'):
|
||
logger.reason('Belief protocol reasoning checkpoint for _confirmation_summary')
|
||
operation = intent.get('operation', '')
|
||
entities = intent.get('entities', {})
|
||
descriptions: dict[str, tuple[str, str]] = {
|
||
'create_branch': (
|
||
'📝 Создание ветки в Git',
|
||
'Будет создана ветка **{branch}** для дашборда **{dashboard}**. '
|
||
'Изменения можно будет коммитить в эту ветку.'
|
||
),
|
||
'commit_changes': (
|
||
'📝 Коммит изменений в Git',
|
||
'Изменения дашборда **{dashboard}** будут закоммичены в Git-репозиторий. '
|
||
'После коммита их можно будет развернуть на других окружениях.'
|
||
),
|
||
'deploy_dashboard': (
|
||
'🚀 Деплой дашборда',
|
||
'Дашборд **{dashboard}** будет развёрнут в окружение **{env}**. '
|
||
'Изменения станут доступны на целевом окружении.'
|
||
),
|
||
'execute_migration': (
|
||
'🔄 Миграция дашборда',
|
||
'Дашборд **{dashboard}** будет перенесён с **{src}** на **{tgt}**.'
|
||
),
|
||
'run_backup': (
|
||
'💾 Бэкап',
|
||
'Будет запущено резервное копирование окружения **{env}**{dashboard}.'
|
||
),
|
||
'run_llm_validation': (
|
||
'🤖 LLM-валидация дашборда',
|
||
'Будет запущена LLM-валидация дашборда **{dashboard}**{env}. '
|
||
'Это займёт некоторое время.'
|
||
),
|
||
'run_llm_documentation': (
|
||
'📄 Генерация документации',
|
||
'Будет сгенерирована документация для датасета **{dataset}**{env}.'
|
||
),
|
||
'start_maintenance': (
|
||
'🛠 Обслуживание',
|
||
'Будет запущено окно обслуживания для таблиц **{tables}**.'
|
||
),
|
||
'end_maintenance': (
|
||
'✅ Завершение обслуживания',
|
||
'Обслуживание будет завершено{event}.'
|
||
),
|
||
}
|
||
entry = descriptions.get(operation)
|
||
if not entry:
|
||
logger.reflect('Belief protocol postcondition checkpoint for _confirmation_summary')
|
||
return 'Подтвердите выполнение операции или отмените.'
|
||
|
||
title, desc = entry
|
||
|
||
def _label(value: Any, prefix: str=' ') -> str:
|
||
logger.reflect('Belief protocol postcondition checkpoint for _confirmation_summary')
|
||
return f'{prefix}{value}' if value else ''
|
||
dashboard = entities.get('dashboard_id') or entities.get('dashboard_ref')
|
||
env_val = entities.get('environment') or entities.get('target_env')
|
||
dataset_val = entities.get('dataset_id')
|
||
event_val = entities.get('event_id')
|
||
tables_val = entities.get('tables')
|
||
tables_str = ', '.join(tables_val[:5]) if isinstance(tables_val, list) else str(tables_val or '')
|
||
text = desc.format(
|
||
branch=_label(entities.get('branch_name')),
|
||
dashboard=_label(dashboard),
|
||
env=_label(env_val),
|
||
src=_label(entities.get('source_env')),
|
||
tgt=_label(entities.get('target_env')),
|
||
dataset=_label(dataset_val),
|
||
tables=tables_str if tables_str else '?',
|
||
event=_label(event_val),
|
||
)
|
||
text = f'**{title}**\n\n{text}'
|
||
if operation == 'execute_migration':
|
||
flags = []
|
||
flags.append('маппинг БД: ' + ('ВКЛ' if _coerce_query_bool(entities.get('replace_db_config', False)) else 'ВЫКЛ'))
|
||
flags.append('исправление кроссфильтров: ' + ('ВКЛ' if _coerce_query_bool(entities.get('fix_cross_filters', True)) else 'ВЫКЛ'))
|
||
dry_run_enabled = _coerce_query_bool(entities.get('dry_run', False))
|
||
flags.append('отчет dry-run: ' + ('ВКЛ' if dry_run_enabled else 'ВЫКЛ'))
|
||
text += f" ({', '.join(flags)})"
|
||
if dry_run_enabled:
|
||
try:
|
||
from src.core.migration.dry_run_orchestrator import MigrationDryRunService
|
||
from src.core.async_superset_client import AsyncSupersetClient
|
||
from src.models.dashboard import DashboardSelection
|
||
src_token = entities.get('source_env')
|
||
tgt_token = entities.get('target_env')
|
||
dashboard_id = await _resolve_dashboard_id_entity(entities, config_manager, env_hint=src_token)
|
||
if dashboard_id and src_token and tgt_token:
|
||
src_env_id = _resolve_env_id(src_token, config_manager)
|
||
tgt_env_id = _resolve_env_id(tgt_token, config_manager)
|
||
if src_env_id and tgt_env_id:
|
||
env_map = {env.id: env for env in config_manager.get_environments()}
|
||
source_env = env_map.get(src_env_id)
|
||
target_env = env_map.get(tgt_env_id)
|
||
if source_env and target_env and (source_env.id != target_env.id):
|
||
selection = DashboardSelection(source_env_id=source_env.id, target_env_id=target_env.id, selected_ids=[dashboard_id], replace_db_config=_coerce_query_bool(entities.get('replace_db_config', False)), fix_cross_filters=_coerce_query_bool(entities.get('fix_cross_filters', True)))
|
||
service = MigrationDryRunService()
|
||
source_client = AsyncSupersetClient(source_env)
|
||
target_client = AsyncSupersetClient(target_env)
|
||
report = service.run(selection, source_client, target_client, db)
|
||
s = report.get('summary', {})
|
||
dash_s = s.get('dashboards', {})
|
||
charts_s = s.get('charts', {})
|
||
ds_s = s.get('datasets', {})
|
||
creates = dash_s.get('create', 0) + charts_s.get('create', 0) + ds_s.get('create', 0)
|
||
updates = dash_s.get('update', 0) + charts_s.get('update', 0) + ds_s.get('update', 0)
|
||
deletes = dash_s.get('delete', 0) + charts_s.get('delete', 0) + ds_s.get('delete', 0)
|
||
text += f'\n\nОтчет dry-run:\n- Будет создано новых объектов: {creates}\n- Будет обновлено: {updates}\n- Будет удалено: {deletes}'
|
||
else:
|
||
text += '\n\n(Не удалось загрузить отчет dry-run: неверные окружения).'
|
||
except Exception as e:
|
||
import traceback
|
||
logger.warning('[assistant.dry_run_summary][failed] Exception: %s\n%s', e, traceback.format_exc())
|
||
text += f'\n\n(Не удалось загрузить отчет dry-run: {e}).'
|
||
logger.reflect('Belief protocol postcondition checkpoint for _confirmation_summary')
|
||
return f'Выполнить: {text}. Подтвердите или отмените.'
|
||
|
||
|
||
# #endregion _async_confirmation_summary
|
||
|
||
|
||
# #region _dispatch_intent [C:1] [TYPE Function]
|
||
# @BRIEF Backward-compat wrapper around the central tool registry dispatch.
|
||
# @DEPRECATED Use `dispatch()` from `_tool_registry` directly.
|
||
async def _dispatch_intent(intent: dict[str, Any], current_user: User, task_manager: TaskManager, config_manager: ConfigManager, db: Session) -> tuple[str, str | None, list[AssistantAction]]:
|
||
"""Backward-compat wrapper around the central tool registry dispatch."""
|
||
operation = intent.get('operation')
|
||
return await _registry_dispatch(operation, intent, current_user, task_manager, config_manager, db)
|
||
|
||
|
||
# #endregion _dispatch_intent
|
||
|
||
|
||
# #endregion AssistantDispatch
|