210 lines
12 KiB
Markdown
210 lines
12 KiB
Markdown
# ТЗ — 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<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):
|
||
```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<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_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).
|