Модульная перестройка хаба: ядро + модуль jira (#87) #88

Merged
aep merged 23 commits from feat/87-modules into main 2026-10-23 22:42:15 +03:00
Owner

Closes #87

Первый шаг к sdlc-hub: код хаба разделён на ядро jira_hub/core/ и модуль jira_hub/modules/jira/, внешнее поведение не изменилось.

Спека: openspec/changes/hub-modules/. План: docs/superpowers/plans/2026-10-23-hub-modules.md.

Что сделано

  • Эталоны поведения (tests/fixtures/baseline/) сняты до переноса и не менялись: инструменты MCP, маршруты HTTP, настройки, схема.
  • Контракт Module (core/modules.py) и реестр (core/registry.py). При старте реестр проверяет пересечения типов команд, ключей настроек и маршрутов.
  • Схема и настройки собираются из ядра и модулей. Схема одна, schema_version = 6.
  • Очередь. Механика пакетов осталась в ядре, смысл команд Jira перенесён в хуки модуля (modules/jira/hooks.py, fetch.py, refresh.py).
  • HTTP. Сервер и общие маршруты — в ядре, маршруты Jira — в модуле.
  • MCP и CLI. Общий слой MCP вынесен в core/mcp.py. Новая форма jira-hub mcp [модуль], по умолчанию jira. Неизвестный модуль — код выхода 2.
  • Веб. Модуль jira лежит в web/src/modules/jira/, оболочка собирается из списка модулей.
  • Граница ядра. Ядро не импортирует модули, это проверяет AST-тест. Понятий Jira в ядре нет, кроме помеченного долга (# долг hub-modules, 10 мест, решение 12). Долг убирается в change wiki.
  • Доки: ARCHITECTURE.md, CLAUDE.md, README.

Не изменилось

  • схема v6;
  • адреса API и веба;
  • ключи localStorage;
  • протокол /agent/* (агент v14 работает как раньше);
  • имя MCP-сервера jira-hub и его инструменты (.mcp.json в awp-ca и регистрацию в user scope менять не нужно);
  • репозиторий jira-agent.

Проверка

  • make check: pytest 763 passed, lint чистый, vitest 45 files / 457 tests passed.
  • npm run e2e: 37 passed, 44 skipped (screens-тесты идут только при SCREENS=1).
  • jira-hub mcp nope: stderr неизвестный модуль nope; модули: jira, код выхода 2.

Rulings I made

Все записаны в openspec/changes/hub-modules/design.md, раздел «Decisions made without discussion» (#7–#20).

  • R-1 (design #7). В test_core_imports был временный список исключений PENDING, который мог только сокращаться; к T6 он опустел.
    Цена ошибки: на шагах T3–T5 граница ядра проверялась слабее.

  • R-2 (#8). Метку долга ставит задача, которая создаёт или переносит код; T8 проверяет метки поиском.
    Цена ошибки: нулевая.

  • R-3 (#9). jira_utc и fill_comment_utc лежат в modules/jira/history.py. Порядок AGENT_KEYS: сначала ключи модуля, потом ядра.
    Цена ошибки: нулевая.

  • R-4 (#10). registry.check получает маршруты ядра только начиная с T5.
    Цена ошибки: пересечение маршрутов проверяется только со T5.

  • R-5 (#11). retry_params и drop_unresolved пол��чают команду целиком. link_types читается в check_confirmed модуля. У позднего отчёта сменился логгер.
    Цена ошибки: фильтр по логгеру jira_hub.core.queue больше не находит эту строку; на каждую выданную команду добавляется лишний запрос к setting.

  • R-6 (#12). Assert commands.estimate == 152 заменён тестом queue.estimate на реальном модуле jira.
    Цена ошибки: нулевая.

  • R-7 (#13). Hub.tick сначала убирает файлы ядра, потом зовёт tick модулей. Метка сбоя — атрибут label функции. Суточная уборка downloads/ — работа модуля.
    Цена ошибки: автообновление теперь идёт после files.sweep; метка в атрибуте — неявный контракт.

  • R-8 (#14). POST /projects отдаёт batch_id первого модуля, который вернул пакет.
    Цена ошибки: если пакеты вернут несколько модулей, остальные в ответе не видны.

  • R-9 (#15). Помощники core/mcp.py стали публичными, mcp.run возвращает код выхода.
    Цена ошибки: нулевая.

  • R-10 (#16). test_mcp_project_option заменён двумя тестами из плана. Проверка «mcp без --project → None» возвращена в T8.
    Цена ошибки: нулевая.

  • R-11 (#17). Веб: SidebarStatus получает {project, compact}, TopbarNotice необязателен, хуки модулей вызываются через .map по константным спискам, qk модуля разворачивает qk ядра.
    Цена ошибки: нулевая.

  • R-12 (#18). Тест границы ищет типы команд по границе слова.
    Цена ошибки: новое слово вне списка тест пропустит; список расширяется вручную.

  • R-13 (#19). Расхождения design.md с кодом, принятые планом:

    • agent publish остался в ядре;
    • вместо одного хука результата — четыре;
    • sweep_downloads остался в core/files.py;
    • сигнатуры tail(cmds) и on_project_created(conn, project, author).

    Цена ошибки: нулевая; при следующем модуле контракт брать из core/modules.py.

  • R-14 (#20). M-1 и M-2 исправлены в fix wave, M-3 отложен.
    Цена ошибки: нулевая.

Находки

Исправлены: T3.1–T3.4, T4.I, T4.1, T4.3, T5.I, T6.1, T6.2, T7.I, T7.2, I-1, M-1, M-2.

Отклонены: T1.1, T1.2, T2.1, T2.2, T4.4, T5.3, T6.3, T7.3, T8.2.

Отложено (уйдёт в follow-up issue при закрытии)

  • T4.2. Цикл fetch↔hooks обходится локальным импортом. Вынести TooLarge/AgentTooOld в отдельный модуль ошибок.
  • T5.1. label как атрибут функции тика — неявный контракт.
  • T5.4. Тест не проверяет повтор уборки downloads/ на следующие сутки.
  • T7.5. В NotFound key={n.to} ломается при дубле адресов.
  • T7.6. modules.test не проверяет уникальность nav.to и имени модуля.
  • T8.1. Тест границы: исключение «предыдущая строка» пропускает строку после трейлинг-метки долга.
  • M-3. Оценка пакета читает pacing отдельно на каждый т��п (N запросов); нужен хук пауз на пакет.

Наблюдения

  • O-1. Исполнитель T6 один раз запустил jira-hub import /nonexistent без JIRA_HUB_HOME. Это вызвало идемпотентный settings.ensure_defaults на живой базе; схема осталась v6, ущерба нет.
  • O-2. Контроллер ждал сабагентов через sleep; исправлено по указанию владельца.

После merge (владелец)

git pull, make web, launchctl kickstart -k gui/$(id -u)/ru.aepif.jira-hub, затем /mcp → reconnect.

🤖 Generated with Claude Code

Closes #87 Первый шаг к sdlc-hub: код хаба разделён на ядро `jira_hub/core/` и модуль `jira_hub/modules/jira/`, внешнее поведение не изменилось. Спека: `openspec/changes/hub-modules/`. План: `docs/superpowers/plans/2026-10-23-hub-modules.md`. ## Что сделано - **Эталоны поведения** (`tests/fixtures/baseline/`) сняты до переноса и не менялись: инструменты MCP, маршруты HTTP, настройки, схема. - **Контракт `Module`** (`core/modules.py`) и реестр (`core/registry.py`). При старте реестр проверяет пересечения типов команд, ключей настроек и маршрутов. - **Схема и настройки** собираются из ядра и модулей. Схема одна, `schema_version` = 6. - **Очередь.** Механика пакетов осталась в ядре, смысл команд Jira перенесён в хуки модуля (`modules/jira/hooks.py`, `fetch.py`, `refresh.py`). - **HTTP.** Сервер и общие маршруты — в ядре, маршруты Jira — в модуле. - **MCP и CLI.** Общий слой MCP вынесен в `core/mcp.py`. Новая форма `jira-hub mcp [модуль]`, по умолчанию `jira`. Неизвестный модуль — код выхода 2. - **Веб.** Модуль jira лежит в `web/src/modules/jira/`, оболочка собирается из списка модулей. - **Граница ядра.** Ядро не импортирует модули, это проверяет AST-тест. Понятий Jira в ядре нет, кроме помеченного долга (`# долг hub-modules`, 10 мест, решение 12). Долг убирается в change wiki. - **Доки:** ARCHITECTURE.md, CLAUDE.md, README. ## Не изменилось - схема v6; - адреса API и веба; - ключи localStorage; - протокол `/agent/*` (агент v14 работает как раньше); - имя MCP-сервера `jira-hub` и его инструменты (`.mcp.json` в awp-ca и регистрацию в user scope менять не нужно); - репозиторий jira-agent. ## Проверка - `make check`: pytest `763 passed`, lint чистый, vitest `45 files / 457 tests passed`. - `npm run e2e`: `37 passed`, `44 skipped` (screens-тесты идут только при `SCREENS=1`). - `jira-hub mcp nope`: stderr `неизвестный модуль nope; модули: jira`, код выхода 2. ## Rulings I made Все записаны в `openspec/changes/hub-modules/design.md`, раздел «Decisions made without discussion» (#7–#20). - **R-1 (design #7).** В `test_core_imports` был временный список исключений `PENDING`, который мог только сокращаться; к T6 он опустел. Цена ошибки: на шагах T3–T5 граница ядра проверялась слабее. - **R-2 (#8).** Метку долга ставит задача, которая создаёт или переносит код; T8 проверяет метки поиском. Цена ошибки: нулевая. - **R-3 (#9).** `jira_utc` и `fill_comment_utc` лежат в `modules/jira/history.py`. Порядок `AGENT_KEYS`: сначала ключи модуля, потом ядра. Цена ошибки: нулевая. - **R-4 (#10).** `registry.check` получает маршруты ядра только начиная с T5. Цена ошибки: пересечение маршрутов проверяется только со T5. - **R-5 (#11).** `retry_params` и `drop_unresolved` пол��чают команду целиком. `link_types` читается в `check_confirmed` модуля. У позднего отчёта сменился логгер. Цена ошибки: фильтр по логгеру `jira_hub.core.queue` больше не находит эту строку; на каждую выданную команду добавляется лишний запрос к `setting`. - **R-6 (#12).** Assert `commands.estimate == 152` заменён тестом `queue.estimate` на реальном модуле jira. Цена ошибки: нулевая. - **R-7 (#13).** `Hub.tick` сначала убирает файлы ядра, потом зовёт `tick` модулей. Метка сбоя — атрибут `label` функции. Суточная уборка `downloads/` — работа модуля. Цена ошибки: автообновление теперь идёт после `files.sweep`; метка в атрибуте — неявный контракт. - **R-8 (#14).** `POST /projects` отдаёт `batch_id` первого модуля, который вернул пакет. Цена ошибки: если пакеты вернут несколько модулей, остальные в ответе не видны. - **R-9 (#15).** Помощники `core/mcp.py` стали публичными, `mcp.run` возвращает код выхода. Цена ошибки: нулевая. - **R-10 (#16).** `test_mcp_project_option` заменён двумя тестами из плана. Проверка «`mcp` без `--project` → None» возвращена в T8. Цена ошибки: нулевая. - **R-11 (#17).** Веб: `SidebarStatus` получает `{project, compact}`, `TopbarNotice` необязателен, хуки модулей вызываются через `.map` по константным спискам, `qk` модуля разворачивает `qk` ядра. Цена ошибки: нулевая. - **R-12 (#18).** Тест границы ищет типы команд по границе слова. Цена ошибки: новое слово вне списка тест пропустит; список расширяется вручную. - **R-13 (#19).** Расхождения design.md с кодом, принятые планом: - `agent publish` остался в ядре; - вместо одного хука результата — четыре; - `sweep_downloads` остался в `core/files.py`; - сигнатуры `tail(cmds)` и `on_project_created(conn, project, author)`. Цена ошибки: нулевая; при следующем модуле контракт брать из `core/modules.py`. - **R-14 (#20).** M-1 и M-2 исправлены в fix wave, M-3 отложен. Цена ошибки: нулевая. ## Находки Исправлены: T3.1–T3.4, T4.I, T4.1, T4.3, T5.I, T6.1, T6.2, T7.I, T7.2, I-1, M-1, M-2. Отклонены: T1.1, T1.2, T2.1, T2.2, T4.4, T5.3, T6.3, T7.3, T8.2. ### Отложено (уйдёт в follow-up issue при закрытии) - **T4.2.** Цикл fetch↔hooks обходится локальным импортом. Вынести `TooLarge`/`AgentTooOld` в отдельный модуль ошибок. - **T5.1.** `label` как атрибут функции тика — неявный контракт. - **T5.4.** Тест не проверяет повтор уборки `downloads/` на следующие сутки. - **T7.5.** В NotFound `key={n.to}` ломается при дубле адресов. - **T7.6.** `modules.test` не проверяет уникальность `nav.to` и имени модуля. - **T8.1.** Тест границы: исключение «предыдущая строка» пропускает строку после трейлинг-метки долга. - **M-3.** Оценка пакета читает `pacing` отдельно на каждый т��п (N запросов); нужен хук пауз на пакет. ### Наблюдения - **O-1.** Исполнитель T6 один раз запустил `jira-hub import /nonexistent` без `JIRA_HUB_HOME`. Это вызвало идемпотентный `settings.ensure_defaults` на живой базе; схема осталась v6, ущерба нет. - **O-2.** Контроллер ждал сабагентов через `sleep`; исправлено по указанию владельца. ## После merge (владелец) `git pull`, `make web`, `launchctl kickstart -k gui/$(id -u)/ru.aepif.jira-hub`, затем `/mcp` → reconnect. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 23 commits 2026-10-23 22:41:15 +03:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep merged commit 603086a02d into main 2026-10-23 22:42:15 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: aep/sdlc-hub#88
No description provided.