diff --git a/openspec/changes/hub-modules/.openspec.yaml b/openspec/changes/hub-modules/.openspec.yaml new file mode 100644 index 0000000..f56fe13 --- /dev/null +++ b/openspec/changes/hub-modules/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-23 diff --git a/openspec/changes/hub-modules/design.md b/openspec/changes/hub-modules/design.md new file mode 100644 index 0000000..9833405 --- /dev/null +++ b/openspec/changes/hub-modules/design.md @@ -0,0 +1,183 @@ +## 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:`, довыдача до 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. + Цена ошибки: нулевая до переименования. diff --git a/openspec/changes/hub-modules/proposal.md b/openspec/changes/hub-modules/proposal.md new file mode 100644 index 0000000..071de21 --- /dev/null +++ b/openspec/changes/hub-modules/proposal.md @@ -0,0 +1,48 @@ +## Why + +Хаб перестаёт быть только мостом Jira. Следом за ним идёт модуль вики (evaTeam Wiki: дерево страниц проекта +Markdown-файлами, MCP для сверки решений с тем, что зафиксировали аналитики) и спринты/бэклог в модуле Jira. Сейчас весь +код — один плоский пакет, где «команда», «агент», «проект» и «зеркало Jira» переплетены: очередь знает про `refresh`, +доски и вложения, `api` — одна таблица из 35 маршрутов, MCP — один сервер на всё. + +Решения владельца 23.10.2026 (brainstorming, jira-hub#87): +- хаб становится **sdlc-hub**: ядро + модули, Jira — первый модуль, вики — второй; +- у вики будет **отдельный агент** (не jira-agent) и **отдельный MCP** — «чтобы не перегружать»; +- **сначала перестраиваем код** под старым именем; переименование (репозиторий `aep/jira-hub`, launchd + `ru.aepif.jira-hub`, `~/.local/share/jira-hub`, имя MCP `jira-hub`, CLI) — позже, отдельной задачей, но помним о нём. + +## What Changes + +- **Ядро и модуль jira в коде.** Пакет делится на `jira_hub/core/` (база и миграции, настройки, проекты, хранилище + файлов, реле, релиз агента, очередь пакетов, HTTP-сервер и авторизация, общий слой MCP, CLI) и + `jira_hub/modules/jira/` (зеркало, журнал изменений, вложения, контракт команд, свои маршруты, инструменты MCP, + импорт). Модуль подключается к ядру описанием `Module`: таблицы, типы команд и обработчики очереди, маршруты, настройки, + фоновые работы, инструменты MCP. +- **Очередь без знания о Jira.** Механика пакетов, выдачи, довыдачи, отчётов и подтверждения остаётся в ядре; всё, что + сейчас завязано на конкретные типы (`refresh`, `ensure_attachment`, `fetch_attachment`, `ensure_sprint`), уходит в + обработчики модуля jira. +- **MCP по модулю.** `jira-hub mcp jira [--project <метка>]` — сервер модуля jira; `jira-hub mcp [--project <метка>]` без + модуля — то же самое, как сейчас (записи в `.mcp.json` awp-ca и user scope не меняются). +- **Веб по модулю.** Страницы «Доска», «Задачи», «Лента», карточка и обновление из Jira переезжают в + `web/src/modules/jira/`; оболочка собирает страницы, пункты боковой панели и действия из списка модулей. +- **Документация.** `ARCHITECTURE.md` и `CLAUDE.md` описывают ядро и модули. + +Внешнее поведение не меняется: те же адреса HTTP API и веба, те же инструменты MCP, та же схема базы (v6), тот же +протокол агента. jira-agent не трогается. + +## Non-Goals + +- Переименование в sdlc-hub (репозиторий, пакет `jira_hub`, launchd, каталог данных, имя MCP, CLI `jira-hub`). +- Модуль wiki, eva-agent, вкладка вики в вебе. +- Несколько видов агентов (токен, `agent_status`, релиз и `claim` по виду агента) — в change модуля wiki, когда появится + второй агент. +- Спринты и бэклог. +- Новые возможности API, MCP или веба. + +## Anti-Goals + +- Живой агент v14 перестал получать команды, обновляться или отчитываться. +- Запись `jira-hub` в `.mcp.json` awp-ca или в user scope перестала работать или потеряла инструменты. +- Сменились адреса веба (`/app/board`, `/app/issues`, `/app/feed`, `/app/issues/{key}`) или ключи `localStorage`. +- Миграция схемы или перенос файлов в каталоге данных. +- «Большой взрыв»: одна правка на весь пакет, которую нельзя проверить по частям; набор тестов красный между шагами. diff --git a/openspec/changes/hub-modules/specs/mcp/spec.md b/openspec/changes/hub-modules/specs/mcp/spec.md new file mode 100644 index 0000000..840744c --- /dev/null +++ b/openspec/changes/hub-modules/specs/mcp/spec.md @@ -0,0 +1,22 @@ +## MODIFIED Requirements + +### Requirement: Сервер и регистрация +`jira-hub mcp [<модуль>] [--project <метка>]` SHALL запускать MCP-сервер модуля хаба по stdio (пакет `mcp`), ходящий в +HTTP API хаба клиентским токеном из настроек. Модуль по умолчанию — `jira`: `jira-hub mcp [--project <метка>]` и +`jira-hub mcp jira [--project <метка>]` SHALL поднимать один и тот же сервер с именем `jira-hub` и одним и тем же набором +инструментов. Неизвестный модуль — код выхода 2 и список модулей в stderr, сервер не запускается. Общая регистрация — в +user scope Claude Code как `jira-hub` (`uv run --project <путь> jira-hub mcp`), без проекта. Репозиторий проекта SHALL +регистрировать `jira-hub` в своём `.mcp.json` (project scope) с `--project <метка>`; эта запись перекрывает общую. Если хаб +недоступен, каждый инструмент SHALL вернуть текст «хаб не запущен: jira-hub serve», а не исключение. + +#### Scenario: Хаб выключен +- **WHEN** вызван `issues` при остановленном хабе +- **THEN** ответ инструмента — одна строка с подсказкой, сервер MCP продолжает работать + +#### Scenario: Модуль по умолчанию +- **WHEN** запущены `jira-hub mcp --project AWPCA` и `jira-hub mcp jira --project AWPCA` +- **THEN** оба сервера называются `jira-hub`, список инструментов и их описания совпадают, сессия привязана к `AWPCA` + +#### Scenario: Неизвестный модуль +- **WHEN** запущен `jira-hub mcp nope` +- **THEN** код выхода 2, в stderr — «неизвестный модуль nope» и список модулей (`jira`) diff --git a/openspec/changes/hub-modules/specs/modules/spec.md b/openspec/changes/hub-modules/specs/modules/spec.md new file mode 100644 index 0000000..f339e8d --- /dev/null +++ b/openspec/changes/hub-modules/specs/modules/spec.md @@ -0,0 +1,23 @@ +## ADDED Requirements + +### Requirement: Ядро и модули хаба +Хаб SHALL состоять из ядра и подключаемых модулей. Ядро SHALL владеть базой и миграциями, настройками хаба, проектами +(метка, название), хранилищем файлов, реле общей папки, релизом агента, очередью пакетов команд, HTTP-сервером с +авторизацией, раздачей веб-приложения и общим слоем MCP. Модуль SHALL подключаться к ядру описанием: таблицы, типы команд +и их обработчики в очереди, маршруты HTTP, ключи настроек, фоновые работы, инструменты MCP, подкоманды CLI. Первый модуль — +`jira` (зеркало задач, журнал изменений, вложения, команды Jira). Ядро MUST NOT импортировать модули, кроме списка +подключённых модулей и истории миграций схемы. + +При старте хаб SHALL проверять, что типы команд, ключи настроек и маршруты (метод и шаблон адреса) ядра и модулей не +пересекаются; пересечение — ошибка старта с указанием конфликта. + +Схема базы SHALL оставаться одной, с одной версией `setting.schema_version` на весь хаб; таблицы модуля входят в неё. + +#### Scenario: Конфликт типов команд +- **WHEN** два подключённых модуля объявляют тип команды `refresh` +- **THEN** хаб не стартует, ошибка называет тип и оба модуля + +#### Scenario: Поведение без изменений +- **WHEN** хаб после перестройки стартует на базе v6 с агентом v14 +- **THEN** схема остаётся v6, копия базы не создаётся; адреса API, веба, ответы и инструменты MCP — прежние; агент берёт + команды и отчитывается как раньше