chore(openspec): архив sdlc-rename, Purpose спек hub-api, mcp, web-app под sdlc-hub (#97)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-24 15:58:10 +04:00
parent 93bb968235
commit b92d5e8582
15 changed files with 108 additions and 50 deletions

View File

@ -195,7 +195,7 @@ SHALL обнулить `issues_count` и `links_count` её `sync_run` (`started
передачи `board_id` агенту как запасной доски для команд без доски в параметрах (поставленных до миграции);
`GET /settings` SHALL отдавать вычисленную `base_jql` только для чтения, а запись `base_jql` или `board_id` через
`PUT /settings` — 422 с пояснением. Спринты агент читает с досок проектов, отдельной настройки имён спринтов нет. Начальные значения SHALL импортироваться из `admin/jira-sync/config.example.json`
командой `jira-hub init`; пароль и логин Jira в настройках MUST NOT присутствовать. `issue_types` — объект
командой `sdlc-hub init`; пароль и логин Jira в настройках MUST NOT присутствовать. `issue_types` — объект
`{task, epic, bug}`, у каждого `{name, id}` (`id` может быть `null`); `init` берёт их из ключей конфига
`issue_type_<тип>` и `issue_type_<тип>_id`; `bug` попадает в `issue_types`, только если в конфиге есть хотя бы
один из ключей `issue_type_bug`, `issue_type_bug_id` (`task` и `epic` — как раньше, всегда).
@ -205,7 +205,7 @@ SHALL обнулить `issues_count` и `links_count` её `sync_run` (`started
- **THEN** `settings_version` увеличен, следующий `claim` отдаёт агенту новую версию
#### Scenario: Конфиг без ключей ошибки
- **WHEN** `jira-hub init --config` с `issue_type_task`, `issue_type_epic`, без `issue_type_bug`
- **WHEN** `sdlc-hub init --config` с `issue_type_task`, `issue_type_epic`, без `issue_type_bug`
- **THEN** `issue_types` содержит `task` и `epic`, `bug` нет; постановка `ensure_issue` с `issue_type = bug` — 422
#### Scenario: Неверный relay_dir

View File

@ -1,16 +1,16 @@
# hub-api Specification
## Purpose
HTTP API хаба `jira-hub serve`: маршруты агента (выдача команд, отчёты, загрузка состояния), маршруты интерфейсов (зеркало, заметки, пакеты, настройки), резервные копии, автообновление зеркала и реле общей папки для агента за VPN.
HTTP API хаба `sdlc-hub serve`: маршруты агента (выдача команд, отчёты, загрузка состояния), маршруты интерфейсов (зеркало, заметки, пакеты, настройки), резервные копии, автообновление зеркала и реле общей папки для агента за VPN.
## Requirements
### Requirement: Процесс и доступ
`jira-hub serve` SHALL запускать один процесс: HTTP на `0.0.0.0:<port>` (по умолчанию 8790), база и настройки в
`~/.local/share/jira-hub/` (переопределяется `JIRA_HUB_HOME`). Все маршруты API SHALL требовать
`sdlc-hub serve` SHALL запускать один процесс: HTTP на `0.0.0.0:<port>` (по умолчанию 8790), база и настройки в
`~/.local/share/sdlc-hub/` (переопределяется `SDLC_HUB_HOME`). Все маршруты API SHALL требовать
`Authorization: Bearer <token>`: токен агента модуля открывает только `/agent/*` (маршруты ядра — от имени этого агента,
маршруты модуля со scope `agent` — только агенту своего модуля, см. `modules`, «Агенты модулей»), клиентский — всё
остальное. `jira-hub tokens` SHALL печатать клиентский токен и токены агентов с именами модулей. Исключение —
остальное. `sdlc-hub tokens` SHALL печатать клиентский токен и токены агентов с именами модулей. Исключение —
раздача веб-приложения (`GET /app`, `GET /app/…`) и перенаправления `GET /`, `GET /links`: это статические файлы без
данных, они отдаются без токена. Ответы API — JSON, ошибки — `{error, detail}` с кодами 400 (неверное тело или
`Content-Length`), 401, 404, 409, 413 (тело больше предела), 422, 500 (внутренняя ошибка); реле общей папки
@ -30,7 +30,7 @@ JSON.
- **THEN** 200, `Content-Type: text/html; charset=utf-8`, тело — `index.html` сборки; данных зеркала в нём нет
#### Scenario: Токены после миграции
- **WHEN** база v6 с агентским токеном T мигрировала в v7, запущен `jira-hub tokens`
- **WHEN** база v6 с агентским токеном T мигрировала в v7, запущен `sdlc-hub tokens`
- **THEN** вывод содержит клиентский токен, `jira: T` и новый токен `wiki`
### Requirement: Маршруты агента
@ -249,7 +249,7 @@ SHALL скопировать файл базы в `backups/<дата-время>
- **THEN** поставлен один `refresh` с JQL `project = AURORA AND labels in ("AWPCA", "STOCKS_DRRK")`
### Requirement: Реле через общую папку
`jira-hub serve` SHALL запускать поток реле вместе с HTTP-сервером. Пока настройка `relay_dir` пуста (`null`), реле
`sdlc-hub serve` SHALL запускать поток реле вместе с HTTP-сервером. Пока настройка `relay_dir` пуста (`null`), реле
бездействует; настройку оно SHALL перечитывать не реже раза в 5 с, включение и выключение не требуют перезапуска. Если
каталог `relay_dir` существует, реле SHALL создать в нём подкаталоги `req/`, `resp/`, `work/`; если не существует —
записать одно предупреждение в лог и проверять снова.
@ -340,10 +340,10 @@ SHALL скопировать файл базы в `backups/<дата-время>
- **THEN** 401
### Requirement: Публикация версии агента
`jira-hub agent publish [--module <модуль>] PATH` SHALL публиковать файл агента модуля (по умолчанию `jira`;
`sdlc-hub agent publish [--module <модуль>] PATH` SHALL публиковать файл агента модуля (по умолчанию `jira`;
неизвестный модуль или модуль без команд — код 2 и список модулей с агентами в stderr): текст в UTF-8 не больше 1 МБ,
ровно одна строка `AGENT_VERSION = <целое>`, версия больше опубликованной для этого модуля. Файл SHALL записываться в
`<JIRA_HUB_HOME>/release/<модуль>/agent.py` (каталоги 0700), затем `manifest.json` `{version, sha256, size,
`<SDLC_HUB_HOME>/release/<модуль>/agent.py` (каталоги 0700), затем `manifest.json` `{version, sha256, size,
published_at}`; оба — через временный файл с `fsync` и переименование, манифест последним. Прежний `agent.py` на время
публикации SHALL переименовываться в `agent.py.prev` и удаляться после записи манифеста; если запись нового файла или
манифеста не удалась (`OSError`), хаб SHALL вернуть `agent.py.prev` на место `agent.py`, удалить временные файлы и
@ -363,17 +363,17 @@ published_at}` или `null`), `update_state`, `launcher_version`, `agent_report
(`rolled_back`), «v<to> отвергнута: <error>» (`rejected`), иначе `null`.
#### Scenario: Публикация новой версии
- **WHEN** опубликована v5, `jira-hub agent publish jira-agent/src/agent.py` с `AGENT_VERSION = 6`
- **WHEN** опубликована v5, `sdlc-hub agent publish jira-agent/src/agent.py` с `AGENT_VERSION = 6`
- **THEN** в `release/jira/` — новый `agent.py` и манифест с `version = 6`, `agent.py.prev` нет; вывод «опубликована v6,
sha256 …»; код 0
#### Scenario: Публикация eva-agent
- **WHEN** `jira-hub agent publish --module wiki eva-agent/src/agent.py` с `AGENT_VERSION = 1`, опубликована jira v14
- **WHEN** `sdlc-hub agent publish --module wiki eva-agent/src/agent.py` с `AGENT_VERSION = 1`, опубликована jira v14
- **THEN** в `release/wiki/` — v1; `claim` агента `wiki` получает `agent_release {version: 1, …}`, `claim` агента `jira` —
`{version: 14, …}`
#### Scenario: Неизвестный модуль
- **WHEN** `jira-hub agent publish --module nope agent.py`
- **WHEN** `sdlc-hub agent publish --module nope agent.py`
- **THEN** код 2, в stderr «неизвестный модуль nope» и список модулей с агентами; `release/` не изменён
#### Scenario: Версия не выше опубликованной
@ -407,7 +407,7 @@ published_at}` или `null`), `update_state`, `launcher_version`, `agent_report
- **THEN** публикация v14 лежит в `release/jira/`, `claim` агента `jira` получает `agent_release {version: 14, …}`
### Requirement: Раздача веб-приложения
Хаб SHALL отдавать собранное веб-приложение (спека `web-app`) из каталога пакета `src/jira_hub/web/app/`, читая файлы
Хаб SHALL отдавать собранное веб-приложение (спека `web-app`) из каталога пакета `src/sdlc_hub/web/app/`, читая файлы
с диска на каждый запрос:
- `GET /app/assets/<путь>` — файл из `assets/` сборки; путь, который после разрешения выходит за `assets/`, не
существует или содержит нулевой байт, — 404; `Content-Type` по расширению (`.js` — `text/javascript; charset=utf-8`, `.css` —
@ -416,7 +416,7 @@ published_at}` или `null`), `update_state`, `launcher_version`, `agent_report
`Cache-Control: public, max-age=31536000, immutable`;
- любой другой `GET /app` или `GET /app/…` — `index.html` сборки, `text/html; charset=utf-8`, `Cache-Control: no-cache`;
- если `index.html` сборки нет — на любой `GET /app…` 503 `text/html; charset=utf-8` с текстом, что приложение не
собрано и нужно выполнить `make web` в каталоге jira-hub;
собрано и нужно выполнить `make web` в каталоге sdlc-hub;
- `GET /` и `GET /links` — 302 с `Location: /app/board`.
Сборка приложения не хранится в git; её создаёт `make web`.
@ -665,3 +665,52 @@ from_string, to, to_string}`. До записи хаб SHALL проверить
#### Scenario: Два вложения с одним именем
- **WHEN** у задачи два вложения «скрин.png» (#10 от 14.10, #20 от 20.10); комментарий — «!скрин.png!»
- **THEN** у комментария `attachment_ids = ["20"]`
### Requirement: Каталог данных прежнего имени
Если `SDLC_HUB_HOME` не задан, `~/.local/share/sdlc-hub/` не существует, а `~/.local/share/jira-hub/` существует, любая
команда `sdlc-hub`, которой нужен каталог данных (`serve`, `mcp`, `tokens`, `init`, `import`, `agent publish`), SHALL
завершиться с кодом 2 и текстом в stderr: каталог данных лежит под прежним именем, перенести его —
`scripts/cutover.sh` (или `mv ~/.local/share/jira-hub ~/.local/share/sdlc-hub` при остановленном хабе). Команда MUST NOT
создавать новый каталог, переносить или менять прежний. Если существуют оба каталога, используется новый, в stderr —
предупреждение, что прежний можно удалить после проверки.
#### Scenario: Код обновлён, каталог не перенесён
- **WHEN** есть только `~/.local/share/jira-hub/`, запущен `sdlc-hub serve`
- **THEN** код выхода 2, в stderr — подсказка о переносе; `~/.local/share/sdlc-hub/` не создан, прежний каталог не тронут
#### Scenario: Задан SDLC_HUB_HOME
- **WHEN** `SDLC_HUB_HOME=/tmp/x`, есть `~/.local/share/jira-hub/`
- **THEN** команда работает с `/tmp/x`, проверка прежнего каталога не выполняется
### Requirement: Переключение живого хаба
`scripts/cutover.sh` SHALL переводить живую установку Mac mini со старого имени на новое. Без `--apply` скрипт SHALL
только печатать шаги и найденные проблемы, ничего не меняя. С `--apply` — по порядку, с остановкой на первой ошибке:
1. проверки: основной checkout `jira-bridge/jira-hub` чистый, на `main` и совпадает с `origin/main` (владелец сделал
`git pull --ff-only` прямо перед запуском: скрипт приходит этим pull; неотслеживаемая прежняя сборка `src/jira_hub/`
без tracked-файлов не считается грязью); в Gitea доступен `aep/sdlc-hub` (владелец переименовал репозиторий; адрес
выводится из `origin` заменой `/aep/jira-hub` на `/aep/sdlc-hub`, протокол сохраняется, `CUTOVER_REMOTE`
переопределяет); есть `uv`, `make`, `node`, `npm`, `claude`, `launchctl`; `~/.local/share/sdlc-hub/` и `jira-bridge/sdlc-hub` не существуют;
2. остановить launchd `ru.aepif.jira-hub`, дождаться закрытия порта и переименовать его plist в `*.pre-sdlc`;
3. перенести каталог данных `~/.local/share/jira-hub` → `~/.local/share/sdlc-hub`;
4. перенести checkout `jira-bridge/jira-hub` → `jira-bridge/sdlc-hub`, удалить неотслеживаемый `src/jira_hub`,
`git worktree repair`, `origin` → `aep/sdlc-hub`, пересоздать `.venv` (`uv sync`), `make web`;
5. поставить и запустить launchd `ru.aepif.sdlc-hub` (`uv run --project <checkout> sdlc-hub serve`, лог
`~/Library/Logs/sdlc-hub.log`) и дождаться ответа хаба на порту из настроек;
6. в проектах-потребителях (по умолчанию `~/projects/awp-ca`, `~/projects/stocks_drrk` и каждый
`~/projects/*/.mcp.json`, где есть `jira-hub`, например `sfm_foxpro`; сухой прогон печатает полный список): в `.mcp.json` заменить записи
`jira-hub` → `sdlc-jira` (`sdlc-hub mcp jira`) и `jira-hub-wiki` → `sdlc-wiki` (`sdlc-hub mcp wiki`) с новым путём; в
`.claude/settings*.json` — имена серверов (`enabledMcpjsonServers` и т. п.) и разрешения `mcp__jira-hub__` →
`mcp__sdlc-jira__`, `mcp__jira-hub-wiki__` → `mcp__sdlc-wiki__`; в `CLAUDE.md` и `.claude/skills/**` — те же имена
серверов и инструментов. Прочие файлы проектов (заметки, отчёты) не трогаются. Перед правкой файл копируется рядом
с суффиксом `.pre-sdlc`; коммиты скрипт не делает;
7. в user scope Claude Code заменить `jira-hub` на `sdlc-jira` (`claude mcp remove`/`add`).
Каждый выполненный шаг SHALL печататься; в конце — сводка, что осталось владельцу (`/mcp` reconnect в открытых сессиях,
`codegraph init` в checkout и worktree, удалить копии `.pre-sdlc` после проверки). Шаги откатываются в обратном порядке по инструкции из README.
#### Scenario: Сухой прогон
- **WHEN** `bash scripts/cutover.sh` без аргументов
- **THEN** напечатаны шаги и проверки, файловая система, launchd и конфигурации Claude не изменены
#### Scenario: Репозиторий в Gitea не переименован
- **WHEN** `bash scripts/cutover.sh --apply`, `aep/sdlc-hub` в Gitea не найден
- **THEN** скрипт останавливается на шаге 1 с подсказкой, launchd и каталоги не тронуты

View File

@ -1,30 +1,35 @@
# mcp Specification
## Purpose
MCP-сервер хаба (`jira-hub mcp`, stdio) для Claude: инструменты чтения зеркала и заметок, постановки и отслеживания пакетов команд, обновления зеркала, статуса агента и настроек — поверх HTTP API хаба с клиентским токеном.
MCP-серверы модулей хаба (`sdlc-hub mcp <модуль>`, stdio) для Claude: инструменты чтения зеркала и заметок, постановки и отслеживания пакетов команд, обновления зеркала, статуса агента и настроек — поверх HTTP API хаба с клиентским токеном.
## 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», а не исключение.
`sdlc-hub mcp <модуль> [--project <метка>]` SHALL запускать MCP-сервер модуля хаба по stdio (пакет `mcp`), ходящий в
HTTP API хаба клиентским токеном из настроек; сервер называется `sdlc-<модуль>` (`sdlc-jira`, `sdlc-wiki`). Модуль
обязателен: без него — код выхода 2 и список модулей в stderr, сервер не запускается; неизвестный модуль — так же.
Общая регистрация — в user scope Claude Code как `sdlc-jira` (`uv run --project <путь> sdlc-hub mcp jira`), без
проекта. Репозиторий проекта SHALL регистрировать серверы модулей в своём `.mcp.json` (project scope) с
`--project <метка>`; эта запись перекрывает общую. Если хаб недоступен, каждый инструмент SHALL вернуть текст «хаб не
запущен: sdlc-hub serve», а не исключение.
#### Scenario: Хаб выключен
- **WHEN** вызван `issues` при остановленном хабе
- **THEN** ответ инструмента — одна строка с подсказкой, сервер MCP продолжает работать
#### Scenario: Сервер модуля jira
- **WHEN** запущен `sdlc-hub mcp jira --project AWPCA`
- **THEN** сервер называется `sdlc-jira`, список инструментов и их описания совпадают с прежним сервером `jira-hub`,
сессия привязана к `AWPCA`
#### Scenario: Модуль по умолчанию
- **WHEN** запущены `jira-hub mcp --project AWPCA` и `jira-hub mcp jira --project AWPCA`
- **THEN** оба сервера называются `jira-hub`, список инструментов и их описания совпадают, сессия привязана к `AWPCA`
- **WHEN** запущен `sdlc-hub mcp --project AWPCA`
- **THEN** модуля по умолчанию больше нет: код выхода 2, в stderr — подсказка указать модуль и список модулей (`jira`, `wiki`)
#### Scenario: Неизвестный модуль
- **WHEN** запущен `jira-hub mcp nope`
- **THEN** код выхода 2, в stderr — «неизвестный модуль nope» и список модулей (`jira`)
- **WHEN** запущен `sdlc-hub mcp nope`
- **THEN** код выхода 2, в stderr — «неизвестный модуль nope» и список модулей (`jira`, `wiki`)
### Requirement: Инструменты
| Инструмент | Параметры | Результат |
@ -61,7 +66,7 @@ user scope Claude Code как `jira-hub` (`uv run --project <путь> jira-hub
других проектов запрещены, а команды над задачами вне проекта отклоняются; `board_id` для `ensure_sprint` выставляет хаб.
Ответ `project_create` SHALL содержать фрагмент `.mcp.json`
`{"mcpServers": {"jira-hub": {"command": "uv", "args": ["run", "--project", "<корень хаба>", "jira-hub", "mcp",
`{"mcpServers": {"sdlc-jira": {"command": "uv", "args": ["run", "--project", "<корень хаба>", "sdlc-hub", "mcp", "jira",
"--project", "<метка>"]}}}`, где корень хаба — корень репозитория, из которого загружен пакет текущего процесса MCP
(вычисляется от расположения пакета, без зашитого пути), и указание: положить файл в корень репозитория проекта (слить
с имеющимся `.mcp.json`) и переподключить MCP (`/mcp` → reconnect, одобрить сервер проекта).
@ -121,7 +126,7 @@ user scope Claude Code как `jira-hub` (`uv run --project <путь> jira-hub
- **THEN** ответ «у AURORA-9001 нет комментария #999»
### Requirement: Привязка сессии к проекту
`jira-hub mcp --project <метка>` SHALL привязывать сервер MCP к проекту. Привязка SHALL проверяться при каждом вызове
`sdlc-hub mcp <модуль> --project <метка>` SHALL привязывать сервер MCP к проекту. Привязка SHALL проверяться при каждом вызове
инструмента по `GET /projects`: метки нет — ответ «проект <метка> не заведён в хабе: project_create или исправьте
--project в .mcp.json» (кроме `projects`, `project_create`).
@ -226,7 +231,7 @@ user scope Claude Code как `jira-hub` (`uv run --project <путь> jira-hub
- **WHEN** `comment {key: "AURORA-1650", comment_id: "101"}` показал «вложения: #31337 скрин 1.png (234,4 КБ)»; вызван
`attachment {key: "AURORA-1650", attachment: "#31337"}`, агент онлайн, файла у хаба нет
- **THEN** поставлена `fetch_attachment`; через несколько секунд ответ — путь
`…/jira-hub/downloads/31337/скрин 1.png` и строка «скрин 1.png (234,4 КБ), image/png, задача AURORA-1650»; файл по пути
`…/sdlc-hub/downloads/31337/скрин 1.png` и строка «скрин 1.png (234,4 КБ), image/png, задача AURORA-1650»; файл по пути
читается
#### Scenario: Повторный вызов
@ -249,14 +254,14 @@ user scope Claude Code как `jira-hub` (`uv run --project <путь> jira-hub
комментария 101 строка «файлы: #31337 скрин 1.png»
### Requirement: MCP вики
`jira-hub mcp wiki [--project <метка>]` SHALL запускать по stdio MCP-сервер модуля `wiki` с именем `jira-hub-wiki`,
ходящий в HTTP API хаба клиентским токеном; хаб недоступен — каждый инструмент отвечает «хаб не запущен: jira-hub serve».
`sdlc-hub mcp wiki [--project <метка>]` SHALL запускать по stdio MCP-сервер модуля `wiki` с именем `sdlc-wiki`,
ходящий в HTTP API хаба клиентским токеном; хаб недоступен — каждый инструмент отвечает «хаб не запущен: sdlc-hub serve».
Привязка к проекту проверяется так же, как у сервера jira («Привязка сессии к проекту»). Инструменты:
| Инструмент | Параметры | Ответ |
|---|---|---|
| `projects` | — | таблица: метка, название, корень вики, страниц, последняя синхронизация; текущий проект отмечен |
| `project_create` | `label`, `name`, `wiki_root` (необязательно) | `POST /projects` с автором `claude`; проект и фрагмент `.mcp.json` с записью `jira-hub-wiki` |
| `project_create` | `label`, `name`, `wiki_root` (необязательно) | `POST /projects` с автором `claude`; проект и фрагмент `.mcp.json` с записью `sdlc-wiki` |
| `root_set` | `root` | `PATCH /projects/<текущий> {wiki_root}`; новый корень и id пакета синхронизации |
| `tree` | `code` (необязательно), `depth` (по умолчанию 2) | дерево от корня или от `code`: отступ, код, название, размер |
| `page` | `code` или `title`, `offset` (по умолчанию 0) | шапка (код, путь в дереве, адрес в Eva, дата синхронизации, вложения с `id`) и Markdown с `offset` до 20 000 символов, в конце — «продолжение: offset=<N>», если текст длиннее; `title` ищется точно, иначе — до 10 кандидатов с кодами |
@ -267,7 +272,7 @@ user scope Claude Code как `jira-hub` (`uv run --project <путь> jira-hub
Инструменты, кроме `projects` и `project_create`, SHALL требовать привязки к проекту. В привязанной сессии без корня
(кроме `root_set`, `status`) ответ SHALL быть «у проекта <метка> нет корня вики: root_set DOC-…»; с корнем, но без
синхронизаций — «вики проекта ещё не синхронизирована: sync». Набор инструментов и их описания у сервера `jira-hub`
синхронизаций — «вики проекта ещё не синхронизирована: sync». Набор инструментов и их описания у сервера `sdlc-jira`
(модуль jira) SHALL остаться прежним.
#### Scenario: Поиск из сессии awp-ca
@ -284,5 +289,5 @@ user scope Claude Code как `jira-hub` (`uv run --project <путь> jira-hub
- **THEN** ответ «у проекта sfm_kedr нет корня вики: root_set DOC-…», запроса `GET /wiki/tree` нет
#### Scenario: Сервер jira не изменился
- **WHEN** запущен `jira-hub mcp --project AWPCA`
- **THEN** имя сервера `jira-hub`, список инструментов и описания совпадают с эталоном до change
- **WHEN** запущен `sdlc-hub mcp jira --project AWPCA`
- **THEN** имя сервера `sdlc-jira`, список инструментов и описания совпадают с эталоном до change

View File

@ -6,7 +6,7 @@
## Requirements
### Requirement: Инициализация настроек
`jira-hub init --config <admin/jira-sync/config.example.json>` SHALL перенести в `setting`: `project_key`, типы задач,
`sdlc-hub init --config <admin/jira-sync/config.example.json>` SHALL перенести в `setting`: `project_key`, типы задач,
`epic_name_field_id`, `fields`, `epic_link`, `priority_map`, `labels`, `status_labels`, `kind_labels`, `comment_marker`,
`pacing` (в новую форму по типам команд), `transitions.map`, `link_types`; сгенерировать два токена; завести проект
`AWPCA` «АРМ КА» с `board_id` из `sprints.board_id` конфига (если проекта `AWPCA` ещё нет). Поля `auth`, `base_url`,
@ -21,7 +21,7 @@
- **THEN** есть проект `AWPCA` «АРМ КА» с `board_id = 4216`; в `setting` нет `base_jql` и `board_id`
### Requirement: Импорт CSV-пакета
`jira-hub import <папка admin/jira-sync>` SHALL прочитать `tasks.csv`, `new_tasks.csv`, `epics.csv` и заполнить
`sdlc-hub import <папка admin/jira-sync>` SHALL прочитать `tasks.csv`, `new_tasks.csv`, `epics.csv` и заполнить
`issue_note` по ключу Jira: статус по факту, вид работы, что сделано, что осталось, внутренние колонки. Строки
`new_tasks.csv` без ключа SHALL получать временный ключ `new:<номер строки>`; строки `tasks.csv` и `epics.csv` без ключа
SHALL пропускаться. `links.csv` SHALL импортироваться как сверочный набор в отчёт импорта (ожидаемые связи против зеркала

View File

@ -1,20 +1,24 @@
# web-app Specification
## Purpose
Веб-приложение хаба (`web/`, React + TypeScript + Vite) под префиксом `/app/`: вход по клиентскому токену, доска связей (перенос `links.html`) и страницы задач со служебными заметками; сборка в `src/jira_hub/web/app/`.
Веб-приложение хаба (`web/`, React + TypeScript + Vite) под префиксом `/app/`: вход по клиентскому токену, доска связей (перенос `links.html`) и страницы задач со служебными заметками; сборка в `src/sdlc_hub/web/app/`.
## Requirements
### Requirement: Вход по токену
Приложение SHALL работать с API хаба того же хоста под клиентским токеном: хранить его в `localStorage` под ключом
`jira-hub-token` (и в памяти, если хранилище закрыто) и передавать в заголовке `Authorization: Bearer` каждого запроса.
`sdlc-hub-token` (и в памяти, если хранилище закрыто) и передавать в заголовке `Authorization: Bearer` каждого запроса.
Если токена нет или любой запрос ответил 401, приложение SHALL показывать экран входа вместо страниц: поле токена с
подсказкой `uv run jira-hub tokens`, без панелей страниц и без фоновых запросов, которые могли бы изменить поле ввода.
подсказкой `uv run sdlc-hub tokens`, без панелей страниц и без фоновых запросов, которые могли бы изменить поле ввода.
Введённый токен SHALL проверяться запросом `GET /settings` и сохраняться только при успехе. Действие «Сменить токен»
SHALL очищать токен и показывать экран входа. Ответ 401 SHALL выводить на экран входа, только если запрос ушёл с текущим токеном: запоздавший 401 на
запрос с прежним токеном после входа с новым не учитывается. Если проверка токена получила ответ 4xx, кроме 401,
экран входа SHALL показывать текст ошибки хаба, а не «хаб не отвечает».
Ключи `localStorage` приложения с префиксом `sdlc-hub-` (`sdlc-hub-token`, `sdlc-hub-sidebar`, `sdlc-hub-legend`) SHALL
при первом чтении, если ключа нет, брать значение одноимённого ключа с префиксом `jira-hub-`, записывать его под новым
ключом и удалять прежний.
#### Scenario: Первый вход
- **WHEN** приложение открыто впервые, токена нет
- **THEN** показан экран входа; после ввода верного токена открывается запрошенная страница, токен сохранён
@ -24,8 +28,8 @@ SHALL очищать токен и показывать экран входа.
- **THEN** показан экран входа с пояснением, что токен не подошёл; панели доски и задач не видны
#### Scenario: Токен от прежней доски
- **WHEN** в `localStorage` есть `jira-hub-token`, сохранённый страницей `links.html`
- **THEN** приложение входит с ним без запроса токена
- **WHEN** в `localStorage` нет `sdlc-hub-token`, но есть `jira-hub-token` (сохранён до переименования хаба)
- **THEN** приложение входит с ним без запроса токена; значение перенесено в `sdlc-hub-token`, `jira-hub-token` удалён
#### Scenario: Запоздавший 401 от прежнего токена
- **WHEN** запрос ушёл с прежним токеном, пользователь вошёл с новым, и после этого запрос ответил 401
@ -40,13 +44,13 @@ SHALL очищать токен и показывать экран входа.
задач), `/app/feed` (лента изменений, требование «Лента изменений») и `/app/issues/{key}` (карточка задачи — окно поверх страницы, требование «Карточка задачи»); `/app` и `/app/` SHALL вести на `/app/board`, неизвестный путь —
на страницу «не найдено» со ссылками на разделы. Боковая панель SHALL быть закреплена у левого края окна на всю его
высоту и MUST NOT прокручиваться вместе со страницей; страница занимает остальную ширину. Сверху вниз панель SHALL
содержать: название «Хаб Jira» и кнопку «Свернуть панель»; выпадающий список проектов («<метка> — <название>», текущий
содержать: название «SDLC Hub» и кнопку «Свернуть панель»; выпадающий список проектов («<метка> — <название>», текущий
отмечен, в конце «+ Новый проект…»; требование «Проект в приложении»); разделы «Доска связей», «Задачи» и «Лента» — вертикальным
списком ссылок со значками (текущий отмечен `aria-current="page"`); внизу — блок данных Jira с действием «Обновить из
Jira» (требование «Обновление из Jira»), переключатель темы (требование «Тема») и меню с действием «Сменить токен».
Свёрнутая панель SHALL быть узкой полосой с теми же действиями значками: «Развернуть панель», метка текущего проекта,
разделы, «Обновить из Jira» с точкой состояния агента, тема, меню; названия — в подсказках и `aria-label`. Свёртка
SHALL храниться в `localStorage` под ключом `jira-hub-sidebar` (`collapsed`; без записи — развёрнута). На экранах уже
SHALL храниться в `localStorage` под ключом `sdlc-hub-sidebar` (`collapsed`; без записи — развёрнута). На экранах уже
640 px панели в потоке нет: сверху SHALL быть закреплена строка с кнопкой «Меню», названием и кнопкой «Обновить из
Jira»; «Меню» открывает развёрнутую панель поверх страницы, панель закрывается выбором раздела, клавишей Escape или
щелчком мимо неё. Горизонтальной прокрутки страницы на любой ширине нет.
@ -116,7 +120,7 @@ Jira»; «Меню» открывает развёрнутую панель по
управления: мультиселекты «Спринты» и «Исполнители», переключатели группы «Показать» («Соседи по связям», «Только
передачи», «Скрыть без связей») и группы «Режим» («Критический путь», «По датам»), каждый с `aria-pressed` и прежней
подсказкой. Под панелью — легенда, которая сворачивается кнопкой «Легенда»; свёрнутая показывает число скрытых видов
работ и типов связей; состояние свёртки хранится в `localStorage` под ключом `jira-hub-legend`. «Обновить из Jira»,
работ и типов связей; состояние свёртки хранится в `localStorage` под ключом `sdlc-hub-legend`. «Обновить из Jira»,
тема и «Сменить токен» — в боковой панели приложения, на доске их нет.
У карточки задачи (не у заглушки) SHALL быть значок «Открыть карточку»,
@ -367,15 +371,15 @@ SHALL сообщать об этом с кнопкой «Закрыть»; ха
### Requirement: Сборка и разработка
Приложение SHALL быть npm-проектом в `web/` (React, TypeScript, Vite) с точными версиями зависимостей и
`package-lock.json`. `make web` SHALL устанавливать зависимости (`npm ci`) и собирать приложение в
`src/jira_hub/web/app/`; каталог сборки не хранится в git. Шрифты (Mulish — текст интерфейса, IBM Plex Mono — ключи
`src/sdlc_hub/web/app/`; каталог сборки не хранится в git. Шрифты (Mulish — текст интерфейса, IBM Plex Mono — ключи
задач и исходники), иконки и прочие ресурсы страницы SHALL входить в сборку: приложение не обращается к внешним
адресам. `make check` SHALL запускать `uv run pytest`, проверку типов и линтер фронта и тесты фронта; зелёным считается
только весь набор. `npm run dev` SHALL поднимать сервер разработки с прокси маршрутов API на хаб из `JIRA_HUB_URL` (по
только весь набор. `npm run dev` SHALL поднимать сервер разработки с прокси маршрутов API на хаб из `SDLC_HUB_URL` (по
умолчанию тестовый `http://127.0.0.1:8791`).
#### Scenario: Сборка с нуля
- **WHEN** в чистом checkout выполнен `make web`
- **THEN** в `src/jira_hub/web/app/` есть `index.html` и `assets/`; `git status` не показывает файлов сборки
- **THEN** в `src/sdlc_hub/web/app/` есть `index.html` и `assets/`; `git status` не показывает файлов сборки
#### Scenario: Без интернета
- **WHEN** приложение открыто на машине без доступа в интернет

View File

@ -81,7 +81,7 @@ links: [{ref, code}]}`, где `attachments` — ссылки и картинк
- **THEN** 409 `sync_in_progress` с `batch_id` текущего пакета, нового пакета нет
### Requirement: Markdown-копия вики
Копия проекта SHALL лежать в `<JIRA_HUB_HOME>/wiki/<метка>/` (каталоги 0700): у каждой страницы — каталог
Копия проекта SHALL лежать в `<SDLC_HUB_HOME>/wiki/<метка>/` (каталоги 0700): у каждой страницы — каталог
`NN_<название>` внутри каталога родителя (`NN` — двузначная позиция среди соседей по `orderno`, затем по коду; у корня
номера нет; название без символов `<>:"/\|?*` и управляющих, обрезано до 80 символов; совпадение у соседей —
суффикс `_2`, `_3`), в нём `index.md`: frontmatter `code`, `title`, `url`, `synced_at`, строка `# <title>` и текст. В