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>
132 lines
5.8 KiB
JavaScript
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'));
|
|
});
|
|
});
|