17 KiB
MODIFIED Requirements
Requirement: Команда — желаемый результат на одной задаче
Каждая команда SHALL описывать желаемое состояние одной задачи Jira и выполняться агентом как «прочитать → сравнить →
записать только разницу». Повторное выполнение любой команды с тем же результатом в Jira SHALL завершаться done
без записи. Типы команд и их контракт:
| Тип | Параметры | Что читает | Что пишет |
|---|---|---|---|
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 |
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) |
нет нужной и нет конфликта — 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 |
ensure_attachment |
key, file (sha256 файла в хранилище хаба, 64 hex-символа), filename (имя вложения в Jira, без /, \, " и управляющих символов); size хаб дописывает при выдаче |
GET /issue/{key}?fields=attachment |
вложения с тем же filename и размером нет — файл у хаба (GET /agent/files/{file}), затем POST /issue/{key}/attachments (multipart); есть — ничего; результат attachment_id |
refresh |
keys (список) или jql |
search с полями зеркала, по 100 на страницу; плюс дочитывание соседей по issuelinks |
ничего в Jira; состояние уходит в хаб |
Семантика ensure_link: source блокирует/ссылается на target; агент в POST /issueLink SHALL ставить source в
inwardIssue, target в outwardIssue (проверено на Jira банка 11.10.2026).
Текст описания (ensure_fields) и тело комментария (ensure_comment) агент SHALL сравнивать с текущим значением в Jira
без различия переводов строк \r\n и \n; записывается текст команды как есть. Jira хранит присланный текст, а правленный
в веб-интерфейсе — с \r\n (проверено на Jira банка 13.10.2026).
Scenario: Повтор уже выполненной команды
- WHEN
ensure_fields {key: AURORA-1533, duedate: 2026-10-23}выполняется, а в Jira срок уже 23.10 - THEN агент не делает
PUT, отчётdone,jira_calls = 1
Scenario: Описание отличается только переводами строк
- WHEN
ensure_fields {key: X, description: "а\nб"}, а в Jira описание X —"а\r\nб" - THEN агент не делает
PUT, отчётdoneсchanged = []
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_status {key: X, target: ["Разработка", "Готово"]}и X в «Ожидание разработки» - THEN два
POST /transitionsс паузой между ними; если X уже «Готово» —doneбез записи
Scenario: Создание ошибки
- WHEN
ensure_issue {temp_key: new:1, issue_type: bug, summary: "ОСВ 60 не распознана", labels: [AWPCA]}, в настройкахissue_types.bug = {name: "Ошибка"}безid - THEN агент создаёт задачу с
issuetype = {name: "Ошибка"}; отчётdoneс ключом
Scenario: Вложение уже есть
- WHEN
ensure_attachment {key: X, file: <sha>, filename: "ОСВ 60.xlsx"}, у X в Jira есть вложение «ОСВ 60.xlsx» того же размера - THEN агент не скачивает файл у хаба и не загружает его в Jira; отчёт
doneсattachment_idнайденного,jira_calls = 1
Scenario: Вложение с тем же именем, другого размера
- WHEN та же команда, у X есть «ОСВ 60.xlsx» другого размера
- THEN агент загружает файл вторым вложением с тем же именем (Jira это допускает); отчёт
doneс новымattachment_id
Scenario: Создание с проверкой дубля
- WHEN
ensure_issueповторяется после падения агента, а задача с той же темой от нас уже создана час назад - THEN агент не создаёт вторую, отчёт
doneс ключом найденной
Requirement: Политика темпа и пауза на команде
Хаб SHALL хранить pacing как минимальные зазоры в секундах по типу команды (по умолчанию: ensure_issue и
ensure_fields 120, ensure_comment, ensure_status, ensure_link, ensure_sprint, ensure_attachment 30, refresh 2). При выдаче команды
хаб SHALL сообщить pause_seconds = зазор по типу выдаваемой команды; агент SHALL выдержать его от времени своего
последнего вызова Jira. Внутри одной команды с несколькими записями (ensure_link с replace, путь переходов) агент
SHALL выдерживать зазор типа команды между записями.
Scenario: Нет следующей команды — нет паузы
- WHEN очередь пуста
- THEN
claimвозвращает пустой ответ, агент не спит сверх долгого запроса
Requirement: Ошибки и подтверждение по состоянию
Ошибка команды SHALL записываться в error (HTTP-код, текст Jira); хаб SHALL хранить не больше 1500 символов error,
обрезая присланное. Пакет после ошибки продолжается.
Повторная выдача. Агент запрашивает новую команду, только когда у него нет незавершённой (см. agent), поэтому
команда в claimed к моменту следующего claim считается не доставленной или потерянной агентом. При claim хаб SHALL
сначала выдать снова самую старую (по claimed_at, затем id) команду в claimed: тот же id и параметры,
pause_seconds по текущей политике темпа, новый claimed_at, deliveries + 1. Если у неё уже deliveries >= 3, хаб SHALL
перевести её в failed с error = "агент не отчитался после 3 выдач" и перейти к следующей (довыдача другой claimed
или следующая queued). Довыдача — доставка той же выдачи, а не повтор команды: новых команд хаб MUST NOT создавать
сам, повтор после failed — новая команда с теми же параметрами по запросу интерфейса. Если агент всё же отчитался
done с ключом по ensure_issue, переведённой в failed по пределу, хаб SHALL записать ключ в результат и перенести
заметку, не меняя статус (как для отменённой, см. hub-api).
Подтверждение по состоянию. Если после любого refresh желаемый результат команды в claimed виден в зеркале, хаб
SHALL перевести её в confirmed_by_state: ensure_fields — все поля совпадают (строки сравниваются без различия
\r\n и \n; null и пустая строка равны); ensure_status — статус равен последнему в пути; ensure_link — связь
есть и конфликтов нет; ensure_sprint — спринт совпадает. ensure_issue, ensure_comment, ensure_attachment и refresh по
состоянию не подтверждаются (вложений в зеркале нет): потерянный отчёт по ним закрывается повторной выдачей (агент находит созданную задачу поиском дубля)
или отменой человеком.
Scenario: Отчёт потерян, факт на месте
- WHEN
ensure_sprint {key: X, sprint: "Спринт 2"}вclaimed, после выполненногоrefreshзеркало показывает X в «Спринт 2» - THEN команда
confirmed_by_state
Scenario: Описание с CRLF в зеркале
- WHEN
ensure_fields {key: X, description: "a\nb"}вclaimed, послеrefreshв зеркале у Xdescription = "a\r\nb" - 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 {temp_key: new:5, summary: "Отчёт"}выполнена агентом, отчёт не дошёл, агент прислалclaim - THEN команда выдаётся снова; агент находит созданную задачу поиском дубля и отчитывается
doneс её ключом; второй задачи нет, заметкаnew:5перенесена на ключ
ADDED Requirements
Requirement: Описание параметров и ошибки валидации
Описание параметров каждого типа команды (обязательные, необязательные, формат значения, условие на набор) SHALL
храниться в коде хаба в одном месте с правилами валидации; описание инструмента MCP batch_create SHALL собираться из
него (см. mcp). Ошибка валидации «нет <параметры>» и «лишние параметры <параметры>» SHALL заканчиваться ожидаемым
набором параметров типа: «ожидаются <обязательные>» и, если у типа есть необязательные, «необязательные <список>».
Scenario: Неверное имя параметра
- WHEN
POST /batchesсensure_status {key: X, status: "Готово"} - THEN 422 «команда 1 (ensure_status): нет target; ожидаются key, target»
Scenario: Лишний параметр у типа с необязательными
- WHEN
POST /batchesсensure_link {source: X, target: Y, link_type: "связанные", force: true} - THEN 422 «команда 1 (ensure_link): лишние параметры force; ожидаются source, target, link_type; необязательные replace»
Requirement: Проверки ensure_issue с типом bug и ensure_attachment при постановке
При постановке пакета хаб SHALL отвергать 422 без создания пакета: ensure_issue с issue_type = bug, если в настройках
нет issue_types.bug с непустым name или id («нет настройки issue_types.bug»); ensure_attachment, если file — не
64 hex-символа в нижнем регистре, файла с таким sha256 нет в хранилище хаба («файл не загружен: POST /files»),
filename пустой (или из одних пробелов) или содержит /, \, " или управляющие символы (код < 32 и 127,
в том числе CR и LF): такие имена ломают multipart/form-data. Клиент size не передаёт (лишний параметр — 422). При выдаче агенту
(claim, в том числе повторной) хаб SHALL добавить к параметрам ensure_attachment size — текущий размер файла в
хранилище, или size = null, если файла уже нет; в базе параметры команды остаются без size, поэтому повтор команды
(POST /commands/{id}/retry) проходит проверку как новая.
Scenario: Ошибка без настройки типа
- WHEN
POST /batchesсensure_issue {issue_type: bug, …}, вissue_typesтолькоtaskиepic - THEN 422 «команда 1: нет настройки issue_types.bug», пакет не создан
Scenario: Вложение без загруженного файла
- WHEN
POST /batchesсensure_attachment {key: X, file: <64 hex>, filename: "a.xlsx"}, файла нет в хранилище - THEN 422 «команда 1: файл не загружен: POST /files», пакет не создан
Scenario: Ошибка с вложением одним пакетом
- WHEN пакет
[ensure_issue {temp_key: new:1, issue_type: bug, …}, ensure_sprint {key: new:1, sprint: "Спринт 2"}, ensure_attachment {key: new:1, file: <sha>, filename: "ОСВ 60.xlsx"}], файл загружен - THEN пакет создан;
ensure_attachmentвыдаётся после завершенияensure_issueс подставленным ключом иsizeфайла