Files
wiki/tasks/orchestrator/ORCHESTRATOR_DOCS.md
2026-06-04 03:00:01 +03:00

408 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Документация: Orchestrator Multi-Agent Pipeline
## Статус: 2026-06-03 (актуально)
> **Свежее (03.06.2026):** переход на **status-only verdict model** (PR #12) — вердикт Славы теперь через **смену статуса** Issue (Approved/Rejected), а НЕ через коммент `:approved:`. Закрыты **7 багов** конвейера (PR #12#18) при боевой обкатке на ET-011 (task 29). Конвейер впервые прошёл **analysis→architecture→development end-to-end** с одного Approved, дошёл до **честного CI-гейта** и получил **автономное самовосстановление** на красном CI (с лимитом попыток). Подробности — в разделах «Переходы статусов», «Механизмы автономности» и Changelog.
---
## Архитектура
```
Plane (Work Items) → Webhook → Orchestrator → Claude CLI agents → Gitea → Deploy
```
**Orchestrator** — Python FastAPI приложение в Docker на mva154 (port 8500).
Слушает webhooks от Plane и Gitea, управляет жизненным циклом задач.
---
## Конвейер (Pipeline)
```
created → analysis → architecture → development → review → testing → deploy → done
```
| Stage | Agent | QG (вход) | Что делает |
|-------|-------|-----------|------------|
| analysis | analyst | — | BRD, ТЗ, AC, Test Plan |
| architecture | architect | check_analysis_approved (`:approved:` от Славы) | ADR, архитектурные решения |
| development | developer | check_architecture_done (файлы ADR есть) | Код + тесты + PR. **Выход гейтится `check_ci_green`** (PR #17): зелёный CI → review; красный CI → developer retry (PR #18, max 3) |
| review | reviewer | check_ci_green (Gitea CI status, выход development) | Code review → 12-review.md |
| testing | tester | check_reviewer_verdict (APPROVED в 12-review.md) | Прогон тестов → 13-test-report.md |
| deploy | deployer | check_tests_passed (PASS в 13-test-report.md) | Merge PR → tag → deploy → smoke |
| done | — | — | Задача завершена |
---
## Агенты
### Конфигурация (AGENT_CONFIGS)
| Agent | Task file | System prompt | Model |
|-------|-----------|---------------|-------|
| analyst | `.task.md` | `.openclaw/agents/analyst.md` | claude-sonnet-4-6 |
| architect | `.task-arch.md` | `.openclaw/agents/architect.md` | **claude-opus-4-7** |
| developer | `.task-dev.md` | `.openclaw/agents/developer.md` | claude-sonnet-4-6 |
| reviewer | `.task-review.md` | `.openclaw/agents/reviewer.md` | **claude-opus-4-7** |
| tester | `.task-test.md` | `.openclaw/agents/tester.md` | claude-sonnet-4-6 |
| deployer | `.task-deploy.md` | `.openclaw/agents/deployer.md` | claude-sonnet-4-6 |
### Deployer (добавлен 2026-06-01)
**Функции:**
1. Merge PR через Gitea API
2. Создать semver tag (patch increment)
3. Deploy (git pull main на сервере)
4. Healthcheck (до 60 сек, 12 попыток)
5. Smoke test (ключевые endpoints)
6. Rollback к предыдущему тегу при fail
7. Записать `14-deploy-log.md` + обновить `CHANGELOG.md`
**Запрещено:** менять код, force push, деплоить без merge.
---
## Quality Gates
| QG | Функция | Что проверяет |
|----|---------|---------------|
| QG-0 | Валидация при создании Issue | title 5-80 chars, description ≥2 предложений |
| check_analysis_approved | `:approved:` в комментарии Plane от стейкхолдера | Человеческое подтверждение ТЗ |
| check_architecture_done | Наличие ADR файлов в `docs/work-items/<id>/06-adr/` | Архитектура задокументирована |
| check_ci_green | Gitea commit status API | CI pipeline зелёный (гейт стадии **development**, PR #17) |
| check_reviewer_verdict | Парсинг `12-review.md` → APPROVED/REQUEST_CHANGES | Код прошёл ревью |
| check_tests_passed | Парсинг `13-test-report.md` → PASS/FAIL | Тесты пройдены |
| ~~check_tests_local~~ | ~~локальный pytest целевого репо~~ | **DEPRECATED (PR #17):** заменён `check_ci_green` на development. Остаётся в `QG_CHECKS` для совместимости, не wired ни к одной стадии |
---
## Plane Integration (полная)
### Статусы Issue
| Статус | ID | Когда | Что значит для Славы |
|--------|-----|-------|---------------------|
| **Backlog** | `113b24f6...` | Создан, ещё не взят | Ничего не происходит |
| **Todo** | `2c7d3df3...` | Прошёл QG-0, ждёт запуска | Скоро начнётся |
| **In Progress** | `b873d9eb...` | Агент работает | Система работает, ждать |
| **Needs Input** | `babf08a3...` | Analyst задал вопросы | **Слава, ответь в комментарии** |
| **In Review** | `38fb1f64...` | ТЗ готово, ждёт approve | **Слава, прочитай и `:approved:` / `:rejected:`** |
| **Blocked** | `6c4543f9...` | Ошибка / retry исчерпаны | **Нужно ручное вмешательство** |
| **Done** | `381a2833...` | Всё задеплоено | Готово |
| **Cancelled** | `b1cae7f9...` | Отменена | — |
### Переходы статусов (status-only verdict model, PR #12, 03.06.2026)
> **Ключевое изменение:** вердикт Славы принимается **только по смене статуса** Issue в Plane (**Approved** / **Rejected**), не по комменту `:approved:`. Комменты теперь `logged only, no pipeline action`. Причина Rejected берётся из последнего коммента.
```
Backlog → [QG-0 pass] → In Progress (analyst запущен)
In Progress → [analyst questions] → Needs Input
Needs Input → [Слава ответил] → In Progress (analyst перезапущен)
In Progress → [analyst done] → In Review (BRD/ТЗ/AC готовы, ждёт статус Approved)
In Review → [Слава → статус Approved] → Architecture (architect запущен) ← БАГ 4 фикс (PR #15)
In Review → [Слава → статус Rejected + причина комментом] → In Progress (analyst перезапущен)
Architecture → [architect done + QG] → Development → ... → Done
In Progress → [3 retry исчерпаны / deploy fail] → Blocked
```
**Статусы-вердикты (новые):**
- **Approved** (`a519a341...`) — Слава одобрил ТЗ → конвейер двигается на следующую стадию (analysis→architecture).
- **Rejected** — Слава отклонил → причина из коммента → analyst перезапуск.
- **Architecture** (`3020bbb7...`) — рабочая стадия architect.
- Без мелькания In Progress при advance: `_try_advance_stage` сразу PATCH-ит статус следующей стадии.
### Комментарии в Plane
Orchestrator автоматически пишет комментарии при:
- Каждом переходе stage (с ссылками на branch и PR)
- QG failure (что не прошло и почему)
- Запуске агента
- Вопросах analyst'а (текст вопросов)
- Ошибках (deploy fail, retry exhausted)
- Завершении задачи
### Ссылки в комментариях
При переходах stage / готовности analyst комментарий содержит кликабельные ссылки на доки (BRD/ТЗ/AC) + branch/PR.
> **ВАЖНО (PR #14, 03.06.2026):** ссылки строятся от **отдельного публичного URL** `gitea_public_url` (env `ORCH_GITEA_PUBLIC_URL`, прод: `https://git.mva154.duckdns.org`), А НЕ от внутреннего `gitea_url` (`localhost:3000`, он для git clone/push). Без этого ссылки вели на localhost и не кликались из браузера. Fallback: если `gitea_public_url` пустой → используется `gitea_url`.
- 📂 Branch: ссылка на ветку в Gitea (публичный URL)
- 🔗 PR: ссылка на Pull Request (на этапах review/testing/deploy)
- 📄 Доки: Business request / BRD / ТЗ / Acceptance Criteria (публичный URL, кликабельные)
### Webhook events
| Event | Действие |
|-------|----------|
| `work_item.created` / `issue.created` | QG-0 → create branch → init docs → launch analyst |
| `issue.updated` (смена статуса) | **Главный вердикт-путь (PR #12):** Approved → advance, Rejected → rollback+relaunch, In Progress → start pipeline |
| `comment.created` / `issue_comment.created` | **Только лог** (`logged only, no pipeline action`) — комменты БОЛЬШЕ НЕ двигают конвейер (было: `:approved:`/`:rejected:`). Причина Rejected читается из последнего коммента при вердикте |
### QG-0: Валидация при создании Issue
При создании Issue в Plane, orchestrator проверяет:
- Title: 5-80 символов
- Description: ≥2 предложений
Если не проходит → Issue переходит в **Blocked** + комментарий с описанием что исправить.
---
## Механизмы автономности
### Auto-advance
После завершения агента (exit 0), `_monitor_agent` вызывает `_try_advance_stage`:
1. Определяет текущий stage задачи
2. Проверяет QG следующего stage
3. Если QG green → advance stage → launch next agent
4. Если QG red → stop (ждёт внешнего события)
### Auto-PR
После developer push, `_ensure_pr()` автоматически создаёт PR в Gitea.
### Auto-init
При создании Issue в Plane → webhook → QG-0 → branch → docs → analyst.
Слава просто создаёт Issue — всё остальное автоматически.
### Retry (developer)
При `REQUEST_CHANGES` от reviewer'а — developer перезапускается (до 3 раз).
Код: `src/webhooks/gitea.py` `handle_pr`, ветка `REQUEST_CHANGES`.
### Retry (CI fail) — баг 7, PR #18 (03.06.2026)
При **красном Gitea CI** на стадии `development` конвейер не виснет, а **автономно возвращает задачу developer'у** на доработку — **симметрично** review-retry.
- Триггер: `handle_ci_status`, ветка `state == "failure" and current_stage == "development"`.
- Логика: `notify_qg_failure` (уведомить о провале) → если `retry_count < MAX_DEV_RETRIES``enqueue_job("developer", ..., "CI failed, fix and re-push (attempt N/3)")`; иначе → `notify_error` (escalate, ручное вмешательство).
- Задача УЖЕ в `development` → смены стадии нет (в отличие от review-retry, где был переход review→development).
- **Лимит общий:** `MAX_DEV_RETRIES = 3` считается по ВСЕМ `agent_runs` агента `developer` задачи → review-правки + CI-правки в сумме ≤ 3. После 3 попыток — эскалация.
- ✅ Проверено боевым прогоном на ET-011: красный CI → developer перезапущен (run 1→2, attempt 2/3) полностью автономно.
### Retry (tester fail)
При FAIL тестов — developer перезапускается для фикса (до 3 раз).
После 3 неудач → Issue переходит в **Blocked**.
### Analyst questions
Analyst может создать `01-questions.md` → Issue переходит в **Needs Input**.
Слава отвечает комментарием → analyst перезапускается с ответами (до 3 раундов).
### Notifications
Telegram уведомления на каждом переходе stage + при ошибках.
---
## Сценарии работы
### 🟢 Позитивный (happy path)
1. Слава создаёт Issue в Plane: "Добавить фильтр по высоте"
2. QG-0 ✓ → branch `feature/ET-012-filter-altitude` → analyst запущен
3. Analyst пишет BRD/ТЗ/AC/TestPlan → Issue → **In Review**
4. Слава читает, пишет `:approved:` → Issue → **In Progress**
5. Architect → ADR → auto-advance
6. Developer → код + тесты → PR → auto-advance
7. Reviewer → APPROVED → auto-advance
8. Tester → PASS → auto-advance
9. Deployer → merge → tag → deploy → smoke ✓ → Issue → **Done**
10. Telegram: "🎉 ET-012: задача завершена!"
### 🟡 Analyst задаёт вопросы
1. Analyst не понимает требования → создаёт `01-questions.md`
2. Issue → **Needs Input** + Telegram: "❓ ET-012: Analyst задаёт вопросы"
3. Слава отвечает комментарием в Plane
4. Orchestrator ловит комментарий → Issue → **In Progress** → analyst перезапущен
5. Analyst учитывает ответы → пишет ТЗ → Issue → **In Review**
6. (Максимум 3 раунда вопросов, потом → **Blocked**)
### 🟡 Слава отклоняет ТЗ
1. Issue в **In Review**, Слава пишет `:rejected: Не учтены мобильные устройства`
2. Issue → **In Progress** → analyst перезапущен с причиной отклонения
3. Analyst исправляет → Issue → **In Review** (повторно)
### 🔴 Tester находит баги
1. Tester прогоняет тесты → FAIL в `13-test-report.md`
2. Issue остаётся **In Progress** → developer перезапущен для фикса
3. Developer фиксит → reviewer → tester (повторно)
4. Если 3 попытки developer'а не помогли → Issue → **Blocked**
5. Telegram: "🚨 ET-012: Tests still failing after 3 retries"
### 🔴 Deploy fail
1. Deployer мержит PR, деплоит, smoke test FAIL
2. Deployer откатывает к предыдущему тегу
3. Issue → **Blocked**
4. Telegram: "🚨 ET-012: Deploy failed! Rolled back."
### 🔴 Architect conflict
1. Architect находит конфликт с ТЗ → создаёт `10-conflict.md`
2. Issue → **In Progress** → analyst перезапущен с описанием конфликта
3. Analyst пересматривает ТЗ → Issue → **In Review** (повторно)
---
## Файловая структура
```
/home/slin/repos/orchestrator/
├── src/
│ ├── main.py # FastAPI app
│ ├── config.py # Settings (env vars)
│ ├── db.py # SQLite (tasks, agent_runs, events)
│ ├── stages.py # STAGE_TRANSITIONS
│ ├── notifications.py # Telegram notifications
│ ├── plane_sync.py # Plane API (states, comments, links)
│ ├── agents/
│ │ ├── launcher.py # AgentLauncher (launch, monitor, retry, advance)
│ │ └── __init__.py
│ ├── webhooks/
│ │ ├── gitea.py # Push, PR, CI status handlers
│ │ ├── plane.py # work_item.created, comment handlers
│ │ └── __init__.py
│ └── qg/
│ ├── checks.py # QG check functions
│ └── __init__.py
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
```
---
## Инфраструктура
- **Host:** mva154 (82.22.50.71)
- **Container:** `orchestrator` (port 8500)
- **Gitea:** localhost:3000 (в docker network)
- **Plane:** localhost:8091 (в docker network)
- **Repos:** `/home/slin/repos/orchestrator`, `/repos/enduro-trails` (в контейнере)
- **DB:** SQLite (`/app/data/orchestrator.db`)
- **Logs:** `/app/data/logs/` (per-agent run logs)
---
## Единственная точка ручного вмешательства
**`:approved:` после analyst'а** — Слава подтверждает ТЗ в Plane.
Всё остальное — полностью автономно:
- auto-init при создании Issue
- architect запускается автоматически после approve
- developer → после architecture done
- reviewer → после CI green
- tester → после review approved
- deployer → после tests passed
- done → после deploy success
- retry при ошибках (до 3 раз)
- rollback при deploy fail
---
## Автономный деплой
Deployer выполняет деплой через SSH на хост:
```bash
ssh slin@127.0.0.1 "bash /home/slin/bin/enduro-deploy-hook.sh"
```
Hook (`/home/slin/bin/enduro-deploy-hook.sh`) делает:
1. `git pull origin main` в репо проекта
2. `docker compose up -d app` — перезапуск app контейнера
3. Опционально: `docker compose --profile batch run --rm gps-collector` (флаг `--run-gps-collector`)
4. Логирует всё в `/var/log/enduro-trails/deploy-hook.log`
SSH ключ orchestrator'а: `/home/slin/.orchestrator-ssh/id_ed25519` (смонтирован в контейнер как `/root/.ssh/`)
---
## Расхождения с Proposal v1
Полная таблица: `tasks/multi-agent/PROPOSAL_VS_REALITY.md`
Ключевые отличия от идеала:
- Designer не реализован (skip для не-UI задач)
- QG упрощены (проверка файлов, не lint-скрипты)
- Один environment (test), нет prod
- Нет budget tracking
- Architect и Reviewer на Opus, остальные на Sonnet (proposal: все на Sonnet)
- Нет Plane подзадач (7 subtasks) — один Issue, этапы в orchestrator DB
---
## 🐛 Баги входа/выхода analyst — сессия 03.06.2026 (PR #12#15)
Цепочка из 4 багов, вскрытых при переходе на status-only verdict model и прогоне реальной задачи ET-011 (#6, task 29). Все подтверждены боевыми прогонами.
| # | PR | Баг | Корень | Фикс |
|---|----|----|--------|------|
| Баг 3 | **#12** | Эхо-самоудар: собственный коммент/статус analyst сбивал стадию | Коммент-вердикт (`:approved:`) ловился на собственных комментах бота | **status-only verdict model**: вердикт только по смене статуса, комменты `logged only` |
| Баг A | **#13** | `description` из Plane НЕ попадал в `.task.md` (пустышка 101 байт) | Описание тянулось из API, но не писалось в файл задачи | `.task.md` = 973 байта с полным ТЗ |
| Баг B | **#13** | `name` не дотягивался → ветка `untitled` | Имя Issue не читалось из API | ветка `feature/ET-011-popup-enduro-trails` |
| Баг C | **#13** | Устаревший коммент «Жду :approved:» + нет ссылок на доки | Коммент analyst не обновлён под status-only | коммент про Approved-статус + ссылки на 6 доков |
| — | **#14** | Ссылки в комменте вели на `localhost:3000` — не кликались из браузера | Ссылки строились от внутреннего `gitea_url` | новое поле `gitea_public_url` (env `ORCH_GITEA_PUBLIC_URL`), fallback на `gitea_url`; git-операции не тронуты |
| **Баг 4** | **#15** | **БЛОКЕР:** ручной Approved из analysis НЕ двигал конвейер — task застревал в analysis, architect не запускался | `advance_stage()`: ветка `check_analysis_approved` ВСЕГДА делала ранний `return result` → блок Advance недостижим. На webhook-пути (Approved, `finished_agent=None`) `_handle_analysis_approved_flow` выходил ничего не делая | развёл ветку по `agent`: `analyst`(launcher)→flow+return (In Review+коммент), `None`(вердикт)→провал в Advance (update_task_stage+enqueue architect) |
**Итог:** конвейер впервые прошёл **analysis→architecture end-to-end** через живой Approved Славы. architect запущен, создал ADR-014/ADR-015 + infra-requirements.
---
## 🐛 Баги QG-гейта development — сессия 03.06.2026 (PR #16#18)
Продолжение боевой обкатки ET-011: конвейер дошёл до стадии `development``review` и вскрыл цепочку багов QG-гейта (спуск по слоям инфраструктуры: каждый фикс открывал следующий). Все подтверждены боевыми прогонами.
| # | PR | Баг | Корень | Фикс |
|---|----|----|--------|------|
| **Баг 5** | **#16** | QG `check_tests_local` падал — `make` НЕТ в контейнере оркестратора | `check_tests_local` звал `["make","test"]`, но в контейнере только pytest, без make | прямой вызов `["python","-m","pytest","../../tests/","-v"]`, `cwd=<repo>/src/api` (1:1 с Makefile целью `test`). make в Dockerfile НЕ добавляли намеренно |
| **Баг 6** | **#17** | `check_tests_local` дублировал CI и гонял тесты enduro-trails в окружении оркестратора без зависимостей проекта (`ModuleNotFoundError: lxml/shapely/defusedxml`) | `check_tests_local` — легаси-затычка эпохи S-1 «Gitea CI не настроен → always false». CI теперь настроен (`.gitea/workflows/ci.yml`, гоняет в ПРАВИЛЬНОМ окружении с `pip install ".[dev]"`) → затычка устарела | QG стадии `development`: `check_tests_local``check_ci_green` (доверились CI). `check_tests_local` НЕ удалён — помечен DEPRECATED, остался в `QG_CHECKS`, не wired ни к одной стадии. CI-failure больше не подавляется → `notify_qg_failure` |
| **Баг 7** | **#18** | **ДЫРА:** красный CI на `development` НЕ возвращал задачу developer'у — конвейер вис, требовал ручного вмешательства | побочка фикса бага 6: CI стал авторитетным гейтом, но `handle_ci_status` при failure делал ТОЛЬКО `notify_qg_failure`. retry developer'а был только на review request_changes | добавлен retry developer'а на красный CI **симметрично** review: `retry_count < MAX_DEV_RETRIES(=3)``enqueue_job("developer", attempt N/3)`; иначе → escalate. Лимит **общий** (review+CI ≤ 3). Подробно — раздел «Retry (CI fail)» |
| **Баг 8** | **#19** | **КРИТИЧНЫЙ:** провалившийся деплой уходил в `done` — фича нерабочая, а задача «выполнена». Вскрыто на ET-011: deployer честно написал `Status: FAILED` (hook permission denied, контейнер не пересобрался), но done | (1) `stages.py` стадия deploy имела `qg: None` — ГЕЙТА ВЫХОДА НЕТ вообще. (2) `exit_code` = код LLM-процесса (всегда 0 при успешной сессии) → защита `launcher.py:475 exit_code!=0` не срабатывала | новый QG **`check_deploy_status`** (по образцу `check_reviewer_verdict`): читает frontmatter `deploy_status:` из `14-deploy-log.md`. `deploy.qg`=`check_deploy_status`. Вердикт SUCCESS→done, FAILED→откат в development + Blocked + alert (по ВЕРДИКТУ, не exit_code). deployer-промпт enduro-trails обязан писать `deploy_status: SUCCESS/FAILED` |
**Итог:** после бага 7 конвейер **самовосстанавливается** на красном CI. Боевой прогон ET-011: красный CI (10× E402 — импорты не в шапке `src/api/main.py`) → developer автономно перезапущен (run 1→2, attempt 2/3).
**Баг 8 — важный урок:** «зелёный в CI ≠ работает на проде». Конвейер пометил ET-011 `done`, но фича (скачивание GPX) НЕ работала: deploy-hook упал на permission denied (`/var/log/enduro-trails` root-owned, `slin` без sudo), контейнер остался на старом образе 37 часов. **Инфра-блокер (TODO ops):** `sudo chown slin:slin /var/log/enduro-trails` или сменить LOG-путь в hook на доступный slin. После фикса бага 8 такой провал будет честно откатывать задачу в development, а не помечать done.
> **Важный принцип (из багов 5/6):** оркестратор **НЕ гоняет тесты целевых проектов в своём контейнере** — это делает Gitea CI в правильном окружении со всеми зависимостями. QG-гейт development доверяет CI-статусу (`check_ci_green`), а не локальному прогону. НЕ ставить lxml/shapely/defusedxml/make в Dockerfile оркестратора.
---
## Changelog
| Дата | Изменение |
|------|-----------|
| 2026-06-03 | **PR #19** Fix Баг 8: deploy→done гейтится новым `check_deploy_status` (вердикт `deploy_status:` из 14-deploy-log.md), а не exit-code LLM-процесса. Провал deploy → откат в development + Blocked. + deployer-промпт enduro-trails пишет frontmatter (enduro-trails #23) |
| 2026-06-03 | **enduro-trails #23** Feat (A1, решение владельца): enduro_russia `download_allowed: true` — скачивание GPX включено |
| 2026-06-03 | **PR #18** Fix Баг 7: красный CI на development автономно возвращает задачу developer'у (retry с общим лимитом 3, симметрично review) |
| 2026-06-03 | **PR #17** Fix Баг 6: QG development `check_tests_local``check_ci_green` (доверились CI, не гоняем чужие тесты в контейнере; `check_tests_local` DEPRECATED, CI-failure больше не подавляется) |
| 2026-06-03 | **PR #16** Fix Баг 5: `check_tests_local` зовёт pytest напрямую вместо `make` (make нет в контейнере) |
| 2026-06-03 | **PR #15** Fix Баг 4: Approved-вердикт двигает analysis→architecture (развёд `check_analysis_approved` по `finished_agent`) |
| 2026-06-03 | **PR #14** Add: `gitea_public_url` — внешний URL для кликабельных ссылок в комментах (git-операции на localhost не тронуты) |
| 2026-06-03 | **PR #13** Fix Баги A/B/C: `description``.task.md`, `name`→ветка (не untitled), коммент analyst под status-only + ссылки на доки |
| 2026-06-03 | **PR #12** Add: **status-only verdict model** — вердикт по смене статуса (Approved/Rejected), комменты `logged only` (фикс Баг 3 эхо-самоудар) |
| 2026-05-31 | Fix: `_monitor_agent` PIPE streaming (race condition) |
| 2026-05-31 | Fix: `check_reviewer_verdict` вместо `check_review_approved` |
| 2026-05-31 | Add: `_ensure_pr` (auto-PR after developer push) |
| 2026-05-31 | Add: REQUEST_CHANGES retry logic (3 attempts) |
| 2026-06-01 | Add: **deployer agent** (merge → tag → deploy → smoke → rollback) |
| 2026-06-01 | Remove: `_auto_merge_pr` hardcode from `_try_advance_stage` |
| 2026-06-01 | Add: **Plane states** — Needs Input, In Review, Blocked |
| 2026-06-01 | Add: **Analyst questions flow** (01-questions.md → Needs Input → relaunch) |
| 2026-06-01 | Add: **:rejected: handler** с причиной + relaunch |
| 2026-06-01 | Add: **Tester FAIL → developer retry** (до 3 раз → Blocked) |
| 2026-06-01 | Add: **Deploy FAIL → Blocked** + rollback |
| 2026-06-01 | Add: **Architect conflict** (10-conflict.md → rollback to analysis) |
| 2026-06-01 | Add: **QG-0** валидация при создании Issue |
| 2026-06-01 | Add: **Ссылки в комментариях** (branch + PR URLs) |
| 2026-06-01 | Add: **Max 3 question rounds** для analyst |
| 2026-06-01 | Fix: **Analyst prompt** — добавлена явная инструкция использовать Write tool (артефакты на диск, не в stdout) |
| 2026-06-01 | Fix: **Analyst model** — возвращён на Sonnet (был переключён на Opus, расходовал лимиты Max 5x) |
| 2026-06-01 | Add: **Startup timeout 120s** — если Claude CLI не выдаёт output за 120 сек → kill + Telegram уведомление |
| 2026-06-01 | Fix: **Plane comment webhook**`handle_comment` теперь читает поле `issue` (Plane шлёт именно его, не `work_item_id`/`issue_id`) + `comment_stripped` вместо `comment_html` |
| 2026-06-01 | Add: **SSH deploy hook** — deployer использует SSH для вызова `/home/slin/bin/enduro-deploy-hook.sh` на хосте |
| 2026-06-01 | Add: **openssh-client** в Dockerfile orchestrator, SSH ключ смонтирован с хоста (`/home/slin/.orchestrator-ssh/`) |
| 2026-06-01 | Fix: **Plane comment webhook**`handle_comment` читает поле `issue` (Plane шлёт именно его) + `comment_stripped` |
| 2026-06-01 | Fix: **ET-008 GPS-треки** — pipeline завершён, v0.0.1 задеплоен, gps-collector запущен |