/** * 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 user’s 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(''); 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'); }); });