/** * OPT scanner tests — optimization lens / mechanism-fit (v5.7 Fase 1 Chunk 2a). * * Chunk 2a ships ONE deterministic, high-precision detector: a multi-step * procedure in CLAUDE.md should be a skill (BP-MECH-003). Each case has a * positive fixture (fires) and negatives (silent) — the negative corpus proves * the null-false-positive requirement. Prose-judgment heuristics (lifecycle→hook, * "never"→permission) are deliberately deferred to the Chunk 2b opus analyzer. * * The scanner reads discovery's claude-md files directly and pulls its * recommendation/provenance from the best-practices register (BP-MECH-003). */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { join } from 'node:path'; import { mkdtemp, writeFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { resetCounter } from '../../scanners/lib/output.mjs'; import { scan } from '../../scanners/optimization-lens-scanner.mjs'; const tmp = () => mkdtemp(join(tmpdir(), 'ca-opt-')); const PROCEDURE_TITLE = 'A multi-step procedure in CLAUDE.md belongs in a skill'; /** numbered list of n imperative steps */ const steps = (n, sep = '\n') => Array.from({ length: n }, (_, i) => `${i + 1}. Run deploy step ${i + 1} and verify it.`).join(sep); async function runOpt(claudeMd, relPath = 'CLAUDE.md') { const repo = await tmp(); const abs = join(repo, relPath.replace(/\//g, '-')); await writeFile(abs, claudeMd, 'utf-8'); resetCounter(); try { return await scan(repo, { files: [{ absPath: abs, relPath, type: 'claude-md', scope: 'project', size: claudeMd.length }], }); } finally { await rm(repo, { recursive: true, force: true }); } } const hasProc = (result) => (result.findings || []).find((f) => f.title === PROCEDURE_TITLE); describe('OPT scanner — result shape', () => { it('returns an ok OPT scanner result', async () => { const result = await runOpt('# Project\n\nJust facts here.\n'); assert.equal(result.scanner, 'OPT'); assert.equal(result.status, 'ok'); }); it('emits nothing and stays ok when no claude-md files are discovered', async () => { const repo = await tmp(); resetCounter(); try { const result = await scan(repo, { files: [] }); assert.equal(result.status, 'ok'); assert.deepEqual(result.findings, []); } finally { await rm(repo, { recursive: true, force: true }); } }); }); describe('OPT CA-OPT-001 — procedure in CLAUDE.md → skill (positive)', () => { it('fires on a 6-step numbered procedure', async () => { const f = hasProc(await runOpt(`# Release\n\n${steps(6)}\n`)); assert.ok(f, 'expected the procedure finding'); assert.equal(f.scanner, 'OPT'); assert.equal(f.severity, 'low'); assert.match(f.id, /^CA-OPT-\d{3}$/); }); it('fires when steps are separated by blank lines', async () => { const f = hasProc(await runOpt(`# Release checklist\n\n${steps(7, '\n\n')}\n`)); assert.ok(f); assert.equal(f.details.steps >= 6, true); }); it('reads its recommendation + provenance from register BP-MECH-003', async () => { const f = hasProc(await runOpt(`# Steps\n\n${steps(6)}\n`)); assert.equal(f.details.register, 'BP-MECH-003'); assert.equal(f.details.mechanism, 'skill'); assert.ok(f.recommendation && f.recommendation.length > 0); assert.ok(f.description && f.description.length > 0); }); it('emits one finding per file, not one per step', async () => { const result = await runOpt(`# Steps\n\n${steps(12)}\n`); const procs = (result.findings || []).filter((x) => x.title === PROCEDURE_TITLE); assert.equal(procs.length, 1); }); }); describe('OPT CA-OPT-001 — negative corpus (must be silent)', () => { it('does NOT fire on a short (5-step) list', async () => { assert.equal(hasProc(await runOpt(`# Steps\n\n${steps(5)}\n`)), undefined); }); it('does NOT fire on a bulleted (non-numbered) list of 8 items', async () => { const bullets = Array.from({ length: 8 }, (_, i) => `- point ${i + 1}`).join('\n'); assert.equal(hasProc(await runOpt(`# Notes\n\n${bullets}\n`)), undefined); }); it('does NOT fire on prose with no list', async () => { assert.equal(hasProc(await runOpt('# Project\n\nWe use TypeScript. Build with npm.\n')), undefined); }); it('does NOT fire on two separate short numbered lists', async () => { const md = `# A\n\n${steps(3)}\n\n## B\n\nsome prose breaks the run\n\n${steps(3)}\n`; assert.equal(hasProc(await runOpt(md)), undefined); }); });