From 1150cd9144bf333473733af71f24a67e79f5e4c7 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Fri, 5 Jun 2026 12:15:40 +0000 Subject: [PATCH] architect(ET): auto-commit from architect run_id=95 --- .../06-adr/ADR-001-unified-status-comment.md | 203 ++++++++++++++++++ docs/work-items/ORCH-016/10-tech-risks.md | 112 ++++++++++ 2 files changed, 315 insertions(+) create mode 100644 docs/work-items/ORCH-016/06-adr/ADR-001-unified-status-comment.md create mode 100644 docs/work-items/ORCH-016/10-tech-risks.md diff --git a/docs/work-items/ORCH-016/06-adr/ADR-001-unified-status-comment.md b/docs/work-items/ORCH-016/06-adr/ADR-001-unified-status-comment.md new file mode 100644 index 0000000..7e2154f --- /dev/null +++ b/docs/work-items/ORCH-016/06-adr/ADR-001-unified-status-comment.md @@ -0,0 +1,203 @@ +# ADR-001: Единый формат status-коммента агентов в Plane + +- **Work Item:** ORCH-016 +- **Стадия:** architecture +- **Статус:** Accepted +- **Дата:** 2026-06-05 +- **Автор:** architect + +## Контекст + +ТЗ ORCH-016 требует привести коммент-формат всех агентов (architect/developer/reviewer/tester/deployer + сохранение совместимости с analyst) к единому виду по эталону `src/stage_engine.py::_build_analyst_ready_comment` и дополнительно встроить **строку длительности работы агента**. + +ТЗ оставил архитектору пять открытых вопросов (см. §2.2, §2.5, §2.7, §6): +1. Где живёт общий хелпер построения коммента (один файл vs. два). +2. Как ведём себя с usage-метрикой (tokens / $cost) в новом формате (Q-1 из ТЗ §2.7). +3. Локализация метки длительности — «Длительность:» vs «Duration:». +4. Парсинг frontmatter артефакта (verdict / deploy_status / staging_status) — переиспользовать `src/qg/checks.py` или дублировать. +5. Контракт хелпера БД-фоллбэка длительности и его форма. + +Дополнительно: текущий `usage_comment(...)` — публичная (внутри проекта) функция, вызывается из `src/agents/launcher.py::_post_usage_comments`. Менять формат «на месте» без явного решения о судьбе старой сигнатуры рискованно. + +## Решение + +### 1. Архитектура хелперов + +Вводим **ровно один публичный хелпер** в `src/usage.py`: + +```python +def build_status_comment( + agent: str, # "analyst" | "architect" | ... | "deployer" + *, + repo: str | None = None, + branch: str | None = None, + work_item_id: str | None = None, + pr_number: int | None = None, + stage: str | None = None, # "deploy" vs "deploy-staging" (для deployer) + usage: dict | None = None, # tokens/cost (опционально) + duration_s: int | None = None, # если известно — иначе fallback по БД + task_id: int | None = None, # требуется ТОЛЬКО для DB-фоллбэка длительности + worktree_root: str | None = None, # для чтения артефактов; None → опускаем verdict +) -> str: +``` + +Что делает: +- Собирает заголовок `{ICON} {RoleName} — {описание}` (описание per-agent — см. §2 ниже). +- Опционально дописывает строку `Verdict: …` / `Status: …` (только для reviewer/tester/deployer и только если frontmatter артефакта присутствует и распознан). +- Всегда (если известна) дописывает строку `Длительность: …` через `fmt_duration(...)`. +- Дописывает блок `Документы:`. +- Опционально дописывает технический хвост `{tokens}/{cost}` — см. §3. + +`_build_analyst_ready_comment(...)` в `src/stage_engine.py` переписывается как **тонкая обёртка** над `build_status_comment(agent="analyst", ...)`. Аналитик-специфичный текст (инструкция «переведите в Approved/Rejected» + полный список 01-brd / 02-trz / 03-acceptance-criteria / 04-test-plan) добавляется ВНУТРИ `build_status_comment` через ветку `agent == "analyst"` — это единственное место, где per-agent текст шире одной строки. Альтернатива (передавать кастомный текст параметром) добавляет API-площадь без пользы. + +**Старый `usage_comment(...)` удаляется**; единственный его внешний вызов — `src/agents/launcher.py::_post_usage_comments` — переписывается на `build_status_comment(...)`. Это упрощает дальнейшее сопровождение (один формат → одна функция); риск минимален, потому что `usage_comment` — внутренний API. + +### 2. Per-agent описания (финализация ТЗ §2.2) + +| Агент | Описание (HTML, без точки в конце) | +|-------|------------------------------------| +| analyst | «Подготовил BRD / ТЗ / Acceptance Criteria. Для продвижения переведите задачу в статус Approved» (плюс существующая инструкция про Approved/Rejected уходит как продолжение) | +| architect | «Завершил архитектурную проработку. См. ADR ниже» | +| developer | «Завершил разработку. См. PR / branch ниже» | +| reviewer | «Завершил ревью изменений» | +| tester | «Завершил прогон тестов» | +| deployer (deploy) | «Завершил прод-деплой» | +| deployer (deploy-staging) | «Завершил staging-деплой» | + +### 3. Решение по Q-1 (usage-метрика) + +**Сохраняем** usage-метрику как **техническую ``-строку в конце** коммента, объединённую с длительностью НЕ нужно — длительность остаётся ОТДЕЛЬНОЙ строкой нормального веса (требование ТЗ §2.5). + +Конкретно: +```html +8.5M in (8.4M cached) / 45.8k out · $7.29 +``` + +Почему НЕ удаляем: +- Тех-метрика полезна для оценки стоимости задачи на пост-мортеме (особенно для ORCH-задач, где orchestrator расходует свой же бюджет). +- `task_summary_comment` (Deployer end-of-task) суммирует по задаче, но не покрывает per-agent breakdown в момент завершения каждой стадии — для трассировки «кто сколько потратил» полезно видеть сразу. + +Почему ``, а не обычная строка: +- Стейкхолдер (Слава) явно просил «без раздувания»; визуально приглушённый хвост не конкурирует за внимание с описанием/вердиктом/длительностью/ссылками. +- Plane корректно рендерит `` (проверено ранее на PR #13). + +При `usage = None` или нулевых значениях — хвост опускается полностью. + +### 4. Решение по Q-2 (локализация метки длительности) + +Используем русский: **`Длительность: 4m 12s`**. +Обоснование: все человеческие тексты комментов уже на русском (заголовок «Документы:», описания стадий). Метка `4m 12s` сама по себе универсальна и понятна без перевода (стандарт CLI-инструментов: `time`, `gh`, `kubectl`). + +### 5. Решение по Q-4 (парсинг frontmatter) + +Создаём НОВЫЙ маленький утилитный модуль **`src/frontmatter.py`** с единственной функцией: + +```python +def read_frontmatter_value(path: str, key: str) -> str | None: + """Read a single key from leading YAML frontmatter. Never raises. + + Returns None if file missing, frontmatter absent/malformed, or key not set. + """ +``` + +Реализация — yaml.safe_load на блоке между двумя `---` строками; всё ловится одним `try/except` → `logger.debug` → `None`. + +Этот модуль используют: +- `src/usage.py::build_status_comment` — для извлечения `verdict:` / `deploy_status:` / `staging_status:`. +- `src/qg/checks.py` — НЕ обязательно мигрировать в этом PR (out-of-scope ORCH-016); миграция может пройти отдельной задачей-рефакторингом. **В этом PR `qg/checks.py` НЕ трогаем** — снижает blast radius и риск регрессии гейтов. + +Дублирование (~10 строк YAML-парсера в `qg/checks.py` остаётся) сознательно принято: scope discipline > DRY на одном переиспользовании. + +### 6. Решение по Q-5 (DB-фоллбэк длительности) + +Хелпер в `src/usage.py`: + +```python +def get_agent_duration(task_id: int, agent: str) -> int | None: + """Return last finished agent_runs duration (seconds) for (task, agent). + Never raises. None on missing row / NULL finished_at / negative / error. + """ +``` + +SQL — ровно как в ТЗ §2.5 (фоллбэк): +```sql +SELECT CAST((julianday(finished_at) - julianday(started_at)) * 86400 AS INTEGER) +FROM agent_runs +WHERE task_id=? AND agent=? + AND finished_at IS NOT NULL +ORDER BY id DESC LIMIT 1 +``` + +Чтение через `get_db()` (стандартный путь модуля), обёрнутое в `try/except Exception` → `logger.debug(...)` → `None`. Соединение всегда закрывается в `finally`. + +`build_status_comment` вызывает `get_agent_duration(...)` ТОЛЬКО когда: +- `duration_s is None`, И +- `task_id is not None` (вызывающая сторона согласилась оплатить лишний SELECT). + +Если оба источника пусты → строка «Длительность:» опускается (AC-14). + +### 7. Решение по HTML vs Markdown (ТЗ §6) + +Целевой рендер — **HTML**, как у эталона аналитика. Конкретно: +- Заголовок и описание — plain text + emoji. +- Verdict / Длительность — отдельные строки, разделяются `
` (или `\n` если Plane корректно интерпретирует переводы строк; экспериментально подтвердить на staging — см. R-2 в `10-tech-risks.md`). +- Блок документов — `Документы:`. +- Технический хвост — `` отдельной строкой через `
`. + +`artifact_links(...)` (сейчас возвращает markdown-строки `[label](url)`) — **переписывается на HTML-якоря** `label`. Эмодзи-префиксы (📂/🔗/📐/📄) сохраняются. Возвращаемый тип меняется: `list[str]` остаётся, но содержимое — HTML-фрагменты (документировано в docstring). + +Это breaking-change для внутреннего API `artifact_links`, но единственный внешний вызов был из `usage_comment`, который тоже удаляется. Других вызовов в `tests/`/`scripts/` нет (developer проверит grep'ом в development-стадии). + +### 8. Контракт `fmt_duration` (полностью по AC-13) + +```python +def fmt_duration(seconds: int | None) -> str: + """0..59 → '{s}s'; 60..3599 → '{m}m {ss:02d}s'; >=3600 → '{h}h {mm:02d}m'. + None / negative → '' (caller should drop the line).""" +``` + +Чистая функция, без I/O, easily unit-testable. Размещение: `src/usage.py` (рядом с `fmt_tokens` / `fmt_cost`). + +## Альтернативы + +1. **Два отдельных хелпера** (`build_analyst_status_comment` + `build_agent_status_comment`). + Отклонено: ТЗ явно просит «единый эталонный формат»; дублирование шаблона расходится со временем. + +2. **Оставить `usage_comment` как deprecated-обёртку.** + Отклонено: один внутренний вызов, deprecation добавляет когнитивный шум без выигрыша. + +3. **Перенести usage-метрику в `task_summary_comment` (вариант B из ТЗ §2.7).** + Отклонено: теряем per-stage видимость затрат; финальный summary не отвечает на вопрос «сколько съел конкретно reviewer». + +4. **Markdown вместо HTML.** + Отклонено: эталон аналитика (PR #13) уже HTML; смена ломает визуальный паритет. + +5. **Английская метка «Duration:».** + Отклонено: ассиметрия с остальными русскими подписями в комменте. + +6. **Рефакторить `qg/checks.py` на `src/frontmatter.py` в этом же PR.** + Отклонено: расширяет blast radius на гейты; делаем отдельной задачей. + +## Последствия + +### Положительные +- Единая точка изменения формата комментов на будущее — `build_status_comment`. +- Удаление дубликата `usage_comment` уменьшает API-площадь модуля. +- `src/frontmatter.py` подготавливает почву для будущего рефактора `qg/checks.py` (DRY-победа в один заход следующей задачей). +- HTML-рендеринг даёт стейкхолдеру кликабельные ссылки и приглушённый тех-хвост. + +### Отрицательные / ограничения +- Дублирование YAML-парсинга на ~10 строк (qg/checks.py остаётся со своим). +- Дополнительный SELECT к `agent_runs` на каждый коммент аналитика (1 запрос, по индексу `task_id`, ничтожно). +- HTML-разметка ломается визуально, если Plane изменит политику санитизации `` или `