feat(#1517): support custom reviewer instances for /gsd:review (#1766)

* feat(#1517): support custom reviewer instances for /gsd:review

Add a bounded review.reviewer_instances config surface so one model-capable
adapter (e.g. opencode) can run as several independent reviewer identities in a
single /gsd:review pass. Instances participate only via review.default_reviewers,
expand before built-in slugs, are available iff their cli is detected, and a
non-matching entry is a hard error (typo must be loud). >=2 same-cli instances
emit a shared-adapter caveat in REVIEWS.md. Default path with no instances is
byte-for-byte unchanged.

Single-source instance->cli resolution lives in resolveReviewerSelection /
normalizeReviewerInstances (parity-locked in
tests/review-reviewer-instances.test.cjs). cli validated against
KNOWN_REVIEWER_SLUGS only (never arbitrary shell); model/agent opaque, never
shell-interpolated.

Closes #1517

* chore(#1517): backfill changeset pr:1766

---------

Co-authored-by: review-bot <review-bot@gsd>
This commit is contained in:
Tom Boucher
2026-06-26 23:35:04 -04:00
committed by GitHub
parent ce62f2b68d
commit b0d5ca3379
30 changed files with 835 additions and 66 deletions

View File

@@ -224,6 +224,49 @@ Example:
}
```
### Reviewer instances for `/gsd-review` (#1517)
Use `review.reviewer_instances` to run one model-capable adapter as several independent
reviewer identities — e.g. two OpenCode-backed reviews with different models in a single
`/gsd-review` pass. Each entry maps an instance name to `{ cli, model?, agent? }`.
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `review.reviewer_instances.<name>.cli` | string | (required) | A known reviewer adapter the instance reuses (e.g. `opencode`). Must be a built-in slug; never an arbitrary shell command. |
| `review.reviewer_instances.<name>.model` | string | (adapter default) | Opaque `provider/model` id passed through verbatim to the adapter's `--model`. GSD does not parse it. |
| `review.reviewer_instances.<name>.agent` | string | (none) | Opaque agent name; honoured only by adapters with a native agent concept (OpenCode `--agent` in v1). |
Instance names must match `^[a-z0-9][a-z0-9-]*$` and must not equal a built-in reviewer slug.
Instances participate ONLY through `review.default_reviewers` (there are no per-instance CLI
flags). Instance references are expanded before built-in slugs; an instance is available iff
its `cli` is detected. An entry that is neither a defined instance nor a built-in slug is a
hard error (a typo'd instance name must be loud). When two or more selected instances share
the same `cli`, `REVIEWS.md` prints a one-line shared-adapter caveat so review consensus is
not silently overstated. See [ADR-1517](adr/1517-reviewer-instances-config-surface.md).
Example:
```json
{
"review": {
"reviewer_instances": {
"opencode-deepseek": { "cli": "opencode", "model": "deepseek/deepseek-v4-pro", "agent": "review" },
"opencode-mimo": { "cli": "opencode", "model": "xiaomi/mimo-v2.5-pro" }
},
"default_reviewers": ["opencode-deepseek", "opencode-mimo", "codex"]
}
}
```
Set each field via `config-set`:
```bash
gsd config-set review.reviewer_instances.opencode-deepseek.cli opencode
gsd config-set review.reviewer_instances.opencode-deepseek.model deepseek/deepseek-v4-pro
gsd config-set review.reviewer_instances.opencode-deepseek.agent review
gsd config-set review.default_reviewers '["opencode-deepseek","opencode-mimo","codex"]'
```
### Agent-skill injection (dynamic)
`agent_skills.<agent-type>` extends the `agent_skills` map documented below. Slug is validated against `[a-zA-Z0-9_-]+` — no path separators, no whitespace, no shell metacharacters. Configured interactively via `/gsd-config --integrations`.

View File

@@ -248,6 +248,7 @@
"research-documentation-lookup.md",
"research-philosophy.md",
"research-verification-protocol.md",
"reviewer-instances.md",
"revision-loop.md",
"scout-codebase.md",
"security-asvs-levels.md",

View File

@@ -312,6 +312,7 @@ Full roster at `gsd-core/references/*.md`. References are shared knowledge docum
| `loop-hook-dispatch.md` | Generic dispatch contract for consuming `gsd_run loop render-hooks <point> --raw` output in any host-loop workflow — envelope shape, per-kind dispatch rules (contribution/step/gate), and liveness banner. |
| `scout-codebase.md` | Phase-type→codebase-map selection table for discuss-phase scout step (extracted via the discuss-phase/modes progressive-disclosure split, #717). |
| `revision-loop.md` | Plan revision iteration patterns. |
| `reviewer-instances.md` | Custom reviewer instances for `/gsd-review` (#1517) — same-adapter multi-model review: config shape, resolution rules, invocation, and the REVIEWS.md contract. Lazily loaded by `review.md` when `review.reviewer_instances` is configured. |
| `universal-anti-patterns.md` | Universal anti-patterns to detect and avoid. |
| `worktree-branch-check.md` | Canonical spawn-time worktree HEAD/base guard (worktree_branch_check): verify-only and fail-closed — per-agent-branch assertion, protected-ref refusal (#2924), and an exact-base assertion that halts with `exit 42` on mismatch so the orchestrator (worktree lifecycle owner) performs recovery (#48). Embedded into worktree sub-agent prompts at dispatch. |
| `worktree-path-safety.md` | Worktree guard suite: HEAD assertion, cwd-drift sentinel (step 0a, #3097), and absolute-path guard (step 0b, #3099) — loaded into executor spawn prompts via `<execution_context>`. |

View File

@@ -0,0 +1,104 @@
# ADR-1517: Reviewer instances — bounded config surface for same-adapter multi-model review
- **Status:** Accepted
- **Date:** 2026-06-26
- **Issue:** #1517
- **Builds on:** Review Reviewer Selection Module, config-schema manifest (ADR-457 generated single source)
## Context
`/gsd:review` exposes one reviewer identity per built-in slug (`KNOWN_REVIEWER_SLUGS`).
This works when reviewers are independent CLIs (`codex`, `gemini`), but breaks down when a
single model-capable CLI can route to several models. The motivating adapter is **OpenCode**:
a solo developer who wants two OpenCode-backed reviews with different models must manually
flip `review.models.opencode`, rerun, and hand-merge `REVIEWS.md`. That is easy to forget,
easy to overwrite, and does not participate in one review/convergence pass.
The feature (#1517, `approved-feature`) adds a **bounded config surface** so one adapter can
run as several independent reviewer identities. The maintainer's spec-of-record resolved the
three blocking design questions; this ADR pins the resulting contract (field names,
REVIEWS.md section-header format, frontmatter shape) because, once shipped, these become a
depended-on interface (Hyrum's Law).
## Decision
### Config shape
A new `review.reviewer_instances` object under the existing `review` top-level config
namespace. Each entry maps an instance name to `{ cli, model?, agent? }`:
```json
{
"review": {
"reviewer_instances": {
"opencode-deepseek": { "cli": "opencode", "model": "deepseek/deepseek-v4-pro", "agent": "review" },
"opencode-mimo": { "cli": "opencode", "model": "xiaomi/mimo-v2.5-pro" }
},
"default_reviewers": ["opencode-deepseek", "opencode-mimo", "codex"]
}
}
```
- **Instance name:** `^[a-z0-9][a-z0-9-]*$`, MUST NOT equal a built-in slug. Validated at
`config-set` time.
- **`cli`:** MUST be a known adapter from `KNOWN_REVIEWER_SLUGS` — never an arbitrary shell
command (Kerckhoffs / Postel: strict at the invocation boundary).
- **`model`:** a single opaque `provider/model` string (OpenCode's native format). GSD does
NOT parse model IDs; pass through verbatim.
- **`agent`:** opaque string; honoured only by adapters with a native agent concept
(OpenCode `--agent` in v1). Ignored by other adapters.
### Resolution contract (single source)
Instance→cli resolution lives in ONE place: `resolveReviewerSelection` /
`normalizeReviewerInstances` in `review-reviewer-selection.cjs`. The `/gsd:review` workflow
applies the SAME rules. A parity test (`tests/review-reviewer-instances.test.cjs`) asserts the
resolved mapping never diverges from the configured `cli` field — the
`DEFECT.GENERATIVE-FIX` guard against two surfaces drifting.
Rules:
1. Instances participate ONLY via `review.default_reviewers` (no per-instance CLI flags).
2. Instance references expand BEFORE the built-in-slug check.
3. An instance is available iff its base `cli` is detected.
4. An entry that is neither a defined instance nor a built-in slug is a **hard error** when
instances are configured (typo must be loud); legacy warn-and-drop when no instances are
configured (backward compatibility).
5. ≥2 selected instances sharing a base `cli` set `sharedAdapterCaveat` and emit a one-line
caveat in REVIEWS.md.
### REVIEWS.md contract
- **Frontmatter `reviewers:`** records actual identities: built-in slugs and instance names
(e.g. `[opencode-deepseek, opencode-mimo, codex]`).
- **Section headers:** each instance gets its own section,
`## <Adapter> Review (<instance-name>)`, e.g. `## OpenCode Review (opencode-deepseek)`.
Same-cli instances are never collapsed.
- **Shared-adapter caveat:** a one-line note after the frontmatter when ≥2 instances share
an adapter, so consensus is never silently overstated.
## Alternatives considered
1. **Per-instance CLI flags (`--opencode-1`/`--opencode-2`):** solves only one adapter, does
not scale, clutters the flag surface. Rejected (spec-of-record, non-blocking decision).
2. **Arbitrary shell commands as reviewers:** maximally flexible but reintroduces quoting,
portability, and injection risk. Rejected — bounded adapter config is safer.
3. **A parallel instance registry separate from the slug resolver:** rejected via Gall's Law
/ Choose Boring Technology — generalize the existing slug-resolution pattern rather than
bolting on a second mechanism.
## Consequences
- **Forward-compatibility:** the field names (`cli`, `model`, `agent`), the REVIEWS.md
section-header format, and the frontmatter identity list are now a depended-on contract.
Changing them requires a migration + a new ADR amendment.
- **Maintenance:** a per-adapter "supported fields" matrix emerges (OpenCode: model+agent;
others: model only). Bounded while the spec stays declarative.
- **Security:** the `cli` allow-list is the trust boundary. `model`/`agent`/instance-name
are opaque and never interpolated into shell strings by the resolver; the workflow passes
them as separate argv elements.
## Related
- #1517 — approved feature (spec-of-record in the triage comments)
- `src/review-reviewer-selection.cts` — `normalizeReviewerInstances`, `resolveReviewerSelection`
- `gsd-core/bin/shared/config-schema.manifest.json` — `review.reviewer_instances.*` dynamic pattern