config-audit/tests/lib/lens-prefilter.test.mjs
Kjell Tore Guttormsen 7b3b487d26 feat(opt): optimization lens Chunk 2b — opus prose-judgment analyzer + /config-audit optimize
The recall+precision halves of the CA-OPT hybrid motor for the three
mechanism-fit cases the deterministic OPT scanner (2a) deliberately skips:
lifecycle→hook (BP-MECH-001), unscoped path-specific→rule (BP-MECH-002),
absolute "never"→permission (BP-MECH-004).

New:
- scanners/lib/lens-prefilter.mjs — cheap, recall-oriented line scan of the
  CLAUDE.md body; detector names mirror the register lensCheck fields; skips
  fenced code, gates the path class on an instruction verb. Pure + 13 tests.
- scanners/optimize-lens-cli.mjs — discovery + OPT scanner + pre-filter; attaches
  only the CONFIRMED register entry to each candidate (unverifiable → dropped,
  Verifiseringsplikt); emits {deterministic, candidates, register, counts}.
- agents/optimization-lens-agent.md — opus precision gate (7th agent, orange):
  reads the real CLAUDE.md, drops low-confidence candidates, keeps only genuine
  opportunities, cites register id + source.
- commands/optimize.md — /config-audit optimize orchestrates pre-filter→agent→report.

Agent-driven → deliberately NOT byte-stable (own command, outside the snapshot
suite). No new orchestrated scanner → scanner count stays 15. Counts: agents
6→7, commands 18→19, suite 1055→1068. Self-audit A/A unchanged, readmeCheck
passed (clean HOME). Plan: docs/v5.7-optimization-lens-plan.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 14:01:18 +02:00

132 lines
5.8 KiB
JavaScript

/**
* lens-prefilter tests — the deterministic, recall-oriented half of the v5.7
* optimization-lens hybrid motor (Chunk 2b).
*
* The pre-filter is intentionally CHEAP and recall-oriented: it surfaces
* candidate lines for the three prose-judgment mechanism-fit classes that the
* deterministic OPT scanner (Chunk 2a) deliberately skips. The opus
* optimization-lens-agent is the PRECISION gate downstream — so these tests
* assert the pre-filter (a) detects the obvious signals and tags them with the
* right register id / lensCheck / mechanism, (b) reports correct 1-based line
* numbers, and (c) does not fire inside fenced code blocks. They do NOT demand
* zero false positives — that is the agent's job, by design.
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { prefilterClaudeMd, LENS_DETECTORS } from '../../scanners/lib/lens-prefilter.mjs';
const byCheck = (candidates, lensCheck) => candidates.filter((c) => c.lensCheck === lensCheck);
describe('lens-prefilter — detector table', () => {
it('exposes the three prose-judgment detectors keyed by register id + lensCheck', () => {
const map = new Map(LENS_DETECTORS.map((d) => [d.lensCheck, d]));
assert.deepEqual(map.get('claude-md-lifecycle-phrasing')?.registerId, 'BP-MECH-001');
assert.deepEqual(map.get('claude-md-lifecycle-phrasing')?.mechanism, 'hook');
assert.deepEqual(map.get('unscoped-path-specific-instruction')?.registerId, 'BP-MECH-002');
assert.deepEqual(map.get('unscoped-path-specific-instruction')?.mechanism, 'rule');
assert.deepEqual(map.get('never-instruction')?.registerId, 'BP-MECH-004');
assert.deepEqual(map.get('never-instruction')?.mechanism, 'permission');
});
});
describe('lens-prefilter — lifecycle phrasing (BP-MECH-001 → hook)', () => {
it('flags "After every commit, run the linter."', () => {
const c = byCheck(prefilterClaudeMd('After every commit, run the linter.'), 'claude-md-lifecycle-phrasing');
assert.equal(c.length, 1);
assert.equal(c[0].registerId, 'BP-MECH-001');
assert.equal(c[0].mechanism, 'hook');
assert.equal(c[0].line, 1);
assert.match(c[0].text, /After every commit/);
});
it('flags several lifecycle markers (before each, every time, whenever)', () => {
const text = [
'Before each push, run the test suite.',
'Every time you edit a config file, re-validate it.',
'Whenever you finish a task, update STATE.md.',
].join('\n');
assert.equal(byCheck(prefilterClaudeMd(text), 'claude-md-lifecycle-phrasing').length, 3);
});
it('does not flag a plain fact with no lifecycle phrasing', () => {
assert.equal(
byCheck(prefilterClaudeMd('This project uses TypeScript and pnpm.'), 'claude-md-lifecycle-phrasing').length,
0,
);
});
});
describe('lens-prefilter — never-instruction (BP-MECH-004 → permission)', () => {
it('flags "Never commit secrets to the repository."', () => {
const c = byCheck(prefilterClaudeMd('Never commit secrets to the repository.'), 'never-instruction');
assert.equal(c.length, 1);
assert.equal(c[0].registerId, 'BP-MECH-004');
assert.equal(c[0].mechanism, 'permission');
});
it('flags a bulleted "never" directive', () => {
const c = byCheck(prefilterClaudeMd('- Never push directly to main.'), 'never-instruction');
assert.equal(c.length, 1);
});
});
describe('lens-prefilter — unscoped path-specific instruction (BP-MECH-002 → rule)', () => {
it('flags an instruction referencing a glob', () => {
const c = byCheck(prefilterClaudeMd('Always format src/**/*.ts with prettier.'), 'unscoped-path-specific-instruction');
assert.equal(c.length, 1);
assert.equal(c[0].registerId, 'BP-MECH-002');
assert.equal(c[0].mechanism, 'rule');
});
it('flags an instruction referencing a concrete filename', () => {
const c = byCheck(prefilterClaudeMd('Run migrations from db/schema.sql before tests.'), 'unscoped-path-specific-instruction');
assert.equal(c.length, 1);
});
it('does not flag a bare path reference with no instruction verb', () => {
assert.equal(
byCheck(prefilterClaudeMd('See docs/architecture.md for the overview.'), 'unscoped-path-specific-instruction').length,
0,
);
});
});
describe('lens-prefilter — structural rules', () => {
it('reports correct 1-based line numbers', () => {
const text = ['# Title', '', 'Some facts here.', 'Never delete the .env file.'].join('\n');
const c = byCheck(prefilterClaudeMd(text), 'never-instruction');
assert.equal(c.length, 1);
assert.equal(c[0].line, 4);
});
it('ignores signals inside fenced code blocks', () => {
const text = [
'Real prose: never run this in production.',
'```bash',
'# after every deploy, never skip the smoke test',
'echo "every time"',
'```',
'More prose after the fence.',
].join('\n');
const all = prefilterClaudeMd(text);
// only the first prose line's "never" should match; nothing inside the fence
assert.ok(all.every((c) => c.line === 1), `expected only line 1; got lines ${all.map((c) => c.line).join(',')}`);
});
it('returns an empty array for a clean facts-only document', () => {
const text = [
'# Project',
'A web API written in Go. Data lives in Postgres.',
'The team deploys via GitHub Actions.',
].join('\n');
assert.deepEqual(prefilterClaudeMd(text), []);
});
it('a single line can yield candidates for two distinct detectors', () => {
// "never" + a concrete path → both the prohibition and the path-scope angle.
const c = prefilterClaudeMd('Never edit src/config.ts by hand.');
const checks = new Set(c.map((x) => x.lensCheck));
assert.ok(checks.has('never-instruction'));
assert.ok(checks.has('unscoped-path-specific-instruction'));
});
});