21 KiB
Архитектура
Хаб моста 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(из веб-приложения и из MCPattachment);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, MCPchanges. - Агент за 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.