From 57a3f6c9f770bdfaa4637d2b317e79b6269720f5 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Fri, 5 Jun 2026 12:05:17 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=94 --- docs/work-items/ORCH-016/01-brd.md | 18 ++++-- docs/work-items/ORCH-016/02-trz.md | 57 ++++++++++++++++--- .../ORCH-016/03-acceptance-criteria.md | 40 +++++++++++-- docs/work-items/ORCH-016/04-test-plan.yaml | 57 ++++++++++++++----- 4 files changed, 141 insertions(+), 31 deletions(-) diff --git a/docs/work-items/ORCH-016/01-brd.md b/docs/work-items/ORCH-016/01-brd.md index 928be65..94d6ad8 100644 --- a/docs/work-items/ORCH-016/01-brd.md +++ b/docs/work-items/ORCH-016/01-brd.md @@ -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-иконкой роли и человекочитаемым названием, - 1–2 предложения с описанием результата стадии на русском языке, - кликабельную ссылку (-и) на артефакт(ы) этого агента в Gitea, -- одну строку вердикта (Verdict / Status), если артефакт его содержит. +- одну строку вердикта (Verdict / Status), если артефакт его содержит, +- **одну строку длительности работы агента** (`Длительность: `), всегда, если значение известно. **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/`. diff --git a/docs/work-items/ORCH-016/02-trz.md b/docs/work-items/ORCH-016/02-trz.md index 6d64498..d27251b 100644 --- a/docs/work-items/ORCH-016/02-trz.md +++ b/docs/work-items/ORCH-016/02-trz.md @@ -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: ] # опционально, см. 2.3 +Длительность: # см. 2.5; опускается, только если значение неизвестно Документы:{label} # одна или несколько ссылок ``` @@ -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-метрику как **последнюю строку** коммента (мелким техническим хвостом, например `8.5M in / 45.8k out · $7.29`), либо +- **Сохранить** usage-метрику как **последнюю строку** коммента (мелким техническим хвостом, например `8.5M in / 45.8k out · $7.29 · Длительность: 4m 12s`), либо - **Перенести** в `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=)` — сохраняется. Все агенты комментируют под своим 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 корректно рендерит `