config-audit/tests/scanners/optimize-lens-cli.test.mjs
Kjell Tore Guttormsen 7df8e0d65b feat(scanners): a redundancy claim that belongs to one model is scoped to it
Anthropic documents that Claude Opus 5 verifies its own work, and that telling
it to double-check or to delegate verification to a subagent causes
over-verification -- token cost with no quality gain. The general subtraction
detector (BP-SUB-001) already surfaces those blocks for every user, with no
model-awareness at all.

`optimize --subtract --for-model <name>` adds the missing half. It ANNOTATES a
subset of the candidates --subtract already produced; it is not a second
detector and can never widen the candidate set. A second SUBTRACT_DETECTORS
entry would have collided with BP-SUB-001 on de-dup, and a prose-only signal in
the agent prompt would have been untestable.

There is no auto-detection, by measurement rather than omission: a CLAUDE.md has
no frontmatter and no resolvable target model, and this operator's own `route`
skill deliberately runs a different model per session -- the same file is read
by whichever model comes next. So the model is named, and the citation is
reported as conditional everywhere a human sees it (agent report copy, and the
Step 7a listing that is the last surface before an approval file).

Precision comes from the TARGET, not the verb list. Measured across the
409-file corpus: 392 BP-SUB-001 candidates, 31 (7.9%) carry a verify verb, and
0 also carry a reflexive or delegated target. Two independent raw-text greps
found 0 as well, so the zero is the corpus rather than an over-narrow regex.
Those 31 verb-only blocks -- "sjekk relevante config-filer", "Type-sjekk:
pyright", "To verify plugin functionality" -- are exactly the false positives a
verb-only version would have produced, which is BP-JUDG-001's 7/7 failure
arriving one lens over. The numbers live in the register entry's note and are
pinned by a test, because a session that cannot see the measurement reads the
zero as a broken detector and loosens it.

`recognized` is reported separately from `matchedCount`: a typo'd model name and
a genuinely clean config both yield zero, and without the distinction the CLI
would report a silent no-op as good news. Dogfooded on the real machine --
`opus-5` gives recognized:true/matchedCount:0, `oppus5` gives recognized:false.

source.published is absent because the guide carries no visible publish date;
its absence is asserted so a later session does not invent one to match the
other entries' shape. Both quoted sentences were verified verbatim 2026-08-12.

The payload stays additive -- forModel and per-candidate modelScope appear only
under the flag, so a plain --subtract run is byte-identical to before (asserted
on the serialized bytes, since a key set to undefined passes a shallow check).

Suite 1724 -> 1752 (+28). The one remaining failure is the pre-existing
drift-cli --output-file crash, untouched by this work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRuXt6tZyowi8QYNKLSHQm
2026-08-12 23:16:23 +02:00

