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