Andrey Epifancev 6a9f793748 docs(openspec): архив hub-hygiene-d (#32)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:04:39 +04:00

16 KiB
Raw Blame History

commands Specification

Purpose

Контракт команд моста: каждая команда — желаемое состояние одной задачи Jira (ensure_*) или чтение состояния (refresh). Здесь типы команд и их параметры, временные ключи внутри пакета, порядок выполнения, политика темпа, обработка ошибок, повторная выдача и подтверждение по состоянию зеркала.

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) нет нужной и нет конфликта — 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).

Текст описания (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 повторяется после падения агента, а задача с той же темой от нас уже создана час назад
  • THEN агент не создаёт вторую, отчёт done с ключом найденной

Requirement: Временные ключи и ссылки внутри пакета

Параметр-ключ любой команды MAY быть временным new:<n>. Хаб SHALL выдавать агенту такую команду только после done команды ensure_issue с тем же temp_key в том же пакете, подставив настоящий ключ. Если ensure_issue завершилась failed или cancelled, зависимые команды SHALL получить skipped с ошибкой «создание задачи new: отменено», если ensure_issue отменена, иначе «задача new: не создана». Временный ключ вне пакета с его ensure_issue SHALL отвергаться при создании пакета (422).

Scenario: Создать и привязать

  • WHEN пакет: ensure_issue {temp_key: new:60, …}, ensure_link {source: new:60, target: AURORA-1537, …}
  • THEN вторая команда выдаётся агенту с source = AURORA-1590 после завершения первой

Scenario: Создание отменено

  • WHEN пакет: ensure_issue {temp_key: new:901, …} (отменена с force: true), ensure_comment {key: new:901, …}
  • THEN ensure_comment — skipped с ошибкой «создание задачи new:901 отменено»

Scenario: Создание упало

  • WHEN ensure_issue {temp_key: new:902, …} завершилась failed, зависимая ensure_link {source: new:902, …}
  • THEN ensure_link — skipped с ошибкой «задача new:902 не создана»

Requirement: Пакет и порядок выполнения

Пакет SHALL создаваться одним вызовом с автором, заголовком и списком команд; хаб SHALL назначить seq по порядку списка и добавить в хвост refresh по всем ключам пакета (кроме пакетов, состоящих только из refresh). Хаб SHALL проверять типы параметров: текстовые параметры и ключи — строки (null у необязательного параметра — «не задан»; агент трактует его по контракту типа, для ensure_fields — очистка поля), target команды ensure_status — строка или список строк, labels, labels_add, labels_remove, keys — списки строк, replace — логическое значение; нарушение — 422 с именем параметра. Пакет с двумя и более 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

Scenario: Две задачи с одной темой

  • WHEN пакет содержит ensure_issue {temp_key: new:1, summary: "Отчёт"} и ensure_issue {temp_key: new:2, summary: " Отчёт "}
  • THEN 422 с темой «Отчёт», пакет не создан

Scenario: Параметр не того типа

  • WHEN пакет содержит ensure_fields {key: X, labels_add: "AWPCA"}
  • THEN 422 с указанием labels_add, пакет не создан

Requirement: Политика темпа и пауза на команде

Хаб SHALL хранить pacing как минимальные зазоры в секундах по типу команды (по умолчанию: ensure_issue и ensure_fields 120, ensure_comment, ensure_status, ensure_link, ensure_sprint 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 и 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 в зеркале у X description = "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 перенесена на ключ