12 KiB
ТЗ — 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):
## Findings
### P0 — Blocker
- [ ] <описание>
### P1 — Must fix
- [ ] <описание>
### P2 — Should fix
- [ ] <описание>
Требования к реализации:
- Никогда не бросает исключение. Любая ошибка (нет файла, IOError, кривой
markdown, нет секции
## Findings) → возврат""(пустая строка). - Парсит только подсекции P0 и P1 (must-fix). P2/P3 игнорируются.
- Заголовки подсекций распознаются устойчиво к регистру и к тире/дефису:
соответствие по наличию токена
P0/P1в строке-заголовке уровня###. - Из распознанных подсекций берётся текст до следующего заголовка
###/##(т. е. тело подсекции дословно: пункты списка- [ ] …/- …). - Пустые подсекции (нет содержательных пунктов, только
(если есть)-плейсхолдер или ничего) — пропускаются. Если ни одного содержательного P0/P1 пункта нет → возврат"". - Результат — компактный многострочный текст, пригодный для вставки в
task_desc(например, заголовок подсекции + её пункты). Длина результата ограничивается разумным лимитом (MAX_FINDINGS_CHARS, напр. 2000) с усечением и маркером…(truncated); полный контекст всё равно остаётся в файле. - Frontmatter (верхний
--- … ---) при необходимости отбрасывается, чтобы не попасть в тело; парсинг секции делается по телу markdown.
Сигнатура и контракт (стабильны):
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, ## Итог.
Требования к реализации:
- Никогда не бросает исключение. Любая ошибка → возврат
"". - Извлекает релевантный фрагмент тела, помогающий понять причину FAIL.
Приоритет источников (берём первый непустой):
- секция
## Вывод pytest(вывод прогона — где видно упавшие тесты), и/или - строки таблицы
## Результаты, содержащиеFAIL, и/или - секция
## Итог.
- секция
- Результат усекается до
MAX_FAILURES_CHARS(напр. 2000) с маркером…(truncated). - Если ничего извлечь не удалось → возврат
""(вызывающий код делает fallback на ссылку).
Примечание: «reason» из самого гейта (
check_tests_passed→ второй элемент кортежа) у вызывающего кода уже есть (reason) — он добавляется вtask_descвызывающим кодом (как и сейчас в комментарии тестера).extract_test_failuresдобавляет фрагмент тела отчёта поверх этого reason.
Сигнатура и контракт (стабильны):
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):
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<text>), а ссылку на файл оставить как «полный контекст» (Полный контекст: docs/work-items/<id>/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):
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<fragment>), плюс ссылку на файл как полный контекст. - Если фрагмент пустой —
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_telegramalert. Порядок и условия идентичны. 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. Контроль качества / проверка
python -m pytest tests/ -q # в контейнере; все тесты зелёные
Обязательно: стадия deploy-staging (8501) перед прод-деплоем (self-hosting).