analyst(ET): auto-commit from analyst run_id=52
Some checks failed
CI / lint (push) Failing after 4s
CI / test (push) Failing after 5s
CI / build (push) Has been skipped

This commit is contained in:
2026-06-02 18:07:57 +00:00
parent 00fda7b61e
commit e09cb12f82

View File

@@ -0,0 +1,256 @@
---
type: acceptance-criteria
work_item_id: ET-012
title: "Acceptance Criteria: Единый stage-engine оркестратора (M-3)"
version: 1
status: draft
created_at: 2026-06-02
updated_at: 2026-06-02
authors:
- "agent:analyst"
target_repo: "orchestrator"
---
# Acceptance Criteria — ET-012
Все критерии формализованы в Gherkin-стиле. Задача считается принятой,
когда **каждый** прошёл проверку в pytest и/или в проверочном
сценарии на test-стенде оркестратора.
## AC-01 — `StageEngine` существует и имеет каноническую сигнатуру
**Given** репозиторий `orchestrator` на ветке
`feature/ET-012-m-3-stage-engine-try-advance-s`
**When** developer завершил работу
**Then** существует файл `src/stage_engine.py`, в котором
определён класс `StageEngine` с публичным методом
`advance(task_id: int, trigger: StageTrigger,
ctx: AdvanceContext) -> AdvanceResult`,
и `StageTrigger`, `AdvanceContext`, `AdvanceResult`,
`HookOutcome` экспортированы из этого же модуля.
## AC-02 — В `launcher.py` остался один тонкий вызов
**Given** `src/agents/launcher.py`
**When** инспекция кода
**Then**:
- метод `_try_advance_stage` существует;
- его тело занимает ≤ 30 строк (без блока try/except);
- тело состоит из: чтение task, сборка `AdvanceContext`, вызов
`self.stage_engine.advance(...)` с `trigger=AGENT_FINISHED`;
- нет прямых вызовов `update_task_stage`, `QG_CHECKS[...]`,
`notify_stage_change`, `plane_notify_stage`, `launcher.launch(...)`
внутри `_try_advance_stage`.
## AC-03 — В `plane.py` остался один тонкий вызов
**Given** `src/webhooks/plane.py`
**When** инспекция кода
**Then**:
- функция `_try_advance_stage` существует;
- её тело занимает ≤ 20 строк;
- тело состоит из сборки `AdvanceContext` и вызова
`await asyncio.to_thread(stage_engine.advance, task_id,
StageTrigger.MANUAL_APPROVE, ctx)`;
- нет прямых вызовов `update_task_stage`, `QG_CHECKS[...]`,
`notify_stage_change`, `plane_notify_stage`, `launcher.launch(...)`
внутри тела.
## AC-04 — Auto-flow analyst → review request — без регрессии
**Given** задача в стадии `analysis`, агент `analyst` только что
завершился с `exit_code=0`, в репозитории есть полные артефакты
(`01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`,
`04-test-plan.yaml`)
**When** оркестратор фиксирует завершение subprocess
**Then**:
- `tasks.stage` остаётся `analysis` (стадия НЕ продвигается до
ручного `:approved:`);
- в Plane выставлен state `in_review`;
- в Plane появился комментарий «📋 BRD/ТЗ/AC/TestPlan готовы. Прошу
review и реакцию :approved: для продвижения в Architecture.» (точный
текст совпадает с baseline до M-3);
- Telegram-уведомление `notify_approve_requested` отправлено
один раз.
## AC-05 — Auto-flow analyst → has questions — без регрессии
**Given** задача в `analysis`, analyst завершился, артефактов нет,
но есть `docs/work-items/<wid>/01-questions.md`
**When** subprocess закончился
**Then**:
- `tasks.stage` остаётся `analysis`;
- в Plane выставлен state `needs_input`;
- в Plane комментарий начинается с «❓ Analyst нуждается в
уточнении:» и содержит текст questions-файла;
- Telegram-сообщение «❓ {wid}: Analyst задаёт вопросы. Ответь в Plane.»
отправлено.
## AC-06 — Manual approve `analysis → architecture`
**Given** задача в `analysis`, в Plane на любом комментарии стоит
реакция `:approved:`
**When** webhook получил эту реакцию
**Then**:
- `tasks.stage` становится `architecture`;
- агент `architect` запущен (есть запись в `agent_runs` с
`agent='architect'` и тем же `task_id`);
- `plane_notify_stage(work_item_id, "analysis", "architecture")` вызван
ровно один раз с теми же аргументами, что и в baseline.
## AC-07 — Reviewer REQUEST_CHANGES (auto-flow) — rollback + relaunch
**Given** задача в `review`, reviewer завершился с
`exit_code=0`, QG `check_reviewer_verdict` возвращает
`(False, "REQUEST_CHANGES: ...")`, в `agent_runs` ранее было 0
запусков developer на этом task_id
**When** subprocess reviewer завершён
**Then**:
- `tasks.stage` становится `development`;
- agent `developer` запущен с `task_desc`, содержащим строку
`Note: REQUEST_CHANGES from reviewer (attempt 1/3).`;
- `notify_stage_change(task_id, "review", "development")` вызван;
- `plane_notify_stage(wid, "review", "development")` вызван.
## AC-08 — Reviewer REQUEST_CHANGES — max retries
**Given** задача в `review`, reviewer завершился, QG False с
REQUEST_CHANGES, в `agent_runs` уже 3 запуска developer на task_id
**When** subprocess reviewer завершён
**Then**:
- `tasks.stage` НЕ становится `development` (или становится — см.
Q-3, но developer **не перезапускается**);
- Telegram-сообщение «⚠️ {wid}: Max developer retries (3) reached.
Manual intervention needed.» отправлено;
- в логе `error: max retries reached`.
## AC-09 — Tester FAIL — rollback + relaunch
**Given** задача в `testing`, tester завершился, QG
`check_tests_passed` возвращает `(False, "<reason>")`, retry_count
developer = 1
**When** subprocess tester завершён
**Then**:
- `tasks.stage` становится `development`;
- в Plane state — `in_progress`;
- комментарий в Plane: «❌ Тесты не прошли: <reason>. Developer
перезапущен для фикса.»;
- developer запущен с `task_desc`, содержащим
`Note: Tests FAILED. Fix failures described in
docs/work-items/<wid>/13-test-report.md`.
## AC-10 — Architect conflict — rollback в analysis
**Given** задача в `architecture`, architect завершился, QG
`check_architecture_done` False, файл
`docs/work-items/<wid>/10-conflict.md` существует
**When** subprocess architect завершён
**Then**:
- `tasks.stage` становится `analysis`;
- в Plane state — `in_progress`;
- в Plane комментарий начинается с «⚠️ Architect нашёл конфликт с
ТЗ. Возврат в Analysis.» и содержит первые 500 символов конфликта;
- analyst запущен с `Note: Architect conflict. Revise TRZ.` в
`task_desc`.
## AC-11 — Manual approve в стадии `review` — путь через
`check_review_approved`
**Given** задача в `review`, PR-ветка `feature/...` имеет открытый PR
в Gitea, в PR — approval reviewer'а; в Plane стоит `:approved:`
**When** webhook получил реакцию
**Then**:
- `find_pr_by_branch(repo, branch)` вернула PR number;
- `check_review_approved(repo, pr_number)` вернула `(True, ...)`;
- `tasks.stage` становится `testing`;
- agent `tester` запущен.
## AC-12 — Manual approve в `review`, PR не найден, есть
`12-review.md`
**Given** задача в `review`, в Gitea PR на этой ветке нет
(закрыт/удалён), но есть файл
`docs/work-items/<wid>/12-review.md`, в Plane `:approved:`
**When** webhook получил реакцию
**Then**:
- `tasks.stage` становится `testing`;
- agent `tester` запущен.
## AC-13 — Manual approve в `review`, нет ни PR, ни файла
**Given** задача в `review`, PR нет, файлов
`12-review.md`/`09-review.md` нет, `:approved:` стоит
**When** webhook
**Then**:
- `tasks.stage` остаётся `review`;
- `notify_qg_failure(task_id, "review", "check_review_approved",
"No open PR found and no review file")` вызван;
- `plane_notify_qg(wid, "review", "check_review_approved",
"No open PR found and no review file")` вызван.
## AC-14 — Терминальная стадия — noop
**Given** задача в стадии `done`
**When** по какой-то причине вызвана `StageEngine.advance`
**Then**:
- `tasks.stage` не меняется;
- никаких уведомлений не отправляется;
- `advance` возвращает `AdvanceResult(outcome="noop",
reason="terminal stage", ...)`.
## AC-15 — Идемпотентность параллельных вызовов
**Given** одновременно поступают два события на один и тот же task:
завершился agent (AGENT_FINISHED) и пришла реакция `:approved:`
(MANUAL_APPROVE)
**When** оба вызывают `StageEngine.advance`
**Then**:
- стадия в БД продвигается **ровно один раз** (это гарантируется тем,
что `advance` повторно читает `tasks.stage` из БД и сверяет с
ожидаемым `next_stage`);
- не возникает двойного запуска одного и того же агента
(в `agent_runs` не появляются две записи с одинаковым `agent` и
разницей < 1 секунды);
- допустим вариант: один из вызовов получит `outcome="noop"` с
`reason="stage already advanced"`.
## AC-16 — Регресс-тесты launcher и webhooks проходят
**Given** ветка ET-012
**When** `pytest tests/test_launcher.py tests/test_webhooks.py`
**Then** все ранее существовавшие тестовые кейсы проходят (за
исключением тех, что были адаптированы под новый импорт — изменения
свыше переименования модулей не допускаются).
## AC-17 — Покрытие unit-тестами
**Given** ветка ET-012
**When** `pytest --cov=src/stage_engine --cov=src/stage_hooks`
**Then** line-coverage обоих модулей ≥ 90%.
## AC-18 — Линт и типизация
**Given** ветка ET-012
**When** `ruff check src/` и `mypy src/stage_engine.py
src/stage_hooks.py`
**Then** оба запускаются без ошибок (warnings допустимы только в
существующих модулях, не в новых).
## AC-19 — Текстовые snapshot-тесты для уведомлений
**Given** ветка ET-012
**When** `pytest tests/test_notifications_snapshot.py`
**Then** для каждого сценария AC-04..AC-13 текст
`plane_add_comment`/`send_telegram`/`notify_*` совпадает с baseline
(snapshot, зафиксированный по версии перед рефакторингом).
## AC-20 — Документация
**Given** ветка ET-012
**When** инспекция docstrings
**Then**:
- `StageEngine`, `StageEngine.advance`, `HookOutcome`,
`AdvanceContext` имеют docstrings, описывающие назначение и
контракт каждого поля;
- модуль `stage_engine.py` имеет module-level docstring со ссылкой
на ET-012 BRD и ТЗ.