## 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: }` — у этих команд нет ключей, а перенесённые при закрытии задачи заранее неизвестны. `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` у агента).