* 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>
6.9 KiB
ADR-019: Deterministic review posting tools (post-review, post-docs-review)
Статус
Accepted (2026-07-24)
Контекст
Reviewer и docs-reviewer агенты постят verdict-комментарии на PR через raw gh pr comment с ручным форматированием heading + verdict line:
- reviewer:
## Code Review Summary+### Verdict: APPROVE|REQUEST_CHANGES|NEEDS_DISCUSSION - docs-reviewer:
## Docs Review Summary+### Verdict: APPROVE|FIXED|NO_CHANGES
pipeline-status.py парсит эти комментарии regex'ами для определения статуса фаз REVIEW и DOCS:
REVIEW_VERDICT_RE = re.compile(r"## Code Review Summary.*?###\s*Verdict:\s*(\w+)", re.IGNORECASE | re.DOTALL)DOCS_REVIEW_RE = re.compile(r"Docs Review", re.IGNORECASE)— substring match
Риски текущего подхода (raw gh pr comment):
- Wrong heading — reviewer случайно пишет
## Code Review(без "Summary") или## Review Summary→REVIEW_VERDICT_REне матчит → pipeline NOT_DONE (блокировка) - Verdict typo —
APROVEвместоAPPROVE,REQUEST_CHANGESс пробелом → regex не матчит verdict → NOT_DONE - No mutual exclusion — любой агент может постить
## Code Review Summary(нет guard "только reviewer") - False positive —
DOCS_REVIEW_REsubstring matchDocs Review(без##prefix требования), теоретический false positive если любой комментарий упомянет "Docs Review" в тексте
Root cause: формат комментария контролируется промптом (soft guard), не type system. Модель может опечататься, забыть heading, изменить формат.
Решение
Детерминированные TS tools, которые гарантируют heading + verdict формат через type system:
1. post-review.ts tool (.opencode/tools/post-review.ts)
- Args:
pr_number: number,verdict: enum(APPROVE, REQUEST_CHANGES, NEEDS_DISCUSSION),body: string - Генерирует comment:
## Code Review Summary\n\n${body}\n\n### Verdict: ${verdict} - Вызывает
gh pr comment N --body <comment> --repo slaid098/opencode-configчерез spawnSync - Body — содержимое БЕЗ heading и verdict (tool добавляет их сам)
- Verdict enum (zod) гарантирует: только APPROVE/REQUEST_CHANGES/NEEDS_DISCUSSION, опечатки отклоняются на schema layer
2. post-docs-review.ts tool (.opencode/tools/post-docs-review.ts)
- Args:
pr_number: number,verdict: enum(APPROVE, FIXED, NO_CHANGES),body: string - Генерирует comment:
## Docs Review Summary\n\n${body}\n\n### Verdict: ${verdict} - Вызывает
gh pr comment N --body <comment> --repo slaid098/opencode-configчерез spawnSync
3. Agent integration
reviewer.md— секция "Output Format" переписана:gh pr comment→post_review({ pr_number, verdict, body }). 3 сценария (APPROVE/REQUEST_CHANGES/NEEDS_DISCUSSION). Rules 7/8 обновлены.docs-reviewer.md— секция "PR Comment (mandatory)" переписана:gh pr comment→post_docs_review({ pr_number, verdict, body }).
4. Role-based access (agent.tools map)
.opencode/opencode.json agent.tools map расширен:
| Agent | post_review | post_docs_review |
|---|---|---|
| general | false | false |
| reviewer | true | false |
| docs-reviewer | false | true |
| memory-syncer | false | false |
Mutual exclusion: только reviewer может постить code review, только docs-reviewer — docs review. General и memory-syncer не могут постить reviews. Это устраняет риск "любой агент постит review comment".
5. pipeline-status.py совместимость
Regex'ы НЕ изменены. Tools генерируют ровно тот формат, который regex ожидает:
## Code Review Summary(exact heading) +### Verdict: <verdict>→REVIEW_VERDICT_REматчит## Docs Review Summary(containsDocs Review) →DOCS_REVIEW_REматчит
6. Тестовая инфраструктура
tests/_ts_loader.mjs расширен:
- zodShim: добавлен
enum: () => chain()метод (дляtool.schema.enum(VERDICTS)) stripTs: stripas constassertions + striptype <Name> = ...type alias declarations (TS-only синтаксис новых tools)
24 новых Python теста (12 на tool) + 14 TS тестов (документационные).
Альтернативы
-
Оставить raw
gh pr comment+ усилить промпт — отклонено: промпт = soft guard, модель (GLM) игнорирует правила форматирования. Root cause (формат контролируется промптом, не type system) не устранён. Документировано в PR#85 (docs-reviewer marker) — промпт-правила ненадёжны. -
Validate comment format post-hoc в pipeline-status.py — отклонено: pipeline-status.py уже парсит regex'ами, но это detection, не prevention. Comment уже постит с wrong heading → pipeline NOT_DONE → нужно перезапустить reviewer. Tool предотвращает ошибку на этапе создания (prevention > detection).
-
Параметризовать
--repoчерезgit remote(как ADR-007 для pipeline-status.py) — отклонено: out of scope.merge-pr.tsтоже хардкодит--squash --delete-branch(не параметризует repo). Для других репо — отдельный PR (параметризация всех tools). Сейчас tools специфичны дляslaid098/opencode-config. -
Запретить raw
gh pr commentв allow-list (принудить к tool) — отклонено: оставлено для обратной совместимости (fallback). Tools используют spawnSync напрямую (не через bash permission layer), так что rawgh pr comment*в allow-list не конфликтует. Если захотеть strict tool-only — отдельный PR с deny правиломgh pr comment*. -
Расширить
DOCS_REVIEW_REдо## Docs Review Summary(exact heading) — отклонено: risk of breaking existing comments. Tools уже генерируют exact heading, но старые comments (до этого PR) могут иметь другой формат. Substring match остаётся как tolerant fallback. Future PR может tighten regex после migration.