Merge branch 'next' into fix/1477-surface-source-marker
This commit is contained in:
5
.changeset/sharp-otters-romp.md
Normal file
5
.changeset/sharp-otters-romp.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 1766
|
||||
---
|
||||
**`/gsd-review` now supports custom reviewer instances** — run one model-capable adapter (e.g. OpenCode) as several independent reviewer identities via a bounded `review.reviewer_instances` config, so two different models can review in a single pass without manually swapping config or hand-merging REVIEWS.md. (#1517)
|
||||
@@ -35,6 +35,12 @@ Phase number: extracted from $ARGUMENTS (required)
|
||||
- `--cursor` — Include Cursor agent review
|
||||
- `--agy` / `--antigravity` — Include Antigravity CLI review
|
||||
- `--all` — Include all available CLIs
|
||||
|
||||
**No flags** — if `review.default_reviewers` is set, review with only those configured
|
||||
reviewers that are detected; otherwise review with all available CLIs. Configured
|
||||
`review.reviewer_instances` names may appear in `review.default_reviewers`; each runs as an
|
||||
independent reviewer identity backed by its configured adapter+model (see
|
||||
`docs/CONFIGURATION.md`). Instance names are not valid as flags.
|
||||
</context>
|
||||
|
||||
<process>
|
||||
|
||||
@@ -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
|
||||
@@ -172,6 +172,11 @@
|
||||
"source": "^review\\.max_prompt_tokens_per_reviewer\\.[a-zA-Z0-9_-]+$",
|
||||
"description": "review.max_prompt_tokens_per_reviewer.<reviewer-slug>"
|
||||
},
|
||||
{
|
||||
"topLevel": "review",
|
||||
"source": "^review\\.reviewer_instances\\.[a-zA-Z0-9_-]+\\.(cli|model|agent)$",
|
||||
"description": "review.reviewer_instances.<instance-name>.<cli|model|agent> (#1517)"
|
||||
},
|
||||
{
|
||||
"topLevel": "model_policy",
|
||||
"source": "^model_policy\\.runtime_tiers\\.[a-zA-Z0-9_-]+\\.(opus|sonnet|haiku)$",
|
||||
|
||||
99
gsd-core/references/reviewer-instances.md
Normal file
99
gsd-core/references/reviewer-instances.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Reviewer Instances (#1517)
|
||||
|
||||
Custom reviewer instances for `/gsd:review`: run one model-capable adapter (e.g. OpenCode)
|
||||
as several independent reviewer identities in a single review pass. Loaded lazily by
|
||||
`gsd-core/workflows/review.md` when `review.reviewer_instances` is configured. See
|
||||
[ADR-1517](../docs/adr/1517-reviewer-instances-config-surface.md) for the contract.
|
||||
|
||||
---
|
||||
|
||||
## Config shape
|
||||
|
||||
A `review.reviewer_instances` object under the `review` 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 (`KNOWN_REVIEWER_SLUGS`) — never an arbitrary shell command.
|
||||
- `model`: opaque `provider/model` string, passed through verbatim. GSD does not parse it.
|
||||
- `agent`: opaque string; honoured only by adapters with a native agent concept (OpenCode
|
||||
`--agent` in v1). Ignored by other adapters.
|
||||
|
||||
---
|
||||
|
||||
## Resolution rules (single source)
|
||||
|
||||
The canonical logic lives in `resolveReviewerSelection` / `normalizeReviewerInstances` in
|
||||
`review-reviewer-selection.cjs`. Apply the SAME rules in the workflow so the two surfaces
|
||||
cannot diverge (`DEFECT.GENERATIVE-FIX`; parity-locked in
|
||||
`tests/review-reviewer-instances.test.cjs`).
|
||||
|
||||
1. Instances participate ONLY via `review.default_reviewers`. They never appear under `--all`
|
||||
or explicit `--<cli>` flags, and there are no per-instance CLI flags.
|
||||
2. Expand instance references BEFORE the built-in-slug check: an entry that is a key in
|
||||
`review.reviewer_instances` is an **instance**; an entry that is a built-in slug is a
|
||||
**builtin**.
|
||||
3. An instance is **available** iff its base `cli` is detected (e.g. `opencode-deepseek` is
|
||||
available iff `opencode` is available).
|
||||
4. An entry that is NEITHER a defined instance NOR a built-in slug is a **hard error** (likely
|
||||
a typo'd instance name) — stop and report it. Do NOT silently drop it. (When
|
||||
`review.reviewer_instances` is absent entirely, fall back to the legacy unknown-slug
|
||||
warn-and-drop behaviour for backward compatibility.)
|
||||
5. `model`/`agent`/instance-name are opaque: pass them as separate argv elements. They are
|
||||
NEVER interpolated into shell strings.
|
||||
|
||||
---
|
||||
|
||||
## Invocation
|
||||
|
||||
For each selected INSTANCE, invoke its base `cli` using the instance's own `model`/`agent` —
|
||||
NOT the global `review.models.<cli>`. Each instance writes to its OWN per-instance output file
|
||||
and runs as a distinct reviewer identity.
|
||||
|
||||
For an OpenCode-backed instance (the motivating adapter):
|
||||
|
||||
```bash
|
||||
# $INSTANCE_MODEL / $INSTANCE_AGENT come from the instance spec; $INSTANCE_NAME is the
|
||||
# reviewer identity (e.g. opencode-deepseek). --agent is OpenCode's native subagent flag;
|
||||
# omit it when the instance has no agent.
|
||||
if [ -n "$INSTANCE_AGENT" ] && [ "$INSTANCE_AGENT" != "null" ]; then
|
||||
cat /tmp/gsd-review-prompt-{phase}.md | opencode run --model "$INSTANCE_MODEL" --agent "$INSTANCE_AGENT" - 2>/dev/null > /tmp/gsd-review-${INSTANCE_NAME}-{phase}.md
|
||||
else
|
||||
cat /tmp/gsd-review-prompt-{phase}.md | opencode run --model "$INSTANCE_MODEL" - 2>/dev/null > /tmp/gsd-review-${INSTANCE_NAME}-{phase}.md
|
||||
fi
|
||||
if [ ! -s /tmp/gsd-review-${INSTANCE_NAME}-{phase}.md ]; then
|
||||
echo "OpenCode review ($INSTANCE_NAME) failed or returned empty output." > /tmp/gsd-review-${INSTANCE_NAME}-{phase}.md
|
||||
fi
|
||||
```
|
||||
|
||||
For an instance backed by a DIFFERENT cli, reuse that cli's invocation block with two
|
||||
substitutions: use the instance's `model` in place of the global `review.models.<cli>` value,
|
||||
and write to `/tmp/gsd-review-${INSTANCE_NAME}-{phase}.md`. Only `opencode` honours an
|
||||
`agent` field in v1; ignore `agent` for other adapters.
|
||||
|
||||
---
|
||||
|
||||
## REVIEWS.md contract
|
||||
|
||||
- **Frontmatter `reviewers:`** records the actual identities invoked. For a built-in slug use
|
||||
the slug (`opencode`); for an instance use the instance name (`opencode-deepseek`), so
|
||||
frontmatter distinguishes the independent voices. Example:
|
||||
`reviewers: [opencode-deepseek, opencode-mimo, codex]`.
|
||||
- **Section headers:** each instance gets its OWN top-level section, headed with the base
|
||||
adapter's display name plus the instance name in parentheses:
|
||||
`## OpenCode Review (opencode-deepseek)`. Same-cli instances are never collapsed.
|
||||
- **Shared-adapter caveat:** when ≥2 invoked instances share the same base `cli`, print a
|
||||
one-line caveat immediately after the frontmatter (before the first section), e.g.:
|
||||
`> Note: opencode-deepseek and opencode-mimo share the opencode adapter; their consensus is cross-model, not cross-tool.`
|
||||
@@ -66,6 +66,11 @@ Reviewer-selection precedence:
|
||||
- Known-but-undetected slugs emit an info note and are ignored
|
||||
- If all configured reviewers are unavailable, fail with an actionable message
|
||||
|
||||
**Reviewer instances (#1517, optional):** if `review.reviewer_instances` is configured,
|
||||
instance names in `review.default_reviewers` run as independent identities. Resolution rules
|
||||
are in `gsd-core/references/reviewer-instances.md` — load it lazily only when instances are
|
||||
configured. Unconfigured → default path unchanged.
|
||||
|
||||
If no CLIs are available:
|
||||
```
|
||||
No external AI CLIs found. Install at least one:
|
||||
@@ -243,6 +248,10 @@ else
|
||||
fi
|
||||
```
|
||||
|
||||
**Reviewer instances (#1517, optional):** when instances are configured, each selected
|
||||
instance invokes its base `cli` with its own `model`/`agent` (opaque argv, never
|
||||
shell-interpolated). Exact invocation in `gsd-core/references/reviewer-instances.md`.
|
||||
|
||||
For each selected CLI, invoke in sequence (not parallel — avoid rate limits):
|
||||
|
||||
**Gemini:**
|
||||
@@ -634,6 +643,11 @@ Combine all review responses into `{phase_dir}/{padded_phase}-REVIEWS.md`:
|
||||
|
||||
After all reviewers complete, collect trim metadata files written during the run. For each reviewer that was trimmed (i.e. a `.metadata.json` file exists and `hardFailed` or `omitted` is non-empty, or `projectMdShrunk` is true, or `planTruncationPct > 0`), include a `trimmed_reviewers` block in the frontmatter. Omit the key entirely if no reviewer was trimmed.
|
||||
|
||||
**Reviewer instances (#1517, optional):** when instances ran, frontmatter records their
|
||||
names, each gets its own `## <Adapter> Review (<instance>)` section, and ≥2 same-cli
|
||||
instances print a one-line shared-adapter caveat. Format in
|
||||
`gsd-core/references/reviewer-instances.md`.
|
||||
|
||||
```markdown
|
||||
---
|
||||
phase: {N}
|
||||
@@ -684,6 +698,18 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
|
||||
|
||||
---
|
||||
|
||||
## OpenCode Review (opencode-deepseek)
|
||||
|
||||
{opencode-deepseek instance review content — only present when this instance was selected}
|
||||
|
||||
---
|
||||
|
||||
## OpenCode Review (opencode-mimo)
|
||||
|
||||
{opencode-mimo instance review content — only present when this instance was selected}
|
||||
|
||||
---
|
||||
|
||||
## Qwen Review
|
||||
|
||||
{qwen review content}
|
||||
|
||||
@@ -26,7 +26,7 @@ const { VALID_PROFILES, getAgentToModelMapForProfile, formatAgentToModelMapAsTab
|
||||
import configSchema = require('./config-schema.cjs');
|
||||
const { VALID_CONFIG_KEYS, isValidConfigKey, getCapabilityConfigSchema } = configSchema;
|
||||
import { isSecretKey, maskSecret } from './secrets.cjs';
|
||||
import { normalizeConfiguredDefaultReviewers } from './review-reviewer-selection.cjs';
|
||||
import { normalizeConfiguredDefaultReviewers, INSTANCE_NAME_PATTERN, KNOWN_REVIEWER_SLUGS } from './review-reviewer-selection.cjs';
|
||||
import { migrateOnDisk } from './configuration.cjs';
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
@@ -695,6 +695,33 @@ function cmdConfigSet(cwd: string, keyPath: string | undefined, value: string |
|
||||
parsedValue = normalized.values;
|
||||
}
|
||||
|
||||
// #1517: validate review.reviewer_instances.<name>.<field> leaves at the
|
||||
// invocation boundary (Postel/Kerckhoffs — strict at accept). The config
|
||||
// schema dynamic pattern admits the path; this block validates the name + the
|
||||
// field value so a misconfigured instance is rejected at config-set time, not
|
||||
// silently at review time. Single-source validators live in
|
||||
// review-reviewer-selection.cjs (INSTANCE_NAME_PATTERN, KNOWN_REVIEWER_SLUGS).
|
||||
const instanceLeaf = kp.match(/^review\.reviewer_instances\.([a-zA-Z0-9_-]+)\.(cli|model|agent)$/);
|
||||
if (instanceLeaf) {
|
||||
const [, instanceName, field] = instanceLeaf;
|
||||
if (!INSTANCE_NAME_PATTERN.test(instanceName)) {
|
||||
error(`Invalid reviewer instance name '${instanceName}'. Must match ^[a-z0-9][a-z0-9-]*$.`);
|
||||
}
|
||||
if (KNOWN_REVIEWER_SLUGS.includes(instanceName)) {
|
||||
error(`Reviewer instance name '${instanceName}' must not equal a built-in reviewer slug.`);
|
||||
}
|
||||
if (field === 'cli') {
|
||||
if (typeof parsedValue !== 'string' || !KNOWN_REVIEWER_SLUGS.includes(parsedValue)) {
|
||||
error(`Invalid reviewer_instances.${instanceName}.cli '${val}'. Must be a known reviewer adapter: ${KNOWN_REVIEWER_SLUGS.join(', ')}.`);
|
||||
}
|
||||
} else {
|
||||
// model | agent — opaque pass-through strings (never interpolated into shell).
|
||||
if (typeof parsedValue !== 'string') {
|
||||
error(`Invalid reviewer_instances.${instanceName}.${field} '${val}'. Must be a string.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setConfigValueResult = setConfigValue(cwd, kp, parsedValue);
|
||||
|
||||
// Mask secrets in both JSON and text output. The plaintext is written
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
*
|
||||
* Owns reviewer-selection policy projection for /gsd:review:
|
||||
* explicit flags > --all > review.default_reviewers > all detected.
|
||||
*
|
||||
* Reviewer instances (#1517): a bounded config surface
|
||||
* `review.reviewer_instances.<name> = {cli, model?, agent?}` lets one
|
||||
* model-capable adapter (e.g. opencode) run as several independent reviewer
|
||||
* identities. Instances participate ONLY in the config_default branch (no
|
||||
* per-instance CLI flags). An instance is available iff its base `cli` is
|
||||
* detected. The instance→cli mapping lives HERE (single source; see the parity
|
||||
* test in tests/review-reviewer-instances.test.cjs — DEFECT.GENERATIVE-FIX).
|
||||
*/
|
||||
|
||||
export const KNOWN_REVIEWER_SLUGS: ReadonlyArray<string> = [
|
||||
@@ -22,17 +30,41 @@ export const KNOWN_REVIEWER_SLUGS: ReadonlyArray<string> = [
|
||||
'llama_cpp',
|
||||
];
|
||||
|
||||
/** Instance names are lowercase slugs that must not shadow a built-in slug. */
|
||||
export const INSTANCE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
||||
|
||||
export interface NormalizedDefaultReviewers {
|
||||
absent: boolean;
|
||||
values: string[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export interface ReviewerInstance {
|
||||
cli: string;
|
||||
model?: string;
|
||||
agent?: string;
|
||||
}
|
||||
|
||||
export interface NormalizedReviewerInstances {
|
||||
instances: Record<string, ReviewerInstance>;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export interface ResolvedReviewer {
|
||||
identity: string;
|
||||
kind: 'builtin' | 'instance';
|
||||
cli: string;
|
||||
model?: string;
|
||||
agent?: string;
|
||||
}
|
||||
|
||||
export interface ReviewerSelectionInput {
|
||||
detected?: unknown[];
|
||||
explicitFlags?: unknown[];
|
||||
allFlag?: unknown;
|
||||
configuredDefaultReviewers?: unknown;
|
||||
/** #1517: the `review.reviewer_instances` config object. */
|
||||
reviewerInstances?: unknown;
|
||||
}
|
||||
|
||||
export interface ReviewerSelectionResult {
|
||||
@@ -41,6 +73,10 @@ export interface ReviewerSelectionResult {
|
||||
warnings: string[];
|
||||
infos: string[];
|
||||
errors: string[];
|
||||
/** #1517: per-identity resolution (builtin slug or expanded instance). */
|
||||
resolvedInstances: ResolvedReviewer[];
|
||||
/** #1517: true when ≥2 selected instances share a base cli (consensus caveat). */
|
||||
sharedAdapterCaveat: boolean;
|
||||
}
|
||||
|
||||
export function normalizeConfiguredDefaultReviewers(
|
||||
@@ -86,6 +122,76 @@ export function normalizeConfiguredDefaultReviewers(
|
||||
return { absent: false, values: normalized, errors };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the `review.reviewer_instances` config object (#1517).
|
||||
* `cli` MUST be a known adapter (never an arbitrary shell command — Kerckhoffs /
|
||||
* Postel: strict at the invocation boundary). `model`/`agent` are opaque
|
||||
* pass-through strings; they are never interpolated into shell strings by this
|
||||
* module. Instance names must not collide with a built-in slug.
|
||||
*/
|
||||
export function normalizeReviewerInstances(
|
||||
rawValue: unknown,
|
||||
): NormalizedReviewerInstances {
|
||||
if (rawValue === undefined || rawValue === null) {
|
||||
return { instances: {}, errors: [] };
|
||||
}
|
||||
if (typeof rawValue !== 'object' || Array.isArray(rawValue)) {
|
||||
return {
|
||||
instances: {},
|
||||
errors: ['review.reviewer_instances must be a JSON object mapping instance names to {cli,model,agent}'],
|
||||
};
|
||||
}
|
||||
|
||||
const obj = rawValue as Record<string, unknown>;
|
||||
const instances: Record<string, ReviewerInstance> = {};
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const [name, spec] of Object.entries(obj)) {
|
||||
if (!INSTANCE_NAME_PATTERN.test(name)) {
|
||||
errors.push(
|
||||
`invalid reviewer instance name '${name}': must match ^[a-z0-9][a-z0-9-]*$`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (KNOWN_REVIEWER_SLUGS.includes(name)) {
|
||||
errors.push(
|
||||
`reviewer instance name '${name}' must not equal a built-in reviewer slug`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {
|
||||
errors.push(`reviewer_instances.${name} must be an object with at least {cli}`);
|
||||
continue;
|
||||
}
|
||||
const s = spec as Record<string, unknown>;
|
||||
const cli = s.cli;
|
||||
if (typeof cli !== 'string' || !KNOWN_REVIEWER_SLUGS.includes(cli)) {
|
||||
errors.push(
|
||||
`reviewer_instances.${name}.cli must be a known reviewer adapter (got: ${JSON.stringify(cli)})`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const instance: ReviewerInstance = { cli };
|
||||
if (s.model !== undefined && s.model !== null) {
|
||||
if (typeof s.model !== 'string') {
|
||||
errors.push(`reviewer_instances.${name}.model must be a string`);
|
||||
continue;
|
||||
}
|
||||
instance.model = s.model;
|
||||
}
|
||||
if (s.agent !== undefined && s.agent !== null) {
|
||||
if (typeof s.agent !== 'string') {
|
||||
errors.push(`reviewer_instances.${name}.agent must be a string`);
|
||||
continue;
|
||||
}
|
||||
instance.agent = s.agent;
|
||||
}
|
||||
instances[name] = instance;
|
||||
}
|
||||
|
||||
return { instances, errors };
|
||||
}
|
||||
|
||||
export function resolveReviewerSelection(
|
||||
input: ReviewerSelectionInput,
|
||||
): ReviewerSelectionResult {
|
||||
@@ -99,10 +205,13 @@ export function resolveReviewerSelection(
|
||||
const normalizedDefaults = normalizeConfiguredDefaultReviewers(
|
||||
input.configuredDefaultReviewers,
|
||||
);
|
||||
const normalizedInstances = normalizeReviewerInstances(input.reviewerInstances);
|
||||
const instances = normalizedInstances.instances;
|
||||
const instancesConfigured = Object.keys(instances).length > 0;
|
||||
|
||||
const warnings: string[] = [];
|
||||
const infos: string[] = [];
|
||||
const errors: string[] = [...normalizedDefaults.errors];
|
||||
const errors: string[] = [...normalizedDefaults.errors, ...normalizedInstances.errors];
|
||||
|
||||
let source = 'no_config_all_detected';
|
||||
let selected: string[] = [];
|
||||
@@ -122,19 +231,37 @@ export function resolveReviewerSelection(
|
||||
selected = [...detected];
|
||||
} else if (!normalizedDefaults.absent) {
|
||||
source = 'config_default';
|
||||
const knownDefaults: string[] = [];
|
||||
for (const slug of normalizedDefaults.values) {
|
||||
if (!KNOWN_REVIEWER_SLUGS.includes(slug)) {
|
||||
warnings.push(`unknown reviewer slug in review.default_reviewers: ${slug}`);
|
||||
// #1517: expand instance references BEFORE the built-in-slug check. An
|
||||
// instance name and a built-in slug are the two legal kinds of entry.
|
||||
for (const entry of normalizedDefaults.values) {
|
||||
if (instances[entry]) {
|
||||
// Instance reference — available iff its base cli is detected.
|
||||
const cli = instances[entry].cli;
|
||||
if (!detected.has(cli)) {
|
||||
infos.push(`configured instance ${entry} not detected (cli ${cli} missing on this host)`);
|
||||
} else {
|
||||
selected.push(entry);
|
||||
}
|
||||
} else if (KNOWN_REVIEWER_SLUGS.includes(entry)) {
|
||||
if (!detected.has(entry)) {
|
||||
infos.push(`configured reviewers not detected on this host: ${entry}`);
|
||||
} else {
|
||||
selected.push(entry);
|
||||
}
|
||||
} else {
|
||||
knownDefaults.push(slug);
|
||||
// Neither a defined instance nor a built-in slug.
|
||||
if (instancesConfigured) {
|
||||
// Most likely a typo'd instance name — must be loud (#1517 design Q2).
|
||||
errors.push(
|
||||
`reviewer instance '${entry}' referenced in review.default_reviewers is not defined in review.reviewer_instances`,
|
||||
);
|
||||
} else {
|
||||
// Backward-compatible behaviour: unknown slug with no instances
|
||||
// configured warns and is dropped.
|
||||
warnings.push(`unknown reviewer slug in review.default_reviewers: ${entry}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const undetected = knownDefaults.filter((slug) => !detected.has(slug));
|
||||
if (undetected.length > 0) {
|
||||
infos.push(`configured reviewers not detected on this host: ${undetected.join(', ')}`);
|
||||
}
|
||||
selected = knownDefaults.filter((slug) => detected.has(slug));
|
||||
if (selected.length === 0 && errors.length === 0) {
|
||||
errors.push('all configured default reviewers are unavailable on this host');
|
||||
}
|
||||
@@ -142,11 +269,38 @@ export function resolveReviewerSelection(
|
||||
selected = [...detected];
|
||||
}
|
||||
|
||||
const selectedSorted = selected.sort();
|
||||
|
||||
// Single-source instance→cli resolution projected onto the selected set.
|
||||
const resolvedInstances: ResolvedReviewer[] = selectedSorted.map((identity) => {
|
||||
const inst = instances[identity];
|
||||
if (inst) {
|
||||
return {
|
||||
identity,
|
||||
kind: 'instance' as const,
|
||||
cli: inst.cli,
|
||||
model: inst.model,
|
||||
agent: inst.agent,
|
||||
};
|
||||
}
|
||||
return { identity, kind: 'builtin' as const, cli: identity };
|
||||
});
|
||||
|
||||
const cliCounts: Record<string, number> = {};
|
||||
for (const r of resolvedInstances) {
|
||||
if (r.kind === 'instance') {
|
||||
cliCounts[r.cli] = (cliCounts[r.cli] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
const sharedAdapterCaveat = Object.values(cliCounts).some((c) => c >= 2);
|
||||
|
||||
return {
|
||||
source,
|
||||
selected: selected.sort(),
|
||||
selected: selectedSorted,
|
||||
warnings,
|
||||
infos,
|
||||
errors,
|
||||
resolvedInstances,
|
||||
sharedAdapterCaveat,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "46deb2174be356dd",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
|
||||
@@ -115,6 +115,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "a602b41bfa081fdf",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -273,7 +274,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "6b9947fba1a97f46",
|
||||
"gsd-core/workflows/remove-workspace.md": "95f05defffe6754e",
|
||||
"gsd-core/workflows/resume-project.md": "cadf390bae95fc76",
|
||||
"gsd-core/workflows/review.md": "439d0088fbc29350",
|
||||
"gsd-core/workflows/review.md": "4d1ea3f9785044b3",
|
||||
"gsd-core/workflows/scan.md": "f2754f3e3ea528ff",
|
||||
"gsd-core/workflows/secure-phase.md": "78705b7f09612464",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -381,7 +382,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "c18b11b12ba134fa",
|
||||
"skills/gsd-resume-work/SKILL.md": "fa2ee37470c6ae07",
|
||||
"skills/gsd-review-backlog/SKILL.md": "d8a150558cc9326a",
|
||||
"skills/gsd-review/SKILL.md": "f9b5e25043b667ac",
|
||||
"skills/gsd-review/SKILL.md": "1abcf1f6a7b73ab2",
|
||||
"skills/gsd-secure-phase/SKILL.md": "47a4ecd2aa680fda",
|
||||
"skills/gsd-settings/SKILL.md": "fda7af8331acd352",
|
||||
"skills/gsd-ship/SKILL.md": "4529b04a357cdacd",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"commands/gsd-quick.md": "a6124a2443394092",
|
||||
"commands/gsd-resume-work.md": "e54b929de88b11ce",
|
||||
"commands/gsd-review-backlog.md": "6e8a0417fab95cd3",
|
||||
"commands/gsd-review.md": "4403de207a9c2582",
|
||||
"commands/gsd-review.md": "87d9b3f0705afb5e",
|
||||
"commands/gsd-secure-phase.md": "a2320ecca4cb160b",
|
||||
"commands/gsd-settings.md": "53b90624a70fd530",
|
||||
"commands/gsd-ship.md": "81136d903d261b33",
|
||||
@@ -110,7 +110,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -184,6 +184,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -342,7 +343,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "d030f80ca0df4fa9",
|
||||
"gsd-core/workflows/remove-workspace.md": "b8817a3a5907f5bc",
|
||||
"gsd-core/workflows/resume-project.md": "b18b51fd15cbce95",
|
||||
"gsd-core/workflows/review.md": "18ab093986814bb3",
|
||||
"gsd-core/workflows/review.md": "fc23a807c6c3d6d9",
|
||||
"gsd-core/workflows/scan.md": "54ff1ff60041d065",
|
||||
"gsd-core/workflows/secure-phase.md": "1d1c66ad9ea01bd2",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -449,7 +450,7 @@
|
||||
"skills/gsd-ns-review/skills/debug/SKILL.md": "4b9bde6d213185f0",
|
||||
"skills/gsd-ns-review/skills/eval-review/SKILL.md": "61d28536af6794c9",
|
||||
"skills/gsd-ns-review/skills/forensics/SKILL.md": "e68fc285c39dad5e",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "022745ee2379823c",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "3d1cbf005e02c948",
|
||||
"skills/gsd-ns-review/skills/secure-phase/SKILL.md": "ff7949b3261c09fa",
|
||||
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "d0e75ee06eaa0165",
|
||||
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "bdab072171d79877",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -114,6 +114,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -272,7 +273,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "f76e1c2a4dd31a09",
|
||||
"gsd-core/workflows/remove-workspace.md": "0e73844f0f41cbc3",
|
||||
"gsd-core/workflows/resume-project.md": "3dcaa7abe1800d35",
|
||||
"gsd-core/workflows/review.md": "c32eac3c18b11691",
|
||||
"gsd-core/workflows/review.md": "1660ff860e79f9b7",
|
||||
"gsd-core/workflows/scan.md": "8e1bbf2eed1752ca",
|
||||
"gsd-core/workflows/secure-phase.md": "b2b9100ff79d6017",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -380,7 +381,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "bd5e4cb79bc41611",
|
||||
"skills/gsd-resume-work/SKILL.md": "e23d2fb963d47d04",
|
||||
"skills/gsd-review-backlog/SKILL.md": "1708aab6cb919223",
|
||||
"skills/gsd-review/SKILL.md": "b2fe23d7725c19f4",
|
||||
"skills/gsd-review/SKILL.md": "17cf20dd9a4796ff",
|
||||
"skills/gsd-secure-phase/SKILL.md": "a6adf4729b606d5a",
|
||||
"skills/gsd-settings/SKILL.md": "6d9fbddb0b00fd46",
|
||||
"skills/gsd-ship/SKILL.md": "9f7929947aac3c27",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -118,6 +118,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -276,7 +277,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "61b68a4af414e3c2",
|
||||
"gsd-core/workflows/remove-workspace.md": "411bfff942216185",
|
||||
"gsd-core/workflows/resume-project.md": "f8f71b5a98374b85",
|
||||
"gsd-core/workflows/review.md": "897a4c72a97178f8",
|
||||
"gsd-core/workflows/review.md": "a74ae666e01ceebb",
|
||||
"gsd-core/workflows/scan.md": "db0c48c3963f9a8b",
|
||||
"gsd-core/workflows/secure-phase.md": "c51b86e369574195",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -361,7 +362,7 @@
|
||||
"skills/gsd-ns-review/skills/debug/SKILL.md": "2017ee8a5c39357a",
|
||||
"skills/gsd-ns-review/skills/eval-review/SKILL.md": "c76022e94ee30cb1",
|
||||
"skills/gsd-ns-review/skills/forensics/SKILL.md": "bbab359eaa388d34",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "04c7f2a8515d97a2",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "befdc84f5a0acf03",
|
||||
"skills/gsd-ns-review/skills/secure-phase/SKILL.md": "ceded474164b4e2d",
|
||||
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "c35f175ccc747d56",
|
||||
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "48267dc071481a89",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"commands/gsd-quick.md": "8585535a111660d1",
|
||||
"commands/gsd-resume-work.md": "9c4ee571a15fefae",
|
||||
"commands/gsd-review-backlog.md": "41ee0337839b8e9d",
|
||||
"commands/gsd-review.md": "4d1b7eaef1560fb4",
|
||||
"commands/gsd-review.md": "763f91f5f823fa3a",
|
||||
"commands/gsd-secure-phase.md": "6daf0c899069ab74",
|
||||
"commands/gsd-settings.md": "57edae813aae049c",
|
||||
"commands/gsd-ship.md": "9190bbab8119a365",
|
||||
@@ -110,7 +110,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -184,6 +184,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -342,7 +343,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "d030f80ca0df4fa9",
|
||||
"gsd-core/workflows/remove-workspace.md": "b8817a3a5907f5bc",
|
||||
"gsd-core/workflows/resume-project.md": "b18b51fd15cbce95",
|
||||
"gsd-core/workflows/review.md": "18ab093986814bb3",
|
||||
"gsd-core/workflows/review.md": "fc23a807c6c3d6d9",
|
||||
"gsd-core/workflows/scan.md": "54ff1ff60041d065",
|
||||
"gsd-core/workflows/secure-phase.md": "1d1c66ad9ea01bd2",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -450,7 +451,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "b895cb9835f3fc0e",
|
||||
"skills/gsd-resume-work/SKILL.md": "f8bc8b92dffd07b7",
|
||||
"skills/gsd-review-backlog/SKILL.md": "c0c743e1376c2eff",
|
||||
"skills/gsd-review/SKILL.md": "7f8c2adde7ec94ba",
|
||||
"skills/gsd-review/SKILL.md": "e47c285346aaba06",
|
||||
"skills/gsd-secure-phase/SKILL.md": "bd36a3157c28df37",
|
||||
"skills/gsd-settings/SKILL.md": "fbe736decd04a85d",
|
||||
"skills/gsd-ship/SKILL.md": "8d40fc9f9436d2c3",
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -150,6 +150,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "a602b41bfa081fdf",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "335a974ea98d5d83",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -308,7 +309,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "75c8ca0dbd1ce404",
|
||||
"gsd-core/workflows/remove-workspace.md": "a9d1dcda7755b6c9",
|
||||
"gsd-core/workflows/resume-project.md": "94ac2cac4c562557",
|
||||
"gsd-core/workflows/review.md": "8c7288479748235f",
|
||||
"gsd-core/workflows/review.md": "014d2f4d7c892e58",
|
||||
"gsd-core/workflows/scan.md": "29f3b65f5d9de885",
|
||||
"gsd-core/workflows/secure-phase.md": "858b5e1152b569ed",
|
||||
"gsd-core/workflows/session-report.md": "dd8fa011c9394075",
|
||||
@@ -396,7 +397,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "b4f4e711ba664aa0",
|
||||
"skills/gsd-resume-work/SKILL.md": "04f6c2e5b579e8c4",
|
||||
"skills/gsd-review-backlog/SKILL.md": "469696b944c4e7b5",
|
||||
"skills/gsd-review/SKILL.md": "06044af5335989dc",
|
||||
"skills/gsd-review/SKILL.md": "a45ab2fdca06793b",
|
||||
"skills/gsd-secure-phase/SKILL.md": "e64ad269c1e6e319",
|
||||
"skills/gsd-settings/SKILL.md": "fcdda8dd545622ae",
|
||||
"skills/gsd-ship/SKILL.md": "9615cc4c8f6de060",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "46deb2174be356dd",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
|
||||
@@ -116,6 +116,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "a602b41bfa081fdf",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -274,7 +275,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "e94ddfe4eabc0e08",
|
||||
"gsd-core/workflows/remove-workspace.md": "f28be7f32249f0d4",
|
||||
"gsd-core/workflows/resume-project.md": "4ebcb4acd3c29302",
|
||||
"gsd-core/workflows/review.md": "58557fe552b6ff89",
|
||||
"gsd-core/workflows/review.md": "7c04ed635c7e1c56",
|
||||
"gsd-core/workflows/scan.md": "28a2847b8d04156e",
|
||||
"gsd-core/workflows/secure-phase.md": "bae509fa254fe435",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -361,7 +362,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "d66072399c0aa371",
|
||||
"skills/gsd-resume-work/SKILL.md": "e3ab06060bdd9df0",
|
||||
"skills/gsd-review-backlog/SKILL.md": "2af20161e555fb42",
|
||||
"skills/gsd-review/SKILL.md": "980080d635e66afe",
|
||||
"skills/gsd-review/SKILL.md": "948c44f78429cb97",
|
||||
"skills/gsd-secure-phase/SKILL.md": "8666d8933e02c74f",
|
||||
"skills/gsd-settings/SKILL.md": "4ef66b6dc3a2b8ff",
|
||||
"skills/gsd-ship/SKILL.md": "ea26e3a839afb372",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"commands/gsd-quick.md": "0061e478a896265d",
|
||||
"commands/gsd-resume-work.md": "a98b447fffe07e1e",
|
||||
"commands/gsd-review-backlog.md": "347abbe2fdad79c9",
|
||||
"commands/gsd-review.md": "473b5a21ef15d510",
|
||||
"commands/gsd-review.md": "14369ea01aff093f",
|
||||
"commands/gsd-secure-phase.md": "1631b1ecedbee373",
|
||||
"commands/gsd-settings.md": "8e4a2673e7c96cad",
|
||||
"commands/gsd-ship.md": "de9d81fe35184ae2",
|
||||
@@ -110,7 +110,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "46deb2174be356dd",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -184,6 +184,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -342,7 +343,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "f76e1c2a4dd31a09",
|
||||
"gsd-core/workflows/remove-workspace.md": "dadbb14d9033ea3f",
|
||||
"gsd-core/workflows/resume-project.md": "3dcaa7abe1800d35",
|
||||
"gsd-core/workflows/review.md": "c12c3029d8640d31",
|
||||
"gsd-core/workflows/review.md": "28d673b6267b12c2",
|
||||
"gsd-core/workflows/scan.md": "8e1bbf2eed1752ca",
|
||||
"gsd-core/workflows/secure-phase.md": "7bd4c335484fd121",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -430,7 +431,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "0fdc1838759e2d25",
|
||||
"skills/gsd-resume-work/SKILL.md": "492e402cb66b1fcf",
|
||||
"skills/gsd-review-backlog/SKILL.md": "cf7ff999c96d2f76",
|
||||
"skills/gsd-review/SKILL.md": "8f6496868bb60e10",
|
||||
"skills/gsd-review/SKILL.md": "f5ebef9d2d12b2ad",
|
||||
"skills/gsd-secure-phase/SKILL.md": "970f53251c7adff7",
|
||||
"skills/gsd-settings/SKILL.md": "c06a8c93de64314e",
|
||||
"skills/gsd-ship/SKILL.md": "c7eeb21a15c66189",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"commands/gsd/quick.toml": "b1f6d47488560def",
|
||||
"commands/gsd/resume-work.toml": "5d1e36d643573d4c",
|
||||
"commands/gsd/review-backlog.toml": "19461ca74224422c",
|
||||
"commands/gsd/review.toml": "b8097bb984942e8e",
|
||||
"commands/gsd/review.toml": "8ce97adf16aec351",
|
||||
"commands/gsd/secure-phase.toml": "bbf0dc4d648f11fa",
|
||||
"commands/gsd/settings.toml": "5369a6fbdce65ea9",
|
||||
"commands/gsd/ship.toml": "fa360f1f63c8adec",
|
||||
@@ -110,7 +110,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -184,6 +184,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -342,7 +343,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "d030f80ca0df4fa9",
|
||||
"gsd-core/workflows/remove-workspace.md": "9f5589b4b3a0639a",
|
||||
"gsd-core/workflows/resume-project.md": "b18b51fd15cbce95",
|
||||
"gsd-core/workflows/review.md": "18ab093986814bb3",
|
||||
"gsd-core/workflows/review.md": "fc23a807c6c3d6d9",
|
||||
"gsd-core/workflows/scan.md": "54ff1ff60041d065",
|
||||
"gsd-core/workflows/secure-phase.md": "a2f6a03034444f14",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -115,6 +115,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -273,7 +274,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "f27cecd8c78008ae",
|
||||
"gsd-core/workflows/remove-workspace.md": "977155e18b9df623",
|
||||
"gsd-core/workflows/resume-project.md": "849f3d49d366ff13",
|
||||
"gsd-core/workflows/review.md": "7dd0a479d2595ff0",
|
||||
"gsd-core/workflows/review.md": "d6c1c729a2627b2b",
|
||||
"gsd-core/workflows/scan.md": "cbde2b8b2b5fa5dd",
|
||||
"gsd-core/workflows/secure-phase.md": "5fc8fbd5e217e48d",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -381,7 +382,7 @@
|
||||
"skills/gsd/gsd-ns-review/skills/debug/SKILL.md": "014bf3421ade2813",
|
||||
"skills/gsd/gsd-ns-review/skills/eval-review/SKILL.md": "609ee9ace8cd424c",
|
||||
"skills/gsd/gsd-ns-review/skills/forensics/SKILL.md": "d31dc39e02abc929",
|
||||
"skills/gsd/gsd-ns-review/skills/review/SKILL.md": "0629aeccf352b059",
|
||||
"skills/gsd/gsd-ns-review/skills/review/SKILL.md": "ace83e30ef7f9060",
|
||||
"skills/gsd/gsd-ns-review/skills/secure-phase/SKILL.md": "f615390f01694378",
|
||||
"skills/gsd/gsd-ns-review/skills/ui-phase/SKILL.md": "aac4a77d61281591",
|
||||
"skills/gsd/gsd-ns-review/skills/ui-review/SKILL.md": "ebe37a6d521dba62",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"command/gsd-quick.md": "704662b2172df14d",
|
||||
"command/gsd-resume-work.md": "e4bb44fb2e8076c0",
|
||||
"command/gsd-review-backlog.md": "1e99d6a7a3806fa5",
|
||||
"command/gsd-review.md": "5f6efc83706fec9b",
|
||||
"command/gsd-review.md": "1e14af38d6d421b3",
|
||||
"command/gsd-secure-phase.md": "65eb1cd3fa6430a5",
|
||||
"command/gsd-settings.md": "9bf5d13f13f7d49c",
|
||||
"command/gsd-ship.md": "c0ae46a2cbc7a2b4",
|
||||
@@ -110,7 +110,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -184,6 +184,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "a602b41bfa081fdf",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -342,7 +343,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "f76e1c2a4dd31a09",
|
||||
"gsd-core/workflows/remove-workspace.md": "7bd5d1b63ef091a8",
|
||||
"gsd-core/workflows/resume-project.md": "3dcaa7abe1800d35",
|
||||
"gsd-core/workflows/review.md": "268745662d5f5df7",
|
||||
"gsd-core/workflows/review.md": "50e945dade0a5a67",
|
||||
"gsd-core/workflows/scan.md": "8e1bbf2eed1752ca",
|
||||
"gsd-core/workflows/secure-phase.md": "87fdcb37a8610e58",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -451,7 +452,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "605e596c680cbb1c",
|
||||
"skills/gsd-resume-work/SKILL.md": "0c92e4a51d1b6d94",
|
||||
"skills/gsd-review-backlog/SKILL.md": "27a9dbfe92e5d04c",
|
||||
"skills/gsd-review/SKILL.md": "7309817dc0d54cef",
|
||||
"skills/gsd-review/SKILL.md": "a967b30f10269e15",
|
||||
"skills/gsd-secure-phase/SKILL.md": "ceded474164b4e2d",
|
||||
"skills/gsd-settings/SKILL.md": "4587e4bef7b8942c",
|
||||
"skills/gsd-ship/SKILL.md": "4ad9695934e069ee",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -151,6 +151,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -309,7 +310,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "d030f80ca0df4fa9",
|
||||
"gsd-core/workflows/remove-workspace.md": "b8817a3a5907f5bc",
|
||||
"gsd-core/workflows/resume-project.md": "b18b51fd15cbce95",
|
||||
"gsd-core/workflows/review.md": "18ab093986814bb3",
|
||||
"gsd-core/workflows/review.md": "fc23a807c6c3d6d9",
|
||||
"gsd-core/workflows/scan.md": "54ff1ff60041d065",
|
||||
"gsd-core/workflows/secure-phase.md": "1d1c66ad9ea01bd2",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -395,7 +396,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "e6aa7d96326fc874",
|
||||
"skills/gsd-resume-work/SKILL.md": "6e83a416db8253e1",
|
||||
"skills/gsd-review-backlog/SKILL.md": "ffc6216cfb6c3002",
|
||||
"skills/gsd-review/SKILL.md": "c248b415238b49ee",
|
||||
"skills/gsd-review/SKILL.md": "9cb8c2bc78c9bc51",
|
||||
"skills/gsd-secure-phase/SKILL.md": "9aa4378c6371e5e4",
|
||||
"skills/gsd-settings/SKILL.md": "dee7f5011c9a46f4",
|
||||
"skills/gsd-ship/SKILL.md": "b2e10966b6781b95",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"command/gsd-quick.md": "07c02ab7547aec8e",
|
||||
"command/gsd-resume-work.md": "3ac18b2a8cc41066",
|
||||
"command/gsd-review-backlog.md": "1e99d6a7a3806fa5",
|
||||
"command/gsd-review.md": "e1aee41cc2d736b5",
|
||||
"command/gsd-review.md": "97bf8bc6ffff9475",
|
||||
"command/gsd-secure-phase.md": "b536ad6e68af3a26",
|
||||
"command/gsd-settings.md": "a3d2cb48b06c9b90",
|
||||
"command/gsd-ship.md": "062d0bb5656fa31b",
|
||||
@@ -110,7 +110,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -184,6 +184,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "a602b41bfa081fdf",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -342,7 +343,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "863c58e99e9d630d",
|
||||
"gsd-core/workflows/remove-workspace.md": "4ed03c52e5c7bd4d",
|
||||
"gsd-core/workflows/resume-project.md": "15153ce5a4c38674",
|
||||
"gsd-core/workflows/review.md": "dd336f71a0b8f228",
|
||||
"gsd-core/workflows/review.md": "544103be3247bbd1",
|
||||
"gsd-core/workflows/scan.md": "4634caefa32a7307",
|
||||
"gsd-core/workflows/secure-phase.md": "9616682fe23cf042",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -451,7 +452,7 @@
|
||||
"skills/gsd-quick/SKILL.md": "a9a971a0d471b64f",
|
||||
"skills/gsd-resume-work/SKILL.md": "aa6a7d9e88fe26b6",
|
||||
"skills/gsd-review-backlog/SKILL.md": "27a9dbfe92e5d04c",
|
||||
"skills/gsd-review/SKILL.md": "9fe549f07cf4da07",
|
||||
"skills/gsd-review/SKILL.md": "4d1897f8d2c65bdf",
|
||||
"skills/gsd-secure-phase/SKILL.md": "b37a0dbae84dd74a",
|
||||
"skills/gsd-settings/SKILL.md": "d72a537765bf690a",
|
||||
"skills/gsd-ship/SKILL.md": "fc88192bae429756",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b7968e3e3af00249",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -115,6 +115,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -273,7 +274,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "17c46fdcef27df2d",
|
||||
"gsd-core/workflows/remove-workspace.md": "1d34788a9b2272d2",
|
||||
"gsd-core/workflows/resume-project.md": "bba3250afbea8f09",
|
||||
"gsd-core/workflows/review.md": "3d7c8df929053b79",
|
||||
"gsd-core/workflows/review.md": "926f2f15676a21cc",
|
||||
"gsd-core/workflows/scan.md": "514d3eba81565193",
|
||||
"gsd-core/workflows/secure-phase.md": "a7e8edb0e5f48256",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -380,7 +381,7 @@
|
||||
"skills/gsd-ns-review/skills/debug/SKILL.md": "42de44884d97d86d",
|
||||
"skills/gsd-ns-review/skills/eval-review/SKILL.md": "b6b6059061388363",
|
||||
"skills/gsd-ns-review/skills/forensics/SKILL.md": "4ef11f4cf902186f",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "a17c6ffbcde6071a",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "3202c2f3f3ba6759",
|
||||
"skills/gsd-ns-review/skills/secure-phase/SKILL.md": "a6adf4729b606d5a",
|
||||
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "7c9404102a9b9d74",
|
||||
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "ef8f643abff1486b",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "46deb2174be356dd",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -115,6 +115,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -273,7 +274,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "5a2521695edd486b",
|
||||
"gsd-core/workflows/remove-workspace.md": "ed8e5e30c33f1bfd",
|
||||
"gsd-core/workflows/resume-project.md": "ee9dcc4e3dd3e32c",
|
||||
"gsd-core/workflows/review.md": "402fcc0b4b6a2794",
|
||||
"gsd-core/workflows/review.md": "6529c2ee9b33f1fc",
|
||||
"gsd-core/workflows/scan.md": "afc48f3339293b30",
|
||||
"gsd-core/workflows/secure-phase.md": "882886f04d3b7e82",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -358,7 +359,7 @@
|
||||
"skills/gsd-ns-review/skills/debug/SKILL.md": "9d814fba870c538e",
|
||||
"skills/gsd-ns-review/skills/eval-review/SKILL.md": "d42b504c0ca2dc69",
|
||||
"skills/gsd-ns-review/skills/forensics/SKILL.md": "250f476c4c547a0f",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "ace2ad1db5c625c6",
|
||||
"skills/gsd-ns-review/skills/review/SKILL.md": "c89722cb096066d4",
|
||||
"skills/gsd-ns-review/skills/secure-phase/SKILL.md": "b35ac9da51635368",
|
||||
"skills/gsd-ns-review/skills/ui-phase/SKILL.md": "89a29a2fa62b177e",
|
||||
"skills/gsd-ns-review/skills/ui-review/SKILL.md": "3e23efe03dafd691",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "46deb2174be356dd",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "67e4addbfd248a7c",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "65dea848d50969a2",
|
||||
"gsd-core/bin/shared/model-catalog.json": "dbe26e683236d8c6",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "f6c8b3af10dcfdc4",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -115,6 +115,7 @@
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
@@ -273,7 +274,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "24559d23e008c9c3",
|
||||
"gsd-core/workflows/remove-workspace.md": "e958c1e932aef492",
|
||||
"gsd-core/workflows/resume-project.md": "c5731b8aabed70f9",
|
||||
"gsd-core/workflows/review.md": "77124fdbb3e3bc7e",
|
||||
"gsd-core/workflows/review.md": "21152d6ce0c4efa8",
|
||||
"gsd-core/workflows/scan.md": "8aa95448b1ba4a4a",
|
||||
"gsd-core/workflows/secure-phase.md": "550152cd82c25c00",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
48
tests/review-reviewer-instances-config.test.cjs
Normal file
48
tests/review-reviewer-instances-config.test.cjs
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Config whitelist gate for review.reviewer_instances (#1517).
|
||||
*
|
||||
* Behavioral test (not source-grep): proves `config-set` accepts the
|
||||
* review.reviewer_instances.<name>.{cli,model,agent} paths. Without the
|
||||
* dynamicKeyPatterns entry in config-schema.manifest.json, config-set rejects
|
||||
* the key and this test fails — that is the regression-must-fail-first signal.
|
||||
*/
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const { createTempProject, cleanup, runGsdTools } = require('./helpers.cjs');
|
||||
|
||||
describe('config-set review.reviewer_instances (#1517)', () => {
|
||||
test('accepts a known-adapter cli for an instance', (t) => {
|
||||
const tmpDir = createTempProject('reviewer-instances-cli');
|
||||
t.after(() => cleanup(tmpDir));
|
||||
|
||||
const result = runGsdTools('config-set review.reviewer_instances.opencode-deepseek.cli opencode', tmpDir);
|
||||
assert.ok(result.success, `config-set should accept the instance cli path: ${result.error || JSON.stringify(result)}`);
|
||||
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
assert.strictEqual(
|
||||
config.review?.reviewer_instances?.['opencode-deepseek']?.cli,
|
||||
'opencode',
|
||||
'instance cli must persist',
|
||||
);
|
||||
});
|
||||
|
||||
test('accepts model and agent fields for an instance', (t) => {
|
||||
const tmpDir = createTempProject('reviewer-instances-model');
|
||||
t.after(() => cleanup(tmpDir));
|
||||
|
||||
const modelRes = runGsdTools('config-set review.reviewer_instances.opencode-deepseek.model deepseek/deepseek-v4-pro', tmpDir);
|
||||
assert.ok(modelRes.success, `model set failed: ${modelRes.error}`);
|
||||
const agentRes = runGsdTools('config-set review.reviewer_instances.opencode-deepseek.agent review', tmpDir);
|
||||
assert.ok(agentRes.success, `agent set failed: ${agentRes.error}`);
|
||||
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
assert.strictEqual(config.review.reviewer_instances['opencode-deepseek'].model, 'deepseek/deepseek-v4-pro');
|
||||
assert.strictEqual(config.review.reviewer_instances['opencode-deepseek'].agent, 'review');
|
||||
});
|
||||
});
|
||||
234
tests/review-reviewer-instances.test.cjs
Normal file
234
tests/review-reviewer-instances.test.cjs
Normal file
@@ -0,0 +1,234 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Reviewer Instances feature (#1517) — custom reviewer instances for /gsd:review.
|
||||
*
|
||||
* Locks the instance-resolution contract:
|
||||
* - normalizeReviewerInstances (config-set validation surface)
|
||||
* - resolveReviewerSelection instance expansion (config_default branch only)
|
||||
* - single-source instance→cli resolution parity (DEFECT.GENERATIVE-FIX)
|
||||
*
|
||||
* Regression-must-fail-first: these tests are written before the implementation
|
||||
* and must demonstrate the failure (missing export / missing behaviour) before
|
||||
* the resolver is extended.
|
||||
*/
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
KNOWN_REVIEWER_SLUGS,
|
||||
normalizeReviewerInstances,
|
||||
resolveReviewerSelection,
|
||||
} = require('../gsd-core/bin/lib/review-reviewer-selection.cjs');
|
||||
|
||||
describe('normalizeReviewerInstances (#1517)', () => {
|
||||
test('accepts a valid instance map and normalizes fields', () => {
|
||||
const r = normalizeReviewerInstances({
|
||||
'opencode-deepseek': { cli: 'opencode', model: 'deepseek/deepseek-v4-pro', agent: 'review' },
|
||||
'codex-fast': { cli: 'codex', model: 'gpt-5-mini' },
|
||||
});
|
||||
assert.deepStrictEqual(r.errors, []);
|
||||
assert.ok(r.instances['opencode-deepseek']);
|
||||
assert.strictEqual(r.instances['opencode-deepseek'].cli, 'opencode');
|
||||
assert.strictEqual(r.instances['opencode-deepseek'].model, 'deepseek/deepseek-v4-pro');
|
||||
assert.strictEqual(r.instances['opencode-deepseek'].agent, 'review');
|
||||
assert.strictEqual(r.instances['codex-fast'].agent, undefined);
|
||||
});
|
||||
|
||||
test('absent (undefined/null) yields an empty instance set with no errors', () => {
|
||||
assert.deepStrictEqual(normalizeReviewerInstances(undefined), { instances: {}, errors: [] });
|
||||
assert.deepStrictEqual(normalizeReviewerInstances(null), { instances: {}, errors: [] });
|
||||
});
|
||||
|
||||
test('rejects a non-object value', () => {
|
||||
const r = normalizeReviewerInstances('nope');
|
||||
assert.ok(r.errors.length > 0);
|
||||
assert.deepStrictEqual(r.instances, {});
|
||||
});
|
||||
|
||||
test('rejects an instance name that does not match the slug pattern', () => {
|
||||
const r = normalizeReviewerInstances({
|
||||
'Opencode_Bad': { cli: 'opencode' }, // uppercase + underscore
|
||||
'opencode deepseek': { cli: 'opencode' }, // space
|
||||
'9lead': { cli: 'opencode' }, // leading digit is allowed; sanity below
|
||||
});
|
||||
// ^[a-z0-9][a-z0-9-]*$ → '9lead' is valid; the other two are not.
|
||||
assert.ok(r.errors.some((e) => e.includes('Opencode_Bad')), `got: ${JSON.stringify(r.errors)}`);
|
||||
assert.ok(r.errors.some((e) => e.includes('opencode deepseek')), `got: ${JSON.stringify(r.errors)}`);
|
||||
assert.ok(r.instances['9lead'], 'leading-digit names are permitted by the pattern');
|
||||
});
|
||||
|
||||
test('rejects an instance name that collides with a built-in reviewer slug', () => {
|
||||
const r = normalizeReviewerInstances({
|
||||
opencode: { cli: 'opencode' }, // shadows a built-in slug
|
||||
});
|
||||
assert.ok(
|
||||
r.errors.some((e) => /must not equal a built-in reviewer slug/i.test(e) && e.includes('opencode')),
|
||||
`got: ${JSON.stringify(r.errors)}`,
|
||||
);
|
||||
assert.ok(!r.instances['opencode']);
|
||||
});
|
||||
|
||||
test('rejects an instance whose cli is not a known adapter', () => {
|
||||
const r = normalizeReviewerInstances({
|
||||
'evil-instance': { cli: 'rm -rf /' }, // not a known slug; must not become a shell command
|
||||
'weird-instance': { cli: 'totally-made-up' },
|
||||
});
|
||||
assert.ok(r.errors.some((e) => e.includes('evil-instance')), `got: ${JSON.stringify(r.errors)}`);
|
||||
assert.ok(r.errors.some((e) => e.includes('weird-instance')), `got: ${JSON.stringify(r.errors)}`);
|
||||
});
|
||||
|
||||
test('rejects an instance with a missing cli', () => {
|
||||
const r = normalizeReviewerInstances({
|
||||
'no-cli': { model: 'some/model' },
|
||||
});
|
||||
assert.ok(r.errors.some((e) => e.includes('no-cli') && /cli/i.test(e)), `got: ${JSON.stringify(r.errors)}`);
|
||||
});
|
||||
|
||||
test('rejects non-string model / agent values', () => {
|
||||
const r = normalizeReviewerInstances({
|
||||
'bad-model': { cli: 'opencode', model: 42 },
|
||||
'bad-agent': { cli: 'opencode', agent: { x: 1 } },
|
||||
});
|
||||
assert.ok(r.errors.some((e) => e.includes('bad-model')), `got: ${JSON.stringify(r.errors)}`);
|
||||
assert.ok(r.errors.some((e) => e.includes('bad-agent')), `got: ${JSON.stringify(r.errors)}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveReviewerSelection — instance expansion (#1517)', () => {
|
||||
const INSTANCES = {
|
||||
'opencode-deepseek': { cli: 'opencode', model: 'deepseek/deepseek-v4-pro', agent: 'review' },
|
||||
'opencode-mimo': { cli: 'opencode', model: 'xiaomi/mimo-v2.5-pro' },
|
||||
};
|
||||
|
||||
test('two same-cli instances in default_reviewers resolve as independent identities and flag the shared adapter', () => {
|
||||
const r = resolveReviewerSelection({
|
||||
detected: ['opencode', 'codex'],
|
||||
explicitFlags: [],
|
||||
allFlag: false,
|
||||
configuredDefaultReviewers: ['opencode-deepseek', 'opencode-mimo', 'codex'],
|
||||
reviewerInstances: INSTANCES,
|
||||
});
|
||||
|
||||
assert.strictEqual(r.source, 'config_default');
|
||||
// selected carries all three identities (codex is a builtin slug)
|
||||
assert.ok(r.selected.includes('opencode-deepseek'));
|
||||
assert.ok(r.selected.includes('opencode-mimo'));
|
||||
assert.ok(r.selected.includes('codex'));
|
||||
// resolvedInstances carries the instance→cli mapping (single-source parity anchor)
|
||||
const ds = r.resolvedInstances.find((x) => x.identity === 'opencode-deepseek');
|
||||
const mimo = r.resolvedInstances.find((x) => x.identity === 'opencode-mimo');
|
||||
assert.ok(ds, 'opencode-deepseek must be resolved');
|
||||
assert.ok(mimo, 'opencode-mimo must be resolved');
|
||||
assert.strictEqual(ds.kind, 'instance');
|
||||
assert.strictEqual(ds.cli, 'opencode');
|
||||
assert.strictEqual(ds.model, 'deepseek/deepseek-v4-pro');
|
||||
assert.strictEqual(ds.agent, 'review');
|
||||
assert.strictEqual(mimo.cli, 'opencode');
|
||||
// ≥2 selected instances share a cli → caveat flag set
|
||||
assert.ok(r.sharedAdapterCaveat, 'two opencode instances must set sharedAdapterCaveat');
|
||||
assert.deepStrictEqual(r.errors, []);
|
||||
});
|
||||
|
||||
test('a single same-cli instance does not trip the shared-adapter caveat', () => {
|
||||
const r = resolveReviewerSelection({
|
||||
detected: ['opencode', 'codex'],
|
||||
explicitFlags: [],
|
||||
allFlag: false,
|
||||
configuredDefaultReviewers: ['opencode-deepseek', 'codex'],
|
||||
reviewerInstances: INSTANCES,
|
||||
});
|
||||
assert.ok(!r.sharedAdapterCaveat);
|
||||
});
|
||||
|
||||
test('an instance referenced in default_reviewers with no definition is a HARD error when instances are configured', () => {
|
||||
const r = resolveReviewerSelection({
|
||||
detected: ['opencode'],
|
||||
explicitFlags: [],
|
||||
allFlag: false,
|
||||
configuredDefaultReviewers: ['opencode-deepseek', 'typo-instance'],
|
||||
reviewerInstances: INSTANCES,
|
||||
});
|
||||
assert.ok(
|
||||
r.errors.some((e) => e.includes('typo-instance')),
|
||||
`expected a hard error naming the undefined instance, got: ${JSON.stringify(r.errors)}`,
|
||||
);
|
||||
assert.ok(!r.selected.includes('typo-instance'));
|
||||
});
|
||||
|
||||
test('backward compat: with no instances configured, an unknown slug still warns + drops (not a hard error)', () => {
|
||||
const r = resolveReviewerSelection({
|
||||
detected: ['gemini'],
|
||||
explicitFlags: [],
|
||||
allFlag: false,
|
||||
configuredDefaultReviewers: ['unknown_slug', 'codex'],
|
||||
});
|
||||
assert.ok(
|
||||
r.warnings.some((w) => w.includes('unknown_slug')),
|
||||
`expected warn+drop for unknown slug when no instances configured, got: ${JSON.stringify(r.warnings)}`,
|
||||
);
|
||||
assert.ok(
|
||||
!r.errors.some((e) => e.includes('unknown_slug')),
|
||||
'unknown slug must NOT be a hard error when no instances are configured',
|
||||
);
|
||||
});
|
||||
|
||||
test('an instance whose base cli is not detected is excluded with an info note', () => {
|
||||
const r = resolveReviewerSelection({
|
||||
detected: ['codex'], // opencode not installed
|
||||
explicitFlags: [],
|
||||
allFlag: false,
|
||||
configuredDefaultReviewers: ['opencode-deepseek', 'codex'],
|
||||
reviewerInstances: INSTANCES,
|
||||
});
|
||||
assert.ok(!r.selected.includes('opencode-deepseek'));
|
||||
assert.ok(
|
||||
r.infos.some((i) => i.includes('opencode-deepseek')),
|
||||
`expected an info note for the undetected instance, got: ${JSON.stringify(r.infos)}`,
|
||||
);
|
||||
assert.ok(r.selected.includes('codex'));
|
||||
});
|
||||
|
||||
test('instances do NOT participate in the explicit_flags or all_flag branches', () => {
|
||||
const explicit = resolveReviewerSelection({
|
||||
detected: ['opencode'],
|
||||
explicitFlags: ['opencode'],
|
||||
allFlag: false,
|
||||
configuredDefaultReviewers: ['opencode-deepseek'],
|
||||
reviewerInstances: INSTANCES,
|
||||
});
|
||||
assert.strictEqual(explicit.source, 'explicit_flags');
|
||||
assert.ok(!explicit.selected.includes('opencode-deepseek'), 'instances must not leak into explicit_flags');
|
||||
|
||||
const all = resolveReviewerSelection({
|
||||
detected: ['opencode'],
|
||||
explicitFlags: [],
|
||||
allFlag: true,
|
||||
configuredDefaultReviewers: ['opencode-deepseek'],
|
||||
reviewerInstances: INSTANCES,
|
||||
});
|
||||
assert.strictEqual(all.source, 'all_flag');
|
||||
assert.ok(!all.selected.includes('opencode-deepseek'), 'instances must not leak into all_flag');
|
||||
});
|
||||
|
||||
test('parity (DEFECT.GENERATIVE-FIX): every resolved instance cli equals its configured cli field', () => {
|
||||
const r = resolveReviewerSelection({
|
||||
detected: ['opencode', 'codex'],
|
||||
explicitFlags: [],
|
||||
allFlag: false,
|
||||
configuredDefaultReviewers: ['opencode-deepseek', 'opencode-mimo', 'codex'],
|
||||
reviewerInstances: INSTANCES,
|
||||
});
|
||||
for (const res of r.resolvedInstances) {
|
||||
if (res.kind === 'instance') {
|
||||
const configured = INSTANCES[res.identity];
|
||||
assert.ok(configured, `resolved instance ${res.identity} has no configured definition`);
|
||||
assert.strictEqual(res.cli, configured.cli, `cli mapping diverged for ${res.identity}`);
|
||||
assert.strictEqual(res.model, configured.model, `model diverged for ${res.identity}`);
|
||||
assert.strictEqual(res.agent, configured.agent, `agent diverged for ${res.identity}`);
|
||||
} else {
|
||||
assert.ok(KNOWN_REVIEWER_SLUGS.includes(res.identity), `builtin identity ${res.identity} is not a known slug`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -63,7 +63,7 @@
|
||||
"remove-phase.md": 8469,
|
||||
"remove-workspace.md": 7507,
|
||||
"resume-project.md": 17226,
|
||||
"review.md": 39404,
|
||||
"review.md": 40539,
|
||||
"scan.md": 7688,
|
||||
"secure-phase.md": 13476,
|
||||
"session-report.md": 4044,
|
||||
|
||||
Reference in New Issue
Block a user