102 lines
11 KiB
Markdown
102 lines
11 KiB
Markdown
## MODIFIED Requirements
|
||
|
||
### Requirement: Команда — желаемый результат на одной задаче
|
||
Каждая команда SHALL описывать желаемое состояние одной задачи Jira и выполняться агентом как «прочитать → сравнить →
|
||
записать только разницу». Повторное выполнение любой команды с тем же результатом в Jira SHALL завершаться `done`
|
||
без записи. Типы команд и их контракт:
|
||
|
||
| Тип | Параметры | Что читает | Что пишет |
|
||
|---|---|---|---|
|
||
| `ensure_issue` | `temp_key` (`new:<n>`), `issue_type` (`task`/`epic`), `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels`, `epic_key`, `assignee` | поиск `project = <proj> AND reporter = currentUser() AND created >= -7d`, точное сравнение `summary` | при отсутствии — `POST /issue` (для эпика — с `epic_name_field_id`); результат `key` |
|
||
| `ensure_fields` | `key`, любое подмножество: `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels_add`, `labels_remove`, `epic_key`, `assignee` | `GET /issue/{key}` | `PUT /issue/{key}` только с отличающимися полями; эпик — по `epic_link.mode` |
|
||
| `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_link` | `source`, `target`, `link_type` (логическое имя из `link_types`), `replace` (bool) | `GET /issue/{source}` (`issuelinks`), `GET /issueLinkType` (симметричность) | нужная связь есть — ничего; нет нужной и нет конфликта — `POST /issueLink`; конфликт (другой тип из `link_types` в любом направлении или обратное направление несимметричного типа) и `replace` — `DELETE` лишней, затем `POST`; конфликт без `replace` — `failed` с описанием |
|
||
| `ensure_sprint` | `key`, `sprint` (имя или `backlog`) | `GET /issue/{key}` (поле спринта), список спринтов доски из `board_id` (активные и будущие) | `POST /rest/agile/1.0/sprint/{id}/issue` или `POST /rest/agile/1.0/backlog/issue` |
|
||
| `refresh` | `keys` (список) или `jql` | `search` с полями зеркала, по 100 на страницу; плюс дочитывание соседей по `issuelinks` | ничего в Jira; состояние уходит в хаб |
|
||
|
||
Семантика `ensure_link`: `source` блокирует/ссылается на `target`; агент в `POST /issueLink` SHALL ставить `source` в
|
||
`inwardIssue`, `target` в `outwardIssue` (проверено на Jira банка 11.10.2026). Тип связи симметричен, если в Jira у него
|
||
`inward == outward`; для симметричного типа связь `target→source` равнозначна `source→target` («нужная связь есть»).
|
||
|
||
#### Scenario: Повтор уже выполненной команды
|
||
- **WHEN** `ensure_fields {key: AURORA-1533, duedate: 2026-10-23}` выполняется, а в Jira срок уже 23.10
|
||
- **THEN** агент не делает `PUT`, отчёт `done`, `jira_calls = 1`
|
||
|
||
#### Scenario: Замена связи
|
||
- **WHEN** `ensure_link {source: AURORA-1537, target: AURORA-1538, link_type: "связанные", replace: true}` и в Jira между
|
||
ними «блокирует начало» 1537→1538
|
||
- **THEN** `DELETE` старой связи, пауза по политике, `POST` новой; отчёт `done` с обеими операциями
|
||
|
||
#### Scenario: Связь-конфликт без replace
|
||
- **WHEN** та же команда с `replace: false`
|
||
- **THEN** `failed`, в `error` найденная связь, Jira не изменена
|
||
|
||
#### Scenario: Симметричная связь в обратную сторону
|
||
- **WHEN** `ensure_link {source: AURORA-1445, target: AURORA-1446, link_type: "связанные"}`, тип симметричен, в Jira
|
||
есть «Связанные» 1446→1445
|
||
- **THEN** агент ничего не пишет, отчёт `done` с `existed: true`
|
||
|
||
#### Scenario: Переход по пути
|
||
- **WHEN** `ensure_status {key: X, target: ["Разработка", "Готово"]}` и X в «Ожидание разработки»
|
||
- **THEN** два `POST /transitions` с паузой между ними; если X уже «Готово» — `done` без записи
|
||
|
||
#### Scenario: Создание с проверкой дубля
|
||
- **WHEN** `ensure_issue` повторяется после падения агента, а задача с той же темой от нас уже создана час назад
|
||
- **THEN** агент не создаёт вторую, отчёт `done` с ключом найденной
|
||
|
||
### Requirement: Пакет и порядок выполнения
|
||
Пакет SHALL создаваться одним вызовом с автором, заголовком и списком команд; хаб SHALL назначить `seq` по порядку списка
|
||
и добавить в хвост `refresh` по всем ключам пакета (кроме пакетов, состоящих только из `refresh`). Пакет с двумя и более
|
||
`ensure_issue` с одинаковой `summary` (после обрезки пробелов по краям) SHALL отвергаться (422). Очередь SHALL выдавать
|
||
команды в порядке `(batch.created_at, seq)`, одну в полёте. Пакет и его `queued` команды MAY быть отменены; `claimed`
|
||
команда MUST NOT отменяться автоматически.
|
||
|
||
#### Scenario: Оценка длительности
|
||
- **WHEN** создаётся пакет из 3 `ensure_fields` и 2 `ensure_comment`
|
||
- **THEN** `estimate_seconds` = сумма пауз по политике для последовательности типов + пауза `refresh`
|
||
|
||
### Requirement: Ошибки и подтверждение по состоянию
|
||
Ошибка команды SHALL записываться в `error` (HTTP-код, текст Jira до 1500 символов), пакет продолжается.
|
||
|
||
**Повторная выдача.** Агент запрашивает новую команду, только когда у него нет незавершённой (см. `agent`), поэтому
|
||
команда в `claimed` к моменту следующего `claim` считается не доставленной или потерянной агентом. При `claim` хаб SHALL
|
||
сначала выдать снова самую старую (по `claimed_at`, затем `id`) команду в `claimed`: тот же `id` и параметры,
|
||
`pause_seconds` по текущей политике темпа, новый `claimed_at`, `deliveries + 1`. Если у неё уже `deliveries >= 3`, хаб SHALL
|
||
перевести её в `failed` с `error = "агент не отчитался после 3 выдач"` и перейти к следующей (довыдача другой `claimed`
|
||
или следующая `queued`). Довыдача — доставка той же выдачи, а не повтор команды.
|
||
|
||
**Подтверждение по состоянию.** Если после любого `refresh` желаемый результат команды в `claimed` виден в зеркале, хаб SHALL
|
||
перевести её в `confirmed_by_state`:
|
||
- `ensure_issue` — задача с той же `summary`, `reporter = agent_status.jira_user`, созданная не раньше 7 дней назад; пока
|
||
`jira_user` неизвестен — не подтверждается;
|
||
- `ensure_fields` — все поля совпадают; `ensure_status` — статус равен последнему в пути; `ensure_sprint` — спринт совпадает;
|
||
- `ensure_link` — нужная связь есть (`source→target` нужного типа или, для типа из `agent_status.symmetric_link_types`,
|
||
`target→source` того же типа) и нет конфликта (связи другого типа из `link_types` между теми же задачами в любом
|
||
направлении, обратной связи несимметричного типа).
|
||
Для `ensure_comment` и `refresh` подтверждения по состоянию нет: такая команда ждёт отчёта агента, довыдачи или отмены
|
||
человеком. Новых команд хаб MUST NOT создавать сам: повтор после `failed` — новая команда с теми же параметрами по запросу
|
||
интерфейса.
|
||
|
||
#### Scenario: Отчёт потерян, факт на месте
|
||
- **WHEN** `ensure_sprint {key: X, sprint: "Спринт 2"}` в `claimed` 20 минут, агент перезапущен, `refresh` показал X в «Спринт 2»
|
||
- **THEN** команда `confirmed_by_state`, следующая команда очереди выдаётся
|
||
|
||
#### Scenario: Ответ на claim потерян
|
||
- **WHEN** команда 40 выдана (`deliveries = 1`), ответ до агента не дошёл, агент снова прислал `claim`
|
||
- **THEN** `claim` отдаёт команду 40, `deliveries = 2`; в очереди есть `queued` команды, но выдаётся 40
|
||
|
||
#### Scenario: Предел выдач
|
||
- **WHEN** команда 41 в `claimed` с `deliveries = 3`, агент снова прислал `claim`, в очереди есть команда 42
|
||
- **THEN** 41 — `failed` «агент не отчитался после 3 выдач»; `claim` отдаёт 42
|
||
|
||
#### Scenario: Создание подтверждается по состоянию
|
||
- **WHEN** `ensure_issue {summary: "Шахматка: комментарии"}` в `claimed`, `agent_status.jira_user = "bridge-user"`,
|
||
`refresh` загрузил задачу с этой темой, `reporter = "bridge-user"`, созданную сегодня
|
||
- **THEN** команда `confirmed_by_state` с ключом задачи; заметка `new:N` перенесена на ключ
|
||
|
||
#### Scenario: Связь симметричного типа в обратную сторону
|
||
- **WHEN** `ensure_link {source: A, target: B, link_type: "связанные"}` в `claimed`, тип в `symmetric_link_types`, в
|
||
зеркале есть только «Связанные» B→A
|
||
- **THEN** команда `confirmed_by_state`
|