Andrey Epifancev 5a6cdecc8e OpenSpec change-history: журнал изменений задач (#70)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 10:04:52 +04:00

7.0 KiB
Raw Blame History

ADDED Requirements

Requirement: Журнал изменений

Если в параметрах refresh есть history, агент (с v13) SHALL после отправки всех пачек состояния прочитать журнал изменений (changelog) Jira и отправить его хабу. history может содержать since, jql или оба:

  • since (регулярная часть) — из задач, выгруженных этим refresh (база и соседи), агент SHALL отобрать те, у которых Jira updated не раньше since (сравнение моментов времени с учётом смещения), и прочитать их журнал запросом search jql = key in (...), fields = key, expand = changelog, по 100 ключей; из журнала каждой задачи взять записи с created не раньше since. Таких задач нет — вызовов Jira на этом шаге нет.
  • jql (стартовая загрузка) — агент SHALL выполнить search по history.jql с fields = key, expand = changelog (постранично, maxResults = 100) и взять журнал найденных задач целиком. Задача, уже прочитанная регулярной частью, повторно не читается.

Если у задачи changelog.total больше числа записей в ответе, агент SHALL дочитать журнал GET /rest/api/2/issue/{key}?fields=key&expand=changelog; если и он неполный — передать что есть, записать в лог ключ и «получено N из M» и добавить ключ в history.incomplete результата.

Элемент для хаба — одно поле записи журнала: {history_id, seq, key, created, author, field_id, field_name, from, from_string, to, to_string}: history_id — id записи строкой; seq — номер элемента в items записи, с 0; created — время записи как есть; author — отображаемое имя автора, иначе логин, иначе null; field_name — field элемента; field_id — fieldId элемента, а если его нет: для fieldtype = custom — id поля, найденный по имени в GET /rest/api/2/field (не больше одного такого вызова за refresh), для прочих — field; не найден — null; from, fromString, to, toString — как есть, нестроковое значение — строкой. Запись без id или с пустым created и элемент с пустым field агент SHALL отбросить с записью в лог (ключ задачи). Элементы SHALL отправляться POST /agent/history пачками до 500; повторы при отправке — как у пачек состояния.

Результат refresh SHALL содержать history = {issues: <задач с хотя бы одним элементом>, changes: <элементов отправлено>, incomplete: [<ключи>]}. Ответ не 2xx (HttpError) от Jira или хаба MUST NOT переводить refresh в failed и MUST NOT вызывать повтор команды: агент SHALL записать ошибку в лог и вернуть done, добавив в history текст ошибки (у ошибки хаба — префикс «хаб: ») — error при ошибке регулярной части или отправки элементов, backfill_error при ошибке search стартовой загрузки (регулярная часть при этом выполняется). Обрыв связи с Jira или хабом (ConnectionError) на шаге журнала обрабатывается, как в остальном refresh. Без history в параметрах агент журнал не читает, ключа history в результате нет.

Scenario: Ничего не менялось

  • WHEN refresh {jql: base, history: {since: "2026-10-22T08:45:00Z"}}, у всех 180 задач updated раньше since
  • THEN вызовов search с expand = changelog нет; результат history = {issues: 0, changes: 0, incomplete: []}

Scenario: Две задачи изменились

  • WHEN тот же refresh, у X и Y updated позже since; у X в журнале записи от вчера и от 09:10 UTC (смена статуса и резолюции), у Y — от 09:30 UTC (правка описания)
  • THEN один search key in (X,Y) с expand = changelog; хабу отправлены 3 элемента (у X — seq 0 и 1 одной записи, вчерашняя запись не отправлена); результат history = {issues: 2, changes: 3, incomplete: []}

Scenario: Стартовая загрузка

  • WHEN refresh {jql: base, history: {jql: "(base) AND sprint in openSprints() AND statusCategory != Done"}}, по ней 12 задач
  • THEN search по history.jql с expand = changelog; журнал 12 задач отправлен целиком

Scenario: Плановая дата без fieldId

  • WHEN элемент журнала {field: "Плановое окончание", fieldtype: "custom"} без fieldId; в GET /field у поля с этим именем id customfield_10201
  • THEN у элемента field_id = "customfield_10201"; GET /field вызван один раз за refresh

Scenario: Ошибка Jira при чтении журнала

  • WHEN search с expand = changelog для регулярной части ответил 500
  • THEN состояние в хабе обновлено; refresh — done с history.error, повтора команды нет; в логе — ошибка

Scenario: Ошибка стартовой загрузки

  • WHEN history = {since: …, jql: …}, search по history.jql ответил 400, у X updated позже since
  • THEN журнал X отправлен; refresh — done, в history — backfill_error с текстом Jira, error нет

Scenario: Неполный журнал

  • WHEN у X changelog.total = 250, и search, и GET /issue/X?expand=changelog вернули 100 записей
  • THEN отправлены 100 записей X; в логе «X: получено 100 из 250»; history.incomplete = ["X"]

Scenario: Хаб без журнала

  • WHEN refresh {jql: base} без history
  • THEN журнал не читается; в результате нет ключа history