186 lines
16 KiB
Markdown
186 lines
16 KiB
Markdown
## ADDED Requirements
|
||
|
||
### Requirement: Маршруты проектов
|
||
Хаб SHALL предоставлять под клиентским токеном:
|
||
- `GET /projects` → список `{label, name, board_id, board_type, created_at, issues_count}` по `created_at`, затем `label`;
|
||
`issues_count` — задачи зеркала с меткой проекта.
|
||
- `POST /projects` `{label, name, board_id?, author?}` → 201 `{project, batch_id}`: в одной транзакции проект
|
||
записывается и ставится пакет «Новый проект <метка>» из одного `refresh` по базовой JQL, уже включающей новую метку;
|
||
автор пакета — `author` (по умолчанию `api`). Неверные поля (см. `data-model`, «Проекты») — 422 с указанием поля;
|
||
метка, совпадающая с существующей без учёта регистра, — 409. При ошибке ни проекта, ни пакета нет.
|
||
|
||
#### Scenario: Новый проект
|
||
- **WHEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК", board_id: 5120, author: "viewer"}`, есть проект `AWPCA`
|
||
- **THEN** 201; `GET /projects` отдаёт два проекта, у `STOCKS_DRRK` `board_type = null`; в очереди пакет автора `viewer`
|
||
с `refresh {jql: "project = AURORA AND labels in (AWPCA, STOCKS_DRRK)"}`
|
||
|
||
#### Scenario: Проект без доски
|
||
- **WHEN** `POST /projects {label: "STOCKS_DRRK", name: "Запасы ДРРК"}`
|
||
- **THEN** 201, `board_id = null`
|
||
|
||
#### Scenario: Повтор метки
|
||
- **WHEN** `POST /projects {label: "AWPCA", name: "…"}`, проект `AWPCA` есть
|
||
- **THEN** 409, пакет не поставлен
|
||
|
||
#### Scenario: Без названия
|
||
- **WHEN** `POST /projects {label: "STOCKS_DRRK", name: " "}`
|
||
- **THEN** 422 с указанием поля `name`
|
||
|
||
## MODIFIED Requirements
|
||
|
||
### Requirement: Маршруты интерфейсов
|
||
Хаб SHALL предоставлять интерфейсам (MCP, веб-приложение) следующие маршруты под клиентским токеном:
|
||
- `GET /issues` с фильтрами `project` (разрез по проекту, см. `data-model`, «Проекты»: свои и соседи, `neighbor`
|
||
относительно проекта; неизвестный проект — 404), `sprint`, `assignee`, `work_kind`, `fact_status`, `status`, `label`,
|
||
`epic`, `neighbor`, `q` (подстрока темы), `hide_closed` (`1` / `true` — без задач, чей `status` входит в настройку
|
||
`closed_statuses`; задача без статуса не закрыта), `limit`/`offset` — строки зеркала (все поля `issue`) с `fact_status`
|
||
и `work_kind` из заметок; сокращение до карточки — дело интерфейса. Без `project` — всё зеркало, `neighbor` — признак
|
||
зеркала (вне базовой JQL).
|
||
- `GET /issues/{key}` — задача целиком: поля, `epic_summary` (тема эпика из зеркала или `null`), связи в обе стороны
|
||
(`links_out: [{target_key, link_type, summary, status}]`, `links_in: [{source_key, link_type, summary, status}]`, где
|
||
`summary` и `status` — связанной задачи из зеркала или `null`, если её там нет), заметки, последние 10 команд по ней,
|
||
`comments: [{comment_id, author, created, updated, body}]` — все комментарии, новые сверху (по `created`, затем
|
||
`comment_id`, по убыванию).
|
||
- `PUT /issues/{key}/note` — заметки (частичное обновление, `updated_by` из тела).
|
||
- `POST /batches` `{title, author, project?, commands: [...]}` → `{id, estimate_seconds}`; 422 при неизвестном типе,
|
||
неверных параметрах (в том числе неверного типа значения), временном ключе без `ensure_issue`, двух и более
|
||
`ensure_issue` с одинаковой `summary` (сравнение после обрезки пробелов по краям; в ответе перечислены повторяющиеся
|
||
темы), по проверкам постановки из `commands` (тип `bug` без настройки, файл вложения не загружен, доска для
|
||
`ensure_sprint`), а также по проверкам пакета проекта (см. `commands`), если передан `project`.
|
||
- `POST /files` `{content}` (`content` — base64 содержимого файла) → `{file, size}`, где `file` — sha256 содержимого
|
||
(64 hex-символа, нижний регистр); см. «Хранилище файлов вложений».
|
||
- `GET /batches?limit=&project=` (с `project` — только пакеты этого проекта), `GET /batches/{id}` (с командами и
|
||
`project`), `POST /batches/{id}/cancel` — отменяет `queued` команды пакета; ответ — вид пакета с полем `still_claimed`:
|
||
список `{id, type}` команд пакета в `claimed` (они будут выполнены агентом; остановить — отмена команды с `force`).
|
||
- `POST /commands/{id}/retry` `{author?, project?}` — новая команда с теми же параметрами в новом пакете «Повтор
|
||
команды N»; хаб снимает с параметров то, что дописывает сам (`board_id` у `ensure_sprint`), и проверяет команду
|
||
заново. `project` передан и у исходного пакета другой проект (или его нет) — 422 «команда #N не из проекта P»; не
|
||
передан — новый пакет получает проект исходного и проходит его проверки.
|
||
- `POST /commands/{id}/cancel` — только для `queued`; для `claimed` — 409, если не передан `force: true`.
|
||
- `POST /refresh` `{keys | jql | full: true}` → пакет из одного `refresh`; `full` — по базовой JQL из проектов, без
|
||
проектов — 422 «нет проектов».
|
||
- `GET /agent`, `GET /sync-runs?limit=`, `GET /settings`, `PUT /settings` (частично; поднимает `settings_version`;
|
||
ответ — все настройки после записи, как у `GET /settings`, включая `settings_version`).
|
||
|
||
#### Scenario: Фильтр по спринту
|
||
- **WHEN** `GET /issues?sprint=Спринт 2`
|
||
- **THEN** только задачи с таким `sprint`, отсортированы по сроку, затем по ключу
|
||
|
||
#### Scenario: Список проекта
|
||
- **WHEN** свои задачи `STOCKS_DRRK` — AURORA-1 и AURORA-4, сосед — AURORA-2; `GET /issues?project=STOCKS_DRRK`
|
||
- **THEN** три задачи, у AURORA-2 `neighbor = true`; с `neighbor=1` — только AURORA-2; задач только с меткой `AWPCA` нет
|
||
|
||
#### Scenario: Неизвестный проект
|
||
- **WHEN** `GET /issues?project=NOPE`
|
||
- **THEN** 404
|
||
|
||
#### Scenario: Пакеты проекта
|
||
- **WHEN** пакет 10 поставлен с `project = STOCKS_DRRK`, пакет 11 — без проекта; `GET /batches?project=STOCKS_DRRK`
|
||
- **THEN** в ответе только пакет 10
|
||
|
||
#### Scenario: Повтор чужой команды
|
||
- **WHEN** команда 40 из пакета проекта `AWPCA`; `POST /commands/40/retry {project: "STOCKS_DRRK"}`
|
||
- **THEN** 422 «команда #40 не из проекта STOCKS_DRRK», пакет не создан
|
||
|
||
#### Scenario: Повтор ensure_sprint
|
||
- **WHEN** `ensure_sprint` 41 failed, в её `params` `board_id = 4216`; `POST /commands/41/retry`
|
||
- **THEN** новый пакет создан, `board_id` в новой команде вычислен хабом заново
|
||
|
||
#### Scenario: Полный прогон без проектов
|
||
- **WHEN** проектов нет, `POST /refresh {full: true}`
|
||
- **THEN** 422 «нет проектов», пакет не поставлен
|
||
|
||
#### Scenario: Отмена взятой команды
|
||
- **WHEN** `POST /commands/15/cancel` без `force`, команда `claimed`
|
||
- **THEN** 409 с `claimed_at`; с `force: true` — `cancelled`, следующий отчёт агента по ней принимается, статус не меняется
|
||
|
||
#### Scenario: Поздний отчёт по отменённому созданию
|
||
- **WHEN** `ensure_issue {temp_key: new:7, …}` отменена с `force: true`, затем агент прислал `done` с `result.key = AURORA-1620`
|
||
- **THEN** команда `cancelled`, в `result` — `AURORA-1620`; заметка `new:7` перенесена на `AURORA-1620`
|
||
|
||
#### Scenario: Дубль темы в пакете
|
||
- **WHEN** `POST /batches` с двумя `ensure_issue`, у обеих `summary = "Шахматка: комментарии"`
|
||
- **THEN** 422 с темой «Шахматка: комментарии», пакет не создан
|
||
|
||
#### Scenario: Тема не строка
|
||
- **WHEN** `POST /batches` с `ensure_issue {temp_key: new:1, summary: 42, …}`
|
||
- **THEN** 422 с указанием параметра `summary`, пакет не создан
|
||
|
||
#### Scenario: Отмена пакета с командой у агента
|
||
- **WHEN** в пакете 9 команда 30 `claimed`, команды 31 и 32 `queued`; `POST /batches/9/cancel`
|
||
- **THEN** 31 и 32 `cancelled`; в ответе `still_claimed = [{id: 30, type: …}]`; 30 остаётся `claimed`
|
||
|
||
#### Scenario: Ответ на запись настроек
|
||
- **WHEN** `PUT /settings {relay_dir: "/Volumes/externalSSD/utm/rshb/jira-bridge"}`
|
||
- **THEN** 200; ответ содержит `relay_dir` с записанным значением, новую `settings_version` и остальные настройки;
|
||
токенов в ответе нет
|
||
|
||
#### Scenario: Карточка с комментариями и связями
|
||
- **WHEN** у AURORA-9001 два комментария (14.10 и 15.10), исходящая связь на AURORA-9002 («API свода», «Новая») и на
|
||
AURORA-1, которой нет в зеркале; эпик AURORA-9100 «Тестовый эпик»
|
||
- **THEN** `GET /issues/AURORA-9001` отдаёт `comments` с комментарием от 15.10 первым, у связи на AURORA-9002 —
|
||
`summary = "API свода"`, `status = "Новая"`, у связи на AURORA-1 — `summary = null`, `status = null`;
|
||
`epic_summary = "Тестовый эпик"`
|
||
|
||
#### Scenario: Без закрытых
|
||
- **WHEN** `closed_statuses = ["Готово", "Снято"]`, в зеркале задачи в статусах «В работе», «Готово», «Снято» и без
|
||
статуса; `GET /issues?hide_closed=1`
|
||
- **THEN** в ответе задачи «В работе» и без статуса; без `hide_closed` — все четыре
|
||
|
||
### Requirement: Автообновление зеркала
|
||
Если `auto_refresh_minutes > 0`, агент на связи и есть хотя бы один проект, хаб SHALL поставить пакет «Автообновление»
|
||
из `refresh {jql: <базовая JQL из проектов>}`, когда в очереди нет команд в `queued` и `claimed` и последний `sync_run`
|
||
начат раньше, чем `auto_refresh_minutes` назад (или `sync_run` ещё нет).
|
||
|
||
#### Scenario: Очередь занята
|
||
- **WHEN** интервал прошёл, но в очереди есть `queued` команды
|
||
- **THEN** автообновление не ставится до опустошения очереди
|
||
|
||
#### Scenario: Есть выданная команда без отчёта
|
||
- **WHEN** интервал прошёл, `queued` нет, одна команда в `claimed`
|
||
- **THEN** автообновление не ставится; команда довыдаётся при следующем `claim`
|
||
|
||
#### Scenario: Недавнее обновление
|
||
- **WHEN** очередь пуста, `auto_refresh_minutes = 60`, последний `sync_run` начат 20 минут назад
|
||
- **THEN** автообновление не ставится
|
||
|
||
#### Scenario: Два проекта
|
||
- **WHEN** интервал прошёл, очередь пуста, проекты `AWPCA` и `STOCKS_DRRK`
|
||
- **THEN** поставлен один `refresh` с JQL `project = AURORA AND labels in (AWPCA, STOCKS_DRRK)`
|
||
|
||
### Requirement: Снимок доски
|
||
Хаб SHALL предоставлять `GET /board?project=` под клиентским токеном — согласованный снимок зеркала для доски связей,
|
||
прочитанный в одной транзакции:
|
||
- `generated_at` — момент снимка (UTC);
|
||
- `last_sync` — последний завершённый полный прогон (`refresh` по текущей базовой JQL из проектов):
|
||
`{started_at, finished_at, issues_count}` или `null`, если такого не было; общий для всех проектов;
|
||
- `issues` — с `project` — свои задачи проекта и его соседи (см. `data-model`, «Проекты»), `neighbor` относительно
|
||
проекта; без `project` — все задачи зеркала, включая эпики и соседей, `neighbor` — признак зеркала. Поля: `key`,
|
||
`summary`, `issue_type`, `status`, `priority`, `assignee`, `sprint`, `duedate`, `planned_start`, `planned_end`,
|
||
`epic_key`, `labels` (список), `neighbor` (bool), `fact_status`, `work_kind` (из заметок, `null` если заметки нет);
|
||
порядок — по ключу;
|
||
- `links` — с `project` — связи, у которых хотя бы один конец — своя задача проекта; без `project` — все связи
|
||
зеркала: `{source, target, link_type}`, `link_type` — имя типа в Jira, направление как в `issue_link` (`source`
|
||
блокирует/ссылается на `target`).
|
||
Неизвестный проект — 404. Перевод в модель доски (вид работы по меткам, имена эпиков, логические типы связей) — дело
|
||
интерфейса; хаб отдаёт данные зеркала без преобразований.
|
||
|
||
#### Scenario: Снимок с задачами и связью
|
||
- **WHEN** в зеркале эпик AURORA-1351, задачи AURORA-1620 (метка `kind-dev`, эпик AURORA-1351) и AURORA-1621, связь
|
||
`AURORA-1620 → AURORA-1621` типа `ASUPR_Блокер_Начала`, заметка `work_kind = "разработка"` у AURORA-1620
|
||
- **THEN** `GET /board` отдаёт три задачи по ключу, у AURORA-1620 `labels = ["kind-dev"]`, `epic_key = "AURORA-1351"`,
|
||
`work_kind = "разработка"`; в `links` — `{source: "AURORA-1620", target: "AURORA-1621", link_type: "ASUPR_Блокер_Начала"}`
|
||
|
||
#### Scenario: Снимок проекта
|
||
- **WHEN** AURORA-1 (метка `STOCKS_DRRK`) связана с AURORA-2 (метка `AWPCA`), AURORA-2 связана с AURORA-3 (метка `AWPCA`);
|
||
`GET /board?project=STOCKS_DRRK`
|
||
- **THEN** `issues` — AURORA-1 (`neighbor = false`) и AURORA-2 (`neighbor = true`); `links` — только AURORA-1 ↔ AURORA-2
|
||
|
||
#### Scenario: Последний полный прогон
|
||
- **WHEN** в журнале синхронизаций полный прогон, завершённый в 10:00, и после него обновление по ключам в 11:00
|
||
- **THEN** `last_sync.finished_at` — 10:00
|
||
|
||
#### Scenario: Без токена
|
||
- **WHEN** `GET /board` без токена или с агентским токеном
|
||
- **THEN** 401
|