feat(scanners): model/effort routing becomes a lever, not a 25th dimension (C4)
New GAP finding CA-GAP-028: authored subagents exist and not one of them names `model:` or `effort:`, so every delegated task runs on the main conversation's model (`model` defaults to `inherit`). Cites BP-MODEL-001/002, landed in C1. `whats-active` and `manifest` now carry `model`/`effort` per agent. Shipped as a conditional LEVER rather than a 25th dimension, and the choice was made by measurement: as a t3 dimension the agent-less marketplace-medium fixture would count it vacuously-present, moving the denominators 41->42 and utilization 44->45 — which flips `segment` "Developing"->"Competent" in the frozen v5.0.0 posture baseline, a field strip-retired-gap.mjs does not mask. A lever never enters those denominators. The general rule is now an invariant in CLAUDE.md. One check across both axes, not one per axis: it fires only when neither is used anywhere, so a deliberate everything-on-one-model policy stays silent. Cost is recall, chosen for precision. Found by dogfooding, fixed red-first: `model: inherit` is the documented default spelled out, so it must not count as routing — otherwise a config opts out of the opportunity without changing anything real. Two pre-existing defects surfaced and closed on the way: - The humanizer guard asserted TRANSLATIONS.GAP.static EQUALS the dimension titles, which forbade humanizing any lever — all three existing levers fell through to the generic "feature opportunity" default, wrong for a budget lever. Guard now requires coverage of every emittable title, seen red against those three before the entries were written. - Two hand-written copies of the lever list (finding-codes guard, humanizer guard) merged into one exported LEVERS registry carrying code AND title. - suppression-validation pinned CA-GAP-028 as an unoccupied number; C4 claimed it. Fixed structurally with a derived first-free id, not by picking a new literal — same class as #60's "bump this again". Suite 1596/0. Frozen v5.0.0 snapshots untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq3nye21RVYk4pZLeT8pGz
This commit is contained in:
parent
e861e63a7b
commit
9ae4be26d2
16 changed files with 512 additions and 31 deletions
|
|
@ -1075,6 +1075,32 @@ describe('enumerateAgents (v5.6)', () => {
|
|||
assert.deepEqual(agents.map(a => a.name).sort(), ['reviewer']);
|
||||
});
|
||||
|
||||
// C4: the two routing axes are part of the inventory, so `whats-active` and
|
||||
// `manifest` can answer "what does each agent actually run on?" without the
|
||||
// reader having to open the files again.
|
||||
it('C4: surfaces model and effort per agent', async () => {
|
||||
const dir = join(root, '.claude', 'agents');
|
||||
await mkdir(dir, { recursive: true });
|
||||
await writeFile(join(dir, 'cheap.md'), '---\nname: cheap\ndescription: mechanical work\nmodel: haiku\neffort: low\n---\nbody\n');
|
||||
const agents = await enumerateAgents(root, []);
|
||||
const a = agents.find(x => x.name === 'cheap');
|
||||
assert.equal(a.model, 'haiku');
|
||||
assert.equal(a.effort, 'low');
|
||||
});
|
||||
|
||||
// Explicit null, not an absent key: `inherit` is the documented default, so a
|
||||
// consumer must be able to tell "not pinned" apart from "field unknown".
|
||||
it('C4: reports null for an agent that pins neither axis', async () => {
|
||||
const dir = join(root, '.claude', 'agents');
|
||||
await mkdir(dir, { recursive: true });
|
||||
await writeFile(join(dir, 'plain.md'), '---\nname: plain\ndescription: no pins\n---\nbody\n');
|
||||
const agents = await enumerateAgents(root, []);
|
||||
const a = agents.find(x => x.name === 'plain');
|
||||
assert.ok('model' in a && 'effort' in a, 'both keys must be present');
|
||||
assert.equal(a.model, null);
|
||||
assert.equal(a.effort, null);
|
||||
});
|
||||
|
||||
// M-BUG-3: CC scans agents dirs recursively, so a valid agent in a subfolder
|
||||
// (e.g. agents/review/security.md) is registered and must be counted.
|
||||
it('M-BUG-3: recurses into agent subdirectories', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue