sdlc-hub/ARCHITECTURE.md
2026-10-23 23:33:17 +04:00

21 KiB
Raw Blame History

Архитектура

Хаб моста Jira: ядро + модули; сейчас один модуль — jira: SQLite-зеркало задач проектов-меток Jira-проекта AURORA, служебные заметки, очередь команд, HTTP API и MCP. Агент (jira-agent, виртуалка) опрашивает хаб, пишет в Jira и возвращает состояние; хаб Jira не вызывает.

Ядро и модули

Хаб — ядро (src/jira_hub/core/) и подключаемые модули (src/jira_hub/modules/<имя>/). Ядро держит механику: базу, очередь команд, HTTP, настройки, реле, выпуск агента, каркас MCP и CLI. Модуль даёт смысл: схему своих таблиц, типы команд, маршруты, настройки, MCP-инструменты, подкоманды. Сейчас модуль один — jira; следующие (wiki, sdlc) подключаются так же, строкой в core/registry.py.

Ядро (core/)

Модуль Ответственность Зависит от
paths каталог данных (JIRA_HUB_HOME), пути базы и backups/, права 0700 (secure_dir) stdlib
errors ошибки, общие для ядра и модулей (CommandError, HttpMapped) stdlib
http общее для обработчиков HTTP: ApiError, Raw, разбор параметров, mapped, not_found errors, projects
modules контракт модуля: Module и CommandHooks (только структуры) stdlib
registry реестр модулей: ленивая сборка, modules(), module(name), module_of(тип_команды), проверка конфликтов (типы команд, маршруты, ключи настроек) modules; модули — ленивым импортом внутри функции
db connect, схема ядра (setting, batch, command, project, agent_status) и схемы модулей из Module.schema, миграции по schema_version (v6; копия базы перед миграцией; история шагов MIGRATIONS) backup, paths; модули — только внутри шагов истории миграций (ленивый импорт)
settings настройки (setting), токены, умолчания модулей (ensure_defaults) registry (база — через conn вызывающего)
projects проекты-метки: таблица project, проверка метки, создание, список (долг: board_id, board_type, issues_count — см. «Границы») db
queue механика очереди: пакеты, выдача (claim), отчёты, отмена, повтор, временные ключи new:<n>; смысл типов команд берёт у модуля через CommandHooks (registry.module_of) backup, db, registry, settings, errors
backup копии файла базы, ротация paths
files хранилище файлов <данные>/files/<sha256> (каталог — параметр, Hub.files_dir): put, touch, sweep, sweep_downloads, path/exists/size/read paths, registry
api HTTP-сервер, авторизация по токенам, фоновый tick, Hub; маршруты ядра CORE_ROUTES (/agent/*, /files, /projects, /settings, раздача /app…) и сводный ROUTES = ядро + маршруты модулей; уборка files/ раз в сутки queue, projects, settings, backup, db, files, relay, release, registry, http, errors, paths
relay реле общей папки (relay_dir): файлы req/ → свой HTTP → resp/; агент с hub_url = folder:<путь> ходит через него settings
release опубликованная версия агента release/agent.py + manifest.json: publish (только вверх), current, load, hint paths
client HTTP-клиент хаба (HubClient, from_home, get_bytes) db, paths, settings (только from_home)
mcp каркас MCP: охрана («хаб выключен» — подсказка), привязка сессии к проекту (PROJECT), общие инструменты projects и project_create, register_core_tools, запуск сервера модуля по stdio client, registry
cli init, serve, mcp [модуль] [--project], tokens, agent publish и подкоманды модулей (Module.cli_register/cli_run) api, mcp, registry, release, settings, db, paths

Модуль jira (modules/jira/)

Точка входа — modules/jira/__init__.py (MODULE: Module).

Модуль Ответственность Зависит от
schema таблицы модуля: issue, issue_link, issue_comment, issue_attachment, attachment_file, issue_change, issue_note, sync_run и др. stdlib
settings умолчания настроек модуля, ключи для агента, проверка значений, темп (pacing), перенос из config.example.json (init_from_config) core.settings, core.projects, projects
mirror зеркало задач, связей, комментариев и вложений; пишет его только apply_state (с проверками check_comments, check_attachments), разрез по проекту, заметки, фильтры, снимок доски board, карточка get_issue core.db, history, attachments
history журнал изменений issue_change: время Jira → UTC (jira_utc), проверка и запись пачек агента, delivery_param для выдачи refresh, лента feed, история задачи core.db, core.settings, projects
attachments ссылки на вложения в вики-разметке, fmt_size, состояние ready/fetching/failed/none core.files
fetch постановка скачивания вложения (request_fetch, TooLarge, AgentTooOld) core.db, core.queue, attachments, commands
commands контракт типов команд jira (ensure_*, refresh, fetch_attachment), валидация, временные ключи, оценка, подтверждение по состоянию mirror
refresh журнал синхронизаций sync_run, полный прогон, автообновление core.db, core.queue, core.settings, projects
projects базовая JQL из меток (base_jql), доски (boards), миграция v3 → v4 (migrate_v3), проект по умолчанию core.projects, core.settings
hooks CommandHooks для очереди ядра: проверки постановки (bug без настройки, файл вложения, проект пакета, board_id у ensure_sprint), паузы, deliver_params (size, boards, history), отчёты (attachment_file, sync_run) core.queue, core.files, core.projects, core.settings, settings, projects, commands, mirror, history, refresh, attachments, fetch
api маршруты модуля MODULE.routes: /issues, /board, /changes, /attachments, /refresh, /sync-runs и др. core.db, core.files, core.queue, core.settings, core.http, core.errors, attachments, projects, mirror, history, fetch, refresh
mcp инструменты MCP: issues, issue, batch_create, batch_*, comment, attachment, changes, refresh, settings_* и др.; общие projects/project_create подключает register_core_tools core.mcp, core.client, commands, attachments
importer, cli разовый импорт CSV-пакета в заметки (jira-hub import FOLDER) mirror, core.db, core.settings

Контракт модуля

core/modules.py задаёт две структуры; ядро знает только их.

  • Module(name, schema, settings_defaults, agent_keys, validate_setting, settings_view, on_init, commands, routes, tick, file_refs, on_project_created, mcp_server, cli_register, cli_run) — всё необязательное, кроме name.
  • CommandHooks — что модуль знает о своих типах команд: types, read_types (не пишут во внешнюю систему: без хвоста и копии базы), validate, check_batch, tail, pause, deliver_params, temp_definer, temp_keys_used, substitute, drop_unresolved, retry_params, check_confirmed, on_claim, on_failed, check_result, on_result, after_result, on_late_result. Очередь вызывает их по типу команды через registry.module_of.

registry.check при сборке падает на конфликте: один тип команды у двух модулей, пересечение маршрутов или ключей настроек. Уникальность имён модулей проверяется только в вебе (web/src/modules/modules.test.tsx); в Python реестр её не проверяет.

Веб (web/)

Веб-приложение хаба — React + TypeScript + Vite. Оболочка (web/src/shell/: Layout.tsx, Sidebar.tsx, pages.tsx, тема), общие части (web/src/ui/ — компоненты дизайн-системы AURA, web/src/styles/tokens.css — токены и единственное место с литералами цвета, web/src/api/ — клиент HTTP, web/src/auth/, web/src/projects/ — проект в адресе ?project=<метка>, выпадающий список, окно «Новый проект») и модули web/src/modules/<имя>/. Контракт веб-модуля — HubModule в web/src/modules/types.ts (pages, nav, Provider, SidebarStatus, TopbarNotice, useOverlay, useOnProjectCreated); список модулей — константа MODULES в web/src/modules/index.ts (хуки вызываются в постоянном порядке).

Модуль jira (web/src/modules/jira/index.tsx): страницы «Доска связей» (/app/board), «Задачи» (/app/issues), «Лента» (/app/feed), карточка /app/issues/{key} окном поверх страницы (issues/overlay.tsx, IssueModal.tsx; оболочка рисует страницу по location.state.background). Внутри: board/ (чистые model, analysis, layout, storage.ts — ключи localStorage прежней страницы, у проекта AWPCA прежние, у остальных с суффиксом @<метка>; отрисовка components/; golden-эталоны __fixtures__/), issues/ (список, карточка, wiki/ — разбор вики-разметки, attachments/ — список, плашки и скачивание: POST /attachments/{id}/fetch, опрос GET /attachments/{id}, затем api.blob на /content), feed/ (период, виды, группировка событий, EventList, общий с блоком «История» карточки), refresh/ (кнопка и плашка обновления данных из Jira в боковой панели, отслеживание пакета: RefreshProvider, useJiraRefresh), api/queries.ts.

Собирается make web в src/jira_hub/web/app/; core/api отдаёт сборку без токена (/app/assets/* — файлы, прочие /app… — index.html, 503 без сборки), / и /links перенаправляет на /app/board. Данные — только через HTTP API с клиентским токеном; mirror.board отдаёт зеркало без преобразований, перевод в модель доски — в board/model.

Цепочки зависимостей (Python):

  • ядро: api → queue → settings/projects/files/registry → db → backup → paths; api → relay → settings; api → release → paths; files → paths; cli → api/mcp/registry; mcp → client → HTTP API; registry → (лениво) modules.*;
  • модуль jira: hooks → commands/mirror/history/refresh/attachments/fetch → core.*; mirror → history → projects; mirror → attachments → core.files; api → mirror/history/fetch/refresh → core.http/core.queue; importer → mirror;
  • веб: shell → modules/index → modules/jira → ui/api/projects (модуль не импортирует оболочку, кроме типов контракта).

Потоки данных

  • Запись в Jira: клиент (MCP/интерфейс) → POST /batches → queue → агент claim → Jira → POST /agent/commands/{id}/result.
  • Вложение: файл → POST /files → files/<sha256> → ensure_attachment {key, file, filename} в пакете (modules/jira/hooks.py (check_batch) проверяет, что файл есть) → claim (хук deliver_params дописывает size, в базе params без него) → агент GET /agent/files/{sha256} → Jira; уборка files/ — из tick раз в сутки.
  • Создание проекта: POST /projects (веб или project_create) → core.projects.create и пакет модуля (Module.on_project_created: «Новый проект <метка>» refresh по общей JQL, batch.project = NULL) в одной транзакции → claim (хаб дописывает в refresh доски проектов, boards) → агент определяет тип доски → отчёт refresh пишет board_type → ensure_sprint проекта получает board_id.
  • Чтение Jira: команда refresh → агент → POST /agent/state → mirror.apply_state.
  • Скачивание вложения: вход — POST /attachments/{id}/fetch (из веб-приложения и из MCP attachment); modules/jira/fetch.request_fetch (одна транзакция: состояние attachments.state + пакет fetch_attachment {key, attachment_id}) → claim (без копии базы) → агент → POST /agent/files → результат {file, size} → report_result проверяет файл в files/ и пишет attachment_file; нет файла или чужой размер — команда failed. Выход — GET /attachments/{id}/content: в вебе Blob и сохранение браузером, в MCP копия downloads/<id>/<имя>.
  • Журнал изменений: выдача refresh (CommandHooks.deliver_params модуля дописывает history — since и/или стартовую jql) → агент v13 после состояния читает changelog Jira → POST /agent/history → history.record_changes (повторы отбрасываются по (history_id, seq)) → отчёт refresh с result.history → sync_run.history_at у полного прогона (новая отметка) → GET /changes, GET /issues/{key}.changes, MCP changes.
  • Агент за VPN: агент → файлы в общей папке (relay_dir) → relay → HTTP хаба → ответ файлом.
  • Самообновление агента: jira-hub agent publish → release/ → claim.agent_release → агент скачивает GET /agent/release; состояние обновления агент шлёт в claim, хаб держит в памяти (вместе с признаком agent_reported — был ли claim с запуска хаба) и отдаёт в GET /agent.
  • Токены: агентский — только /agent/*, клиентский — остальное; MCP берёт клиентский токен из базы через HubClient.from_home.

Границы

  • Ядро (core/) не импортирует модули (jira_hub.modules.*): исключения — core/registry.py (ленивый импорт внутри функции) и шаги истории миграций в core/db.py (ленивый импорт внутри шага). Проверяет tests/test_registry.py::test_core_imports.
  • Типы команд (ensure_*, refresh, fetch_attachment и др.) и термины jira (sync_run, issue_attachment, attachment_file, issue_comment, jql) живут только в модулях; ядро знает их лишь через CommandHooks. Проверяет test_core_has_no_jira_terms (исключения — core/db.py, история миграций, и строки с меткой долг hub-modules).
  • Новые имена в ядре — без «jira». Модуль не вызывается из ядра иначе как через registry и контракт Module.
  • Долг проектов (change wiki): проект-метка пока устроен под jira — board_id, board_type и проверка board_id в core/projects.py, issues_count в списке проектов и projects.create(..., board_id) в HTTP-слое (core/api.py: h_projects_get, h_projects_post), project_create.board_id и fmt_projects в core/mcp.py, поле доски в web/src/projects/NewProjectDialog.tsx. Места помечены комментарием долг hub-modules: проекты → модуль jira (change wiki); убирать их можно только вместе с переносом проектов в модуль.
  • Зеркало (включая issue_attachment) пишет только mirror.apply_state по запросу агента (и заметки через PUT /issues/{key}/note); из очереди и API задач напрямую не правят.
  • Журнал issue_change пишет только history.record_changes по POST /agent/history; API и MCP его только читают.
  • Хаб не знает пароль Jira и не обращается к Jira; init не переносит auth, base_url, verify_ssl, ca_bundle.
  • queue не создаёт команд сам: повтор после failed — новый пакет. Выданная без отчёта команда довыдаётся при следующем claim (до 3 выдач, затем failed).
  • MCP (core/mcp, modules/jira/mcp) не импортирует queue/db/projects и модульные mirror/history напрямую, ходит только через client; из core.files берёт только предел MAX_SIZE, из attachments — только fmt_size; downloads/ пишет только он (хаб эту папку лишь убирает).
  • commands, mirror, queue не знают про HTTP; HTTP живёт только в core/api, modules/*/api, client и relay (реле — клиент своего же HTTP).
  • Веб-приложение (web/) ходит в хаб только через HTTP API (web/src/api/client.ts); словари kind_labels, link_types и набор закрытых статусов closed_statuses берёт из GET /settings и своих копий не держит. Модуль веба не импортирует другой модуль; общее — в shell, ui, api, projects.

Открытые вопросы

  • Правки из UI в Jira (спринт, срок, связи, исполнитель) — change viewer-edit; страницы очереди, агента и настроек — позже.
  • Автоподстройка темпа по фактам 429 не реализована: хаб только показывает их.
  • Переименование хаба в sdlc-hub (модули jira + wiki, eva-agent) — после перестройки кода; имена пакета, команды и MCP-сервера пока jira-hub.