---
pr_number: 46
title: Deterministic review posting tools (post-review, post-docs-review)
---

# PR: Deterministic review posting tools (post-review, post-docs-review)

## Что сделано
- `.opencode/tools/post-review.ts` — TS tool wrapper (3 args: `pr_number`, `verdict` enum, `body`). Генерирует comment `## Code Review Summary\n\n${body}\n\n### Verdict: ${verdict}`. Вызывает `gh pr comment N --body <comment> --repo slaid098/opencode-config` через spawnSync. Verdict enum: `APPROVE`, `REQUEST_CHANGES`, `NEEDS_DISCUSSION`. Паттерн `merge-pr.ts` (spawnSync, `cwd: context.worktree`).
- `.opencode/tools/post-docs-review.ts` — TS tool wrapper (3 args: `pr_number`, `verdict` enum, `body`). Генерирует comment `## Docs Review Summary\n\n${body}\n\n### Verdict: ${verdict}`. Вызывает `gh pr comment N --body <comment> --repo slaid098/opencode-config` через spawnSync. Verdict enum: `APPROVE`, `FIXED`, `NO_CHANGES`.
- `.opencode/agents/reviewer.md` — секция "Output Format" переписана: `gh pr comment` → `post_review({ pr_number, verdict, body })`. Tool auto-generates heading + verdict line, body — содержимое между ними. 3 сценария (APPROVE/REQUEST_CHANGES/NEEDS_DISCUSSION). Frontmatter description + Rules 7/8 обновлены.
- `.opencode/agents/docs-reviewer.md` — секция "PR Comment (mandatory)" переписана: `gh pr comment` → `post_docs_review({ pr_number, verdict, body })`. Frontmatter description обновлён.
- `.opencode/opencode.json` — `agent.tools` map расширен: `post_review` + `post_docs_review` добавлены для 4 агентов (general/reviewer/docs-reviewer/memory-syncer). reviewer: post_review=true, post_docs_review=false; docs-reviewer: post_review=false, post_docs_review=true; general+memory-syncer: оба false.
- `tests/_ts_loader.mjs` — zodShim расширен методом `enum: () => chain()` (для `tool.schema.enum(VERDICTS)`). `stripTs` расширен: strip `as const` assertions + strip `type <Name> = ...` type alias declarations (TS-only синтаксис, используется в новых tools).
- `tests/test_post_review_tool.ts` — 7 TS тестов (документационные): valid APPROVE/REQUEST_CHANGES/NEEDS_DISCUSSION, invalid verdict, heading, verdict line, spawnSync args.
- `tests/test_post_docs_review_tool.ts` — 7 TS тестов: valid APPROVE/FIXED/NO_CHANGES, invalid verdict, heading, verdict line, spawnSync args.
- `tests/test_post_review_tool.py` — 12 Python тестов через `_ts_loader.mjs` (exec_stub_json): load, 3 valid verdicts, invalid verdict (permissive — zod validates, not execute), heading, 3 verdict lines, spawnSync args, cwd propagation, gh failure.
- `tests/test_post_docs_review_tool.py` — 12 Python тестов: load, 3 valid verdicts, invalid verdict, heading, 3 verdict lines, spawnSync args, cwd propagation, gh failure.
- `docs/project-map/README.md` — обновлён: новые tools (post-review.ts, post-docs-review.ts), новые тесты, обновлённые описания agents (reviewer/docs-reviewer используют post_review/post_docs_review tools).
- ADR-019 + этот handoff

## Почему
Reviewer и docs-reviewer постят комментарии через raw `gh pr comment` с ручным форматированием heading + verdict. `pipeline-status.py` парсит эти комментарии regex'ами (`REVIEW_VERDICT_RE = re.compile(r"## Code Review Summary.*?###\s*Verdict:\s*(\w+)")`, `DOCS_REVIEW_RE = re.compile(r"Docs Review")`). Риски:
1. Reviewer случайно использует wrong heading → pipeline NOT_DONE (блокировка)
2. Verdict опечатан (например `APROVE` вместо `APPROVE`) → regex не матчит → NOT_DONE
3. Нет mutual exclusion между review и docs-review комментариями
4. `DOCS_REVIEW_RE` — substring match, теоретический false positive если любой комментарий упомянет "Docs Review"

Решение: детерминированные TS tools, которые гарантируют heading + verdict формат через type system (zod enum). Tool принимает body БЕЗ heading — heading auto-generated. Это устраняет root cause ошибок форматирования: агент не может опечататься в heading или verdict, т.к. они генерируются кодом, не промптом.

Паттерн `merge-pr.ts` (PR#30, ADR-010): thin TS wrapper → spawnSync → `context.worktree` как cwd. Tools auto-discovered через `@opencode-ai/plugin`. agent.tools map в opencode.json контролирует role-based access (reviewer → post_review, docs-reviewer → post_docs_review, general+memory-syncer → none).

## Pending
- Skills/AGENTS.md правила форматов reviews теперь дублируются (agents текст + tools код). Future PR может заменить agents rules на "use post_review/post_docs_review tools" references.
- `gh pr comment*` permission в reviewer.md/docs-reviewer.md frontmatter оставлен — tools вызывают spawnSync напрямую (не через bash permission layer). Если захотеть запретить raw `gh pr comment` (принудить к tool) — добавить deny правило. Сейчас оба пути работают.
- pipeline-status.py regex'ы НЕ изменены (tools совместимы с существующим парсингом — генерируют ровно тот формат, который regex ожидает).

## Watch out
- `_ts_loader.mjs` zodShim НЕ валидирует enum (chainable builder без checks) — enum validation происходит в opencode runtime (zod), НЕ внутри execute(). Тест `test_invalid_verdict_not_validated_by_execute` документирует: execute() permissive, guard в zod schema. Это intentional — не дублировать валидацию в execute().
- `_ts_loader.mjs` `stripTs` расширен для `as const` и `type <Name> = ...` — TS-only синтаксис, которого не было в предыдущих tools (commit/create-pr/create-issue/merge-pr не используют type aliases). Если будущие tools добавят другие TS-only конструкции (interface, generic, etc.) — stripTs нужно будет расширить дальше.
- `post-review.ts` / `post-docs-review.ts` хардкодят `--repo slaid098/opencode-config` (как `merge-pr.ts` хардкодил `--squash --delete-branch`). Для других репо tools нужно параметризовать или создать копии. Альтернатива (через `git remote`) — out of scope этого PR (см. ADR-007 для паттерна параметризации).
- `gh pr comment*` в allow-list reviewer.md/docs-reviewer.md остался — tools используют spawnSync (не bash permission layer), так что raw `gh pr comment` всё ещё доступен агенту. Это intentional для обратной совместимости (fallback). Запретить raw можно отдельным PR если захотеть strict tool-only.
- ADR number = sequential (019), НЕ PR number. Эволюция известного паттерна (PR#26 docs-reviewer typo — записал PR number как ADR number).
- TS-тесты (`test_*.ts`) — документационные, CI гоняет Python-версии через `_ts_loader.mjs` (bun нет на runner).
- 347 тестов всего (323 существующих + 24 новых), все green. ruff check passes.