Andrey Epifancev 30833a466a Архив OpenSpec: sprints-backlog (#108)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:24:17 +04:00

236 lines
30 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, переименование «Доска связей». Цена ошибки — правка веба.
- 24.10.2026 — R-28 (план агента R-64, R-65): поле Jira не найдено — ключ у задач не передаётся (хаб сохраняет прежнее),
а не `null`; ошибка чтения списка спринтов — доска не входит в `boards` (иначе стёрлась бы история спринтов).
Цена ошибки — нет.
- 24.10.2026 — R-13 (контракт ядра): `CommandHooks.tail` получает `(conn, cmds, project)` вместо `(cmds)`: хвост
пакета со спринтовыми командами — `refresh` по JQL проекта пакета (design #13), а без соединения и проекта хук JQL
не построит. Правится ядро (`core/modules.py`, `core/queue.py`), wiki (`tail=lambda conn, cmds, project: None`) и
две заглушки в тестах. Цена ошибки — вернуть сигнатуру и строить хвост в `check_batch` (дописывая `refresh` в конец
пакета).
- 24.10.2026 — R-14 (форма `boards`): у доски в `boards` поле `sprints` отсутствует или `null` — спринты доски в
зеркале не меняются (агент не смог их прочитать, agent spec «прежние правила для спринтов»); `[]` — у доски спринтов
нет, строки удаляются. `columns` отсутствует или `null` — пишется `NULL`. `boards: null` в пачке — как отсутствие.
Цена ошибки — устаревшие спринты на экране до следующего `refresh` с `sprints: []`.
- 24.10.2026 — R-15 (проверки постановки): хаб проверяет `start_date`/`end_date` (`ГГГГ-ММ-ДД`, настоящая дата, начало
не позже конца), непустые `sprint`/`rename_to`, `state` ∈ {`active`, `closed`}, `move_open_to` (обязателен при
`closed`, 422 при `active`, не равен `sprint`), у `ensure_rank` ровно одно из `before`/`after` и не сама задача,
`story_points` — конечное число ≥ 0 или `null`. Цена ошибки — правка проверок.
- 24.10.2026 — R-16 (подтверждение по зеркалу): `ensure_fields` со `story_points` подтверждается, только если
`issue.story_points == params.story_points` точно (`null` = `null`, `0` ≠ `null`); прочие поля — как раньше. Цена
ошибки — лишняя довыдача.
- 24.10.2026 — R-17 (`GET /agile`): `board_type = null` → `sprints: []`, `backlog: null`, `board_issues: null`;
scrum-бэклог исключает задачи в активном/будущем спринте любой доски из `jira_sprint` (спринт, которого нет в
`jira_sprint`, не учитывается); kanban: выполненная задача без `jira_updated` не показывается; `kanban_backlog` в
ответе — `true`/ `false`/`null`; суммы `points`/`points_done` — сумма чисел (без оценок — `0`). Цена ошибки — правка
выборки.
- 24.10.2026 — R-18 (Kanban с бэклогом): при `kanban_backlog = true` первая колонка доски не рисуется — её задачи в
«Бэклог (N)» (как kanban-бэклог Jira). Цена ошибки — вернуть колонку.
- 24.10.2026 — R-19 (три состояния): «Нужен агент v15 и обновление из Jira» — `board = null` или `board.fetched_at =
null`; «Не удалось прочитать настройки доски из Jira» — `fetched_at` есть, `columns = null` (design #23 вместо
противоречивой фразы web-app «`board.columns = null` или ответ без доски»). Цена ошибки — правка условия.
- 24.10.2026 — R-20 (кнопка «Обновить»): на экранах — существующая `RefreshButton` («Обновить из Jira», в шапке и в
сообщениях трёх состояний). Правило «кнопок нет, кроме раскрытия секций и карточки» к ней не относится: она читает
Jira, а не пишет. Цена ошибки — убрать кнопку из шапки.
- 24.10.2026 — R-21 (переименование): «Связи» — подпись пункта меню, заголовок вкладки и ссылка в карточке jira на
«Обзоре»; заголовок `h1` страницы остаётся «Доска связей» (спека говорит о меню и вкладке). Цена ошибки — правка
`BoardHeader` и e2e `geometry.spec.ts`.
- 24.10.2026 — R-22 (смена проекта): фраза «Проект в приложении» («смена проекта на странице, которой нет у нового
проекта, открывает «Обзор»») не меняется в коде: других условных страниц нет, а для экранов доски действует design
#21 (адрес остаётся, сообщение «У проекта нет …-доски») — `Layout.select` уже сохраняет раздел. Цена ошибки —
переход на «Обзор» для новых страниц.
- 24.10.2026 — R-23 (NotFound): «не найдено» перечисляет только безусловные пункты (без `when`). Цена ошибки — нет.
- 24.10.2026 — R-24 (склонение): «N задач» — с русским склонением (1 задача, 2 задачи, 5 задач). Цена ошибки — нет.
- 24.10.2026 — R-25 (тексты MCP): шапки таблиц и отказы `sprints`/`sprint`/`backlog` — по задаче 7. Цена ошибки —
правка текста.
- 24.10.2026 — R-26 (даты): хаб сортирует спринты по `history.jira_utc(дата)`; веб перед `new Date` приводит `±HHMM` к
`±HH:MM`. Цена ошибки — нет.
- 24.10.2026 — R-27 (e2e): тестовый хаб засевает второй проект `KANBAN_E2E` (доска 5120, kanban, задачи
AURORA-9101…9103); доска AWPCA — scrum со спринтами 101 (закрыт), 102 (активный), 103 (будущий). Цена ошибки —
правка сида.
- 24.10.2026 — R-29: трейлер коммитов — Co-Authored-By: Claude Sonnet 5.5 (инструкция сессии точнее перечня плана). Цена ошибки — переписать трейлеры, ничего не пушилось.
- 24.10.2026 — R-30 (задача 3, `GET /agile`, по R-17): доска с заданным `board_id`, но неизвестным типом (`board_type` NULL) —
`board` с `type: null`, `sprints: []`, `backlog: null`, `board_issues: null`; kanban без `columns` при
`kanban_backlog = 1` — `backlog: []`, все задачи в `board_issues`. Выбрано исполнителем задачи 3, подтверждено
ревью как следствие R-17. Цена ошибки — правка выборки в `agile.view`.
- 24.10.2026 — R-31 (задача 4, по R-15): пустая строка в `move_open_to` при `state = active` и в `before`/`after` у
`ensure_rank` трактуется как «не задано»; `ensure_sprint_meta` без параметров, кроме `sprint`, допустима. Выбрано
исполнителем задачи 4. Цена ошибки — ужесточить проверку (422 вместо `failed` у агента).
- 24.10.2026 — R-32 (задача 9, по web-app): блок «Прошлые спринты (N)» на «Бэклоге» не рисуется при N = 0 (спека не
требует пустой блок). Выбрано исполнителем задачи 9. Цена ошибки — рисовать «Прошлые спринты (0)» свёрнутым.
- 24.10.2026 — R-33 (финальное ревью M-3): до выкатки агента v15 команда `ensure_fields` со `story_points`, поставленная
хабу, будет выполнена агентом v14 как `done` с пустым `changed` (неизвестное поле v14 пропускает), хаб при постановке
версию агента не проверяет. Принято как ограничение окна выкатки: README требует публиковать v15 сразу после
перезапуска хаба. Цена ошибки — ложное «done» по оценке в окне между перезапуском и публикацией v15; исправление —
422 при постановке, если `agent_version` модуля jira < 15.