184 lines
21 KiB
Markdown
184 lines
21 KiB
Markdown
## 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` со всеми таблицами, одна цепочка `MIGRATIONS` v1→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
|
||
|
||
1. **Раскладка пакета: `jira_hub/core/` и `jira_hub/modules/jira/`.** Будущий модуль — `jira_hub/modules/wiki/`. Имя
|
||
пакета `jira_hub` и точка входа `jira-hub` остаются до переименования. В ядре новые имена не содержат «jira»
|
||
(переименовывать потом меньше). Старые пути импорта (`jira_hub.queue` и т. п.) не сохраняются — тесты переходят на
|
||
новые; внешних потребителей пакета нет.
|
||
|
||
2. **Контракт модуля — объект `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]`). При сборке ядро проверяет, что имена типов команд,
|
||
ключи настроек и маршруты (метод + шаблон) не пересекаются между ядром и модулями; пересечение — ошибка старта, а не
|
||
молчаливое перекрытие.
|
||
|
||
3. **База: одна схема и одна версия на весь хаб.** Ядро собирает `SCHEMA` из своих таблиц (`setting`, `project`, `batch`,
|
||
`command`, `agent_status`) и `schema` модулей. `schema_version` остаётся одной (6), цепочка `MIGRATIONS` — одной
|
||
упорядоченной историей в `core/db.py` (шаги v1…v6 не переписываются; шаг v3→v4 по-прежнему лениво зовёт функцию
|
||
модуля). Новый модуль добавит свои таблицы следующей версией (v7). *Отвергнуто:* версия схемы на модуль — порядок
|
||
миграций между модулями пришлось бы согласовывать, а копия базы перед миграцией делается на файл целиком.
|
||
Существующие таблицы не переименовываются; `sync_run` остаётся таблицей модуля jira.
|
||
|
||
4. **Очередь: ядро — механика, модуль — смысл типов.** В `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.
|
||
|
||
5. **Один агент — пока.** `agent_status` (одна строка), агентский токен, `release/`, `claim` без вида агента — как сейчас,
|
||
код лежит в ядре. Виды агентов (`jira`, `eva`) появятся в change модуля wiki, вместе со вторым агентом, на котором их
|
||
можно проверить. Здесь только шов: каждый тип команды знает свой модуль (решение 2), значит, выдачу по виду агента
|
||
потом можно будет добавить фильтром в `claim`. *Отвергнуто:* ввести виды агентов сейчас — миграция схемы и смена
|
||
протокола без второго агента, на котором их можно проверить (анти-цель «миграция схемы»).
|
||
|
||
6. **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).
|
||
|
||
7. **Настройки: ядро объединяет.** У ядра свои ключи (`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 (паузы по типам его команд).
|
||
|
||
8. **Проекты: метка и название — ядро, доски — модуль.** Таблица `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.
|
||
|
||
9. **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.
|
||
|
||
10. **Веб: модуль — папка и описание.** `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/`.
|
||
|
||
11. **jira-agent не трогается.** `run.py` и общие части `agent.py` (транспорт папки, журнал, цикл, самообновление)
|
||
eva-agent возьмёт копией — это решение change модуля wiki, здесь только фиксируется, что протокол агента не меняется.
|
||
|
||
12. **Граница ядра — компромисс (решение владельца 23.10.2026).** Ядро не импортирует модуль jira и не содержит понятий
|
||
Jira в логике, **кроме проектов**. Строго в модуль jira уходит:
|
||
- типы команд, вписанные в код ядра: `'refresh','fetch_attachment'` в SQL `claim` (признак «пишущий пакет» — от
|
||
`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`; параметр доски и колонки «доска | тип доски | задач» в MCP `projects`/
|
||
`project_create` (`fmt_projects`); поле доски в `web/src/projects/NewProjectDialog.tsx`. В change модуля wiki проекты
|
||
получат поля от модулей (корень вики, число страниц) — тогда же доски и счётчик задач переедут в модуль jira.
|
||
Каждое место долга помечается в коде комментарием `# долг hub-modules: проекты → модуль jira (change wiki)` (в TS —
|
||
`//`), чтобы найти их одним поиском.
|
||
|
||
13. **Порядок работ — шагами, каждый зелёный.** Сначала 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.json` awp-ca и user scope.
|
||
Цена ошибки: нулевая до переименования.
|