From f5656b1336c15e9495d2003956ee6388d9b7b2b7 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 12:18:50 +0400 Subject: [PATCH] =?UTF-8?q?docs(sdlc-rename):=20README,=20CLAUDE.md,=20ARC?= =?UTF-8?q?HITECTURE.md=20=D0=BF=D0=BE=D0=B4=20sdlc-hub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- ARCHITECTURE.md | 22 ++++---- CLAUDE.md | 24 ++++----- README.md | 135 ++++++++++++++++++++++++++++++++++++------------ 3 files changed, 124 insertions(+), 57 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 283211a..be276e6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -4,7 +4,7 @@ ## Ядро и модули -Хаб — ядро (`src/jira_hub/core/`) и подключаемые модули (`src/jira_hub/modules/<имя>/`). Ядро держит механику: базу, +Хаб — ядро (`src/sdlc_hub/core/`) и подключаемые модули (`src/sdlc_hub/modules/<имя>/`). Ядро держит механику: базу, очередь команд, HTTP, настройки, реле, выпуск агента, каркас MCP и CLI. Модуль даёт смысл: схему своих таблиц, типы команд, маршруты, настройки, MCP-инструменты, подкоманды. Сейчас модулей два — `jira` и `wiki`; следующие (sdlc) подключаются так же, строкой в `core/registry.py`. @@ -13,7 +13,7 @@ | Модуль | Ответственность | Зависит от | |---|---|---| -| `paths` | каталог данных (`JIRA_HUB_HOME`), пути базы и `backups/`, права 0700 (`secure_dir`) | stdlib | +| `paths` | каталог данных (`SDLC_HUB_HOME`), пути базы и `backups/`, права 0700 (`secure_dir`) | stdlib | | `errors` | ошибки, общие для ядра и модулей (`CommandError`, `HttpMapped`) | stdlib | | `http` | общее для обработчиков HTTP: `ApiError`, `Raw`, разбор параметров, `mapped`, `not_found` | `errors`, `projects` | | `modules` | контракт модуля: `Module` и `CommandHooks` (только структуры) | stdlib | @@ -29,7 +29,7 @@ | `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` | +| `cli` | `init`, `serve`, `mcp <модуль> [--project]`, `tokens`, `agent publish [--module]` и подкоманды модулей (`Module.cli_register`/`cli_run`) | `api`, `mcp`, `registry`, `release`, `settings`, `db`, `paths` | ### Модуль `jira` (`modules/jira/`) @@ -49,7 +49,7 @@ | `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` | +| `importer`, `cli` | разовый импорт CSV-пакета в заметки (`sdlc-hub import FOLDER`) | `mirror`, `core.db`, `core.settings` | ### Модуль `wiki` (`modules/wiki/`) @@ -69,7 +69,7 @@ | `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` | +| `mcp` | инструменты сервера `sdlc-wiki`: `projects`, `project_create`, `root_set`, `tree`, `page`, `search`, `attachment`, `sync`, `status`; Markdown и имена вложений недоверенные | `core.mcp`, `core.client` | ### Контракт модуля @@ -106,7 +106,7 @@ golden-эталоны `__fixtures__/`), `issues/` (список, карточк `feed/` (период, виды, группировка событий, `EventList`, общий с блоком «История» карточки), `refresh/` (кнопка и плашка обновления данных из Jira в боковой панели, отслеживание пакета: `RefreshProvider`, `useJiraRefresh`), `api/queries.ts`. -Собирается `make web` в `src/jira_hub/web/app/`; `core/api` отдаёт сборку без токена (`/app/assets/*` — файлы, прочие +Собирается `make web` в `src/sdlc_hub/web/app/`; `core/api` отдаёт сборку без токена (`/app/assets/*` — файлы, прочие `/app…` — `index.html`, 503 без сборки), `/` и `/links` перенаправляет на `/app/board`. Данные — только через HTTP API с клиентским токеном; `mirror.board` отдаёт зеркало без преобразований, перевод в модель доски — в `board/model`. @@ -129,11 +129,11 @@ golden-эталоны `__fixtures__/`), `issues/` (список, карточк - Синхронизация вики: `wiki_sync {label, root}` ставят MCP `sync`, кнопка «Обновить» (`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-label `Lock`) → транзакция отчёта (`on_result` = `sync.commit`: строки `wiki_page`/`wiki_attachment`/FTS, атомарная подмена каталога) → `after_result` снимает подготовку; при сбое — `abort_result`. Отчёт по команде с устаревшим корнем отбрасывается (`result.discarded`), копия прежняя. - Вложение вики: `POST /wiki/attachments/{id}/fetch` (веб, MCP `attachment`) → `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-/<имя>`. - Агент за 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`. +- Самообновление агента: `sdlc-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.json` eva-agent. ## Границы -- Ядро (`core/`) не импортирует модули (`jira_hub.modules.*`): исключения — `core/registry.py` (ленивый импорт внутри функции) и шаги истории миграций в `core/db.py` (ленивый импорт внутри шага). Проверяет `tests/test_registry.py::test_core_imports`. +- Ядро (`core/`) не импортирует модули (`sdlc_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. @@ -146,8 +146,8 @@ golden-эталоны `__fixtures__/`), `issues/` (список, карточк - Веб-приложение (`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` → `/mcp` reconnect. Старый фронт с новым API работает (поля `board_id` и прочие на месте), поэтому сборку можно отложить. -- До kickstart не запускать MCP и CLI `jira-hub` (в том числе `tokens`): `HubClient.from_home()` и CLI открывают базу через `db.connect` и мигрируют её до v7, а хаб на прежнем коде (v6) с такой базой не работает (`agent_status`, токены). +- Порядок: `git pull` → сразу `launchctl kickstart -k gui/$(id -u)/ru.aepif.sdlc-hub` (владелец) → `make web` → `/mcp` reconnect. Старый фронт с новым API работает (поля `board_id` и прочие на месте), поэтому сборку можно отложить. +- До kickstart не запускать MCP и CLI `sdlc-hub` (в том числе `tokens`): `HubClient.from_home()` и CLI открывают базу через `db.connect` и мигрируют её до v7, а хаб на прежнем коде (v6) с такой базой не работает (`agent_status`, токены). - Откат на копию v6 из `backups/` (`*schema-v6*`): вместе с базой вернуть публикацию агента `release/jira/*` в `release/` (прежняя раскладка `release/agent.py`, `manifest.json`); иначе старый код увидит «публикации нет» и самообновление агента молчит. ## Открытые вопросы @@ -157,4 +157,4 @@ golden-эталоны `__fixtures__/`), `issues/` (список, карточк - История страниц вики и частичная перекачка: сейчас каждая синхронизация перекачивает всё дерево, копия без истории. - Переименование проекта-метки и перенос корня вики между проектами не предусмотрены (меняется только корень — `PATCH /projects`). - Ручной просмотр вёрстки веб-страницы «Вики» на живых данных (таблицы, вложенные списки) — за владельцем после выкатки. -- Переименование хаба в sdlc-hub (модули jira + wiki, eva-agent) — после перестройки кода; имена пакета, команды и MCP-сервера пока `jira-hub`. +- Хаб переименован в sdlc-hub (24.10.2026, change `sdlc-rename`): пакет `sdlc_hub`, команда `sdlc-hub`, каталог данных `~/.local/share/sdlc-hub/` (`SDLC_HUB_HOME`), MCP-серверы `sdlc-jira` и `sdlc-wiki`, launchd `ru.aepif.sdlc-hub`. diff --git a/CLAUDE.md b/CLAUDE.md index e884326..06c66f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,21 +1,21 @@ -# jira-hub +# sdlc-hub ## Что это -Хаб моста Jira для АРМ КА: единственный источник истины по задачам проектов-меток Jira-проекта AURORA на Mac mini (`192.168.0.106`) и Markdown-копии вики Eva по проектам (модуль `wiki`). Хранит зеркало задач, служебные заметки и очередь команд в SQLite, отдаёт HTTP API в локальной сети (агенту на виртуалке `jira-agent` и интерфейсам) и MCP-сервер для Claude. Главный сценарий: из чата поставить пакет команд, агент на виртуалке выполняет их в Jira по правилу «проверь, потом сделай» и возвращает состояние задач в зеркало. Спеки: `openspec/specs/`; активные change — `openspec/changes/` (кроме `archive/`). +SDLC-хаб с модулями jira и wiki. Мост Jira для АРМ КА: единственный источник истины по задачам проектов-меток Jira-проекта AURORA на Mac mini (`192.168.0.106`) и Markdown-копии вики Eva по проектам (модуль `wiki`). Хранит зеркало задач, служебные заметки и очередь команд в SQLite, отдаёт HTTP API в локальной сети (агенту на виртуалке `jira-agent` и интерфейсам) и MCP-сервер для Claude. Главный сценарий: из чата поставить пакет команд, агент на виртуалке выполняет их в Jira по правилу «проверь, потом сделай» и возвращает состояние задач в зеркало. Спеки: `openspec/specs/`; активные change — `openspec/changes/` (кроме `archive/`). ## Стек и ограничения -- Веб-приложение — `web/` (React 19 + TypeScript + Vite, TanStack Query, Vitest, Playwright; Node 24, npm); сборка в `src/jira_hub/web/app/` не в git. +- Веб-приложение — `web/` (React 19 + TypeScript + Vite, TanStack Query, Vitest, Playwright; Node 24, npm); сборка в `src/sdlc_hub/web/app/` не в git. - Python 3.13 от uv (`.python-version`, `tool.uv.python-preference = "only-managed"`: системный Python miniconda несёт SQLite 3.51.1 с взаимной блокировкой, O-14 #29), uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10). - Хаб не знает пароль Jira и не ходит в Jira: учётные данные и сеть до Jira только у агента. - Зеркало задач пишет только `mirror.apply_state` по загрузке состояния от агента (`POST /agent/state`); правки идут командами через очередь. - Хаб не создаёт команды сам: повтор после failed — новая команда по запросу человека/интерфейса. Команду, выданную агенту и оставшуюся без отчёта, хаб довыдаёт при следующем claim (до 3 выдач, затем failed). -- Данные: `~/.local/share/jira-hub/` (`JIRA_HUB_HOME`): `hub.sqlite` (зеркало, очередь, журнал изменений `issue_change`), `backups/`, `release/<модуль>/` (опубликованная версия агента модуля), `files/` (файлы вложений по sha256, уборка через 30 дней), `downloads/` (копии вложений для MCP `/<имя>`, у wiki `wiki-/<имя>`, уборка 30 дней), `wiki/<метка>/` (Markdown-копия вики проекта: `index.md` в каталоге страницы, `_tree.json`); каталог данных, `backups/`, `release/<модуль>/`, `files/`, `downloads/` и `wiki/` — права 0700. Порт по умолчанию 8790. +- Данные: `~/.local/share/sdlc-hub/` (`SDLC_HUB_HOME`): `hub.sqlite` (зеркало, очередь, журнал изменений `issue_change`), `backups/`, `release/<модуль>/` (опубликованная версия агента модуля), `files/` (файлы вложений по sha256, уборка через 30 дней), `downloads/` (копии вложений для MCP `/<имя>`, у wiki `wiki-/<имя>`, уборка 30 дней), `wiki/<метка>/` (Markdown-копия вики проекта: `index.md` в каталоге страницы, `_tree.json`); каталог данных, `backups/`, `release/<модуль>/`, `files/`, `downloads/` и `wiki/` — права 0700. Порт по умолчанию 8790. - Статические токены (в таблице `setting`): клиентский — всё, кроме `/agent/*`; по одному токену агента на модуль (`jira`, `wiki`) — только `/agent/*` и только маршруты своего модуля. Токен Eva в хабе не хранится. ## Архитектура -- `src/jira_hub/core/` — ядро: `db`, `settings`, `projects`, `queue` (механика очереди), `files`, `api` (HTTP, `CORE_ROUTES` + маршруты модулей в `ROUTES`, фоновый `tick`), `relay`, `release`, `client`, `mcp` (каркас MCP), `cli`; контракт модуля `Module`/`CommandHooks` — `core/modules.py`, реестр — `core/registry.py`. -- `src/jira_hub/modules/jira/` — модуль jira: `schema`, `mirror`, `history`, `attachments`, `fetch`, `commands`, `refresh`, `projects`, `hooks` (смысл типов команд для очереди), `api`, `mcp`, `settings`, `importer`/`cli`. Входная точка — `MODULE` в `modules/jira/__init__.py`. -- `src/jira_hub/modules/wiki/` — модуль wiki: `schema`, `settings`, `paths`, `projects` (поле `wiki_root`), `commands` (`wiki_sync`, `wiki_fetch` и хуки очереди), `convert` (HTML → Markdown), `stage`, `sync`, `search` (FTS5), `attachments`, `tick`, `api`, `mcp`. Входная точка — `MODULE` в `modules/wiki/__init__.py`. +- `src/sdlc_hub/core/` — ядро: `db`, `settings`, `projects`, `queue` (механика очереди), `files`, `api` (HTTP, `CORE_ROUTES` + маршруты модулей в `ROUTES`, фоновый `tick`), `relay`, `release`, `client`, `mcp` (каркас MCP), `cli`; контракт модуля `Module`/`CommandHooks` — `core/modules.py`, реестр — `core/registry.py`. +- `src/sdlc_hub/modules/jira/` — модуль jira: `schema`, `mirror`, `history`, `attachments`, `fetch`, `commands`, `refresh`, `projects`, `hooks` (смысл типов команд для очереди), `api`, `mcp`, `settings`, `importer`/`cli`. Входная точка — `MODULE` в `modules/jira/__init__.py`. +- `src/sdlc_hub/modules/wiki/` — модуль wiki: `schema`, `settings`, `paths`, `projects` (поле `wiki_root`), `commands` (`wiki_sync`, `wiki_fetch` и хуки очереди), `convert` (HTML → Markdown), `stage`, `sync`, `search` (FTS5), `attachments`, `tick`, `api`, `mcp`. Входная точка — `MODULE` в `modules/wiki/__init__.py`. - Граница: ядро не импортирует модули (кроме ленивого импорта в `registry.py` и шагах миграций `db.py`), типы команд и термины модулей (jira, wiki, eva) живут в модуле, новые имена в ядре — без названий модулей; проверяют `test_core_imports`, `test_core_has_no_jira_terms`, `test_core_has_no_module_terms` в `tests/test_registry.py`. Меток-исключений нет. - Цепочка ядра: `db` ← `settings`, `projects` ← `queue` ← `api` ← `cli`; `files` ← `queue`, `api`; реле `relay` — поток в `serve`; `backup` вызывается из `api` и из `db`. Модуль jira: `hooks → commands/mirror/history/refresh/attachments/fetch → core`. - Веб: оболочка и общее — `web/src/{shell,ui,api,projects,auth,styles}`, модули — `web/src/modules/<имя>/` (контракт `HubModule` в `modules/types.ts`, список — `modules/index.ts`); модуль jira — страницы доски, задач и ленты; модуль wiki (`web/src/modules/wiki/`) — страница «Вики» (`/app/wiki?project=<метка>`). @@ -24,16 +24,16 @@ ## Команды - `uv run pytest` — весь набор тестов. Зелёным считается только весь набор. - `uv sync` — установка окружения. -- `uv run jira-hub init --config PATH [--force]` — настройки проекта из `config.example.json`, создание токенов. -- `uv run jira-hub serve [--host] [--port]` — локальный запуск хаба. +- `uv run sdlc-hub init --config PATH [--force]` — настройки проекта из `config.example.json`, создание токенов. +- `uv run sdlc-hub serve [--host] [--port]` — локальный запуск хаба. - Веб-приложение — `http://127.0.0.1:8790/app/` (доска — `/app/board?project=<метка>`, задачи — `/app/issues?project=<метка>`, лента изменений — `/app/feed?project=<метка>`; вход по клиентскому токену); `/` и `/links` перенаправляют на доску. - `make web` — сборка приложения (после `git pull` на Mac mini, до перезапуска хаба); `make check` — pytest + lint и тесты фронта; `cd web && npm run dev` — разработка с прокси на тестовый хаб `bash web/scripts/test-hub.sh` (:8791); `cd web && npm run e2e` — Playwright; `cd web && npm run golden` — перегенерировать эталоны доски из `links.html@3931a88` (только при смене фикстур). -- `uv run jira-hub import FOLDER` — импорт CSV-пакета `admin/jira-sync`; `uv run jira-hub mcp [модуль] [--project <метка>]` — MCP модуля по stdio (по умолчанию `jira`; `jira-hub mcp wiki` — сервер `jira-hub-wiki`; с привязкой сессии к проекту); `uv run jira-hub tokens` — показать токены (клиентский и по агенту на модуль). -- `uv run jira-hub agent publish [--module jira|wiki] PATH` — опубликовать версию агента модуля для самообновления (только вверх; по умолчанию `jira`; публикация лежит в `release/<модуль>/`). +- `uv run sdlc-hub import FOLDER` — импорт CSV-пакета `admin/jira-sync`; `uv run sdlc-hub mcp <модуль> [--project <метка>]` — MCP модуля по stdio (модуль обязателен: `jira` — сервер `sdlc-jira`, `wiki` — сервер `sdlc-wiki`; с привязкой сессии к проекту); `uv run sdlc-hub tokens` — показать токены (клиентский и по агенту на модуль). +- `uv run sdlc-hub agent publish [--module jira|wiki] PATH` — опубликовать версию агента модуля для самообновления (только вверх; по умолчанию `jira`; публикация лежит в `release/<модуль>/`). ## Тесты - Golden-тесты: `tests/fixtures/.*` + эталон `.expected.*`. Внешние сервисы в тестах мокаются. -- SQLite в памяти или во временном `JIRA_HUB_HOME` (`tmp_path`); API поднимается на порту 0 в отдельном потоке. Jira и агент в тестах не участвуют. +- SQLite в памяти или во временном `SDLC_HUB_HOME` (`tmp_path`); API поднимается на порту 0 в отдельном потоке. Jira и агент в тестах не участвуют. - Соединение SQLite в тесте — через фикстуру `open_db` (`tests/conftest.py`) или фикстуру `conn` модуля, иначе с явным `close()`: `pyproject.toml` (`filterwarnings`) делает незакрытое соединение ошибкой набора. - Фронт: golden-эталоны доски — `web/src/modules/jira/board/__fixtures__/*.expected.json` (их пишет `web/scripts/golden-from-links.ts` из старой страницы); тесты рядом с кодом `*.test.ts(x)`; e2e — `web/e2e/` на тестовом хабе. diff --git a/README.md b/README.md index ff090c1..1f564c3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# jira-hub +# sdlc-hub Хаб моста Jira для АРМ КА: зеркало задач проектов-меток Jira-проекта AURORA (AWPCA и др.) в SQLite, служебные заметки, очередь команд, HTTP API в локальной сети и MCP-сервер для Claude. Хаб работает на Mac mini (`192.168.0.106`); в Jira ходит только @@ -15,24 +15,24 @@ Python для хаба uv ставит свой (3.13, `.python-version`; сис `pyproject.toml`): в SQLite 3.51.0–3.51.1 есть взаимная блокировка, от которой хаб зависал под параллельными запросами. Первый `uv sync` или `uv run` после смены сам пересоздаёт `.venv`. -Код: ядро — `src/jira_hub/core/`, модуль jira — `src/jira_hub/modules/jira/`, модуль wiki — `src/jira_hub/modules/wiki/`, веб — `web/src/` (модуль jira — `web/src/modules/jira/`); карта — `ARCHITECTURE.md`. +Код: ядро — `src/sdlc_hub/core/`, модуль jira — `src/sdlc_hub/modules/jira/`, модуль wiki — `src/sdlc_hub/modules/wiki/`, веб — `web/src/` (модуль jira — `web/src/modules/jira/`); карта — `ARCHITECTURE.md`. -Данные лежат в `~/.local/share/jira-hub/` (каталог переопределяется переменной `JIRA_HUB_HOME`): +Данные лежат в `~/.local/share/sdlc-hub/` (каталог переопределяется переменной `SDLC_HUB_HOME`): `hub.sqlite`, `backups/`, `release/<модуль>/`, `files/` (файлы вложений по sha256), `downloads/` (копии вложений для MCP) и `wiki/<метка>/` (Markdown-копия вики проекта); каталоги — 0700. Пакет `mcp` закреплён версией `<2` (рулинг R-10): MCP SDK 2.x ломает API. ## Первый запуск ```bash -uv run jira-hub init --config ~/projects/awp-ca/admin/jira-sync/config.example.json -uv run jira-hub serve # 0.0.0.0:8790 +uv run sdlc-hub init --config ~/projects/awp-ca/admin/jira-sync/config.example.json +uv run sdlc-hub serve # 0.0.0.0:8790 ``` `init` переносит в настройки хаба всё, что относится к проекту Jira (поля, типы, карту переходов, типы связей, доску, приоритеты), и создаёт токены: клиентский и по одному на агента каждого модуля (`jira`, `wiki`). Адрес и учётные данные Jira (`base_url`, `auth`, `verify_ssl`, `ca_bundle`) остаются у агента и в хаб не попадают. Повторный `init` отказывает ("уже инициализирован"); с `--force` -настройки переписываются. Токены можно показать позже: `uv run jira-hub tokens`. +настройки переписываются. Токены можно показать позже: `uv run sdlc-hub tokens`. -Передать агенту jira нужно адрес хаба и токен агента jira (`uv run jira-hub tokens` → «агент jira»): через общую папку виртуалки положить в `agent.json` +Передать агенту jira нужно адрес хаба и токен агента jira (`uv run sdlc-hub tokens` → «агент jira»): через общую папку виртуалки положить в `agent.json` `"hub_url": "http://192.168.0.106:8790"` и `"hub_token": "<токен агента jira>"`. Клиентский токен остаётся на этой машине: его использует MCP (читает из базы сам). @@ -41,7 +41,7 @@ uv run jira-hub serve # 0.0.0.0:8790 ## Импорт пакета admin/jira-sync ```bash -uv run jira-hub import ~/projects/awp-ca/admin/jira-sync +uv run sdlc-hub import ~/projects/awp-ca/admin/jira-sync ``` Переносит из CSV заметки (статус по факту, вид работы и т. п.) и выводит отчёт JSON: @@ -51,24 +51,24 @@ uv run jira-hub import ~/projects/awp-ca/admin/jira-sync ## MCP в Claude -`jira-hub mcp [модуль]` запускает MCP-сервер модуля по stdio (по умолчанию `jira`); он ходит в API хаба клиентским токеном. Если хаб не запущен, инструменты -отвечают «хаб не запущен: jira-hub serve». Регистрация вручную (файл `~/.claude.json` скрипты хаба не правят), +`sdlc-hub mcp <модуль>` запускает MCP-сервер модуля по stdio (модуль обязателен: `jira` или `wiki`; без него или с неизвестным — код 2); серверы называются `sdlc-jira` и `sdlc-wiki`; он ходит в API хаба клиентским токеном. Если хаб не запущен, инструменты +отвечают «хаб не запущен: sdlc-hub serve». Регистрация вручную (файл `~/.claude.json` скрипты хаба не правят), в user scope — MCP нужен и в `~/projects/jira-bridge`, и в `~/projects/awp-ca`, где ведут задачи AURORA: ```bash -claude mcp add -s user jira-hub -- uv run --project /Users/andrey.epifancev/projects/jira-bridge/jira-hub jira-hub mcp +claude mcp add -s user sdlc-jira -- uv run --project /Users/andrey.epifancev/projects/jira-bridge/sdlc-hub sdlc-hub mcp jira ``` После перезапуска хаба или обновления кода MCP — `/mcp` → reconnect. -MCP вики — отдельный сервер `jira-hub-wiki` (`jira-hub mcp wiki`). Регистрация в `.mcp.json` репозитория проекта (рядом с `jira-hub`, с привязкой к проекту): +MCP вики — отдельный сервер `sdlc-wiki` (`sdlc-hub mcp wiki`). Регистрация в `.mcp.json` репозитория проекта (рядом с `sdlc-jira`, с привязкой к проекту): ```json { "mcpServers": { - "jira-hub-wiki": { + "sdlc-wiki": { "command": "uv", - "args": ["run", "--project", "/Users/andrey.epifancev/projects/jira-bridge/jira-hub", "jira-hub", "mcp", "wiki", "--project", "AWPCA"] + "args": ["run", "--project", "/Users/andrey.epifancev/projects/jira-bridge/sdlc-hub", "sdlc-hub", "mcp", "wiki", "--project", "AWPCA"] } } } @@ -81,7 +81,7 @@ MCP вики — отдельный сервер `jira-hub-wiki` (`jira-hub mcp (метка сравнивается точно, с учётом регистра): ```json -{"mcpServers": {"jira-hub": {"command": "uv", "args": ["run", "--project", "/Users/andrey.epifancev/projects/jira-bridge/jira-hub", "jira-hub", "mcp", "--project", "AWPCA"]}}} +{"mcpServers": {"sdlc-jira": {"command": "uv", "args": ["run", "--project", "/Users/andrey.epifancev/projects/jira-bridge/sdlc-hub", "sdlc-hub", "mcp", "jira", "--project", "AWPCA"]}}} ``` Без привязки (user scope, без `--project`) работают только служебные инструменты (`projects`, `project_create`, @@ -117,7 +117,7 @@ MCP вики — отдельный сервер `jira-hub-wiki` (`jira-hub mcp В примере у `ensure_issue` нет `labels`: в привязанной сессии метку проекта дописывает хаб. Новый проект — `project_create`: в ответе «проект <метка> «<название>» заведён, пакет обновления #N», затем указание положить в корень репозитория проекта `.mcp.json` (слить с имеющимся) и переподключить MCP (`/mcp` → reconnect, одобрить сервер проекта) и сам фрагмент `.mcp.json` -с командой `uv run --project <корень хаба> jira-hub mcp --project <метка>` (модуль по умолчанию `jira`; явно — `jira-hub mcp jira --project <метка>`). +с командой `uv run --project <корень хаба> sdlc-hub mcp jira --project <метка>` (модуль указывается явно). «Ошибка» с файлом одним пакетом (нужна настройка `issue_types.bug`, см. «Контракт команд»): MCP читает файл по `path`, загружает его `POST /files` и ставит `ensure_attachment` с `file` и `filename` (по умолчанию — имя файла). Нет файла, @@ -134,16 +134,16 @@ MCP вики — отдельный сервер `jira-hub-wiki` (`jira-hub mcp Копию вики Eva хаб получает от `eva-agent` — отдельного агента на той же виртуалке (отдельный репозиторий `aep/eva-agent`, каталог `jira-bridge/eva-agent/`; Python 3.9, один файл `src/agent.py` + `run.py` — как jira-agent). Хаб в Eva не ходит и токен Eva не хранит. -1. `uv run jira-hub tokens` — взять токен агента `wiki` (у `jira` и `wiki` токены разные; чужой токен на маршрутах другого агента даёт 401). +1. `uv run sdlc-hub tokens` — взять токен агента `wiki` (у `jira` и `wiki` токены разные; чужой токен на маршрутах другого агента даёт 401). 2. На виртуалке отдельным каталогом положить eva-agent и `agent.json`: `hub_url` (`folder:<путь>` или http), `hub_token` — токен агента `wiki`, `eva: {base_url, token}`, `verify_ssl`, `ca_bundle`, `timeout`, `poll_seconds`, `auto_update`. Токен Eva — только здесь. Первый запуск делает владелец руками (свой процесс и блокировка). -3. Публикация версии для самообновления: `uv run jira-hub agent publish --module wiki ../eva-agent/src/agent.py` +3. Публикация версии для самообновления: `uv run sdlc-hub agent publish --module wiki ../eva-agent/src/agent.py` (файл ложится в `release/wiki/`; `min_agent_version` модуля — настройка `wiki_min_agent_version`). 4. Корень вики проекта: `project_create` с `wiki_root` или MCP `root_set DOC-…` / форма на странице «Вики» (`PATCH /projects/{метка}`) — хаб ставит `wiki_sync`; дальше синхронизация раз в `wiki_auto_sync_hours` (по умолчанию 24, `0` — выключена), кнопка «Обновить» или MCP `sync`. -Копия лежит в `~/.local/share/jira-hub/wiki/<метка>/` (Markdown, по каталогу на страницу). Настройки модуля: `wiki_pacing` (пауза агента между +Копия лежит в `~/.local/share/sdlc-hub/wiki/<метка>/` (Markdown, по каталогу на страницу). Настройки модуля: `wiki_pacing` (пауза агента между запросами к Eva, с), `wiki_auto_sync_hours`, `wiki_min_agent_version`. ## Транспорт через общую папку @@ -152,7 +152,7 @@ MCP вики — отдельный сервер `jira-hub-wiki` (`jira-hub mcp в хаб через общую папку UTM. Реле работает внутри `serve` и включается настройкой: ```bash -TOKEN=$(uv run jira-hub tokens | sed -n 's/^клиентский токен: //p') +TOKEN=$(uv run sdlc-hub tokens | sed -n 's/^клиентский токен: //p') curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ --data '{"relay_dir": "/Volumes/externalSSD/utm/rshb/jira-bridge"}' http://127.0.0.1:8790/settings ``` @@ -176,12 +176,12 @@ curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/j Опубликовать новую версию: ```bash -uv run jira-hub agent publish ../jira-agent/src/agent.py # модуль jira (по умолчанию) -uv run jira-hub agent publish --module wiki ../eva-agent/src/agent.py +uv run sdlc-hub agent publish ../jira-agent/src/agent.py # модуль jira (по умолчанию) +uv run sdlc-hub agent publish --module wiki ../eva-agent/src/agent.py ``` Версия берётся из строки `AGENT_VERSION = N` и должна быть больше опубликованной; файл и `manifest.json` ложатся в -`~/.local/share/jira-hub/release/<модуль>/` (0700). Перезапуск хаба не нужен: версия уходит агенту в ответе `claim` +`~/.local/share/sdlc-hub/release/<модуль>/` (0700). Перезапуск хаба не нужен: версия уходит агенту в ответе `claim` (`agent_release`), файл — по `GET /agent/release`. Агент ставит её в паузе между командами; через несколько минут `agent_status` (MCP) покажет «обновление: ok v<старая> → v<новая>». Если новая версия упала или не вышла на связь, запускатель вернёт прежнюю — `agent_status` покажет «откат с v …»; исправление публикуется следующим номером. @@ -228,25 +228,25 @@ uv run jira-hub agent publish --module wiki ../eva-agent/src/agent.py | `GET /settings`, `PUT /settings` | клиент | настройки (PUT частично, поднимает `settings_version`; ответ — все настройки после записи, как у GET); `base_jql` вычисляется из проектов (без проектов — `null`), запись `base_jql` и `board_id` — 422 | ```bash -TOKEN=$(uv run jira-hub tokens | sed -n 's/^клиентский токен: //p') +TOKEN=$(uv run sdlc-hub tokens | sed -n 's/^клиентский токен: //p') curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8790/issues?sprint=Спринт%202&limit=20" ``` ## Веб-приложение Адреса: `http://127.0.0.1:8790/app/` — доска `/app/board?project=<метка>`, задачи `/app/issues?project=<метка>`, лента изменений `/app/feed?project=<метка>` (период и виды событий, история — и в карточке задачи), карточка `/app/issues/{key}` — окно поверх списка или доски (закрытие — ✕, Esc, щелчок по фону; прямой адрес открывает окно поверх списка задач); `/` и -`/links` перенаправляют на доску. Вход по клиентскому токену (`uv run jira-hub tokens`), токен хранится в браузере; +`/links` перенаправляют на доску. Вход по клиентскому токену (`uv run sdlc-hub tokens`), токен хранится в браузере; `localhost` и `127.0.0.1` — разные сайты для браузера, токен и раскладка у каждого свои. Выкатка на Mac mini: ``` -cd ~/projects/jira-bridge/jira-hub +cd ~/projects/jira-bridge/sdlc-hub git pull -launchctl kickstart -k gui/$(id -u)/ru.aepif.jira-hub +launchctl kickstart -k gui/$(id -u)/ru.aepif.sdlc-hub make web ``` Порядок важен для версии со схемой v7 (модуль wiki): сразу после `git pull` — kickstart, потом `make web`. До kickstart не -запускать MCP и CLI `jira-hub` (`tokens`, `mcp`, `import`): они открывают базу и переводят её в v7, а хаб на прежнем коде +запускать MCP и CLI `sdlc-hub` (`tokens`, `mcp`, `import`): они открывают базу и переводят её в v7, а хаб на прежнем коде с такой базой не работает. Старый фронт с новым API совместим, сборка может подождать. Откат на копию v6 из `backups/` — вместе с возвратом `release/jira/*` в `release/` (прежняя раскладка), иначе старый код не найдёт публикацию агента. @@ -322,8 +322,8 @@ make web ## Пример сценария -1. `jira-hub serve` на Mac mini, агент запущен на виртуалке. -2. В Claude: `refresh` с `full` загружает задачи в зеркало; `jira-hub import` переносит заметки из CSV. +1. `sdlc-hub serve` на Mac mini, агент запущен на виртуалке. +2. В Claude: `refresh` с `full` загружает задачи в зеркало; `sdlc-hub import` переносит заметки из CSV. 3. `batch_create` ставит пакет; `batch_wait` ждёт результата; `issue` показывает итог. ## Эксплуатация @@ -341,7 +341,69 @@ make web перезапущен), хаб выдаёт её снова; `agent_status` показывает такие команды с числом выдач. После 3 выдач без отчёта команда становится `failed` («агент не отчитался после 3 выдач»), очередь идёт дальше; повтор — `command_retry`. Отменить взятую команду: `POST /commands/{id}/cancel` с `{"force": true}`. -- Автозапуск `serve` через launchd: `~/Library/LaunchAgents/ru.aepif.jira-hub.plist` +- Автозапуск `serve` через launchd: `~/Library/LaunchAgents/ru.aepif.sdlc-hub.plist`, лог `~/Library/Logs/sdlc-hub.log` + +```xml + + + + Labelru.aepif.sdlc-hub + ProgramArguments + /opt/homebrew/bin/uvrun--project + /Users/andrey.epifancev/projects/jira-bridge/sdlc-hub + sdlc-hubserve + + EnvironmentVariables + PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin + PYTHONUNBUFFERED1 + + RunAtLoad + KeepAlive + StandardOutPath/Users/andrey.epifancev/Library/Logs/sdlc-hub.log + StandardErrorPath/Users/andrey.epifancev/Library/Logs/sdlc-hub.log + +``` + +Загрузить: `launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ru.aepif.sdlc-hub.plist`. Перезапуск: `launchctl kickstart -k gui/$(id -u)/ru.aepif.sdlc-hub`. + +## Переключение с jira-hub + +Одноразовое переключение живой установки (хаб назывался `jira-hub`) делает скрипт `scripts/cutover.sh` (обёртка над +`scripts/cutover.py`, stdlib). Выполняет владелец; без `--apply` скрипт только проверяет и печатает шаги. + +1. Слить PR переименования; переименовать репозиторий в Gitea `aep/jira-hub` -> `aep/sdlc-hub`. +2. Закрыть или не трогать сессии Claude с MCP хаба (после переключения -- `/mcp` reconnect). +3. `git -C ~/projects/jira-bridge/jira-hub pull --ff-only`, сразу `bash ~/projects/jira-bridge/jira-hub/scripts/cutover.sh` + -- сухой прогон, проверить вывод (шаг 1 скрипта: чистый checkout на `main` = `origin/main`, доступен `aep/sdlc-hub`, + нет целевых каталогов, есть `uv`, `make`, `claude`, `launchctl`, `node`). Между pull и переключением работающий хаб + живёт на загруженном коде, а новые сессии Claude не поднимут MCP -- окно в несколько минут. +4. `bash ~/projects/jira-bridge/jira-hub/scripts/cutover.sh --apply`. Шаги скрипта, останавливается на первой ошибке: + 2. остановить `ru.aepif.jira-hub` и удалить его plist, дождаться закрытия порта 8790; + 3. `~/.local/share/jira-hub` -> `~/.local/share/sdlc-hub`; + 4. checkout `jira-bridge/jira-hub` -> `jira-bridge/sdlc-hub`, `git worktree repair` (включая `.wt/*`), `origin` -> `aep/sdlc-hub`, + новый `.venv`, `uv sync`, `make web`; + 5. поставить `ru.aepif.sdlc-hub` (plist выше), запустить, дождаться порта; + 6. править `.mcp.json`, `.claude/settings*.json`, `CLAUDE.md`, `.claude/skills` в `~/projects/awp-ca` и + `~/projects/stocks_drrk` (копии рядом: `*.pre-sdlc`); + 7. user scope: `claude mcp add -s user sdlc-jira -- uv run --project sdlc-hub mcp jira`, затем `claude mcp remove -s user jira-hub`. +5. Просмотреть правки в awp-ca и stocks_drrk, проверить оставшиеся упоминания `jira-hub` из вывода скрипта, удалить копии `*.pre-sdlc` + (это не git-репозитории -- коммитить нечего). `/mcp` reconnect; `codegraph init` в новом checkout и в каждом worktree `.wt/*`. +6. Проверить: `agent_status` через `sdlc-jira`, `status` через `sdlc-wiki`, веб `http://127.0.0.1:8790/app/` без повторного входа + (токен и раскладка доски переносятся из прежних ключей браузерного хранилища). + +В уже существующем окружении (`.venv` старого checkout) после переезда пакета нужен `uv sync --reinstall-package sdlc-hub` +(скрипт пересоздаёт `.venv` сам). + +### Откат + +Если скрипт остановился или хаб не поднялся (скрипт печатает «Откат: …» со своими путями): + +1. Остановить новый хаб: `launchctl bootout gui/$(id -u)/ru.aepif.sdlc-hub`; удалить `~/Library/LaunchAgents/ru.aepif.sdlc-hub.plist`. +2. Вернуть каталоги: `mv ~/.local/share/sdlc-hub ~/.local/share/jira-hub`; `mv ~/projects/jira-bridge/sdlc-hub ~/projects/jira-bridge/jira-hub`, + затем `git -C ~/projects/jira-bridge/jira-hub worktree repair` (и пути всех `.wt/*`). +3. Вернуть `origin` на `aep/jira-hub` (`git remote set-url origin …`; если репозиторий в Gitea уже переименован -- адрес прежнего имени + перенаправляется, но лучше убедиться) и `git checkout` коммита `main` перед слиянием PR `sdlc-rename` (найти в `git log`); `uv sync` и при необходимости `make web`. +4. Вернуть прежний plist `~/Library/LaunchAgents/ru.aepif.jira-hub.plist` и загрузить его (`launchctl bootstrap gui/$(id -u) …`): ```xml @@ -349,20 +411,25 @@ make web Labelru.aepif.jira-hub ProgramArguments - uvrun--project + /opt/homebrew/bin/uvrun--project /Users/andrey.epifancev/projects/jira-bridge/jira-hub jira-hubserve EnvironmentVariables PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin + PYTHONUNBUFFERED1 RunAtLoad KeepAlive - StandardErrorPath/tmp/jira-hub.err.log + StandardOutPath/Users/andrey.epifancev/Library/Logs/jira-hub.log + StandardErrorPath/Users/andrey.epifancev/Library/Logs/jira-hub.log ``` -Загрузить: `launchctl load ~/Library/LaunchAgents/ru.aepif.jira-hub.plist`. +5. Вернуть файлы проектов из копий: в `~/projects/awp-ca` и `~/projects/stocks_drrk` каждый `*.pre-sdlc` переместить поверх оригинала + (`mv файл.pre-sdlc файл`). +6. Вернуть MCP: `claude mcp add -s user jira-hub -- uv run --project ~/projects/jira-bridge/jira-hub jira-hub mcp`, затем + `claude mcp remove -s user sdlc-jira`; `/mcp` reconnect. ## Тесты