opencode-config/docs/handoff/pr-46-review-posting-tools.md
Sergey 1acac5229f
feat(tools): deterministic review posting tools (post-review, post-docs-review) (#46)
* feat(tools): add post-review and post-docs-review tools

* refactor(agents): use post-review/post-docs-review tools in reviewer and docs-reviewer

* feat(permissions): add post_review and post_docs_review to agent.tools map

* test(tools): add tests for post-review and post-docs-review tools

* docs(handoff): add handoff ADR and project-map for review posting tools

* docs(handoff): set PR number

* docs: fix PR#45→PR#46 refs in project map

---------

Co-authored-by: opencode-agent <agent@slaid098.dev>
2026-07-24 18:26:48 +03:00

45 lines
No EOL
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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.