/** * 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}`); } }); });