Files
claude-bot c7bca51d4b
All checks were successful
CI / test (push) Successful in 12s
analyst(ET): auto-commit from analyst run_id=139
2026-06-06 04:09:41 +00:00

12 KiB
Raw Permalink Blame History

ТЗ — 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
- [ ] <описание>

Требования к реализации:

  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.

Сигнатура и контракт (стабильны):

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.

Сигнатура и контракт (стабильны):

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)

Текущее (~стр. 418424):

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)

Текущее (~стр. 454459):

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_changeplane_notify_stage → (set_issue_in_progress для тестера) → проверка _developer_retry_count < MAX_DEVELOPER_RETRIESenqueue_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. Контроль качества / проверка

python -m pytest tests/ -q      # в контейнере; все тесты зелёные

Обязательно: стадия deploy-staging (8501) перед прод-деплоем (self-hosting).