* test(#2295): failing-first coverage for per-lane resolved-model recording * feat(#2295): record the resolved model per reviewer lane * docs(#2295): document the recorded reviewer model and its provenance * fix(#2295): refuse control characters in a recorded model value * test(#2295): correct watermark assertions for the widened mark shape * fix(#2295): anchor the role-manipulation injection pattern at a word boundary * feat(#2295): record the applied reasoning effort in the model value * chore(#2295): backfill changeset pr number * chore(#2295): restore em-dash in changeset body --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -1307,30 +1307,45 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
||||
return;
|
||||
}
|
||||
|
||||
// Effort argv is resolved per lane by the host's own execution policy, exactly as the legs did
|
||||
// via `resolve-execution … --pick effort_argv_string`. A lane whose slug is not a known host
|
||||
// simply gets none.
|
||||
// Resolved through the SAME `resolve-execution` surface the bash legs used
|
||||
// (`--host <slug> --pick effort_argv_string`), so the host's negotiated effortSurface still
|
||||
// decides whether an argument is emitted and the catalog still owns the syntax (ADR-1239 #2481,
|
||||
// ADR-443's escalation ladder). `cmdResolveExecution` writes to stdout and exits, so it cannot
|
||||
// be called in-process for a value — this spawns the same bounded query the legs did, once per
|
||||
// selected lane. A lane whose slug is not a known host resolves to no effort argument at all.
|
||||
// Effort argv is resolved per lane by the host's own execution policy, through the SAME
|
||||
// `resolve-execution` surface the bash legs used (`--host <slug>`), so the host's negotiated
|
||||
// effortSurface still decides whether an argument is emitted and the catalog still owns the
|
||||
// syntax (ADR-1239 #2481, ADR-443's escalation ladder). `cmdResolveExecution` writes to
|
||||
// stdout and exits, so it cannot be called in-process for a value — this spawns the same
|
||||
// bounded query the legs did, once per selected lane. A lane whose slug is not a known host
|
||||
// resolves to no effort argument at all.
|
||||
//
|
||||
// NOT `--raw` and NOT `--pick` (#2295). `--raw` prints only the resolved EFFORT ('low') with
|
||||
// no host-specific rendering at all. `--pick effort_argv_string` used to be the answer — the
|
||||
// rendered array re-joined into a string ('-c model_reasoning_effort=low') — but the caller
|
||||
// then had to `.split(/\s+/)` that string back apart to get an argv array, and re-splitting a
|
||||
// string the callee just joined is a lossy round trip: any argv element that legitimately
|
||||
// contains a space would come back split into two argv elements, corrupting the very argv it
|
||||
// was rendered to preserve. Reading the UNPICKED object instead gives both `effort_argv` (a
|
||||
// real string array, used verbatim, no re-splitting) and `effort_argv_value` (the bare level,
|
||||
// #2295's `plan.effort`) from the one spawn.
|
||||
const EMPTY_EFFORT = { argv: [], value: null };
|
||||
const effortFor = (slug) => {
|
||||
try {
|
||||
const r = cp.spawnSync(
|
||||
process.execPath,
|
||||
[__filename, 'query', 'resolve-execution', 'gsd-plan-checker',
|
||||
// NOT `--raw`: that prints the resolved EFFORT ('low'), ignoring --pick. The picked
|
||||
// field is what carries the host-specific syntax ('--effort low' for claude,
|
||||
// '-c model_reasoning_effort=low' for codex), which is the whole point of asking.
|
||||
'--host', slug, '--pick', 'effort_argv_string'],
|
||||
[__filename, 'query', 'resolve-execution', 'gsd-plan-checker', '--host', slug],
|
||||
{ cwd, encoding: 'utf8', timeout: 15000, killSignal: 'SIGKILL', maxBuffer: 1024 * 1024 },
|
||||
);
|
||||
if (r.status !== 0) return [];
|
||||
const s = String(r.stdout || '').trim();
|
||||
return s ? s.split(/\s+/).filter(Boolean) : [];
|
||||
} catch { return []; }
|
||||
if (r.status !== 0) return EMPTY_EFFORT;
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(String(r.stdout || ''));
|
||||
} catch { return EMPTY_EFFORT; }
|
||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return EMPTY_EFFORT;
|
||||
const argv = Array.isArray(parsed.effort_argv)
|
||||
? parsed.effort_argv.filter((a) => typeof a === 'string' && a !== '')
|
||||
: [];
|
||||
const value = typeof parsed.effort_argv_value === 'string' && parsed.effort_argv_value
|
||||
? parsed.effort_argv_value
|
||||
: null;
|
||||
return { argv, value };
|
||||
} catch { return EMPTY_EFFORT; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1363,7 +1378,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
||||
// so losing all of them to one bad manifest is strictly worse. Belt and braces on purpose.
|
||||
let r;
|
||||
try {
|
||||
r = resolveLanePlan({ lane, configGet, runDir, repoRoot, effortArgs: effortFor(slug) });
|
||||
const effort = effortFor(slug);
|
||||
r = resolveLanePlan({ lane, configGet, runDir, repoRoot, effortArgs: effort.argv, effortValue: effort.value });
|
||||
} catch (e) {
|
||||
return { slug, ok: false, reason: 'malformed_lane', detail: `resolver threw: ${e && e.message ? e.message : String(e)}` };
|
||||
}
|
||||
@@ -1522,12 +1538,14 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
||||
`model override (it declares no modelConfigKey). The review will use the CLI's own default.\n`,
|
||||
);
|
||||
}
|
||||
const instanceEffort = effortFor(entry.slug);
|
||||
const overridden = resolveLanePlan({
|
||||
lane,
|
||||
configGet: (k) => (key && k === key ? instanceModel : configGet(k)),
|
||||
runDir,
|
||||
repoRoot,
|
||||
effortArgs: effortFor(entry.slug),
|
||||
effortArgs: instanceEffort.argv,
|
||||
effortValue: instanceEffort.value,
|
||||
});
|
||||
if (overridden.ok) {
|
||||
// Preserve any instance retargeting already applied above.
|
||||
|
||||
@@ -423,12 +423,31 @@ names, each gets its own `## <Adapter> Review (<instance>)` section, and ≥2 sa
|
||||
instances print a one-line shared-adapter caveat. Format in
|
||||
`gsd-core/references/reviewer-instances.md`.
|
||||
|
||||
**Resolved model (#2295):** each lane's `review-lane invoke --json` line in
|
||||
`{run_dir}/gsd-review-lane-results.jsonl` carries a `model` object; render `models:` from
|
||||
its `value` and `model_sources:` from its `source`. Both maps carry exactly one entry per
|
||||
reviewer that appears in `reviewers:` — the two key sets always match. Write the literal
|
||||
`unknown` rather than omitting a key: an omitted key is indistinguishable from the feature
|
||||
not having run, and a reader must be able to tell *no model recorded* from *nothing to
|
||||
look at*. Emit every `models:`/`model_sources:` value as a DOUBLE-QUOTED YAML scalar — a
|
||||
legitimate model id can contain `:` (`llama3:70b`, `qwen2.5:7b`), which is unquotable as a
|
||||
bare scalar; a control character is already refused at the recording seam, so quoting is
|
||||
what closes the remaining `:`/`#`/leading-`-` cases. When GSD applied a reasoning effort to
|
||||
a lane, its `value` already carries a `(reasoning=<level>)` suffix (e.g.
|
||||
`gpt-5.6-sol (reasoning=high)`) — render it as-is, without re-deriving or re-formatting it.
|
||||
|
||||
```markdown
|
||||
---
|
||||
phase: {N}
|
||||
reviewers: [gemini, claude, codex, coderabbit, opencode, qwen, cursor, antigravity, ollama, lm_studio, llama_cpp] # populate at runtime with only the reviewers actually invoked
|
||||
reviewed_at: {ISO timestamp}
|
||||
plans_reviewed: [{list of PLAN.md files}]
|
||||
models: # resolved model per reviewer; `unknown` when not recoverable
|
||||
codex: "gpt-5.6-sol (reasoning=low)"
|
||||
antigravity: "unknown"
|
||||
model_sources: # how each value above was determined
|
||||
codex: "banner"
|
||||
antigravity: "unknown"
|
||||
trimmed_reviewers: # only present if at least one reviewer was trimmed
|
||||
ollama:
|
||||
budget: 6000
|
||||
|
||||
Reference in New Issue
Block a user