analyst(ET): auto-commit from analyst run_id=52
This commit is contained in:
256
docs/work-items/ET-012/03-acceptance-criteria.md
Normal file
256
docs/work-items/ET-012/03-acceptance-criteria.md
Normal 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 и ТЗ.
|
||||
Reference in New Issue
Block a user