231 lines
30 KiB
Markdown
231 lines
30 KiB
Markdown
## 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)» свёрнутым.
|