30 KiB
Context
main 46a6fc2, схема v6, агент v14, хаб v6. Пакет src/jira_hub/ плоский (≈4,2 тыс. строк Python), веб — web/src/.
Что уже общее, а что завязано на Jira (разбор 23.10.2026):
- Уже общее, почти без правок:
paths,backup,files,relay,release,client; в вебе —shell/(оболочка, боковая панель, тема),ui/,styles/,auth/,projects/,api/. - Модуль jira:
mirror,history,attachments,commands,importer; в вебе —board/,issues/,feed/,shell/refresh.tsсRefreshProviderиrefreshStatus(кнопка «Обновить из Jira»). - Переплетено:
queue(537 строк): механика пакетов общая, но_check_setup(настройкаbug, файл вложения),_check_project,_assign_boards(ensure_sprint.board_id),request_fetch,deliver_params(boards,history,size),READ_TYPES,sync_runвclaim/_redeliver, проверкаfetch_attachmentвreport_result,maybe_auto_refresh— про Jira.api(700 строк):Hub, сервер, авторизация, раздача веба иtick— общие; 35 маршрутов в одной таблицеROUTES, большая часть — Jira.db: одна строкаSCHEMAсо всеми таблицами, одна цепочкаMIGRATIONSv1→v6 (шаг v3→v4 лениво зовётprojects).settings:DEFAULTSиvalidateсмешивают ключи хаба (port,relay_dir,backup_keep,min_agent_version) и Jira (project_key,issue_types,field_ids,pacing,link_types,closed_statuses…).projects: метка и название — общие;board_id,board_type,boards,base_jql— Jira.mcp_server(680 строк):FastMCP("jira-hub"), привязка к проекту (scoped,guard), инструментыprojectsиproject_create— общие; остальное — Jira.cli:serve,tokens,mcp,init— общие;importиagent publish— Jira.
Goals / Non-Goals
Goals: код делится на ядро и модуль jira с явным контрактом подключения; ядро не импортирует модуль jira (кроме списка подключённых модулей и истории миграций); MCP и веб собираются по модулям; поведение снаружи не меняется.
Non-Goals и Anti-Goals: см. proposal.md.
Decisions
-
Раскладка пакета:
jira_hub/core/иjira_hub/modules/jira/. Будущий модуль —jira_hub/modules/wiki/. Имя пакетаjira_hubи точка входаjira-hubостаются до переименования. В ядре новые имена не содержат «jira» (переименовывать потом меньше). Старые пути импорта (jira_hub.queueи т. п.) не сохраняются — тесты переходят на новые; внешних потребителей пакета нет. -
Контракт модуля — объект
Moduleвcore/modules.py. Поля (все необязательные, кромеname):name—"jira";schema— SQL таблиц модуля (CREATE TABLE IF NOT EXISTS …);commands— типы команд модуля: спецификация параметров (нынешнийcommands.SPEC), признак «только чтение» (нынешниеREAD_TYPES), паузы по умолчанию;- обработчики очереди (решение 4);
routes— маршруты HTTP в формате нынешнейROUTES(method, regex, handler, scope);settings— значения по умолчанию, проверка значений, ключи для агента;tick— фоновые работы (у jira — автообновление);mcp_tools— функция регистрации инструментов на сервере MCP модуля;cli— свои подкоманды (import,agent publish). Список модулей —core/registry.py(MODULES = [jira.MODULE]). При сборке ядро проверяет, что имена типов команд, ключи настроек и маршруты (метод + шаблон) не пересекаются между ядром и модулями; пересечение — ошибка старта, а не молчаливое перекрытие.
-
База: одна схема и одна версия на весь хаб. Ядро собирает
SCHEMAиз своих таблиц (setting,project,batch,command,agent_status) иschemaмодулей.schema_versionостаётся одной (6), цепочкаMIGRATIONS— одной упорядоченной историей вcore/db.py(шаги v1…v6 не переписываются; шаг v3→v4 по-прежнему лениво зовёт функцию модуля). Новый модуль добавит свои таблицы следующей версией (v7). Отвергнуто: версия схемы на модуль — порядок миграций между модулями пришлось бы согласовывать, а копия базы перед миграцией делается на файл целиком. Существующие таблицы не переименовываются;sync_runостаётся таблицей модуля jira. -
Очередь: ядро — механика, модуль — смысл типов. В
core/queue.pyостаются пакеты, порядок, одна команда в полёте, временные ключиnew:<n>, довыдача до 3 раз, отчёты, отмена, повтор, резервная копия перед первым пишущим пакетом, выборка пакетов. Модуль даёт обработчики, которые ядро зовёт по типу команды или на весь пакет:check_batch(conn, cmds, project, files_dir)— нынешние_check_setup,_check_project,_assign_boards;deliver_params(conn, cmd, files_dir)— дописывание параметров при выдаче;on_claim(conn, cmd, now, redelivery)— нынешняя работа сsync_runвclaim;on_failed(conn, cmd, now)— закрытиеsync_runпосле исчерпанных выдач;on_result(conn, cmd, status, result, files_dir)— всё, что отчёт делает по типу команды (решение 12);check_confirmed(conn, cmd)— подтверждение по состоянию (commands.check_confirmed);tail_command(keys)— хвостовойrefreshпишущего пакета;- временные ключи: какой тип их определяет (
ensure_issue.temp_key), где они встречаются в параметрах (referenced_keys,temp_keys_used), подстановка (substitute) и поведение при неразрешённых ключах (уrefresh— выбросить временные ключи и выполнить по остальным). Механика «зависимая команда ждёт создающую, отменённое создание →skippedс текстом» остаётся в ядре. Особые входы модуля (request_fetch,maybe_auto_refresh, создание проекта с пакетомrefresh) переезжают в модуль jira и пользуются публичными функциями очереди ядра (create_batch,_insert_batch→ публичныйinsert_batch). Порядок проверок и тексты ошибок сохраняются: на них смотрят тесты и MCP.
-
Один агент — пока.
agent_status(одна строка), агентский токен,release/,claimбез вида агента — как сейчас, код лежит в ядре. Виды агентов (jira,eva) появятся в change модуля wiki, вместе со вторым агентом, на котором их можно проверить. Здесь только шов: каждый тип команды знает свой модуль (решение 2), значит, выдачу по виду агента потом можно будет добавить фильтром вclaim. Отвергнуто: ввести виды агентов сейчас — миграция схемы и смена протокола без второго агента, на котором их можно проверить (анти-цель «миграция схемы»). -
HTTP: сервер в ядре, маршруты по модулям.
core/api.py—Hub,Handler, авторизация двумя токенами, раздача веба,tick, маршруты ядра (/agent/claim,/agent/settings,/agent/commands/…,/agent/release,/agent/files…,/files,/batches…,/commands/…,/agent,/settings,/projects,/app…,/). Маршруты задач, доски, ленты, заметок, вложений,/agent/state,/agent/history,/agent/known-keys,/refresh,/sync-runs— вmodules/jira/api.py. Адреса, коды и тела ответов те же.POST /projectsостаётся в ядре, а пакет «Новый проект» ставит обработчик модуляon_project_created(решение 8). -
Настройки: ядро объединяет. У ядра свои ключи (
port,relay_dir,backup_keep,min_agent_version,settings_version,initialized,tokens,schema_version), у модуля jira — остальные.GET /settings,PUT /settings,for_agentиinit --configработают по объединённому набору, как сейчас.pacing— ключ модуля jira (паузы по типам его команд). -
Проекты: метка и название — ядро, доски — модуль. Таблица
projectне меняется (колонкиboard_id,board_typeостаются в ней).core/projects.py— создание, список, проверка метки;boards,base_jql,migrate_v3,ensure_legacy— вmodules/jira/projects.py. После создания проекта ядро зовётon_project_created(conn, project)модулей; jira ставит пакет «Новый проект <метка>» в той же транзакции, как сейчас. Корень вики проекта добавит change модуля wiki. -
MCP: общий слой в ядре, сервер на модуль.
core/mcp.py—client,guard,scoped, привязка к проекту, инструментыprojectsиproject_create, запускFastMCP(<имя>).modules/jira/mcp.py— остальные инструменты.jira-hub mcp [<модуль>] [--project <метка>]: модуль по умолчанию —jira; имя сервера модуля jira —jira-hub(как сейчас), набор инструментов и их тексты не меняются. Неизвестный модуль — код 2 и список модулей в stderr. Будет ли сервер wiki нестиprojects/project_create— решит change модуля wiki. -
Веб: модуль — папка и описание.
web/src/modules/jira/вбираетboard/,issues/,feed/и обновление из Jira (refresh.ts,RefreshProvider,refreshStatus).web/src/modules/jira/index.tsxэкспортирует описание модуля: страницы (PAGE_ROUTESбез изменения адресов/board,/issues,/feed,/→/board), пункты боковой панели, действие панели (кнопка обновления с её событием), провайдер дляApp.tsx, окно карточки (/issues/{key}).web/src/modules/index.ts— список модулей;shell/pages.tsx,Sidebar.tsx,Layout.tsx,App.tsxберут всё оттуда. Адреса, ключиlocalStorage(jira-hub-sidebar, ключи доски), golden-эталоны доски и e2e не меняются; эталоны переезжают вместе сboard/. -
jira-agent не трогается.
run.pyи общие частиagent.py(транспорт папки, журнал, цикл, самообновление) eva-agent возьмёт копией — это решение change модуля wiki, здесь только фиксируется, что протокол агента не меняется. -
Граница ядра — компромисс (решение владельца 23.10.2026). Ядро не импортирует модуль jira и не содержит понятий Jira в логике, кроме проектов. Строго в модуль jira уходит:
- типы команд, вписанные в код ядра:
'refresh','fetch_attachment'в SQLclaim(признак «пишущий пакет» — отcommandsмодуля),ensure_attachmentвfiles.sweep(обработчик модуля «файлы, на которые ссылаются команды»); - паузы:
pacing— ключ модуля, ядро берёт паузу команды и оценку пакета через модуль (claim,estimate); - отчёты: вместо одного
check_result—on_result(conn, cmd, status, result, files_dir)модуля, куда переезжает всё, чтоreport_resultделает по типу команды (attachment_file, закрытиеsync_run,record_board_types, переименование заметки, отметка соседей,reconcile); - исключения модуля → коды HTTP: модуль объявляет свои исключения с кодом и
error(TooLarge→ 413,AgentTooOld→ 409), ядро переводит их в ответ, не зная о них; /settings: модуль дописывает вычисляемые поля ответа (base_jql);jira_utcи вспомогательное для зеркала — в модуль; шаги миграций v1…v6 остаются в историиcore/db.pyи зовут функции модуля лениво, как шаг v3→v4 сейчас;downloads/иsweep_downloads— фоновая работа модуля (tick),Hubпро неё не знает;- веб:
shell/DataStatus.tsxи обновление из Jira — вweb/src/modules/jira/, оболочка рисует то, что даёт модуль;refresh.track(batchId)после создания проекта — обработчик модуля «проект создан» в вебе.
Долг — остаётся в ядре до change модуля wiki (убрать там, «не забыть» — владелец): у проекта
board_id,board_type, проверкаboard_idвprojects.create/validate,issues_countиз таблицыissueвlist_projectsиGET /projects; параметр доски и колонки «доска | тип доски | задач» в MCPprojects/project_create(fmt_projects); поле доски вweb/src/projects/NewProjectDialog.tsx. В change модуля wiki проекты получат поля от модулей (корень вики, число страниц) — тогда же доски и счётчик задач переедут в модуль jira. Каждое место долга помечается в коде комментарием# долг hub-modules: проекты → модуль jira (change wiki)(в TS —//), чтобы найти их одним поиском. - типы команд, вписанные в код ядра:
-
Порядок работ — шагами, каждый зелёный. Сначала Python (ядро, затем модуль jira по слоям: настройки и проекты → очередь → API → MCP → CLI), потом веб, потом документация. После каждого шага — весь
uv run pytestиmake checkзелёные; тесты меняют только импорты и места, где подставляли зависимости, проверки (утверждения) не меняются. Добавляются тесты на сборку модулей: пересечение типов команд, ключей настроек или маршрутов — ошибка;jira-hub mcpиjira-hub mcp jiraрегистрируют один и тот же набор инструментов.
Risks / Trade-offs
- Объём переноса и конфликты. Почти каждый файл меняет путь. Пока идёт change, других веток в хабе не открываем.
- Скрытые зависимости от путей импорта (ленивые импорты в
db, подстановки в тестах черезmonkeypatchпо имени модуля). Ловятся набором тестов; поиск по строкам"jira_hub.— часть каждого шага. - Пути от расположения файла (проверка сборщика):
WEB_APP = Path(__file__).parent / "web" / "app"вapi.py(сборка Vite кладётся вsrc/jira_hub/web/app/— место сборки не меняется, меняется вычисление пути),HUB_ROOT = parents[2]вmcp_server.py(от него — фрагмент.mcp.jsonв ответеproject_create);web/scripts/seed-hub.pyимпортируетjira_hub.db/mirror/settings;web/scripts/test-hub.sh,golden-from-links.ts,e2e/board.spec.tsссылаются наsrc/board/__fixtures__иsrc/board/constants.ts. - CLI
mcp <модуль>:argparseсchoicesдаёт свой текст ошибки — модуль проверяется вручную, чтобы текст был как в спеке. - Хаб на Mac mini после merge — обычная выкатка:
git pull,make web,kickstart(за владельцем). Каталог данных, база и.mcp.jsonне меняются.
Migration Plan
Миграции данных нет. Выкатка — как любая версия хаба. Откат — предыдущий коммит main.
Decisions made without discussion
- 23.10.2026 — виды агентов не в этом change (решение 5). Обоснование: нет второго агента для проверки, миграция
схемы противоречит «без изменения поведения». Цена ошибки: change модуля wiki начнётся с правки ядра очереди и
agent_status— +полдня к нему. - 23.10.2026 — одна версия схемы на весь хаб (решение 3). Цена ошибки: если модули понадобится ставить и снимать
независимо — переход на версии по модулю, миграция
setting. - 23.10.2026 — раскладка
core/+modules/<имя>/, без шимов старых импортов (решения 1, 13). Цена ошибки: при переименовании пакета — ещё один массовый перенос (он будет в любом случае). - 23.10.2026 —
sync_runиpacing— модуля jira (решения 3, 7). Цена ошибки: если вики понадобится свой журнал загрузок и паузы — перенос в ядро в change модуля wiki. - 23.10.2026 — метка в коде на каждом месте долга проектов (решение 12). Цена ошибки: нулевая.
- 23.10.2026 —
jira-hub mcpбез модуля =jira(решение 9), чтобы не трогать.mcp.jsonawp-ca и user scope. Цена ошибки: нулевая до переименования. - 23.10.2026 — R-1: тест
test_core_imports(Task 3) работает со списком временных исключений. Обоснование: файлыqueue,api,mcp_server,cli,filesдо Task 4–6 ещё импортируют модуль jira (так задумано переносом Task 2), без списка тест красный сразу; каждая из Task 4–6 сокращает список, к Task 6/8 он пуст. Цена ошибки: граница ядра проверяется слабее на промежуточных шагах; закрывается Task 8. - 23.10.2026 — R-2: метки долга проектов ставит задача, создающая или переносящая код (Task 3 —
core/projects.py, Task 6 —core/mcp.py, Task 7 —NewProjectDialog.tsx); Task 8 только проверяет поиском. Обоснование: иначе метки теряются при переносе. Цена ошибки: нулевая. - 23.10.2026 — R-3:
jira_utcиfill_comment_utcлежат вmodules/jira/history.py, порядок ключей агента — ключи модуля, затем ядра. Обоснование: вmirror.pyони дали бы цикл (mirror импортирует history); порядок ключей сохраняет ответ/agent/settings. Цена ошибки: нулевая. - 23.10.2026 — R-4:
registry.checkполучает маршруты ядра только в Task 5. Обоснование: до разделенияapi.pyотдельных маршрутов ядра нет. Цена ошибки: пересечение маршрутов проверяется со Task 5, не с Task 3. - 23.10.2026 — R-5:
retry_paramsиdrop_unresolvedполучают команду целиком (cmd), а неparams;link_typesчитается вcheck_confirmedмодуля, а не вreconcileядра; логгер позднего отчёта —jira_hub.modules.jira.hooks. Обоснование: хукам нужен тип команды; ядро не должно знать настройкуlink_types. Цена ошибки: если кто-то фильтрует журнал по имени логгераjira_hub.core.queue— строка пропадёт; лишний запрос кsettingна каждую выданную команду (пренебрежимо). - 23.10.2026 — R-6: утверждение
commands.estimate(...) == 152заменено тестомqueue.estimateна реальном модуле jira (test_estimate_sums_module_pauses_replaces_commands_estimate). Обоснование:commands.estimateуходит по плану (Task 4), семантика сохранена. Цена ошибки: нулевая. - 23.10.2026 — R-7:
Hub.tick— сначала уборка файлов ядра, затемtickмодулей по порядку; метка сбоя работы — атрибутlabelфункции; суточная уборкаdownloads/— работа модуля jira с временем прошлой вhub.module_state["jira"]. Обоснование: ядро не знает про автообновление иdownloads/; раньше автообновление шло первым — теперь послеfiles.sweep, который короткий. Цена ошибки: еслиsweepстанет долгим, автообновление сдвинется; метка в атрибуте функции — неявный контракт (можно оформить полемModule). - 23.10.2026 — R-8:
POST /projectsотдаётbatch_idпервого модуля, вернувшего пакет (null, если никто). Обоснование: модуль один; формат ответа прежний. Цена ошибки: при нескольких модулях с пакетами «Новый проект» остальные не видны в ответе — решит change модуля wiki. - 23.10.2026 — R-9: приватные помощники
core/mcp.py(client,unbound_or_unknown,cut,value) стали публичными;mcp.runвозвращает код (0 после остановки сервера, 2 при неизвестном модуле), в сообщении перечислены модули сmcp_server;importоткрывает своё соединение вcli_run. Обоснование: их используют инструменты модуля jira из другого пакета. Цена ошибки: нулевая; тесты, подменяющие эти имена, ссылаются на новые. - 23.10.2026 — R-10: тесты MCP-привязки к проекту (
test_mcp_project_option) заменены двумя тестами из плана (test_mcp_default_module,test_mcp_unknown_module). Обоснование: прежний тест подменял удалённыйmcp_server.run. Цена ошибки: если прежний тест проверял что-то сверх, это нужно вернуть (проверяет ревью Task 6). - 23.10.2026 — R-11: веб —
SidebarStatusпринимает{project, compact}; вHubModuleдобавлено необязательноеTopbarNotice(строка события пакета на узком экране); хуки модулей вызываются через.mapпо константным спискам;LayoutдержитhasOverlayбулевым; рисуется первое окно, вернувшееpageLocation; запросы jira (qk) разворачиваютqkядра, вapi/queries.tsядра остались настройки, проекты,useCreateProject. Обоснование: плашка данных зависит от проекта, строка события жила вSidebarчерезuseJiraRefresh. Цена ошибки: нулевая — поведение прежнее. - 23.10.2026 — R-12: тест границы ядра (Task 8) ищет типы команд по границе слова, а не по подстроке
ensure_. Обоснование:ensure_defaults(настройки ядра) иensure_ascii(json) — не понятия Jira; подстрока дала бы ложные срабатывания. Докстринги ядра с именами типов команд (ensure_attachmentвfiles.py,api.py) переписываются нейтрально. Цена ошибки: тест границы пропустит новое слово вне списка — список в тесте расширяется вручную. - 23.10.2026 — R-13 (I-1 финального ревью): расхождения текста design.md и кода, принятые планом.
(а)
agent publishостаётся подкомандой ядра (core/cli.py), а не модуля jira: команда публикует релиз агента вrelease/, который в этом change общий (решение 5); в модуль jira уходит толькоimport. Решения 2 и 9 в части «agent publish— подкоманда модуля» читать с этой поправкой. (б) Вместо одногоcheck_resultвCommandHooksчетыре хука:check_result(статус и текст ошибки до записи команды),on_result(в той же транзакции),after_result(после фиксации —reconcileу jira),on_late_result(отчёт по уже терминальной команде). Решение 12 («вместо одногоcheck_result—on_result») читать так. (в) Функцияfiles.sweep_downloadsостаётся вcore/files.py(общая работа с каталогом), в модуль jira переехал вызов —downloads_tick; решение 12 («downloads/иsweep_downloads— фоновая работа модуля») в этой части уточняется. (г) Сигнатуры:tail(cmds)вместоtail_command(keys),on_project_created(conn, project, author)вместо(conn, project)— автор нужен для заголовка пакета «Новый проект». Цена ошибки: нулевая; при следующем модуле контракт брать изcore/modules.py, а не из текста решений. - 23.10.2026 — R-14: Minor-находки финального ревью M-1 (неточности
ARCHITECTURE.md) и M-2 (две метки долга вcore/api.py) исправляются в fix wave; M-3 (оценка пакета читаетpacingна каждый тип) откладывается. Обоснование: M-1/M-2 — документация и комментарии; M-3 — N лёгких запросов к локальному SQLite, оптимизация (хук паузы на пакет) при подключении wiki. Цена ошибки: нулевая.