* 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:
@@ -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`.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>`. |
|
||||
|
||||
104
docs/adr/1517-reviewer-instances-config-surface.md
Normal file
104
docs/adr/1517-reviewer-instances-config-surface.md
Normal 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
|
||||
Reference in New Issue
Block a user