diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a93867a..504d953 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,7 +1,7 @@ # Архитектура Агент — один файл `src/agent.py` (Python 3.9, только стандартная библиотека). Тесты в `tests/` работают на фейках -Jira и хаба. Спека: `../jira-hub/openspec/changes/bridge-core/`, план: `../jira-hub/docs/superpowers/plans/2026-10-11-bridge-core-hub.md`. +Jira и хаба. Спека: `../jira-hub/openspec/changes/bridge-core/`, план агента: `docs/superpowers/plans/2026-10-11-bridge-core-agent.md` (план хаба: `../jira-hub/docs/superpowers/plans/2026-10-11-bridge-core-hub.md`). ## Разделы файла `src/agent.py` diff --git a/CLAUDE.md b/CLAUDE.md index cb306a8..0d1af54 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ ## Стек и ограничения - Python 3.9.6 (как на виртуалке), только стандартная библиотека, без venv и зависимостей. - Запрещено: синтаксис 3.10+ и сторонние импорты. - `tests/test_py39.py` ловит синтаксис 3.10+ (match и т. п.) и сторонние импорты; `X | Y` в аннотациях и API новее 3.9 подтверждает только прогон на /usr/bin/python3 3.9.6. + `tests/test_py39.py` ловит синтаксис 3.10+ (match и т. п.) и сторонние импорты; `X | Y` — синтаксис 3.10: в 3.9 аннотации функций вычисляются при импорте, и прогон тестов на /usr/bin/python3 3.9.6 это ловит; аннотации локальных переменных не вычисляются, их и API новее 3.9 проверяет только ревью. - `agent.json` содержит учётные данные Jira — не коммитить, не отправлять в хаб, не выносить с виртуалки. Шаблон без секретов — `src/agent.example.json`. @@ -42,7 +42,7 @@ ## Первая подсистема Исполнитель команд и цикл агента (спека `agent/spec.md`, `commands/spec.md`) — реализованы целиком: все типы команд, журнал, восстановление после перезапуска, CLI `run|check|prune`. -План и спека живут в `jira-hub`: `openspec/changes/bridge-core/` и `docs/superpowers/plans/2026-10-11-bridge-core-hub.md`. +План и спека живут в `jira-hub`: `openspec/changes/bridge-core/` и `docs/superpowers/plans/2026-10-11-bridge-core-hub.md`; план агента лежит здесь: `docs/superpowers/plans/2026-10-11-bridge-core-agent.md`. ## OpenSpec и superpowers - Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать. diff --git a/README.md b/README.md index fc6b291..258b960 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ nohup python3 -u agent.py > agent.log 2>&1 & ``` Остановка: Ctrl-C или `kill `. Первый сигнал — агент доделывает текущую команду, отправляет отчёт и выходит. -Повторный сигнал — немедленный выход (код 130). Команда, которая не успела записаться в журнал `started`, при этом -остаётся `claimed` в хабе (см. ограничение 2). +Повторный сигнал — немедленный выход (код 130). Остановка во время паузы перед командой безопасна: команда уже +записана в журнал как `claimed`, при следующем запуске агент запрашивает её у хаба и выполняет через проверку (без паузы). ## Автозапуск через launchd (опционально) @@ -95,11 +95,12 @@ launchctl unload ~/Library/LaunchAgents/ru.aepif.jira-agent.plist ## Журнал -`journal.jsonl` лежит рядом с `agent.py`. Это только дозапись: на каждую команду пишутся `started` (после паузы, -перед обращением к Jira), `finished` (результат и счётчики вызовов) и `acked` (хаб подтвердил приём отчёта). -По журналу агент после перезапуска досылает неподтверждённые отчёты и находит команды, начатые, но не завершённые. +`journal.jsonl` лежит рядом с `agent.py`. Это только дозапись: на каждую команду пишутся `claimed` (сразу после получения +из хаба, до паузы), `started` (после паузы, перед обращением к Jira), `finished` (результат и счётчики вызовов) и `acked` (хаб подтвердил приём отчёта). +По журналу агент после перезапуска досылает неподтверждённые отчёты и находит команды, полученные или начатые, но не завершённые. -Журнал содержит параметры команд, то есть тексты задач и комментариев. Не выносить его с виртуалки. +В журнале нет параметров команд (текстов задач и комментариев): только id команды, статус, `result`, `error` и счётчики +вызовов. Но `result` и `error` могут содержать ключи задач и фрагменты ответов Jira, поэтому журнал лучше не выносить с виртуалки. Очистка подтверждённых записей старше 30 дней: @@ -131,8 +132,13 @@ python3 agent.py prune - **Отчёт до подтверждения.** Отчёт повторяется каждые 10 с, пока хаб не подтвердит. Ответы 404/409/422 хаб выдаёт, когда команда уже отменена или удалена: агент считает её закрытой и не зацикливается. - **429 и 503.** Агент ждёт `Retry-After` из ответа Jira и повторяет запрос: до 5 запросов, то есть не более 4 ожиданий; - на пятой неудаче — ошибка команды. Если заголовка нет или он в формате HTTP-date (не разбирается), ждёт 60 с, умноженные на номер попытки. Число ожиданий идёт в `rate_limited` отчёта. -- **Версия.** Константа `AGENT_VERSION` в `agent.py` (сейчас 1) отправляется в каждом `claim`. + на пятой неудаче — ошибка команды. Если заголовка нет или он в формате HTTP-date (не разбирается), ждёт 60 с, умноженные на номер попытки. Число ответов 429/503 идёт в `rate_limited` отчёта. +- **Обрыв связи с Jira (R-10).** Если Jira оборвала соединение или ответила 5xx, исход записи неизвестен: агент не пишет + `finished`, ждёт не меньше 30 с и повторяет команду через проверку («проверь, потом сделай») до 3 раз. Счётчики + суммируются. После исчерпания — `failed` «результат неизвестен (обрыв связи с Jira), повтор безопасен». При остановке + по сигналу команду подхватит восстановление. +- **Версия.** Константа `AGENT_VERSION` в `agent.py` (сейчас 1) отправляется в каждом `claim`, в каждом отчёте + (`agent_version`) и в `/agent/state`. Если хаб назначил `min_agent_version` выше, он пишет в `agent_status.warning`: «обновите агента»; команды при этом выдаются по-прежнему. @@ -157,9 +163,10 @@ python3 agent.py prune 1. **`prune` не запускать при работающем агенте.** Блокировки нет. Запись, добавленная агентом во время `prune`, может пропасть при перезаписи файла. Сначала остановить агента. -2. **Ctrl-C во время паузы перед командой.** Команда остаётся `claimed` в хабе, в журнале о ней ничего нет. - Хаб сам её не переиздаёт. Лечение: в хабе отменить команду с force и повторить: - `POST /commands/{id}/cancel` с телом `{"force": true}`, затем `POST /commands/{id}/retry`. +2. **Остановка во время паузы или между claim и выполнением (R-9).** Команда остаётся `claimed` в хабе, но в журнале + есть запись `claimed`: при перезапуске агент запрашивает её у хаба и выполняет без паузы. Отмена с force и повтор + больше не нужны. Если журнал потерян, команду придётся отменить с force (`POST /commands/{id}/cancel`, + `{"force": true}`) и повторить (`POST /commands/{id}/retry`). 3. **HTTPS хаба с корпоративным CA не поддержан.** `HubClient` работает без `ca_bundle` и `verify_ssl`: `hub_url` должен быть `http://`. Для Jira `verify_ssl` и `ca_bundle` работают. 4. **Спринт в `refresh` — только активные и будущие спринты доски.** Поле `sprint` берётся из Agile API по @@ -171,4 +178,4 @@ python3 agent.py prune записи. Наблюдать на реальной Jira. 7. **429/503 без потолка.** Агент ждёт `Retry-After` в секундах сколько бы он ни был (без верхней границы): не более 4 ожиданий, на пятой неудаче — ошибка. Длинный `Retry-After` держит очередь. Формат HTTP-date в `Retry-After` не разбирается: - тогда ждётся 60 с × номер попытки. + тогда ждётся 60 с × номер попытки. Нецелый `Retry-After` (например, `1.5`) тоже не разбирается: те же 60 с × попытка. diff --git a/tests/test_py39.py b/tests/test_py39.py index 170aa5c..c846b4c 100644 --- a/tests/test_py39.py +++ b/tests/test_py39.py @@ -5,8 +5,8 @@ import unittest from _common import SRC # Ограничение проверки: ast.parse(feature_version=(3, 9)) ловит синтаксис 3.10+ (match и т. п.) и сторонние -# импорты; API и конструкции, новые в 3.9 (например, `X | Y` в аннотациях), не проверяет. Их подтверждает только -# прогон на /usr/bin/python3 (3.9.6). +# импорты; API и конструкции новее 3.9 (например, `X | Y` — синтаксис 3.10: аннотации функций +# вычисляются при импорте и их ловит прогон на /usr/bin/python3 3.9.6, аннотации локальных переменных не вычисляются), не проверяет. class Py39Test(unittest.TestCase):