analyst(ET): auto-commit from analyst run_id=94
All checks were successful
CI / test (push) Successful in 13s

This commit is contained in:
2026-06-05 12:05:17 +00:00
parent 0f4d8714dd
commit 57a3f6c9f7
4 changed files with 141 additions and 31 deletions

View File

@@ -4,11 +4,13 @@ Work Item ID: **ORCH-016**
Стадия: analysis
Автор: analyst
Дата: 2026-06-05
Ревизия: 2 (учтён фидбэк стейкхолдера от 2026-06-05 — добавить длительность работы агента в коммент)
---
## 1. Бизнес-цель
Стейкхолдер (Слава) должен мочь из ленты комментариев задачи в Plane **за один клик** перейти к артефакту любого агента (ADR, PR, ревью, отчёт тестера, деплой-лог), а не разбирать «шумные» строки без удобной ссылки и человекочитаемого описания.
Помимо ссылок, по комментариям стейкхолдер хочет **видеть, сколько работал каждый агент** (длительность стадии), не открывая БД оркестратора и не лезя в `agent_runs`.
## 2. Мотивация
Сейчас в Plane комменты двух разных стилей:
@@ -22,6 +24,7 @@ Work Item ID: **ORCH-016**
1. Нет человеческого описания результата стадии — есть только техническая метрика «tokens/cost».
2. Нет краткого вердикта одной строкой там, где он есть в артефакте (Reviewer `APPROVE/REQUEST_CHANGES`, Tester `PASS/FAIL`, Deployer `SUCCESS/FAILED`).
3. Формат разнится по агентам (где-то «📂 Branch + 🔗 PR», где-то «📄 Test report») — нет единого визуального якоря.
4. **Не видно длительности стадии** — стейкхолдер не понимает, агент отработал за 30 секунд или за 12 минут; это важная метрика для оценки SLA, поведения долгих стадий (testing/deploy) и подозрений на «зависание».
## 3. Целевая аудитория
- **Стейкхолдер задачи (Слава, владелец продукта)** — главный потребитель ленты комментариев в Plane.
@@ -32,10 +35,12 @@ Work Item ID: **ORCH-016**
- заголовок-роль (emoji + имя роли),
- короткое человеческое описание результата стадии (1 предложение),
- кликабельная ссылка(и) на СВОЙ артефакт,
- **одна строка-вердикт** там, где это уместно (Reviewer / Tester / Deployer).
- **одна строка-вердикт** там, где это уместно (Reviewer / Tester / Deployer),
- **одна строка-длительность** работы агента — для всех ролей, включая аналитика.
2. Переиспользовать `settings.gitea_public_url` для кликабельных ссылок (готово в PR #14).
3. Сохранить существующее поведение аналитика (PR #13) — он уже соответствует целевому формату; в идеале — переиспользовать общий хелпер.
3. Сохранить существующее поведение аналитика (PR #13) — он уже соответствует целевому формату; в идеале — переиспользовать общий хелпер. К аналитику также добавляется строка длительности.
4. Один коммент на агента за прохождение стадии (без спама).
5. Источник длительности — уже существующая метрика `_duration_s` в `src/agents/launcher.py` (или `agent_runs.started_at` / `finished_at`). Новых таблиц/полей в БД не заводим.
## 5. Out of scope (что НЕ трогаем)
- Логика Quality Gates (`src/qg/checks.py`).
@@ -51,14 +56,16 @@ Work Item ID: **ORCH-016**
- заголовок с emoji-иконкой роли и человекочитаемым названием,
- 12 предложения с описанием результата стадии на русском языке,
- кликабельную ссылку (-и) на артефакт(ы) этого агента в Gitea,
- одну строку вердикта (Verdict / Status), если артефакт его содержит.
- одну строку вердикта (Verdict / Status), если артефакт его содержит,
- **одну строку длительности работы агента** (`Длительность: <human-format>`), всегда, если значение известно.
**BR-3.** Ссылки строятся через `gitea_public_url` (fallback на `gitea_url`).
**BR-4.** Формат должен быть устойчив: отсутствующий артефакт / отсутствующий вердикт не ломает коммент — соответствующая строка просто опускается.
**BR-4.** Формат должен быть устойчив: отсутствующий артефакт / отсутствующий вердикт / неизвестная длительность не ломают коммент — соответствующая строка просто опускается.
**BR-5.** Изменение **не нарушает**:
- status-only verdict model (аналитик по-прежнему ждёт смены статуса Plane),
- дедуп комментов и вебхуков,
- работу `set_issue_done` / `notify_done` на финале конвейера,
- per-agent bot-авторство.
**BR-6.** Длительность отображается в человекочитаемой форме (`12s`, `4m 12s`, `1h 03m`), а не в виде голых секунд. Источник — `agent_runs.started_at` / `finished_at` (или уже посчитанный `_duration_s` в `launcher.py`). Новых полей в БД не вводится.
## 7. Ограничения и риски
- **Self-hosting:** оркестратор правит сам себя; деплой только через staging-гейт (порт 8501) → прод-контейнер `orchestrator` не перезапускать в рамках задачи.
@@ -72,6 +79,7 @@ Work Item ID: **ORCH-016**
- ADR не требуется: сквозной архитектурный сдвиг отсутствует, меняем только формирование текста коммента в существующем потоке.
## 9. Критерии успеха (high-level)
- Слава открывает любую задачу в Plane и в ленте видит однотипные карточки от каждого агента: «{role} — {описание} → ссылка [Verdict: …]».
- Слава открывает любую задачу в Plane и в ленте видит однотипные карточки от каждого агента: «{role} — {описание} → ссылка [Verdict: …] [Длительность: …]».
- По любой ссылке открывается соответствующий документ в Gitea (HTTP 200, корректный путь).
- В каждом статус-комменте присутствует строка «Длительность: …» с человекочитаемым значением (`12s` / `4m 12s` / `1h 03m`).
- Никаких регрессий в существующих тестах `tests/`.

View File

@@ -4,6 +4,7 @@ Work Item ID: **ORCH-016**
Стадия: analysis → architecture → development
Автор: analyst
Дата: 2026-06-05
Ревизия: 2 (по фидбэку стейкхолдера — добавлен §2.5 Duration; обновлены §1, §2.1, §6)
> Контракт: что именно меняем в коде / какие модули задействованы / какие проверки появятся.
> Архитектурные решения принимает архитектор; здесь — границы изменения.
@@ -14,9 +15,10 @@ Work Item ID: **ORCH-016**
| Модуль | Роль в изменении |
|--------|------------------|
| `src/usage.py` | **Главная точка изменения.** Здесь сейчас живут `usage_comment()`, `artifact_links()`, `AGENT_ARTIFACT`, `AGENT_DISPLAY`, `AGENT_ICON` — основа форматирования. Нужно расширить/добавить хелпер построения единого status-коммента. |
| `src/stage_engine.py` | Эталонная функция аналитика `_build_analyst_ready_comment()`. По возможности — переиспользовать новый общий хелпер (или хотя бы выровнять формат: emoji + заголовок + описание + список ссылок). |
| `src/agents/launcher.py` | `_post_usage_comments()` — точка, где постится коммент по завершении агента (architect/developer/reviewer/tester/deployer). Должен звать новый хелпер. |
| `src/usage.py` | **Главная точка изменения.** Здесь сейчас живут `usage_comment()`, `artifact_links()`, `AGENT_ARTIFACT`, `AGENT_DISPLAY`, `AGENT_ICON` — основа форматирования. Нужно расширить/добавить хелпер построения единого status-коммента + утилитку форматирования длительности (`fmt_duration(seconds: int) -> str`). |
| `src/stage_engine.py` | Эталонная функция аналитика `_build_analyst_ready_comment()`. По возможности — переиспользовать новый общий хелпер (или хотя бы выровнять формат: emoji + заголовок + описание + список ссылок). К аналитику также прикручиваем строку длительности (см. §2.5). |
| `src/agents/launcher.py` | `_post_usage_comments()` — точка, где постится коммент по завершении агента (architect/developer/reviewer/tester/deployer). Должен звать новый хелпер. `_duration_s` уже считается на строке `391` — пробросить его (или достать из `agent_runs.started_at`/`finished_at`) в хелпер. |
| `src/db.py` | **Только для чтения** в рантайме коммент-хелпера: `agent_runs.started_at`, `agent_runs.finished_at` (уже существуют). Никаких ALTER. |
| `src/plane_sync.py` | `add_comment()` — без изменений (используется как транспорт). |
| `src/qg/checks.py` | **Только для чтения**: модели парсинга frontmatter `verdict:` / `deploy_status:` / `staging_status:` — переиспользуем эту логику (вынести в отдельную утилитку, либо импортировать там, где она уже есть). |
| `src/config.py` | `settings.gitea_public_url`, `settings.gitea_owner`, `settings.gitea_url` — без изменений, переиспользуются. |
@@ -28,6 +30,7 @@ Work Item ID: **ORCH-016**
{ICON} {RoleName} — {one-line human description of stage result}
[Verdict / Status: <VALUE>] # опционально, см. 2.3
Длительность: <human-format> # см. 2.5; опускается, только если значение неизвестно
<b>Документы:</b>
• <a href="…">{label}</a> # одна или несколько ссылок
```
@@ -37,6 +40,7 @@ Work Item ID: **ORCH-016**
- `{RoleName}` — из `AGENT_DISPLAY` (уже есть).
- `{description}` — фиксированная строка на роль, см. 2.2.
- Verdict / Status — см. 2.3, опускается если не извлекается.
- Длительность — см. 2.5, печатается всегда, когда значение есть; по умолчанию доступна (это нативная метрика `agent_runs`).
- Ссылки — см. 2.4.
### 2.2 Описания стадий (per-agent text)
@@ -83,17 +87,52 @@ Work Item ID: **ORCH-016**
Несуществующий файл в worktree → ссылка опускается (как сейчас в `_build_analyst_ready_comment`).
### 2.5 Один коммент на агента за стадию
### 2.5 Строка длительности работы агента
**Что печатаем:** одну строку вида `Длительность: {human}` (или `Duration: {human}` — финальную локализацию метки фиксирует архитектор; русский предпочтителен, остальные комменты уже на русском).
**Источник значения (приоритет сверху вниз):**
1. **Параметр функции**`_post_usage_comments()` в `src/agents/launcher.py:682` вызывается из контекста, где `_duration_s` уже посчитан на строке `391` (`int(time.time() - _start_ts)`). Простейший путь — пробросить `duration_s` явным аргументом в `usage_comment(...)` / новый `build_status_comment(...)`.
2. **Fallback из БД** — если параметр не передан (например, для аналитика, чей коммент строится в `_build_analyst_ready_comment` в `src/stage_engine.py:298`), читаем
```sql
SELECT
CAST((julianday(finished_at) - julianday(started_at)) * 86400 AS INTEGER)
FROM agent_runs
WHERE task_id = ? AND agent = ?
ORDER BY id DESC LIMIT 1
```
Это последний завершённый run этой роли по задаче.
3. **Если оба источника пусты / `None` / отрицательны** — строка `Длительность:` НЕ печатается (graceful, как и для вердикта).
**Форматирование (`fmt_duration(seconds: int) -> str` в `src/usage.py`):**
| Диапазон | Формат | Пример |
|----------|--------|--------|
| `0 ≤ s < 60` | `{s}s` | `12s`, `45s` |
| `60 ≤ s < 3600` | `{m}m {ss}s` | `4m 12s`, `1m 03s` |
| `s ≥ 3600` | `{h}h {mm}m` (секунды отбрасываем) | `1h 03m`, `2h 47m` |
Округление: целые секунды (input — `int`). При `s == 0` всё равно печатаем `0s` (видно, что метрика известна и стадия отработала почти мгновенно).
**Покрытие ролей:** строка длительности добавляется для **всех** агентов, включая аналитика. Для аналитика — строго через fallback из `agent_runs` (его коммент строится в `stage_engine.py`, не в `launcher.py`).
**Что НЕ делаем:**
- Не меняем схему `agent_runs` (поля `started_at` / `finished_at` уже есть, `_duration_s` уже считается).
- Не изобретаем новый отдельный коммент с длительностью — длительность встраивается в существующий status-коммент.
- Не считаем «время от первого вебхука до коммента» — берём чистое время процесса агента (тот же `_duration_s`, что попадает в `notify_agent_finished`), чтобы значение совпадало с тем, что уже видно в Telegram live tracker / логах.
### 2.6 Один коммент на агента за стадию
Текущий триггер — `_post_usage_comments()` вызывается **один раз** в успешном auto-advance пути после агента. Никаких новых триггеров не добавляем. Дубликаты исключены текущей логикой (одно завершение агента → один коммент).
### 2.6 Usage-метрики (токены / стоимость)
### 2.7 Usage-метрики (токены / стоимость)
Текущий `usage_comment()` встраивает «8.5M in / 45.8k out · $7.29» в первый строкой. По требованиям Славы это «без раздувания», но не запрещено явно. Решение:
- **Сохранить** usage-метрику как **последнюю строку** коммента (мелким техническим хвостом, например `<sub>8.5M in / 45.8k out · $7.29</sub>`), либо
- **Сохранить** usage-метрику как **последнюю строку** коммента (мелким техническим хвостом, например `<sub>8.5M in / 45.8k out · $7.29 · Длительность: 4m 12s</sub>`), либо
- **Перенести** в `task_summary_comment` (только для финального deployer-summary).
Финальный выбор — за архитектором (см. вопрос Q-1 в `10-tech-risks.md`).
Финальный выбор — за архитектором (см. вопрос Q-1 в `10-tech-risks.md`). Длительность из §2.5 — **отдельная** строка от usage-метрики и присутствует независимо от того, как решится вопрос про токены/стоимость.
### 2.7 Бот-авторство
### 2.8 Бот-авторство
`plane_add_comment(..., author=<role>)` — сохраняется. Все агенты комментируют под своим bot-токеном (`PLANE_BOT_TOKENS`). Изменения формата текста на это не влияют.
## 3. Изменения API
@@ -108,6 +147,8 @@ Work Item ID: **ORCH-016**
## 6. Требования к коду
- Все новые функции — с docstring (зачем нужны, какие инварианты сохраняют).
- Парсинг frontmatter артефакта — graceful: исключение → строка вердикта опускается, лог в `logger.debug`.
- Чтение длительности — graceful: исключение или `None` → строка длительности опускается, лог в `logger.debug`. Отрицательные / нулевые значения: `0` печатается как `0s`, отрицательные опускаются.
- `fmt_duration(seconds: int) -> str` — чистая, без БД-зависимостей, легко тестируется юнитом.
- Никаких новых внешних зависимостей: использовать `pyyaml` (уже в проекте) или существующий парсер frontmatter из `src/qg/checks.py`.
- Поведение для проектов **без** артефактов (например, ENDURO-* до запуска агента) — graceful no-op: коммент с описанием и без ссылок (минимум — заголовок).
- HTML (как у аналитика) предпочтительнее markdown — Plane корректно рендерит `<ul><li><a>` и `<b>`.

View File

@@ -1,6 +1,7 @@
# Acceptance Criteria: Единообразные коммент-артефакты в Plane
Work Item ID: **ORCH-016**
Ревизия: 2 (по фидбэку стейкхолдера — все AC по агентам обновлены под строку длительности; добавлены AC-13 / AC-14)
Каждый AC сформулирован как чёткое условие PASS/FAIL. Проверяется автоматически (unit/integration) либо ручной верификацией в staging Plane (порт 8501).
@@ -11,6 +12,7 @@ Work Item ID: **ORCH-016**
- **When** `_post_usage_comments(agent="architect", ...)` вызывается.
- **Then** в Plane появляется **ровно один** коммент со структурой:
- первая строка: `📐 Architect — Завершил архитектурную проработку. См. ADR ниже.`,
- строка `Длительность: <human>` (формат — см. AC-13), значение соответствует фактическому времени работы архитектора (±1с),
- блок «Документы:» с кликабельной ссылкой на `…/src/branch/<branch>/docs/work-items/<wid>/06-adr/`,
- **нет** строки `Verdict / Status`.
- **And** автор коммента — `architect` (`PLANE_BOT_TOKENS["architect"]`, fallback на shared token).
@@ -21,6 +23,7 @@ Work Item ID: **ORCH-016**
- **When** `_post_usage_comments(agent="developer", ...)` вызывается.
- **Then** коммент в Plane:
- `💻 Developer — Завершил разработку. См. PR / branch ниже.`,
- строка `Длительность: <human>`,
- ссылки: `Branch <branch>``…/src/branch/<branch>`, `PR #<num>``…/pulls/<num>`,
- **нет** строки `Verdict`.
@@ -30,8 +33,9 @@ Work Item ID: **ORCH-016**
- **Then** коммент:
- `🔎 Reviewer — Завершил ревью изменений.`,
- строка `Verdict: APPROVE` (или `REQUEST_CHANGES`) — содержимое соответствует frontmatter,
- строка `Длительность: <human>`,
- ссылка `Review``…/12-review.md`.
- **And** если frontmatter не содержит `verdict:` или файл недоступен — строка `Verdict:` опускается, остальное публикуется.
- **And** если frontmatter не содержит `verdict:` или файл недоступен — строка `Verdict:` опускается, остальное (в т.ч. длительность) публикуется.
## AC-4. Тестер пишет коммент с вердиктом
- **Given** `13-test-report.md` содержит frontmatter `verdict: PASS` (или `FAIL`).
@@ -39,6 +43,7 @@ Work Item ID: **ORCH-016**
- **Then** коммент:
- `🧪 Tester — Завершил прогон тестов.`,
- строка `Verdict: PASS` (либо `FAIL`),
- строка `Длительность: <human>`,
- ссылка `Test report``…/13-test-report.md`.
## AC-5. Деплоер пишет коммент со статусом
@@ -47,6 +52,7 @@ Work Item ID: **ORCH-016**
- **Then** коммент:
- `🚀 Deployer — Завершил деплой.`,
- строка `Status: SUCCESS` (или `FAILED`),
- строка `Длительность: <human>`,
- ссылка `Deploy log``…/14-deploy-log.md` (и/или `Staging log``…/15-staging-log.md` для staging-стадии).
## AC-6. Аналитик не регрессирует
@@ -54,7 +60,8 @@ Work Item ID: **ORCH-016**
- **When** аналитик завершает стадию `analysis` с готовыми `01..04`.
- **Then** в Plane:
- issue переведён в `In Review` (не меняется),
- коммент содержит **то же** человеческое описание (Approved/Rejected инструкции) и список ссылок `BRD / ТЗ / AC / Test Plan` — формат либо идентичен текущему, либо построен через тот же общий хелпер, что и остальные агенты, без потери смысла.
- коммент содержит **то же** человеческое описание (Approved/Rejected инструкции) и список ссылок `BRD / ТЗ / AC / Test Plan` — формат либо идентичен текущему, либо построен через тот же общий хелпер, что и остальные агенты, без потери смысла,
- дополнительно к существующему содержимому в комменте присутствует строка `Длительность: <human>` — значение поднимается из `agent_runs` (последний завершённый run агента `analyst` для этой задачи).
## AC-7. Один коммент на агента за стадию
- **Given** агент успешно отработал стадию.
@@ -86,10 +93,33 @@ Work Item ID: **ORCH-016**
- **Given** реализация добавлена в feature-ветку.
- **When** reviewer проверяет PR.
- **Then** в diff присутствуют обновления:
- `CHANGELOG.md` (раздел Unreleased, описание изменения),
- `docs/architecture/README.md` или `docs/architecture/internals.md` (упоминание единого формата status-комментов).
- `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-12 = PASS.
**Финальный PASS задачи:** все AC-1…AC-14 = PASS.

View File

@@ -1,58 +1,59 @@
work_item: ORCH-016
title: "Единообразные коммент-артефакты в Plane от всех агентов"
revision: 2 # +TC-21..TC-25 по длительности (фидбэк стейкхолдера)
tests:
- id: TC-01
type: unit
description: "build_status_comment(architect, ...) формирует HTML c заголовком '📐 Architect — …', описанием стадии и ссылкой на 06-adr/. Строки Verdict нет."
description: "build_status_comment(architect, duration_s=312, ...) формирует HTML c заголовком '📐 Architect — …', описанием стадии, строкой 'Длительность: 5m 12s' и ссылкой на 06-adr/. Строки Verdict нет."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-02
type: unit
description: "build_status_comment(developer, branch=..., pr_number=42) включает ссылки на branch и на PR #42 через gitea_public_url. Строки Verdict нет."
description: "build_status_comment(developer, branch=..., pr_number=42, duration_s=...) включает ссылки на branch и на PR #42 через gitea_public_url + строку 'Длительность: ...'. Строки Verdict нет."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-03
type: unit
description: "build_status_comment(reviewer, ...) при verdict=APPROVE в 12-review.md frontmatter выводит строку 'Verdict: APPROVE' и ссылку на 12-review.md."
description: "build_status_comment(reviewer, duration_s=..., ...) при verdict=APPROVE в 12-review.md frontmatter выводит строку 'Verdict: APPROVE', строку 'Длительность: ...' и ссылку на 12-review.md."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-04
type: unit
description: "build_status_comment(reviewer, ...) при verdict=REQUEST_CHANGES выводит 'Verdict: REQUEST_CHANGES'."
description: "build_status_comment(reviewer, ...) при verdict=REQUEST_CHANGES выводит 'Verdict: REQUEST_CHANGES'. Строка длительности сохраняется."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-05
type: unit
description: "build_status_comment(reviewer, ...) при отсутствии файла 12-review.md публикует коммент без строки Verdict и без ссылки Review (graceful)."
description: "build_status_comment(reviewer, ...) при отсутствии файла 12-review.md публикует коммент без строки Verdict и без ссылки Review (graceful), при этом строка 'Длительность: ...' печатается, если duration_s передан."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-06
type: unit
description: "build_status_comment(tester, ...) при verdict=PASS в 13-test-report.md выводит 'Verdict: PASS' и ссылку на 13-test-report.md."
description: "build_status_comment(tester, ...) при verdict=PASS в 13-test-report.md выводит 'Verdict: PASS', строку 'Длительность: ...' и ссылку на 13-test-report.md."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-07
type: unit
description: "build_status_comment(tester, ...) при verdict=FAIL выводит 'Verdict: FAIL'."
description: "build_status_comment(tester, ...) при verdict=FAIL выводит 'Verdict: FAIL'. Строка длительности сохраняется."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-08
type: unit
description: "build_status_comment(deployer, ...) при deploy_status=SUCCESS в 14-deploy-log.md выводит 'Status: SUCCESS' и ссылку на 14-deploy-log.md."
description: "build_status_comment(deployer, ...) при deploy_status=SUCCESS в 14-deploy-log.md выводит 'Status: SUCCESS', строку 'Длительность: ...' и ссылку на 14-deploy-log.md."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-09
type: unit
description: "build_status_comment(deployer, stage='deploy-staging') читает staging_status: из 15-staging-log.md и выводит соответствующую строку Status."
description: "build_status_comment(deployer, stage='deploy-staging') читает staging_status: из 15-staging-log.md и выводит соответствующую строку Status + строку длительности."
module: tests/test_status_comment_format.py
expected: PASS
@@ -64,7 +65,7 @@ tests:
- id: TC-11
type: unit
description: "Аналитик: _build_analyst_ready_comment (или его замена общим хелпером) сохраняет существующий контракт — текст про Approved/Rejected статус + список существующих BRD/ТЗ/AC/Test Plan ссылок."
description: "Аналитик: _build_analyst_ready_comment (или его замена общим хелпером) сохраняет существующий контракт — текст про Approved/Rejected статус + список существующих BRD/ТЗ/AC/Test Plan ссылок. Дополнительно: при наличии завершённой строки agent_runs(analyst) для задачи коммент содержит строку 'Длительность: ...'."
module: tests/test_analyst_comment_regression.py
expected: PASS
@@ -76,19 +77,19 @@ tests:
- id: TC-13
type: integration
description: "_post_usage_comments(agent='reviewer', ...) вызывает plane_sync.add_comment ровно один раз; передаваемый текст содержит '🔎 Reviewer', 'Verdict:' и href на 12-review.md."
description: "_post_usage_comments(agent='reviewer', ...) вызывает plane_sync.add_comment ровно один раз; передаваемый текст содержит '🔎 Reviewer', 'Verdict:', 'Длительность:' и href на 12-review.md."
module: tests/test_post_usage_comments_integration.py
expected: PASS
- id: TC-14
type: integration
description: "_post_usage_comments(agent='tester', ...) вызывает add_comment ровно один раз с автором 'tester' и корректным текстом."
description: "_post_usage_comments(agent='tester', ...) вызывает add_comment ровно один раз с автором 'tester' и корректным текстом, включая строку 'Длительность: ...'."
module: tests/test_post_usage_comments_integration.py
expected: PASS
- id: TC-15
type: integration
description: "_post_usage_comments(agent='deployer', ...) для стадии deploy постит коммент со ссылкой на 14-deploy-log.md И task_summary_comment (если оно сохраняется) — поведение не регрессирует."
description: "_post_usage_comments(agent='deployer', ...) для стадии deploy постит коммент со ссылкой на 14-deploy-log.md, строкой 'Длительность: ...' И task_summary_comment (если оно сохраняется) — поведение не регрессирует."
module: tests/test_post_usage_comments_integration.py
expected: PASS
@@ -121,3 +122,33 @@ tests:
description: "Quality Gates не изменены: реестр QG_CHECKS и STAGE_TRANSITIONS идентичны контрольному снапшоту (smoke-тест против случайных правок)."
module: tests/test_qg_registry_snapshot.py
expected: PASS
- id: TC-21
type: unit
description: "fmt_duration(seconds) — табличная проверка форматирования: 0→'0s', 12→'12s', 59→'59s', 60→'1m 00s', 252→'4m 12s', 3599→'59m 59s', 3600→'1h 00m', 3780→'1h 03m', 10020→'2h 47m'."
module: tests/test_fmt_duration.py
expected: PASS
- id: TC-22
type: unit
description: "fmt_duration(None) и fmt_duration(-1) возвращают пустую строку (или None); вызывающая сторона при этом строку 'Длительность:' НЕ печатает."
module: tests/test_fmt_duration.py
expected: PASS
- id: TC-23
type: unit
description: "build_status_comment(architect, duration_s=None) и build_status_comment(architect) — коммент НЕ содержит строки 'Длительность:'; остальные строки (заголовок/описание/ссылки) на месте."
module: tests/test_status_comment_format.py
expected: PASS
- id: TC-24
type: integration
description: "Fallback по БД: при отсутствии явного duration_s билдер коммента читает agent_runs.started_at/finished_at для последней завершённой строки (task_id, agent) и подставляет fmt_duration результата. Проверка через тестовую SQLite с заранее проставленными timestamp'ами."
module: tests/test_status_comment_duration_db_fallback.py
expected: PASS
- id: TC-25
type: integration
description: "Регрессия: исключение при чтении agent_runs (например, БД залочена) → строка 'Длительность:' опускается, остальное публикуется; logger.debug содержит запись о неудачном чтении длительности."
module: tests/test_status_comment_duration_db_fallback.py
expected: PASS