# ТЗ — ORCH-046: встраивание текста findings reviewer/tester в task_desc Work Item ID: ORCH-046 Stage: analysis Author: analyst Date: 2026-06-06 > Вариант A (минимальный, низкий риск). Это правка ЯДРА — обязателен ADR > (per-work-item, `docs/work-items/ORCH-046/06-adr/`). ## 1. Задействованные модули `src/` | Модуль | Изменение | |--------|-----------| | `src/review_parse.py` | **НОВЫЙ** хелпер-парсер: `extract_review_findings(path) -> str`, `extract_test_failures(path) -> str`. | | `src/stage_engine.py` | Две ветки в `_handle_qg_failure_rollbacks`: reviewer REQUEST_CHANGES (~стр. 419) и tester `check_tests_passed` FAIL (~стр. 455) — встраивают извлечённый текст в `task_desc`. | Источники-образцы (не менять, использовать как референс паттерна «never raise» и формата артефактов): - `src/qg/checks.py::_parse_tests_verdict` — образец «never raise», split по `---`, `yaml.safe_load`. - `src/frontmatter.py::read_frontmatter_value` — образец defensive-парсера. - `.openclaw/agents/reviewer.md` — канонический формат `12-review.md`. - `.openclaw/agents/tester.md` — канонический формат `13-test-report.md`. ## 2. Новый модуль `src/review_parse.py` ### 2.1. `extract_review_findings(path: str) -> str` Назначение: вернуть **дословный** текст must-fix findings (P0 + P1) из `12-review.md` для встраивания в `task_desc`. Формат входного файла (канон reviewer.md, секция `## Findings`): ```markdown ## Findings ### P0 — Blocker - [ ] <описание> ### P1 — Must fix - [ ] <описание> ### P2 — Should fix - [ ] <описание> ``` Требования к реализации: 1. **Никогда не бросает исключение.** Любая ошибка (нет файла, IOError, кривой markdown, нет секции `## Findings`) → возврат `""` (пустая строка). 2. Парсит **только** подсекции P0 и P1 (must-fix). P2/P3 игнорируются. 3. Заголовки подсекций распознаются устойчиво к регистру и к тире/дефису: соответствие по наличию токена `P0` / `P1` в строке-заголовке уровня `###`. 4. Из распознанных подсекций берётся текст до следующего заголовка `###`/`##` (т. е. тело подсекции дословно: пункты списка `- [ ] …` / `- …`). 5. Пустые подсекции (нет содержательных пунктов, только `(если есть)`-плейсхолдер или ничего) — пропускаются. Если ни одного содержательного P0/P1 пункта нет → возврат `""`. 6. Результат — компактный многострочный текст, пригодный для вставки в `task_desc` (например, заголовок подсекции + её пункты). Длина результата ограничивается разумным лимитом (`MAX_FINDINGS_CHARS`, напр. 2000) с усечением и маркером `…(truncated)`; полный контекст всё равно остаётся в файле. 7. Frontmatter (верхний `--- … ---`) при необходимости отбрасывается, чтобы не попасть в тело; парсинг секции делается по телу markdown. Сигнатура и контракт (стабильны): ```python def extract_review_findings(path: str) -> str: """Дословный текст P0/P1 findings из 12-review.md. Never raises; '' при ошибке/пусто.""" ``` ### 2.2. `extract_test_failures(path: str) -> str` Назначение: вернуть текст причины падения тестов из `13-test-report.md` для встраивания в `task_desc`. Формат входного файла (канон tester.md): frontmatter `result: PASS|FAIL`, далее тело с секциями `## Результаты` (таблица TC), `## Вывод pytest`, `## Итог`. Требования к реализации: 1. **Никогда не бросает исключение.** Любая ошибка → возврат `""`. 2. Извлекает релевантный фрагмент тела, помогающий понять причину FAIL. Приоритет источников (берём первый непустой): - секция `## Вывод pytest` (вывод прогона — где видно упавшие тесты), и/или - строки таблицы `## Результаты`, содержащие `FAIL`, и/или - секция `## Итог`. 3. Результат усекается до `MAX_FAILURES_CHARS` (напр. 2000) с маркером `…(truncated)`. 4. Если ничего извлечь не удалось → возврат `""` (вызывающий код делает fallback на ссылку). > Примечание: «reason» из самого гейта (`check_tests_passed` → второй элемент > кортежа) у вызывающего кода уже есть (`reason`) — он добавляется в `task_desc` > вызывающим кодом (как и сейчас в комментарии тестера). `extract_test_failures` > добавляет **фрагмент тела отчёта** поверх этого reason. Сигнатура и контракт (стабильны): ```python def extract_test_failures(path: str) -> str: """Релевантный фрагмент тела 13-test-report.md (причина FAIL). Never raises; '' при ошибке/пусто.""" ``` ### 2.3. Общие требования модуля - Модуль логирует диагностические сообщения на уровне `logger.debug` (`logging.getLogger("orchestrator.review_parse")`), как `frontmatter.py`. - Никаких сетевых вызовов, только чтение файла с диска. - Константы лимитов вынесены модульными (`MAX_FINDINGS_CHARS`, `MAX_FAILURES_CHARS`). ## 3. Изменения `src/stage_engine.py` ### 3.1. Ветка reviewer REQUEST_CHANGES (внутри `_handle_qg_failure_rollbacks`) Текущее (~стр. 418–424): ```python task_desc = ( f"Work item: {work_item_id}\nRepo: {repo}\nBranch: {branch}\n" f"Stage: development\nNote: REQUEST_CHANGES from reviewer " f"(attempt {retry_count+1}/3). Fix findings in " f"docs/work-items/{work_item_id}/12-review.md" ) ``` Целевое поведение: - Сформировать путь к `12-review.md` через `get_worktree_path(repo, branch)` + `docs/work-items/{work_item_id}/12-review.md` (как в `_check_review_approved_by_branch`). - Вызвать `extract_review_findings(path)`. - Если результат непустой — встроить findings **дословно** в `task_desc` (под подзаголовком, напр. `Findings (P0/P1):\n`), а ссылку на файл оставить как «полный контекст» (`Полный контекст: docs/work-items//12-review.md`). - Если результат пустой (graceful fallback) — `task_desc` остаётся **как сейчас** (ссылка-строка). Никаких исключений. - Префиксная часть (`Work item / Repo / Branch / Stage / Note: REQUEST_CHANGES … (attempt N/3)`) сохраняется без изменений. ### 3.2. Ветка tester FAIL (`check_tests_passed`, внутри `_handle_qg_failure_rollbacks`) Текущее (~стр. 454–459): ```python task_desc = ( f"Work item: {work_item_id}\nRepo: {repo}\nBranch: {branch}\n" f"Stage: development\nNote: Tests FAILED. " f"Fix failures described in docs/work-items/{work_item_id}/13-test-report.md" ) ``` Целевое поведение: - Сформировать путь к `13-test-report.md` аналогично. - Вызвать `extract_test_failures(path)`. - В `task_desc` всегда включить `reason` (он уже доступен в этой ветке — передаётся в `_handle_qg_failure_rollbacks`). - Если фрагмент тела непустой — встроить его дословно (`Причина: {reason}\nДетали:\n`), плюс ссылку на файл как полный контекст. - Если фрагмент пустой — `task_desc` содержит `reason` + ссылку (graceful fallback, не хуже текущего поведения). Никаких исключений. - Префиксная часть и существующий Plane-комментарий тестера (`❌ Тесты не прошли: {reason}…`) НЕ меняются. ### 3.3. Инварианты (НЕ менять поведение) - Последовательность rollback в обеих ветках: `update_task_stage(task_id, "development")` → `notify_stage_change` → `plane_notify_stage` → (`set_issue_in_progress` для тестера) → проверка `_developer_retry_count` < `MAX_DEVELOPER_RETRIES` → `enqueue_job("developer", …)` либо `send_telegram` alert. Порядок и условия идентичны. - `result.rolled_back_to`, `result.enqueued_agent`, `result.enqueued_job_id`, `result.alerted` выставляются как сейчас. - Меняется **только содержимое строки `task_desc`**, передаваемой в `enqueue_job`. - Импорт нового модуля — `from .review_parse import extract_review_findings, extract_test_failures` в шапке `stage_engine.py`. ## 4. Изменения API Нет. Публичные HTTP-эндпоинты (`/health`, `/status`, `/queue`, `/webhook/plane`, `/webhook/gitea`) не затрагиваются. ## 5. Изменения схемы БД Нет. Таблицы `tasks`, `agent_runs`, `jobs`, `events` не меняются. `enqueue_job` вызывается с прежней сигнатурой. ## 6. Требования к новым QG checks Нет. Реестр `QG_CHECKS` и все `check_*` не трогаются (явно out of scope). ## 7. Артефакты pipeline (создать/обновить в этом PR) - `src/review_parse.py` — новый модуль. - `tests/test_review_parse.py` — юнит-тесты парсера (см. 04-test-plan.yaml). - Возможные дополнения в `tests/test_stage_engine.py` — проверка встраивания текста в `task_desc` (rollback-ветки). - `docs/work-items/ORCH-046/06-adr/ADR-001-*.md` — ADR (правка ядра). - `docs/architecture/README.md` / `internals.md` — описание нового хелпера и поведения заворотов (если reviewer сочтёт необходимым; компонент описать в разделе Stage Engine / Откаты). - `CHANGELOG.md` — запись о ORCH-046. ## 8. Контроль качества / проверка ```bash python -m pytest tests/ -q # в контейнере; все тесты зелёные ``` Обязательно: стадия `deploy-staging` (8501) перед прод-деплоем (self-hosting).