* 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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user