32 KiB
Архитектура
Хаб моста Jira: ядро + два модуля. jira — SQLite-зеркало задач проектов-меток Jira-проекта AURORA, служебные заметки, очередь команд, HTTP API и MCP; wiki — Markdown-копия вики Eva по проектам, поиск и вложения по запросу. У каждого модуля свой агент на виртуалке (jira-agent, eva-agent): он опрашивает хаб, ходит в свою внешнюю систему и возвращает данные; хаб во внешние системы не ходит.
Ядро и модули
Хаб — ядро (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(тип_команды), agent_modules(), DEFAULT_AGENT, проверка конфликтов (типы команд, маршруты, ключи настроек, поля проекта) |
modules; модули — ленивым импортом внутри функции |
db |
connect, схема ядра (setting, batch, command, project, agent_status) и схемы модулей из Module.schema, миграции по schema_version (v7; копия базы перед миграцией; история шагов MIGRATIONS) |
backup, paths; модули — только внутри шагов истории миграций (ленивый импорт) |
settings |
настройки (setting), токены (клиентский + по агенту на модуль: setting.tokens = {client, agents: {<модуль>: <токен>}}), умолчания модулей (ensure_defaults) |
registry (база — через conn вызывающего) |
projects |
проекты-метки: таблица project (label, name, created_at), проверка метки, создание и правка (PATCH) с полями модулей, список = поля ядра + project_view модулей |
db, registry |
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] и подкоманды модулей (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 |
Модуль wiki (modules/wiki/)
Точка входа — modules/wiki/__init__.py (MODULE: Module). Копию страниц хаб хранит файлами wiki/<метка>/ в каталоге данных (Markdown + _tree.json), строки и FTS — в базе.
| Модуль | Ответственность | Зависит от |
|---|---|---|
schema |
таблицы модуля: wiki_project, wiki_page, wiki_attachment, wiki_sync_stage; таблица FTS5 wiki_page_fts создаётся лениво при первой синхронизации (search.ensure), в schema её нет |
stdlib |
settings |
умолчания (wiki_pacing, wiki_auto_sync_hours, wiki_min_agent_version), ключи для агента, проверка значений |
core.settings |
paths |
каталог копий <каталог базы>/wiki/<метка>/ |
core.db |
projects |
поле проекта wiki_root (изменяемое), project_view (wiki_root, pages_count, synced_at…), on_project_created/on_project_updated (постановка wiki_sync, отмена queued, сброс копии при смене корня) |
core.projects, core.queue, commands, paths |
commands |
типы wiki_sync и wiki_fetch (оба — чтение Eva, без хвоста и копии базы), CommandHooks модуля: проверки, паузы, before_result/on_result/after_result/abort_result, отбрасывание отчёта по сменённому корню |
core.queue, stage, sync, attachments |
convert |
HTML Eva → Markdown (markdownify): замена ссылок по ref на attachment:<id> и page:<код>; HTML считается недоверенным |
stdlib, markdownify |
stage |
приём пачек страниц от агента: проверка формы, перевод вне транзакции, стадия wiki_sync_stage |
core.db, convert |
sync |
сборка копии из стадии в wiki/.<метка>.new/ (prepare), запись строк и подмена каталога (commit), FTS |
core.db, paths, search, convert |
search |
поиск по FTS5 (запасной LIKE по index.md, если FTS5 в SQLite нет), фрагменты |
paths |
attachments |
вложения по запросу: состояние none/fetching/ready/failed, постановка wiki_fetch, хуки результата; адрес берётся только из wiki_attachment |
core.queue, core.files |
tick |
фоновая синхронизация проектов с корнем раз в wiki_auto_sync_hours |
core.db, core.settings, commands, sync, search |
api |
маршруты /wiki/tree, /wiki/pages/{code}, /wiki/search, /wiki/sync, /wiki/status, /wiki/attachments/{id}[/fetch|/content] (клиент) и POST /agent/wiki/pages (агент wiki) |
core.http, core.queue, stage, attachments, search |
mcp |
инструменты сервера jira-hub-wiki: projects, project_create, root_set, tree, page, search, attachment, sync, status; Markdown и имена вложений недоверенные |
core.mcp, core.client |
Контракт модуля
core/modules.py задаёт две структуры; ядро знает только их.
Module(name, schema, settings_defaults, agent_keys, agent_min_version_key, validate_setting, settings_view, on_init, commands, routes, tick, file_refs, project_fields, project_view, on_project_created, on_project_updated, mcp_server, cli_register, cli_run)— всё необязательное, кромеname. Модуль сcommands— «вид агента»: у него свой агент, токен,agent_statusи релиз.- Поля проекта: ядро знает только
label,name,created_at. Остальное объявляет модуль:ProjectField(name, validate, editable); значения дляGET /projects(в т.ч. вычисляемые) отдаётproject_view(conn, labels);on_project_created(conn, project, fields, author)иon_project_updated(...)пишут свои поля и возвращают пакеты.POST /projectsпринимает поля всех модулей (неизвестное — 422),PATCH /projects/{label}— только изменяемые (editable).registry.checkпадает при совпадении имени поля у двух модулей или с полем ядра. 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и два необязательных —before_result,abort_result. Очередь вызывает их по типу команды черезregistry.module_of.before_result(conn, cmd, status, result, files_dir)ядро вызывает вreport_resultдо транзакции отчёта, только для выданной (claimed) команды. Нужен, чтобы тяжёлая подготовка (сборка копии вики, запись файлов) шла внеBEGIN IMMEDIATEи не держала блокировку базы, в которой ждутclaimдругого агента и веб (рулинг R-6).ValueErrorиз него ядро превращает вRuntimeError(500, агент повторит отчёт), чтобы не спутать с неверным статусом отчёта.abort_result(conn, cmd)ядро вызывает, если отчёт не дошёл доafter_result(поздний или идемпотентный выход, исключение в транзакции): модуль снимает подготовкуbefore_resultи её блокировку. При успехе подготовку снимаетafter_result.
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 (хуки вызываются в постоянном порядке).
Модуль wiki (web/src/modules/wiki/index.tsx): страница «Вики» (/app/wiki): дерево (Tree) с поиском (Search), страница
(WikiPage, PageView — react-markdown + remark-gfm, без rehype-raw; ссылки page: ведут на страницу вики, attachment:
скачивают файл через download.ts, urlTransform.ts пропускает только эти схемы и http(s)), форма корня без привязки (RootForm),
плашка синхронизации в боковой панели (SyncStatus); поле «Корень вики» в окне «Новый проект» — через projectFields.
Модуль 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.*; - модуль wiki:
commands → stage/sync/attachments → convert/search/paths → core.*;projects → commands/paths;api → stage/attachments/search → core.http/core.queue;tick → commands/sync; - модуль 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, modules/wiki → 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. - Синхронизация вики:
wiki_sync {label, root}ставят MCPsync, кнопка «Обновить» (POST /wiki/sync), привязка/смена корня (on_project_created/on_project_updated) иtick(раз вwiki_auto_sync_hours) →claimагентаwiki→ eva-agent обходит дерево и шлёт пачкиPOST /agent/wiki/pages(хаб принимает ≤50 страниц в пачке; срез по 4 МБ делает агент) →stage.acceptпереводит HTML в Markdown вне транзакции и кладёт вwiki_sync_stage→ отчётdone {root_title, codes}→before_result(sync.prepare: сборкаwiki/.<метка>.new/, выделение id вложений, под per-labelLock) → транзакция отчёта (on_result=sync.commit: строкиwiki_page/wiki_attachment/FTS, атомарная подмена каталога) →after_resultснимает подготовку; при сбое —abort_result. Отчёт по команде с устаревшим корнем отбрасывается (result.discarded), копия прежняя. - Вложение вики:
POST /wiki/attachments/{id}/fetch(веб, MCPattachment) →attachments.request_fetch(одна транзакция:state = fetching+ пакетwiki_fetch {attachment_id, url}) →claim→ eva-agent качает файл только с хоста Eva →POST /agent/files→ результат{file, size}→ хаб проверяет файл вfiles/и ставитready(иначеfailed) →GET /wiki/attachments/{id}/content; в MCP копияdownloads/wiki-<id>/<имя>. - Агент за VPN: агент → файлы в общей папке (
relay_dir) →relay→ HTTP хаба → ответ файлом. - Самообновление агента:
jira-hub agent publish [--module M] PATH→release/<модуль>/→claim.agent_release→ агент скачиваетGET /agent/release; состояние обновления агент шлёт вclaim, хаб держит в памяти (вместе с признакомagent_reported— был лиclaimс запуска хаба) и отдаёт вGET /agent. - Токены: клиентский — всё, кроме
/agent/*; по токену на каждый модуль-агент (jira,wiki) — только/agent/*, причём маршрутыscope=agentмодуля пускают лишь его агента (токен чужого — 401);claimвыдаёт агенту только команды его модуля, по одной в полёте на агента;agent_status(ключ — имя агента), релизrelease/<модуль>/и самообновление — по агентам; MCP берёт клиентский токен из базы черезHubClient.from_home. Токен Eva в хабе не хранится — только вagent.jsoneva-agent.
Границы
- Ядро (
core/) не импортирует модули (jira_hub.modules.*): исключения —core/registry.py(ленивый импорт внутри функции) и шаги истории миграций вcore/db.py(ленивый импорт внутри шага). Проверяетtests/test_registry.py::test_core_imports. - Типы команд (
ensure_*,refresh,fetch_attachment,wiki_sync,wiki_fetchи др.) и термины модулей (jira:sync_run,issue_attachment,attachment_file,issue_comment,jql, поля досок; wiki:eva,wiki_,wiki/,DOC-) живут только в модулях; ядро знает их лишь черезCommandHooksи поля проекта. Проверяютtest_core_has_no_jira_terms(исключение —core/db.py, история миграций) иtest_core_has_no_module_terms(исключение —core/registry.py). Меток-исключений «долг» нет. - Новые имена в ядре — без названий модулей. Модуль не вызывается из ядра иначе как через
registryи контрактModule. - Копию вики (
wiki/<метка>/,wiki_page, FTS) пишет толькоsync.commitв отчётеwiki_sync(и удаляет при сбросе корняprojects._drop_copyв транзакции PATCH); MCP и API её читают. Модуль wiki не обращается к Eva и не хранит токен Eva. - Зеркало (включая
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/*/mcp) не импортируетqueue/db/projectsи модульныеmirror/historyнапрямую, ходит только черезclient; изcore.filesберёт только пределMAX_SIZE, изattachments— толькоfmt_size;downloads/пишет только он (jira —<id>/, wiki —wiki-<id>/) (хаб эту папку лишь убирает). 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.
Выкатка и откат версии со схемой v7
- Порядок:
git pull→ сразуlaunchctl kickstart -k gui/$(id -u)/ru.aepif.jira-hub(владелец) →make web→/mcpreconnect. Старый фронт с новым API работает (поляboard_idи прочие на месте), поэтому сборку можно отложить. - До kickstart не запускать MCP и CLI
jira-hub(в том числеtokens):HubClient.from_home()и CLI открывают базу черезdb.connectи мигрируют её до v7, а хаб на прежнем коде (v6) с такой базой не работает (agent_status, токены). - Откат на копию v6 из
backups/(*schema-v6*): вместе с базой вернуть публикацию агентаrelease/jira/*вrelease/(прежняя раскладкаrelease/agent.py,manifest.json); иначе старый код увидит «публикации нет» и самообновление агента молчит.
Открытые вопросы
- Правки из UI в Jira (спринт, срок, связи, исполнитель) — change
viewer-edit; страницы очереди, агента и настроек — позже. - Автоподстройка темпа по фактам 429 не реализована: хаб только показывает их.
- Запись в вики Eva (правка страниц из хаба) не делается: модуль только читает.
- История страниц вики и частичная перекачка: сейчас каждая синхронизация перекачивает всё дерево, копия без истории.
- Переименование проекта-метки и перенос корня вики между проектами не предусмотрены (меняется только корень —
PATCH /projects). - Ручной просмотр вёрстки веб-страницы «Вики» на живых данных (таблицы, вложенные списки) — за владельцем после выкатки.
- Переименование хаба в sdlc-hub (модули jira + wiki, eva-agent) — после перестройки кода; имена пакета, команды и MCP-сервера пока
jira-hub.