256 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* optimize-lens-cli tests — payload scoping + candidate identity (M-BUG-11).
*
* The lens CLI feeds the precision-gate agent. Two correctness invariants it must
* hold, both established elsewhere in the codebase but originally missing here:
*
* 1. SCOPING — plugin-bundled CLAUDE.md (anything under `.claude/plugins/`:
* vendored plugin config + its bundled tests/fixtures + examples, active or
* stale) is NOT the user's authored config. The user can't act on a
* mechanism-fit suggestion against a file a plugin ships. So the lens must
* drop them — the M-BUG-2 `isPluginBundled` rule, applied to the lens.
*
* 2. IDENTITY — a candidate's `file` must uniquely name a readable file. The
* user-global `~/.claude/CLAUDE.md` and a repo-root `CLAUDE.md` both have
* relPath `CLAUDE.md`; labelling candidates by relPath collides them, and
* the agent's `Read(file)` then resolves the wrong one. Candidates carry an
* absolute path.
*
* Hermetic: a temp target with a real CLAUDE.md and a nested plugin-bundled one.
* `.claude/plugins/` is not in SKIP_DIRS, so the normal walk discovers it —
* no ~/.claude / --global needed.
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { join, isAbsolute } from 'node:path';
import { mkdtemp, mkdir, writeFile, rm, readFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
const execFileP = promisify(execFile);
const CLI = fileURLToPath(new URL('../../scanners/optimize-lens-cli.mjs', import.meta.url));
/** Build a temp target, run the lens CLI on it, return the parsed payload. */
async function runLens(files) {
const root = await mkdtemp(join(tmpdir(), 'ca-lens-cli-'));
try {
for (const [rel, content] of Object.entries(files)) {
const abs = join(root, rel);
await mkdir(join(abs, '..'), { recursive: true });
await writeFile(abs, content, 'utf-8');
}
const out = join(root, 'payload.json');
await execFileP('node', [CLI, root, '--output-file', out]);
return JSON.parse(await readFile(out, 'utf-8'));
} finally {
await rm(root, { recursive: true, force: true });
}
}
const REAL = '# Project\n\nNever commit secrets to the repo.\n';
const PLUGIN = '# Vendored plugin\n\nNever delete the plugin cache directory.\n';
describe('optimize-lens-cli — scoping (M-BUG-11)', () => {
it('excludes plugin-bundled CLAUDE.md from candidates', async () => {
const payload = await runLens({
'CLAUDE.md': REAL,
'.claude/plugins/cache/mkt/plug/1.0.0/CLAUDE.md': PLUGIN,
});
const bundled = payload.candidates.filter(
(c) => c.file.includes(`.claude/plugins/`) || c.file.includes(`/plugins/`),
);
assert.deepEqual(
bundled.map((c) => c.file),
[],
'no candidate should come from a file under .claude/plugins/',
);
});
it('still surfaces the users real CLAUDE.md', async () => {
const payload = await runLens({
'CLAUDE.md': REAL,
'.claude/plugins/cache/mkt/plug/1.0.0/CLAUDE.md': PLUGIN,
});
const real = payload.candidates.filter((c) => c.file.endsWith(`${join('', 'CLAUDE.md')}`));
assert.ok(real.length >= 1, 'the real CLAUDE.md never-instruction should survive scoping');
});
it('excludes plugin-bundled CLAUDE.md from deterministic findings too', async () => {
const procedure =
'# Plugin release\n\n' +
Array.from({ length: 7 }, (_, i) => `${i + 1}. Do release step ${i + 1} and verify.`).join('\n') +
'\n';
const payload = await runLens({
'CLAUDE.md': REAL,
'.claude/plugins/cache/mkt/plug/1.0.0/CLAUDE.md': procedure,
});
const bundled = (payload.deterministic || []).filter((f) => String(f.file).includes('plugins/'));
assert.deepEqual(bundled, [], 'deterministic CA-OPT-001 must not fire on vendored plugin CLAUDE.md');
});
});
describe('optimize-lens-cli — candidate identity (M-BUG-11)', () => {
it('labels every candidate with an absolute, unique path', async () => {
const payload = await runLens({ 'CLAUDE.md': REAL });
assert.ok(payload.candidates.length >= 1, 'expected at least one candidate');
for (const c of payload.candidates) {
assert.ok(isAbsolute(c.file), `candidate file must be absolute, got: ${c.file}`);
}
});
});
/**
* `--for-model` — the model-scoped ANNOTATION layer (BP-PROMPT-001).
*
* The flag never widens the candidate set: it tags a SUBSET of the candidates
* the general `compensatory-instruction` detector (BP-SUB-001) already produced.
* The fixture below is chosen to prove exactly that — all three lines are
* BP-SUB-001 candidates, but only two carry a reflexive/delegate verification
* target. The third ("check the CI status") is EXTERNAL verification: still a
* subtraction candidate, never a model-scoped one. A blanket tagger would pass
* a fixture where every candidate qualifies; this one it cannot pass.
*
* `recognized` exists because a typo'd model name would otherwise be a silent
* no-op indistinguishable from "your config is already clean" — the CLI has to
* report whether it knew the name, not just a bare zero.
*/
const VERIFY_MD = [
'# Project',
'',
'## Workflow',
'',
'Always double-check your own work before responding to the user.',
'',
'Verify complex changes with a subagent before you finish.',
'',
'Check the CI status before merging any pull request.',
'',
].join('\n');
/** Run the lens CLI with arbitrary extra argv; never throws on a non-zero exit. */
async function runLensArgs(files, extraArgs) {
const root = await mkdtemp(join(tmpdir(), 'ca-lens-model-'));
try {
for (const [rel, content] of Object.entries(files)) {
const abs = join(root, rel);
await mkdir(join(abs, '..'), { recursive: true });
await writeFile(abs, content, 'utf-8');
}
const out = join(root, 'payload.json');
let code = 0;
let stderr = '';
try {
const r = await execFileP('node', [CLI, root, '--output-file', out, ...extraArgs]);
stderr = r.stderr || '';
} catch (err) {
code = typeof err.code === 'number' ? err.code : 1;
stderr = err.stderr || '';
}
let payload = null;
let raw = null;
try {
raw = await readFile(out, 'utf-8');
payload = JSON.parse(raw);
} catch {
payload = null;
}
// Every run gets its own temp root, so absolute paths differ between two
// otherwise-identical runs. Normalizing the root is what makes a byte
// comparison across runs mean "same payload" rather than "same directory".
const normalized = raw === null ? null : raw.split(root).join('<ROOT>');
return { code, stderr, payload, normalized };
} finally {
await rm(root, { recursive: true, force: true });
}
}
describe('optimize-lens-cli — --for-model argument surface', () => {
it('rejects a bare --for-model as "needs a value", not "unknown flag"', async () => {
const { code, stderr } = await runLensArgs({ 'CLAUDE.md': VERIFY_MD }, ['--subtract', '--for-model']);
assert.equal(code, 3, 'a malformed flag is exit 3 (it did not do the job)');
assert.match(stderr, /value/i, `expected a "needs a value" message, got: ${stderr}`);
assert.doesNotMatch(
stderr,
/unknown/i,
'--for-model must be a KNOWN value flag; reporting it as unknown hides the real error'
);
});
});
describe('optimize-lens-cli — --for-model annotation', () => {
it('tags only the candidates whose verification target is reflexive or delegated', async () => {
const { code, payload } = await runLensArgs({ 'CLAUDE.md': VERIFY_MD }, [
'--subtract',
'--for-model',
'opus-5',
]);
assert.equal(code, 0);
const cands = payload.subtract.candidates;
assert.equal(cands.length, 3, 'fixture must produce three BP-SUB-001 candidates');
const tagged = cands.filter((c) => c.modelScope);
assert.equal(tagged.length, 2, 'exactly the two self/delegate-targeted blocks are model-scoped');
for (const t of tagged) {
assert.equal(t.modelScope.registerId, 'BP-PROMPT-001');
assert.equal(t.modelScope.requestedModel, 'opus-5');
assert.ok(t.modelScope.claim, 'the citation must carry the register claim');
}
const external = cands.find((c) => /CI status/.test(c.signalText));
assert.ok(external, 'the external-verification candidate must still be present');
assert.equal(
external.modelScope,
undefined,
'external verification is a BP-SUB-001 candidate but NOT model-scoped'
);
});
it('reports the model as recognized, with a match count', async () => {
const { payload } = await runLensArgs({ 'CLAUDE.md': VERIFY_MD }, [
'--subtract',
'--for-model',
'Opus 5',
]);
assert.deepStrictEqual(payload.subtract.forModel, {
requested: 'Opus 5',
recognized: true,
matchedCount: 2,
});
});
it('reports an unrecognized model honestly instead of a silent zero', async () => {
const { code, payload } = await runLensArgs({ 'CLAUDE.md': VERIFY_MD }, [
'--subtract',
'--for-model',
'oppus5',
]);
assert.equal(code, 0, 'an unknown model name is not an error — it is a reported non-match');
assert.deepStrictEqual(payload.subtract.forModel, {
requested: 'oppus5',
recognized: false,
matchedCount: 0,
});
for (const c of payload.subtract.candidates) {
assert.equal(c.modelScope, undefined, 'no candidate may be tagged for an unrecognized model');
}
});
it('is a no-op without --subtract, matching the --apply-without-subtract shape', async () => {
const { code, payload } = await runLensArgs({ 'CLAUDE.md': VERIFY_MD }, ['--for-model', 'opus-5']);
assert.equal(code, 0);
assert.equal(payload.subtract, undefined, '--for-model alone must not switch the subtraction axis on');
});
it('leaves a plain --subtract run byte-identical to a run without the flag', async () => {
// The additive-payload invariant: --for-model is the ONLY path that grows a
// field. Asserted on the serialized bytes, because a key added with an
// undefined value would pass a shallow key check and still change the shape.
const before = await runLensArgs({ 'CLAUDE.md': VERIFY_MD }, ['--subtract']);
assert.equal(before.code, 0);
assert.equal(before.payload.subtract.forModel, undefined, 'no forModel key without --for-model');
const again = await runLensArgs({ 'CLAUDE.md': VERIFY_MD }, ['--subtract']);
assert.equal(again.normalized, before.normalized, 'a --subtract run must be byte-stable');
});
});