* 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>
85 lines
No EOL
6.9 KiB
Markdown
85 lines
No EOL
6.9 KiB
Markdown
# 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`):
|
||
1. **Wrong heading** — reviewer случайно пишет `## Code Review` (без "Summary") или `## Review Summary` → `REVIEW_VERDICT_RE` не матчит → pipeline NOT_DONE (блокировка)
|
||
2. **Verdict typo** — `APROVE` вместо `APPROVE`, `REQUEST_CHANGES` с пробелом → regex не матчит verdict → NOT_DONE
|
||
3. **No mutual exclusion** — любой агент может постить `## Code Review Summary` (нет guard "только reviewer")
|
||
4. **False positive** — `DOCS_REVIEW_RE` substring match `Docs 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` (contains `Docs Review`) → `DOCS_REVIEW_RE` матчит
|
||
|
||
### 6. Тестовая инфраструктура
|
||
|
||
`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)
|
||
|
||
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), так что raw `gh 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. |