Andrey Epifancev 15db39d57d OpenSpec: change sprints-backlog (#108)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:06:15 +04:00

173 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Context
Основа — `main` `a2bf3fc` (схема v8 после fact-status-removal #104). Issue #108, ветка `feat/108-sprints-backlog`.
Агент — jira-agent v14 (`../jira-agent`), его решения — в design-only change `agent-v15-sprints` репозитория jira-agent.
Что есть сейчас (разведка 24.10.2026):
- у задачи в зеркале `issue.sprint` — имя одного спринта строкой; агент собирает имена со спринтов досок `scrum`
(активные и будущие, `GET /rest/agile/1.0/board/{id}/sprint?state=active,future` и `/sprint/{id}/issue`); у задач
закрытых спринтов `sprint = NULL` — принятое ограничение;
- спринтов как сущностей (id, состояние, даты, цель), ранга задач, оценок и колонок доски хаб не знает;
- `ensure_sprint {key, sprint}` переносит задачу в спринт или бэклог, `board_id` хаб дописывает по проекту задачи;
- доска проекта — `jira_project.board_id`, `board_type` (`scrum`/`kanban`/`NULL`), тип пишет только результат `refresh`;
- живые данные: доска только у `AWPCA` (4216, scrum), спринты `AWPCA_Sprint_2_VGO` (73 задачи) и
`AWPCA_Sprint_3_Corrections` (37), 188 своих задач вне спринтов; 11 статусов; у `STOCKS_DRRK` и `sfm_kedr` досок нет;
- оценок в AURORA пока нет (`field_ids` без story points); веб — `/app/jira/board` («Доска» — доска связей),
`/app/jira/issues`, `/app/jira/feed`.
## Goals / Non-Goals
**Goals:** в вебе — бэклог, доска спринта и kanban по образцу Jira, только просмотр; история закрытых спринтов; оценки
(story points) в зеркале, на экранах и в MCP; из чата — создание и правка спринта, запуск и завершение, ранжирование,
оценка.
**Non-Goals и Anti-Goals:** см. proposal.md.
## Decisions
1. **Веб — только просмотр** (решение владельца 24.10.2026: «веб только смотреть… вэб это только вью»). Ни
перетаскивания, ни кнопок, меняющих Jira, на новых экранах нет; все изменения — пакетами команд из MCP.
2. **Экраны по типу доски проекта** (решение владельца): scrum — «Бэклог» и «Спринт»; kanban — «Kanban»; без доски —
пунктов нет. Тип доски неизвестен (`NULL`) — пунктов нет, как без доски.
3. **Kanban-бэклог — раздел страницы «Kanban», а не отдельный пункт меню** (решение агента): признак kanban-бэклога
известен только из данных доски (`GET /agile`), а меню строится по `GET /projects`; отдельный пункт мигал бы при
загрузке. Если бэклог у доски включён — над колонками свёрнутый список «Бэклог (N)» по рангу.
4. **«Доска» переименовывается в «Связи»** (решение владельца): подпись в меню и заголовке вкладки — «Связи», адрес —
`/app/jira/links`; `/app/jira/board` и прежний `/app/board` перенаправляют на него с сохранением строки запроса.
5. **Спринты — сущности зеркала.** Таблица модуля jira `jira_sprint` (id Jira, доска, имя, состояние `active` /
`future` / `closed`, даты начала, конца и завершения, цель) и `issue_sprint` (задача ↔ все её спринты, любого
состояния). `issue.sprint` (имя текущего спринта) остаётся как есть — фильтры списка задач и доска связей его
используют. История закрытых спринтов (решение владельца: «лучше иметь исторические данные») — из `issue_sprint`:
Jira хранит у задачи все её спринты, агент передаёт их id.
6. **Колонки доски — из настроек доски Jira** (`GET /rest/agile/1.0/board/{id}/configuration`): `jira_board` хранит
колонки `[{name, statuses: [имя статуса…]}]` и признак kanban-бэклога. Статус задачи без колонки — колонка «Без
колонки» в конце (видна, только если в ней есть задачи).
7. **«Выполнено» — статус в последней колонке доски** (правило Jira для scrum-доски). Для закрытого спринта —
по текущему статусу задачи (решение агента): статуса на момент закрытия хаб не знает (журнал изменений покрывает
только открытые спринты и с 22.10.2026). Задача, выполненная после закрытия спринта, в прошлом спринте покажется
выполненной — принятое ограничение.
8. **Свои задачи проекта.** Экраны и MCP показывают только свои задачи проекта (метка проекта), соседей нет. Спринты —
все спринты доски проекта, в том числе без своих задач.
9. **Ранг — строка Jira (LexoRank) как есть**, порядок — сравнение строк; задачи без ранга — в конце, по ключу.
10. **Оценка — story points**, число `REAL` (`issue.story_points`, `NULL` — нет оценки). Поле Jira: настройка
`field_ids.story_points`, если задана; иначе агент находит поле сам (решение агента в `agent-v15-sprints`).
Суммы по спринту: всего и выполнено; задача без оценки в сумму не входит, а число таких задач показывается.
11. **Новые типы команд**, все «проверь, потом сделай», темп по умолчанию 30 с:
- `ensure_sprint_meta {sprint, rename_to?, start_date?, end_date?, goal?}` — спринт доски проекта с этим именем
(среди активных и будущих) существует и имеет эти поля; нет — создаётся;
- `ensure_sprint_state {sprint, state: "active" | "closed", move_open_to?}` — спринт в этом состоянии; при
`closed` невыполненные задачи (не в последней колонке) сначала переносятся в `move_open_to` (имя активного или
будущего спринта или `backlog`; для `closed` обязателен);
- `ensure_rank {key, before | after}` — задача выше (`before`) или ниже (`after`) другой задачи; уже так — без
записи.
`board_id` у `ensure_sprint_meta` и `ensure_sprint_state` хаб дописывает при постановке из проекта пакета (команды
не про задачу — проекта задачи нет). Пакет без проекта, проект без доски, kanban-доска, неизвестный тип доски — 422
с тем же текстом, что у `ensure_sprint`.
12. **Оценка ставится через `ensure_fields`** (новое поле `story_points`: число ≥ 0 или `null` — очистить), отдельной
команды нет.
13. **Данные досок — в каждом `refresh`.** Агент v15 к первой пачке состояния `refresh` прикладывает `boards` —
по каждой доске из параметров команды: тип, колонки, kanban-бэклог, все спринты (любого состояния); хаб заменяет
данные доски целиком. У задач — `rank`, `story_points`, `sprint_ids`. Результат команд видно после хвостового `refresh`, который
хаб уже дописывает к пишущему пакету (`hooks.tail`, по ключам задач команд); пакет с `ensure_sprint_meta` или
`ensure_sprint_state` получает хвост `refresh {jql: <JQL проекта пакета>}` — у этих команд нет ключей, а
перенесённые при закрытии задачи заранее неизвестны. `refresh` без задач (все ключи пропали) агент v15 всё равно
отправляет одной пустой пачкой с `boards`.
14. **Совместимость с агентом v14.** Без `boards` и новых полей хаб ничего не теряет: прежние данные досок и задач
остаются; экраны без данных доски показывают «Нужен агент v15 и обновление из Jira». Новые типы команд агенту v14 —
`failed` «неизвестный тип» на его стороне; `min_agent_version` хаб не поднимает (поднимет владелец после выкатки v15).
15. **API — один маршрут `GET /agile?project=<метка>`** (данные доски, спринты со своими задачами и суммами, бэклог).
Веб и MCP читают его; отдельных маршрутов на спринт и бэклог нет.
16. **Схема v9**: `issue.rank`, `issue.story_points`, таблицы `jira_board`, `jira_sprint`, `issue_sprint`.
17. **Kanban без бэклога** (`kanban_backlog` `false` или `null`): первая колонка — обычная колонка доски, её задачи — в
`board_issues`; `backlog = null`.
18. **`ensure_sprint_meta` ищет спринт по имени среди всех спринтов доски**: закрытый — `failed` «спринт закрыт»;
несколько с этим именем — `failed` с их id; `rename_to` на имя другого спринта доски — `failed` «имя занято».
`ensure_sprint_state {state: closed}` для будущего спринта — `failed` «спринт не начат»; `active` для закрытого —
`failed` «спринт закрыт». Результаты: `ensure_sprint_meta` — `{sprint_id, created}`, `ensure_sprint_state` —
`{sprint_id, moved}`, `ensure_rank` — `{changed}`. Подтверждения по зеркалу у новых типов нет (`check_confirmed` —
нет): повторная выдача без отчёта выполняется агентом заново, это безопасно по «проверь, потом сделай».
19. **Проектный пакет:** `ensure_rank` принадлежит проекту по `key` (своя задача или временный ключ пакета); `before` /
`after` — любая задача зеркала. `ensure_sprint_meta`/`ensure_sprint_state` в пакете проекта проверяются по доске
проекта (решение 11), не по задаче. `command_retry` снимает `board_id` у всех трёх типов со спринтом, как у
`ensure_sprint`.
20. **Форма новых полей состояния:** `story_points: null` и `rank: null` — «очистить»; `sprint_ids: []` — у задачи нет
спринтов; `sprint_ids: null` или не-список целых — 422 `bad_state`.
21. **Меню модуля зависит от проекта:** пункт раздела модуля веба может нести условие по проекту (тип доски из `GET
/projects`); пока проекты не загружены, условные пункты скрыты. Смена проекта на странице, которой у нового проекта
нет, оставляет адрес и показывает «У проекта нет …-доски» (как прямой переход).
22. **Тип доски — одна правда, `jira_project.board_type`:** тип из `boards` пачки состояния хаб записывает и в
`jira_board.type`, и в `jira_project.board_type` проектов этой доски (как результат `refresh`, только `scrum`/
`kanban`). Меню и 422 постановки читают `jira_project`, экраны — `GET /agile`, тип в нём — из `jira_project`.
23. **Три состояния данных доски на экранах:** данных нет (`fetched_at = null`) — «Нужен агент v15 и обновление из
Jira»; данные есть, колонок нет (агент не смог прочитать настройки) — «Не удалось прочитать настройки доски из
Jira» и обновление; колонки есть — экран. Без колонок `done` у всех задач — `false`.
24. **Даты:** в командах — `ГГГГ-ММ-ДД` (агент переводит в формат Jira); в `jira_sprint` — строки Jira как есть
(ISO-8601 со смещением); веб показывает их датой «ДД.ММ» в часовом поясе браузера; «осталось N дн.» — от сегодня до
`end_date`.
25. **История закрытых спринтов — из базовой JQL:** базовая JQL проектов не отсекает закрытые задачи (`labels in
(…)`), полный `refresh` возвращает и их — членство всех своих задач в закрытых спринтах приходит первым же полным
`refresh` агента v15.
26. **«Доска связей» → «Связи»** (сейчас пункт меню называется «Доска связей», адрес `/jira/board`); `/app/board`
перенаправляет сразу на `/app/jira/links`. Документы, где адрес упомянут (README, CLAUDE.md репозитория, e2e), —
в том же change.
## Контракт данных
`POST /agent/state` (агент v15), необязательные поля:
- у задачи: `rank` (строка или `null`), `story_points` (число или `null`), `sprint_ids` (список целых — все спринты
задачи); присланное `sprint_ids` заменяет все строки `issue_sprint` задачи, задача без поля свои строки сохраняет;
- в пачке: `boards: [{id, type, columns: [{name, statuses: [строка]}] | null, kanban_backlog: bool | null,
sprints: [{id, name, state, start_date, end_date, complete_date, goal}]}]`; доска из `boards` заменяет свою строку
`jira_board` и все свои строки `jira_sprint`. Даты спринтов — строки Jira как есть или `null`. Пустой `refresh` —
одна пачка `{issues: [], links: [], boards}`.
`GET /agile?project=P` →
```
{board: {id, type, columns, kanban_backlog, fetched_at} | null,
sprints: [{id, name, state, start_date, end_date, complete_date, goal,
count, count_done, unestimated, points, points_done, issues: [строка]}],
backlog: [строка] | null,
board_issues: [строка] | null}
```
Строка задачи: `key, summary, issue_type, status, priority, assignee, epic_key, story_points, rank, done` (`done` —
статус в последней колонке). Порядок спринтов: активные (по дате начала), будущие (по id), закрытые (по дате
завершения, новые сверху). `backlog`: scrum — свои задачи не в активном и не в будущем спринте и не выполненные;
kanban — свои задачи в статусах первой колонки, если kanban-бэклог включён, иначе `null`. `board_issues` (только
kanban) — свои задачи в статусах остальных колонок (при выключенном или неизвестном бэклоге — и первой) и без колонки;
выполненные — только с `jira_updated` за последние 14 дней. Всё
по рангу.
## Risks / Trade-offs
- **Поля Jira банка не проверены**: имя поля story points, тип поля ранга, признак kanban-бэклога в
`board/{id}/configuration` Jira Server. Проверка — на живой Jira при выкатке агента; не найдено — агент передаёт
`null`, экраны показывают «—», признак kanban-бэклога `null` — раздела «Бэклог» нет.
- **Объём закрытых спринтов**: агент читает список всех спринтов доски каждым `refresh` (постранично, без задач) — на
доске с десятками спринтов это несколько запросов; задачи закрытых спринтов не дочитываются, членство — из поля
спринта задач.
- **Story points на экране редактирования Jira**: если поле не на экране AURORA, `ensure_fields story_points` получит
ошибку Jira — `failed` с её текстом; нужна настройка Jira (у владельца/администратора).
## Decisions made without discussion
- 24.10.2026 — R-1 (design #3): kanban-бэклог — раздел страницы «Kanban», не пункт меню. Цена ошибки — перенести
раздел в отдельную страницу.
- 24.10.2026 — R-2 (design #7): «выполнено» у закрытого спринта — по текущему статусу. Цена ошибки — считать по
журналу изменений, когда он появится по закрытым спринтам.
- 24.10.2026 — R-3 (design #8): только свои задачи проекта на экранах и в MCP. Цена ошибки — добавить соседей флагом.
- 24.10.2026 — R-4 (design #10): поле оценки — `field_ids.story_points` или поиск агентом. Цена ошибки — настройка руками.
- 24.10.2026 — R-5 (design #11): имена и параметры команд `ensure_sprint_meta`, `ensure_sprint_state`, `ensure_rank`;
«выше/ниже» — отношение, а не «вплотную». Цена ошибки — переименование типов до выкатки.
- 24.10.2026 — R-6 (design #13): данные досок — в каждом `refresh`, в том числе по ключам. Цена ошибки — лишние
запросы к Jira (несколько на доску).
- 24.10.2026 — R-7 (design #15): один маршрут `GET /agile`. Цена ошибки — разделить маршрут.
- 24.10.2026 — R-8 (контракт): kanban — выполненные задачи за 14 дней. Цена ошибки — число в настройке.
- 24.10.2026 — R-9 (design #6): колонка «Без колонки» для статусов вне настроек доски. Цена ошибки — нет.
- 24.10.2026 — R-10 (design #13): хвост пакета со спринтами — `refresh` по JQL проекта; пустой `refresh` шлёт пачку с
`boards`. Цена ошибки — лишнее чтение проекта (~3 запроса `search`).
- 24.10.2026 — R-11 (design #17–#20): правила kanban без бэклога, поиска спринта по имени, ошибок состояний, проектной
принадлежности `ensure_rank`, формы полей. Цена ошибки — правка проверок.
- 24.10.2026 — R-12 (design #21–#26): условные пункты меню, тип доски в `jira_project`, три состояния данных доски,
даты в поясе браузера, история из базовой JQL, переименование «Доска связей». Цена ошибки — правка веба.