## 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`