# Acceptance Criteria: Единообразные коммент-артефакты в Plane Work Item ID: **ORCH-016** Ревизия: 2 (по фидбэку стейкхолдера — все AC по агентам обновлены под строку длительности; добавлены AC-13 / AC-14) Каждый AC сформулирован как чёткое условие PASS/FAIL. Проверяется автоматически (unit/integration) либо ручной верификацией в staging Plane (порт 8501). --- ## AC-1. Архитектор пишет единообразный коммент - **Given** task завершила стадию `architecture` успешно, `06-adr/` содержит как минимум один ADR. - **When** `_post_usage_comments(agent="architect", ...)` вызывается. - **Then** в Plane появляется **ровно один** коммент со структурой: - первая строка: `📐 Architect — Завершил архитектурную проработку. См. ADR ниже.`, - строка `Длительность: ` (формат — см. AC-13), значение соответствует фактическому времени работы архитектора (±1с), - блок «Документы:» с кликабельной ссылкой на `…/src/branch//docs/work-items//06-adr/`, - **нет** строки `Verdict / Status`. - **And** автор коммента — `architect` (`PLANE_BOT_TOKENS["architect"]`, fallback на shared token). - **PASS** при выполнении всех пунктов; **FAIL** при отсутствии любого. ## AC-2. Разработчик пишет единообразный коммент - **Given** task завершила стадию `development`, есть open PR. - **When** `_post_usage_comments(agent="developer", ...)` вызывается. - **Then** коммент в Plane: - `💻 Developer — Завершил разработку. См. PR / branch ниже.`, - строка `Длительность: `, - ссылки: `Branch ` → `…/src/branch/`, `PR #` → `…/pulls/`, - **нет** строки `Verdict`. ## AC-3. Ревьюер пишет коммент с вердиктом - **Given** `12-review.md` содержит frontmatter `verdict: APPROVE` (или `REQUEST_CHANGES`). - **When** `_post_usage_comments(agent="reviewer", ...)` вызывается. - **Then** коммент: - `🔎 Reviewer — Завершил ревью изменений.`, - строка `Verdict: APPROVE` (или `REQUEST_CHANGES`) — содержимое соответствует frontmatter, - строка `Длительность: `, - ссылка `Review` → `…/12-review.md`. - **And** если frontmatter не содержит `verdict:` или файл недоступен — строка `Verdict:` опускается, остальное (в т.ч. длительность) публикуется. ## AC-4. Тестер пишет коммент с вердиктом - **Given** `13-test-report.md` содержит frontmatter `verdict: PASS` (или `FAIL`). - **When** `_post_usage_comments(agent="tester", ...)` вызывается. - **Then** коммент: - `🧪 Tester — Завершил прогон тестов.`, - строка `Verdict: PASS` (либо `FAIL`), - строка `Длительность: `, - ссылка `Test report` → `…/13-test-report.md`. ## AC-5. Деплоер пишет коммент со статусом - **Given** task прошла стадию `deploy` (или `deploy-staging`), артефакт-лог существует с frontmatter `deploy_status: SUCCESS` (или `staging_status: SUCCESS`). - **When** `_post_usage_comments(agent="deployer", ...)` вызывается. - **Then** коммент: - `🚀 Deployer — Завершил деплой.`, - строка `Status: SUCCESS` (или `FAILED`), - строка `Длительность: `, - ссылка `Deploy log` → `…/14-deploy-log.md` (и/или `Staging log` → `…/15-staging-log.md` для staging-стадии). ## AC-6. Аналитик не регрессирует - **Given** существующий поток PR #12/#13 (status-only verdict). - **When** аналитик завершает стадию `analysis` с готовыми `01..04`. - **Then** в Plane: - issue переведён в `In Review` (не меняется), - коммент содержит **то же** человеческое описание (Approved/Rejected инструкции) и список ссылок `BRD / ТЗ / AC / Test Plan` — формат либо идентичен текущему, либо построен через тот же общий хелпер, что и остальные агенты, без потери смысла, - дополнительно к существующему содержимому в комменте присутствует строка `Длительность: ` — значение поднимается из `agent_runs` (последний завершённый run агента `analyst` для этой задачи). ## AC-7. Один коммент на агента за стадию - **Given** агент успешно отработал стадию. - **When** наблюдаем ленту Plane. - **Then** для **каждого** агента (`architect`, `developer`, `reviewer`, `tester`, `deployer`) на стадию приходится **ровно один** status-коммент с артефактами. Дополнительные сервисные комменты (`notify_stage_change`, `notify_qg_failure`, `notify_done`) сохраняются — они не считаются status-комментом. ## AC-8. Graceful fallback при отсутствии артефакта - **Given** артефакт (например, `12-review.md`) ОТСУТСТВУЕТ в worktree на момент коммента (нестандартный сценарий). - **When** `_post_usage_comments(agent="reviewer", ...)` вызывается. - **Then** коммент всё равно публикуется: заголовок + описание, без ссылки на отсутствующий артефакт и без строки `Verdict:`. Исключения не пробрасываются. ## AC-9. Кликабельность через gitea_public_url - **Given** в `.env` задан `GITEA_PUBLIC_URL=https://git.mva154.duckdns.org`, отличный от `GITEA_URL`. - **When** любой агент пишет status-коммент. - **Then** href всех артефакт-ссылок начинается с `https://git.mva154.duckdns.org/` (а не с внутреннего `gitea_url`). - **And** при отсутствии `gitea_public_url` (пустая строка) — fallback на `gitea_url` (обратная совместимость). ## AC-10. Существующие тесты зелёные - **Given** новый код влит в feature-ветку. - **When** запускается `pytest tests/ -q`. - **Then** все ранее существовавшие тесты проходят (нет регрессий status-only verdict, дедупа, `set_issue_done`). ## AC-11. Quality Gates не меняются - **Given** изменения формата комментов. - **When** инспектируется `src/qg/checks.py` и `src/stages.py`. - **Then** реестр `QG_CHECKS` и `STAGE_TRANSITIONS` остаются идентичными версии до PR (diff в этих файлах = ∅). ## AC-12. Документация обновлена - **Given** реализация добавлена в feature-ветку. - **When** reviewer проверяет PR. - **Then** в diff присутствуют обновления: - `CHANGELOG.md` (раздел Unreleased, описание изменения — включая «строку длительности агента в комментах»), - `docs/architecture/README.md` или `docs/architecture/internals.md` (упоминание единого формата status-комментов и строки длительности). - **And** при отсутствии обновлений документации reviewer ставит `verdict: REQUEST_CHANGES` (правило проекта). ## AC-13. Формат строки длительности - **Given** утилитка `fmt_duration(seconds: int) -> str` в `src/usage.py`. - **When** ей передаются граничные значения. - **Then** возвращаемая строка соответствует таблице: - `0` → `"0s"` - `12` → `"12s"` - `59` → `"59s"` - `60` → `"1m 00s"` - `252` → `"4m 12s"` - `3599` → `"59m 59s"` - `3600` → `"1h 00m"` - `3780` → `"1h 03m"` - `10020` → `"2h 47m"` - **And** ввод `None` или отрицательное значение → функция возвращает пустую строку (или `None`), а вызывающая сторона строку `Длительность:` не печатает. - **PASS** при полном совпадении со всеми примерами таблицы. ## AC-14. Длительность — graceful fallback - **Given** агент завершился, но `_duration_s` не пробрасывается явным параметром в коммент-хелпер (например, для аналитика). - **When** строится status-коммент. - **Then** хелпер запрашивает БД: последний `agent_runs` для `(task_id, agent)` с непустым `finished_at`, считает `int((julianday(finished_at) - julianday(started_at)) * 86400)` и подставляет в `fmt_duration`. - **And** при отсутствии подходящей строки `agent_runs` (или `finished_at IS NULL`, или результат < 0) — строка `Длительность:` опускается; остальные части коммента (заголовок, описание, вердикт, ссылки) публикуются без изменений. - **And** ошибка чтения БД не пробрасывает исключение наружу — логируется в `logger.debug` и трактуется как «значение неизвестно». --- **Финальный PASS задачи:** все AC-1…AC-14 = PASS.