Архив OpenSpec: sp-estimation-board (#113)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-25 22:29:43 +04:00
parent 69bc4492e8
commit 8c606ff8a0
9 changed files with 48 additions and 6 deletions

View File

@ -534,6 +534,18 @@ id всех спринтов задачи из поля спринта Jira (л
Агент v15 SHALL выполнять `ensure_sprint_meta`, `ensure_sprint_state`, `ensure_rank` и `story_points` в Агент v15 SHALL выполнять `ensure_sprint_meta`, `ensure_sprint_state`, `ensure_rank` и `story_points` в
`ensure_fields` по контракту `commands`; сравнение — до записи, повтор без изменений — `done` без записи. `ensure_fields` по контракту `commands`; сравнение — до записи, повтор без изменений — `done` без записи.
Агент v16 SHALL ставить `story_points` из `ensure_fields` с `board_id` через оценку доски, а не через `PUT /issue`: поле
оценки может не быть на экране редактирования, и Jira отвечает «Field cannot be set. It is not on the appropriate screen,
or unknown» (Jira банка, 25.10.2026), а бэклог Scrum ставит оценку и без экрана. Текущее значение —
`GET /rest/agile/1.0/issue/{key}/estimation?boardId=<board_id>` (`{fieldId, value}`); отличается — `PUT` туда же с
`{"value": …}` (`null` — очистить); сравнение — как у v15 (`3` равно `3.0`, `null` — только `null`). `field_ids.story_points`
для записи с доской не нужен. `GET /issue` и `PUT /issue` агент делает только для остальных полей команды; в `changed` —
`story_points`, если оценка записана. Если в той же команде записан и `PUT /issue`, перед записью оценки — зазор типа
`ensure_fields` (политика темпа). Если `field_ids.story_points` задан и `fieldId` оценки доски с ним не совпадает —
`failed` «у доски <id> оценка — поле <fieldId>, а не story points (<field_ids.story_points>)», без записи. Команда без
`board_id` — оценка через `PUT /issue`, как v15. Ошибка Jira на
`estimation` — `failed` с текстом Jira; остальные поля к этому моменту уже записаны (повтор их не меняет).
#### Scenario: Доска с закрытыми спринтами #### Scenario: Доска с закрытыми спринтами
- **WHEN** `refresh {jql: …, boards: [{id: 4216, type: "scrum"}]}`, у доски закрытые спринты 1–2, активный 3, будущий 4 - **WHEN** `refresh {jql: …, boards: [{id: 4216, type: "scrum"}]}`, у доски закрытые спринты 1–2, активный 3, будущий 4
- **THEN** первая пачка состояния несёт `boards` с доской 4216, четырьмя спринтами и колонками; у задачи спринта 2, - **THEN** первая пачка состояния несёт `boards` с доской 4216, четырьмя спринтами и колонками; у задачи спринта 2,
@ -543,3 +555,16 @@ id всех спринтов задачи из поля спринта Jira (л
- **WHEN** в Jira нет поля «Story Points», `field_ids.story_points` не задан - **WHEN** в Jira нет поля «Story Points», `field_ids.story_points` не задан
- **THEN** у задач нет ключа `story_points`; `ensure_fields {key: X, story_points: 3}` — `failed` «поле оценки не найдено - **THEN** у задач нет ключа `story_points`; `ensure_fields {key: X, story_points: 3}` — `failed` «поле оценки не найдено
в Jira: задайте field_ids.story_points» в Jira: задайте field_ids.story_points»
#### Scenario: Оценка не на экране редактирования
- **WHEN** агент v16, `ensure_fields {key: AURORA-1704, story_points: 2, board_id: 4216}`, оценка в Jira — `1.0`
- **THEN** `GET /rest/agile/1.0/issue/AURORA-1704/estimation?boardId=4216`, затем `PUT` туда же `{"value": 2}`; `PUT /issue`
нет; отчёт `done` с `changed: ["story_points"]`, `jira_calls = 2`
#### Scenario: Доска оценивает другим полем
- **WHEN** `field_ids.story_points = customfield_15852`, `GET …/estimation?boardId=5120` отдаёт `fieldId: timeoriginalestimate`
- **THEN** `failed` «у доски 5120 оценка — поле timeoriginalestimate, а не story points (customfield_15852)», `PUT` нет
#### Scenario: Оценка уже стоит
- **WHEN** та же команда, оценка в Jira — `2.0`
- **THEN** только `GET …/estimation`; отчёт `done` с `changed: []`, `jira_calls = 1`

View File

@ -13,7 +13,7 @@
| Тип | Параметры | Что читает | Что пишет | | Тип | Параметры | Что читает | Что пишет |
|---|---|---|---| |---|---|---|---|
| `ensure_issue` | `temp_key` (`new:<n>`), `issue_type` (`task`/`epic`/`bug`), `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels`, `epic_key`, `assignee` | поиск `project = <proj> AND reporter = currentUser() AND created >= -7d`, точное сравнение `summary` | при отсутствии — `POST /issue` с типом из `issue_types.<issue_type>` (для эпика — с `epic_name_field_id`); результат `key` | | `ensure_issue` | `temp_key` (`new:<n>`), `issue_type` (`task`/`epic`/`bug`), `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels`, `epic_key`, `assignee` | поиск `project = <proj> AND reporter = currentUser() AND created >= -7d`, точное сравнение `summary` | при отсутствии — `POST /issue` с типом из `issue_types.<issue_type>` (для эпика — с `epic_name_field_id`); результат `key` |
| `ensure_fields` | `key`, любое подмножество: `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels_add`, `labels_remove`, `epic_key`, `assignee`, `story_points` (число ≥ 0 или `null` — очистить) | `GET /issue/{key}` | `PUT /issue/{key}` только с отличающимися полями; эпик — по `epic_link.mode`; оценка — в поле story points (см. `agent`, «Спринты, ранг и оценки»); поля нет — `failed` «поле оценки не найдено в Jira: задайте field_ids.story_points» | | `ensure_fields` | `key`, любое подмножество: `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels_add`, `labels_remove`, `epic_key`, `assignee`, `story_points` (число ≥ 0 или `null` — очистить); при `story_points` `board_id` хаб дописывает при постановке (от клиента — 422) | `GET /issue/{key}` — если есть поля, кроме оценки; оценка — `GET /rest/agile/1.0/issue/{key}/estimation?boardId=<board_id>` | `PUT /issue/{key}` только с отличающимися полями, кроме оценки; эпик — по `epic_link.mode`; оценка отличается — `PUT /rest/agile/1.0/issue/{key}/estimation?boardId=<board_id>` с `{value}` (поле оценки доски, экран редактирования Jira не нужен); команда без `board_id` — оценка через `PUT /issue/{key}` в поле story points, как в v15 (см. `agent`, «Спринты, ранг и оценки») |
| `ensure_comment` | `key`, `marker`, `body` | `GET /issue/{key}/comment` | комментарий с `marker` в начале: `PUT` если текст отличается, `POST` если нет | | `ensure_comment` | `key`, `marker`, `body` | `GET /issue/{key}/comment` | комментарий с `marker` в начале: `PUT` если текст отличается, `POST` если нет |
| `ensure_status` | `key`, `target` (имя статуса или список-путь) | `GET /issue/{key}` (статус), `GET /issue/{key}/transitions` на каждом шаге | `POST /transitions` по шагам пути; при отсутствии перехода — `failed` с перечнем доступных | | `ensure_status` | `key`, `target` (имя статуса или список-путь) | `GET /issue/{key}` (статус), `GET /issue/{key}/transitions` на каждом шаге | `POST /transitions` по шагам пути; при отсутствии перехода — `failed` с перечнем доступных |
| `ensure_link` | `source`, `target`, `link_type` (логическое имя из `link_types`), `replace` (bool) | `GET /issue/{source}` (`issuelinks`) | нет нужной и нет конфликта — `POST /issueLink`; конфликт (другой тип из `link_types` или обратное направление) и `replace` — `DELETE` лишней, затем `POST`; конфликт без `replace` — `failed` с описанием | | `ensure_link` | `source`, `target`, `link_type` (логическое имя из `link_types`), `replace` (bool) | `GET /issue/{source}` (`issuelinks`) | нет нужной и нет конфликта — `POST /issueLink`; конфликт (другой тип из `link_types` или обратное направление) и `replace` — `DELETE` лишней, затем `POST`; конфликт без `replace` — `failed` с описанием |
@ -25,10 +25,10 @@
| `refresh` | `keys` (список) или `jql`; `boards` (`[{id, type}]` — доски проектов, `type` — `scrum`, `kanban` или `null`) хаб дописывает при выдаче | `search` с полями зеркала, по 100 на страницу; плюс дочитывание соседей по `issuelinks`; доски с неизвестным типом — `GET /rest/agile/1.0/board/{id}` | ничего в Jira; состояние уходит в хаб; результат — `boards: {"<id>": "scrum" | "kanban" | null}` для досок, чей тип агент запрашивал | | `refresh` | `keys` (список) или `jql`; `boards` (`[{id, type}]` — доски проектов, `type` — `scrum`, `kanban` или `null`) хаб дописывает при выдаче | `search` с полями зеркала, по 100 на страницу; плюс дочитывание соседей по `issuelinks`; доски с неизвестным типом — `GET /rest/agile/1.0/board/{id}` | ничего в Jira; состояние уходит в хаб; результат — `boards: {"<id>": "scrum" | "kanban" | null}` для досок, чей тип агент запрашивал |
| `fetch_attachment` | `key`, `attachment_id` (id вложения Jira, строка из цифр) | `GET /issue/{key}?fields=attachment` (имя и размер вложения) | ничего в Jira; содержимое — `GET <base_url>/secure/attachment/{id}/{имя}`, затем хабу `POST /agent/files`; результат `{attachment_id, file, size}` | | `fetch_attachment` | `key`, `attachment_id` (id вложения Jira, строка из цифр) | `GET /issue/{key}?fields=attachment` (имя и размер вложения) | ничего в Jira; содержимое — `GET <base_url>/secure/attachment/{id}/{имя}`, затем хабу `POST /agent/files`; результат `{attachment_id, file, size}` |
Доску для `ensure_sprint` хаб SHALL определять при постановке: проект задачи — проекты, чьи метки есть у задачи в Доску для `ensure_sprint` и для `ensure_fields` с `story_points` хаб SHALL определять при постановке: проект задачи — проекты, чьи метки есть у задачи в
зеркале (для временного ключа — в `labels` её `ensure_issue` в пакете, после дописывания метки проекта); `board_id` — зеркале (для временного ключа — в `labels` её `ensure_issue` в пакете, после дописывания метки проекта); `board_id` —
доска этого проекта. 422 при постановке: задача ни в одном проекте; в нескольких проектах с разными досками; у проекта доска этого проекта. 422 при постановке: задача ни в одном проекте; в нескольких проектах с разными досками; у проекта
нет доски; тип доски `kanban` («у проекта P канбан-доска, спринтов нет»); тип доски ещё не известен («тип доски проекта P нет доски; тип доски `kanban` («у проекта P канбан-доска, спринтов нет»; у `ensure_fields` — «у проекта P канбан-доска, оценка — только на scrum-доске»); тип доски ещё не известен («тип доски проекта P
ещё не известен — дождитесь обновления из Jira»). `boards` у `refresh` хаб SHALL дописывать при каждой выдаче (в базе ещё не известен — дождитесь обновления из Jira»). `boards` у `refresh` хаб SHALL дописывать при каждой выдаче (в базе
не хранится): все доски проектов без повторов. По результату `refresh` хаб SHALL записать присланные типы `scrum` и не хранится): все доски проектов без повторов. По результату `refresh` хаб SHALL записать присланные типы `scrum` и
`kanban` в проекты с этой доской; `null` тип не меняет. `kanban` в проекты с этой доской; `null` тип не меняет.
@ -37,7 +37,7 @@
(`project` пакета): пакет без проекта — 422 «команды спринта — только в пакете проекта»; у проекта нет доски, доска (`project` пакета): пакет без проекта — 422 «команды спринта — только в пакете проекта»; у проекта нет доски, доска
`kanban` или тип ещё не известен — 422 с теми же текстами, что у `ensure_sprint`. `ensure_rank` в пакете проекта `kanban` или тип ещё не известен — 422 с теми же текстами, что у `ensure_sprint`. `ensure_rank` в пакете проекта
принадлежит проекту по `key`; `before`/`after` — любая задача. Повтор команды (`command_retry`) SHALL снимать `board_id` принадлежит проекту по `key`; `before`/`after` — любая задача. Повтор команды (`command_retry`) SHALL снимать `board_id`
у `ensure_sprint_meta` и `ensure_sprint_state`, как у `ensure_sprint`. Хвостовой `refresh` пишущего пакета с у `ensure_sprint_meta`, `ensure_sprint_state` и `ensure_fields`, как у `ensure_sprint`: доска определяется заново. Хвостовой `refresh` пишущего пакета с
`ensure_sprint_meta` или `ensure_sprint_state` SHALL быть `refresh {jql: <JQL проекта пакета>}` (`project = `ensure_sprint_meta` или `ensure_sprint_state` SHALL быть `refresh {jql: <JQL проекта пакета>}` (`project =
<project_key> AND labels in ("<метка>")`); иначе — как раньше, по ключам команд. Подтверждения по состоянию зеркала у <project_key> AND labels in ("<метка>")`); иначе — как раньше, по ключам команд. Подтверждения по состоянию зеркала у
`ensure_sprint_meta`, `ensure_sprint_state`, `ensure_rank` нет. `ensure_sprint_meta`, `ensure_sprint_state`, `ensure_rank` нет.
@ -105,6 +105,23 @@
- **WHEN** проект `STOCKS_DRRK` с доской 5120 и `board_type = null`; выдаётся `refresh`, агент вернул `boards: {"5120": "kanban"}` - **WHEN** проект `STOCKS_DRRK` с доской 5120 и `board_type = null`; выдаётся `refresh`, агент вернул `boards: {"5120": "kanban"}`
- **THEN** при выдаче в параметрах `boards` есть `{id: 5120, type: null}`; после отчёта у проекта `board_type = kanban` - **THEN** при выдаче в параметрах `boards` есть `{id: 5120, type: null}`; после отчёта у проекта `board_type = kanban`
#### Scenario: Оценка задачи проекта
- **WHEN** у AURORA-1692 метка `AWPCA`, доска проекта 4216 типа `scrum`; `ensure_fields {key: AURORA-1692, story_points: 1}`
- **THEN** пакет создан, в параметрах команды `board_id = 4216`; агент читает и пишет оценку через
`/rest/agile/1.0/issue/AURORA-1692/estimation?boardId=4216`, `PUT /issue` не делает
#### Scenario: Поля без оценки — без доски
- **WHEN** `ensure_fields {key: AURORA-1, duedate: 2026-10-30}`, задача вне проектов
- **THEN** пакет создан, `board_id` в параметрах нет
#### Scenario: Оценка задачи вне проектов
- **WHEN** `ensure_fields {key: AURORA-1, story_points: 2}`, у AURORA-1 нет меток проектов
- **THEN** 422 «команда 1: AURORA-1 не входит ни в один проект — доску для оценки не определить»
#### Scenario: Оценка на канбане
- **WHEN** у AURORA-1 метка `STOCKS_DRRK`, доска проекта — `kanban`; `ensure_fields {key: AURORA-1, story_points: 2}`
- **THEN** 422 «команда 1: у проекта STOCKS_DRRK канбан-доска, оценка — только на scrum-доске»
### Requirement: Временные ключи и ссылки внутри пакета ### Requirement: Временные ключи и ссылки внутри пакета
Параметр-ключ любой команды MAY быть временным `new:<n>`. Хаб SHALL выдавать агенту такую команду только после `done` Параметр-ключ любой команды MAY быть временным `new:<n>`. Хаб SHALL выдавать агенту такую команду только после `done`
команды `ensure_issue` с тем же `temp_key` в том же пакете, подставив настоящий ключ. Если `ensure_issue` завершилась команды `ensure_issue` с тем же `temp_key` в том же пакете, подставив настоящий ключ. Если `ensure_issue` завершилась

View File

@ -302,8 +302,8 @@ MCP `sdlc-jira` SHALL давать инструменты чтения по `GET
- `backlog` (`limit`, по умолчанию 50) — бэклог по рангу: позиция, ключ, тема, статус, исполнитель, SP; в конце «ещё N», - `backlog` (`limit`, по умолчанию 50) — бэклог по рангу: позиция, ключ, тема, статус, исполнитель, SP; в конце «ещё N»,
если задач больше `limit`. если задач больше `limit`.
Таблица `issues` SHALL получить колонку «SP». Описание `batch_create` SHALL перечислять `ensure_sprint_meta`, Таблица `issues` SHALL получить колонку «SP». Описание `batch_create` SHALL перечислять `ensure_sprint_meta`,
`ensure_sprint_state`, `ensure_rank` и `story_points` в `ensure_fields` с параметрами; хвостовой `refresh` хаб `ensure_sprint_state`, `ensure_rank` и `story_points` в `ensure_fields` с параметрами; хвостовой `refresh` и `board_id`
дописывает сам (см. `commands`). (у `ensure_sprint`, команд спринта и `ensure_fields` с `story_points`) хаб дописывает сам (см. `commands`).
#### Scenario: Спринты проекта #### Scenario: Спринты проекта
- **WHEN** сессия привязана к `AWPCA`, вызван `sprints` - **WHEN** сессия привязана к `AWPCA`, вызван `sprints`