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>
This commit is contained in:
parent
c1409ae9b9
commit
7b3b487d26
9 changed files with 687 additions and 13 deletions
33
CLAUDE.md
33
CLAUDE.md
|
|
@ -20,6 +20,7 @@ Analyzes and optimizes Claude Code configuration across three pillars:
|
|||
| `/config-audit tokens` | prompt-cache-aware token hotspots (6 patterns: cache-breaking, redundant perms, deep imports, oversized cascade, bloated SKILL.md desc, MCP tool-schema budget), each ranked hotspot tagged with its load pattern (always / on-demand / external) — optional `--accurate-tokens` API calibration, `--with-telemetry-recipe` cache-hit recipe pointer |
|
||||
| `/config-audit manifest` | Ranked table of every token source (CLAUDE.md, rules, agents, skills, output styles, MCP, hooks) sorted by estimated tokens, each tagged with its load pattern (always-loaded / on-demand / external) + an always-loaded subtotal ("tokens that enter context every turn") |
|
||||
| `/config-audit feature-gap` | Context-aware feature recommendations grouped by impact (incl. a conditional `disableBundledSkills` lever when the active skill listing is over budget — remediation companion to SKL `CA-SKL-002`) |
|
||||
| `/config-audit optimize` | Optimization lens (mechanism-fit) — config that works but fits a better mechanism: procedure→skill (CA-OPT-001, deterministic), lifecycle→hook / unscoped path→rule / "never"→permission (prose-judgment via opus `optimization-lens-agent`). Hybrid motor; every finding cites a best-practices-register rule. Agent-driven, **not byte-stable** |
|
||||
| `/config-audit fix` | Auto-fix deterministic issues with backup + verification |
|
||||
| `/config-audit rollback` | Restore configuration from backup |
|
||||
| `/config-audit plan` | Create action plan from audit findings |
|
||||
|
|
@ -49,6 +50,7 @@ Analyzes and optimizes Claude Code configuration across three pillars:
|
|||
| implementer-agent | Execute changes | sonnet | magenta | Read, Write, Edit, Bash, Glob |
|
||||
| verifier-agent | Verify results | sonnet | purple | Read, Glob, Grep |
|
||||
| feature-gap-agent | Context-aware feature recommendations | opus | green | Read, Glob, Grep, Write |
|
||||
| optimization-lens-agent | Mechanism-fit precision gate (prose-judgment lens cases) | opus | orange | Read, Glob, Grep, Write |
|
||||
|
||||
## Hooks
|
||||
|
||||
|
|
@ -109,7 +111,7 @@ Default: auto-detects scope from git context. Override with `/config-audit full|
|
|||
node --test 'tests/**/*.test.mjs'
|
||||
```
|
||||
|
||||
1055 tests across 59 test files (18 lib + 31 scanner + 1 hook + 1 agent + 3 commands + 1 knowledge + 4 top-level). Test fixtures in `tests/fixtures/`. Top-level humanizer tests: `json-backcompat.test.mjs`, `raw-backcompat.test.mjs`, `scenario-read-test.test.mjs`, `snapshot-default-output.test.mjs`.
|
||||
1068 tests across 60 test files (19 lib + 31 scanner + 1 hook + 1 agent + 3 commands + 1 knowledge + 4 top-level). Test fixtures in `tests/fixtures/`. Top-level humanizer tests: `json-backcompat.test.mjs`, `raw-backcompat.test.mjs`, `scenario-read-test.test.mjs`, `snapshot-default-output.test.mjs`.
|
||||
|
||||
### active-config-reader — load-pattern model + rule/agent/output-style enumeration (v5.6 Foundation)
|
||||
|
||||
|
|
@ -366,11 +368,30 @@ First detector of the «optimal?» axis (vs «correct?»). `optimization-lens-sc
|
|||
best-practices register and flags config that works but fits a better mechanism. **`CA-OPT-001`**
|
||||
(low, *Missed opportunity*): a CLAUDE.md procedure (≥6 consecutive numbered steps) that belongs in a
|
||||
skill — recommendation/provenance from register `BP-MECH-003`. Conservative (negative corpus = null
|
||||
false-positive); prose-judgment cases (lifecycle→hook, «never»→permission) are deferred to the
|
||||
Chunk 2b opus analyzer. Wiring mirrors OST: orchestrator entry, humanizer `OPT:'Missed opportunity'`
|
||||
+ family, scoring `OPT:'CLAUDE.md'` (existing area → no new posture row → byte-stable), strip-helper
|
||||
`OPT`, SC-5 regenerated (additive). **Test-isolation note:** the TOK test reads the real `~/.claude`
|
||||
(non-hermetic) — run the suite with a clean `HOME` for deterministic results (pre-existing).
|
||||
false-positive); prose-judgment cases (lifecycle→hook, unscoped path→rule, «never»→permission) are
|
||||
handled by the Chunk 2b opus analyzer (below). Wiring mirrors OST: orchestrator entry, humanizer
|
||||
`OPT:'Missed opportunity'` + family, scoring `OPT:'CLAUDE.md'` (existing area → no new posture row →
|
||||
byte-stable), strip-helper `OPT`, SC-5 regenerated (additive).
|
||||
|
||||
### Optimization lens Chunk 2b — opus analyzer (prose-judgment half, `/config-audit optimize`)
|
||||
|
||||
The hybrid motor's recall + precision halves for the three cases the deterministic OPT scanner skips.
|
||||
**Pre-filter** (`scanners/lib/lens-prefilter.mjs`, pure + tested): cheap, recall-oriented line scan
|
||||
of CLAUDE.md body for lifecycle phrasing (`BP-MECH-001`→hook), unscoped path-specific instructions
|
||||
(`BP-MECH-002`→rule), and absolute «never» prohibitions (`BP-MECH-004`→permission); skips fenced
|
||||
code, gates the path class on an instruction verb. Detector names = the register `lensCheck` fields.
|
||||
**CLI** (`optimize-lens-cli.mjs`, `-cli` → not a scanner): runs discovery + OPT scanner + pre-filter,
|
||||
attaches the **confirmed** register entry to each candidate (unverifiable → dropped, Verifiseringsplikt),
|
||||
emits `{deterministic, candidates, register, counts}`. **Agent** (`optimization-lens-agent`, opus,
|
||||
orange — the 7th agent, **precision gate**): reads the real CLAUDE.md, drops low-confidence candidates,
|
||||
keeps only genuine opportunities, cites register id + source. **Command** `/config-audit optimize`
|
||||
orchestrates pre-filter→agent→report. **Agent-driven → deliberately NOT byte-stable** (own command,
|
||||
outside the snapshot suite); the pre-filter lib *is* unit-tested (13 tests). No new orchestrated
|
||||
scanner → scanner count stays 15; agents 6→7, commands 18→19, suite 1055→1068.
|
||||
|
||||
**Test-isolation fix (this session):** `token-hotspots.test.mjs` `runScanner` now wraps `scan()` in
|
||||
the shared `withHermeticHome` helper — the suite is green on BOTH a real and a clean `HOME` (the OPT
|
||||
section's old «run with clean HOME» caveat is resolved). Snapshot/byte tests were already hermetic.
|
||||
|
||||
## Gotchas
|
||||
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -9,10 +9,10 @@
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
A Claude Code plugin that checks configuration health, suggests context-aware improvements, and auto-fixes issues — `CLAUDE.md`, `settings.json`, hooks, rules, MCP servers, `@imports`, and plugins. 15 deterministic scanners across 10 quality areas, context-aware feature recommendations, auto-fix with backup/rollback, a prompt-cache-aware Token Hotspots scanner with optional API-calibrated `--accurate-tokens` mode, plus cache-prefix stability, dead-tool, cross-plugin collision, and output-style detection. Zero external dependencies.
|
||||
|
|
@ -278,6 +278,7 @@ Your team configuration changes over time. Track it:
|
|||
| `/config-audit tokens` | prompt-cache-aware token hotspots — ranked by estimated waste, each tagged with its load pattern (always / on-demand / external); 6 patterns + optional `--accurate-tokens` API calibration |
|
||||
| `/config-audit manifest` | Ranked table of every token source (CLAUDE.md, rules, agents, skills, output styles, MCP, hooks) sorted by estimated tokens — each tagged with its **load pattern** (always-loaded / on-demand / external) plus an **always-loaded subtotal** ("≈X tokens enter context every turn before you type"). Component-level: no coarse plugin roll-up (it would double-count) |
|
||||
| `/config-audit feature-gap` | Context-aware feature recommendations grouped by impact |
|
||||
| `/config-audit optimize` | Optimization lens (mechanism-fit): config that works but fits a better mechanism — procedure→skill, lifecycle→hook, unscoped path→rule, "never"→permission. Hybrid motor (deterministic pre-filter + opus precision gate), every finding cites a best-practices-register rule |
|
||||
| `/config-audit fix` | Auto-fix deterministic issues with backup + verification |
|
||||
| `/config-audit rollback` | Restore configuration from a previous backup |
|
||||
| `/config-audit plan` | Generate prioritized action plan from audit findings |
|
||||
|
|
@ -325,7 +326,7 @@ By default, `/config-audit` auto-detects scope from your git context. Override w
|
|||
| `collision-scanner.mjs` | COL | Cross-plugin skill name collisions; user-vs-plugin overlaps |
|
||||
| `skill-listing-scanner.mjs` | SKL | Skill-listing token budget: a single skill description over the ~1,536-char listing cap Claude Code truncates (`CA-SKL-001`), and the summed active-skill descriptions exceeding the ~2%-of-context listing budget (`CA-SKL-002`) |
|
||||
| `output-style-scanner.mjs` | OST | Output-style validation: a custom (user/project) style missing `keep-coding-instructions: true` that silently strips built-in software-engineering instructions (`CA-OST-001`), a plugin style with `force-for-plugin: true` overriding the user's selected `outputStyle` (`CA-OST-002`), and a settings `outputStyle` resolving to no built-in or custom style — dead config (`CA-OST-003`) |
|
||||
| `optimization-lens-scanner.mjs` | OPT | Optimization lens (mechanism-fit): a multi-step procedure in CLAUDE.md that would fit better as a skill (`CA-OPT-001`) — reads the machine-readable best-practices register, framed as an opportunity, not a failure. The deterministic half of the lens; prose-judgment cases (lifecycle→hook, "never"→permission) are handled by the Chunk 2b analyzer |
|
||||
| `optimization-lens-scanner.mjs` | OPT | Optimization lens (mechanism-fit): a multi-step procedure in CLAUDE.md that would fit better as a skill (`CA-OPT-001`) — reads the machine-readable best-practices register, framed as an opportunity, not a failure. The deterministic half of the lens; prose-judgment cases (lifecycle→hook, unscoped path→rule, "never"→permission) are judged by the opus `optimization-lens-agent` via `/config-audit optimize` |
|
||||
|
||||
> **Cross-scanner remediation — diagnosis meets the fix.** SKL diagnoses an over-budget
|
||||
> skill listing (`CA-SKL-002`); GAP prescribes the remedy. When the active skill listing
|
||||
|
|
@ -432,6 +433,7 @@ Six specialized agents collaborate through the audit workflow, each matched to a
|
|||
| **implementer-agent** | Sonnet | Change execution with mandatory backups | Read, Write, Edit, Bash, Glob |
|
||||
| **verifier-agent** | Sonnet | Post-implementation verification | Read, Glob, Grep |
|
||||
| **feature-gap-agent** | Opus | Context-aware feature recommendations | Read, Glob, Grep, Write |
|
||||
| **optimization-lens-agent** | Opus | Mechanism-fit precision gate — judges prose-judgment lens candidates (lifecycle→hook, path→rule, never→permission), cites the best-practices register | Read, Glob, Grep, Write |
|
||||
|
||||
### Orchestration Flow
|
||||
|
||||
|
|
@ -585,8 +587,8 @@ Reference documents that inform the feature-gap agent and context-aware recommen
|
|||
**Machine-readable register (`best-practices.json`).** Alongside the human-readable documents
|
||||
above, `knowledge/best-practices.json` is a provenance-stamped, schema-validated register of
|
||||
best-practice claims and mechanism-fit rules — each entry carries `source.url`, a `verified`
|
||||
date, and a `confidence`. It is the source of truth for the forthcoming optimization lens; the
|
||||
Markdown files remain the human-readable mirror. Loaded and validated by
|
||||
date, and a `confidence`. It is the source of truth for the optimization lens (OPT scanner +
|
||||
`/config-audit optimize`); the Markdown files remain the human-readable mirror. Loaded and validated by
|
||||
`scanners/lib/best-practices-register.mjs` (zero-dependency, native JSON). See
|
||||
`docs/v5.7-optimization-lens-plan.md`.
|
||||
|
||||
|
|
|
|||
120
agents/optimization-lens-agent.md
Normal file
120
agents/optimization-lens-agent.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
name: optimization-lens-agent
|
||||
description: |
|
||||
Judges CLAUDE.md mechanism-fit for the v5.7 optimization lens (CA-OPT). Reads
|
||||
deterministic pre-filter candidates and decides, with prose judgement, whether
|
||||
each is a genuine "you use mechanism X, but Y fits this better" opportunity —
|
||||
lifecycle phrasing → hook, unscoped path-specific instruction → path-scoped
|
||||
rule, absolute "never" prohibition → permission. Precision-gated: cites the
|
||||
best-practices register rule + source, and stays silent when unsure.
|
||||
model: opus
|
||||
color: orange
|
||||
tools: ["Read", "Glob", "Grep", "Write"]
|
||||
---
|
||||
|
||||
# Optimization Lens Agent
|
||||
|
||||
You are the **precision gate** of the optimization lens's hybrid motor. A cheap
|
||||
deterministic pre-filter (`lens-prefilter`) has already surfaced candidate lines
|
||||
in CLAUDE.md that *might* fit a better mechanism. Your job is to read each
|
||||
candidate **in its real context** and keep only the genuine opportunities.
|
||||
|
||||
This is the "is the config **optimal?**" axis, not "is it **correct?**" — every
|
||||
finding is a *Missed opportunity*, never a mistake. The config works as written;
|
||||
you are pointing at a mechanism that would fit the content better.
|
||||
|
||||
## The judgement you make
|
||||
|
||||
For each candidate, the register rule names the better-fit mechanism. Decide
|
||||
whether the line is *really* that kind of instruction:
|
||||
|
||||
| lensCheck | Register | Keep it ONLY if the line is… | Better mechanism |
|
||||
|---|---|---|---|
|
||||
| `claude-md-lifecycle-phrasing` | BP-MECH-001 | a recurring automation the model is *told* to perform ("after every commit, run X") — something that should happen deterministically, not at the model's discretion | a **hook** (PreToolUse / PostToolUse / Stop) |
|
||||
| `unscoped-path-specific-instruction` | BP-MECH-002 | a constraint that only applies when a *specific* file/path/glob is touched, sitting in root CLAUDE.md where it loads every turn regardless | a **path-scoped rule** (`.claude/rules/` with `paths:` frontmatter) |
|
||||
| `never-instruction` | BP-MECH-004 | an *absolute* prohibition — something that must NEVER happen, where relying on the model to remember is the wrong guarantee | a **permission deny rule** or PreToolUse hook |
|
||||
|
||||
## Input
|
||||
|
||||
You receive an `optimize-lens` payload (JSON) with:
|
||||
- `target` — the repo path.
|
||||
- `deterministic` — OPT scanner findings already confirmed (CA-OPT-001:
|
||||
procedure → skill). Report these **as-is**; do not re-judge them.
|
||||
- `candidates` — pre-filter candidates, each with `file`, `line`, `lensCheck`,
|
||||
`mechanism`, `signalText`, and a `register` block (`id`, `claim`,
|
||||
`recommendation`, `severity`, `source`). Only CONFIRMED register rules reach
|
||||
you.
|
||||
- `register` — the full confirmed prose-judgment entries, for reference.
|
||||
|
||||
Always **Read the actual CLAUDE.md file(s)** named in the candidates before
|
||||
judging — `signalText` is one line out of context; the surrounding lines decide
|
||||
whether it is really lifecycle/path-specific/prohibition phrasing.
|
||||
|
||||
## Precision rules (non-negotiable)
|
||||
|
||||
1. **Keep only high-confidence opportunities.** When the line is ambiguous,
|
||||
rhetorical, an example, a heading, or already correctly placed (e.g. it is
|
||||
*inside* a path-scoped rule, or already references a hook) — **drop it**. A
|
||||
missed suggestion is far cheaper than a wrong one (Verifiseringsplikt).
|
||||
2. **Never invent a recommendation.** Use the `register.recommendation` and cite
|
||||
`register.id` + `register.source.url`. If a candidate has no register block,
|
||||
skip it.
|
||||
3. **De-duplicate.** If one line yields two candidates (e.g. "never edit
|
||||
src/config.ts"), pick the single mechanism that fits best and say why,
|
||||
rather than emitting two findings for one line.
|
||||
4. **No false urgency.** These are LOW-severity opportunities. Do not imply the
|
||||
config is broken.
|
||||
|
||||
## Output
|
||||
|
||||
Write `optimization-lens-report.md` to the session directory (≤120 lines).
|
||||
|
||||
```markdown
|
||||
# Optimization Lens — mechanism-fit
|
||||
|
||||
**Date:** YYYY-MM-DD | **Target:** {repo}
|
||||
**Confirmed opportunities:** {N kept} · **Candidates reviewed:** {M} · **Dropped (low confidence):** {M-N}
|
||||
|
||||
> The config works as written. These are places where a different Claude Code
|
||||
> mechanism would fit the content better — usually cheaper per turn or more
|
||||
> reliable.
|
||||
|
||||
## Procedures → skills (deterministic)
|
||||
|
||||
{For each `deterministic` finding — render title/recommendation verbatim, cite CA-OPT-001 + BP-MECH-003.}
|
||||
|
||||
## Lifecycle → hooks
|
||||
|
||||
{Kept BP-MECH-001 findings. For each:}
|
||||
**{file}:{line}** — {one-line restatement of the line}
|
||||
Why: {register.claim, condensed}
|
||||
Move to: {register.recommendation}
|
||||
Source: {register.source.url}
|
||||
|
||||
## Path-specific → scoped rules
|
||||
|
||||
{Kept BP-MECH-002 findings, same shape.}
|
||||
|
||||
## Absolute prohibitions → permissions
|
||||
|
||||
{Kept BP-MECH-004 findings, same shape.}
|
||||
|
||||
## What I deliberately left alone
|
||||
|
||||
{Brief, honest: candidates you dropped and why — "line 22 mentions a path but is
|
||||
a cross-reference, not an instruction." This is the precision gate showing its
|
||||
work. Keep to a few lines.}
|
||||
```
|
||||
|
||||
Omit any section with zero kept findings (except keep the "left alone" note when
|
||||
you dropped anything). If nothing survived the gate, say so plainly — a clean
|
||||
CLAUDE.md is a good outcome, not a failure to find problems.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Frame everything as *opportunities*, never failures.
|
||||
- Cite the register rule id + source URL on every finding — provenance is the
|
||||
product.
|
||||
- Be concrete: name the file and line, and what the replacement mechanism is.
|
||||
- Prefer dropping a borderline candidate over stretching to keep it.
|
||||
- Do not recommend a mechanism the project already uses for that exact content.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: config-audit
|
||||
description: Claude Code Configuration Intelligence - audit, analyze, and optimize your configuration
|
||||
argument-hint: "[posture|tokens|manifest|feature-gap|fix|rollback|plan|implement|help|discover|analyze|interview|drift|plugin-health|whats-active|status|cleanup]"
|
||||
argument-hint: "[posture|tokens|manifest|feature-gap|optimize|fix|rollback|plan|implement|help|discover|analyze|interview|drift|plugin-health|whats-active|status|cleanup]"
|
||||
allowed-tools: Read, Write, Glob, Grep, Bash, Agent, AskUserQuestion
|
||||
model: opus
|
||||
---
|
||||
|
|
@ -17,6 +17,7 @@ If a subcommand is provided, route to it:
|
|||
- `tokens` → `/config-audit:tokens`
|
||||
- `manifest` → `/config-audit:manifest`
|
||||
- `feature-gap` → `/config-audit:feature-gap`
|
||||
- `optimize` → `/config-audit:optimize`
|
||||
- `fix` → `/config-audit:fix`
|
||||
- `rollback` → `/config-audit:rollback`
|
||||
- `plan` → `/config-audit:plan`
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ if echo "$ARGUMENTS" | grep -q -- "--raw"; then RAW_FLAG="--raw"; fi
|
|||
| `/config-audit tokens` | prompt-cache token hotspots; optional `--accurate-tokens` API calibration |
|
||||
| `/config-audit manifest` | Ranked table of every system-prompt token source |
|
||||
| `/config-audit feature-gap` | Deep analysis of features you're not using |
|
||||
| `/config-audit optimize` | Optimization lens — config that works but fits a better mechanism (procedure→skill, lifecycle→hook, path→rule, never→permission) |
|
||||
| `/config-audit fix` | Auto-fix deterministic issues; a copy of every changed file is saved first so you can roll back with one command |
|
||||
| `/config-audit rollback` | Restore configuration from a saved copy |
|
||||
|
||||
|
|
|
|||
123
commands/optimize.md
Normal file
123
commands/optimize.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
name: config-audit:optimize
|
||||
description: Optimization lens — config that works but would fit a better mechanism (procedure→skill, lifecycle→hook, path→rule, never→permission)
|
||||
argument-hint: "[path]"
|
||||
allowed-tools: Read, Write, Glob, Grep, Bash, Agent
|
||||
model: opus
|
||||
---
|
||||
|
||||
# Config-Audit: Optimization Lens
|
||||
|
||||
The "is the config **optimal?**" axis (vs. the health scanners' "is it
|
||||
**correct?**"). It finds configuration that *works* but uses a mechanism a
|
||||
better one would fit — and frames every one as a *Missed opportunity*, never a
|
||||
mistake.
|
||||
|
||||
Mechanism-fit rules come from the provenance-stamped best-practices register
|
||||
(`knowledge/best-practices.json`); only CONFIRMED rules are surfaced. The motor
|
||||
is hybrid: a cheap deterministic pre-filter finds candidates, then the opus
|
||||
`optimization-lens-agent` judges each in context (precision-gated).
|
||||
|
||||
## What the user gets
|
||||
|
||||
- **Procedures → skills** (deterministic, CA-OPT-001)
|
||||
- **Lifecycle phrasing → hooks** (BP-MECH-001)
|
||||
- **Unscoped path-specific instructions → path-scoped rules** (BP-MECH-002)
|
||||
- **Absolute "never" prohibitions → permissions / hooks** (BP-MECH-004)
|
||||
|
||||
Each finding cites its register rule + source URL. A clean CLAUDE.md returns "no
|
||||
opportunities" — that is a good result, not a failure.
|
||||
|
||||
## Implementation
|
||||
|
||||
### Step 1: Determine target
|
||||
|
||||
Split `$ARGUMENTS` into a path (first non-flag argument; default: current working
|
||||
directory) and flags. Recognized flags: `--global` (include the user `~/.claude`
|
||||
cascade in discovery).
|
||||
|
||||
Tell the user:
|
||||
|
||||
```
|
||||
## Optimization Lens
|
||||
|
||||
Looking for configuration that works but would fit a better Claude Code mechanism...
|
||||
```
|
||||
|
||||
### Step 2: Run the lens CLI
|
||||
|
||||
Generate a session ID (`YYYYMMDD_HHmmss`) if no active session exists.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.claude/config-audit/sessions/{session-id} 2>/dev/null
|
||||
GLOBAL_FLAG=""
|
||||
if echo "$ARGUMENTS" | grep -q -- "--global"; then GLOBAL_FLAG="--global"; fi
|
||||
node ${CLAUDE_PLUGIN_ROOT}/scanners/optimize-lens-cli.mjs <target-path> --output-file ~/.claude/config-audit/sessions/{session-id}/optimize-lens.json $GLOBAL_FLAG 2>/dev/null; echo $?
|
||||
```
|
||||
|
||||
Exit code 0 is normal. Only exit code 3 is a real error → "The lens couldn't run.
|
||||
Check that the path exists and contains a CLAUDE.md."
|
||||
|
||||
### Step 3: Read the payload
|
||||
|
||||
Read `~/.claude/config-audit/sessions/{session-id}/optimize-lens.json` with the
|
||||
Read tool. It has `deterministic` (already-confirmed OPT findings), `candidates`
|
||||
(pre-filter candidates with register provenance), `register`, and `counts`.
|
||||
|
||||
**Early exit:** if `counts.deterministic === 0` and `counts.candidates === 0`,
|
||||
skip the agent and tell the user plainly:
|
||||
|
||||
```
|
||||
✓ No mechanism-fit opportunities found.
|
||||
|
||||
Your CLAUDE.md holds facts, not procedures/automation/prohibitions that would be
|
||||
better as skills, hooks, rules, or permissions. Nothing to change here.
|
||||
```
|
||||
|
||||
Then go to Step 5.
|
||||
|
||||
### Step 4: Spawn the precision gate
|
||||
|
||||
Tell the user what's happening and set expectations:
|
||||
|
||||
```
|
||||
Found {counts.candidates} candidate line(s) + {counts.deterministic} deterministic finding(s).
|
||||
Asking the optimization-lens agent to judge each in context (~20-40 seconds)...
|
||||
```
|
||||
|
||||
Spawn the `optimization-lens-agent` (Agent tool) with:
|
||||
- the full payload from Step 3 (deterministic + candidates + register),
|
||||
- the session directory path so it can write `optimization-lens-report.md`.
|
||||
|
||||
The agent reads the actual CLAUDE.md, drops low-confidence candidates, and keeps
|
||||
only genuine opportunities — each citing its register rule + source.
|
||||
|
||||
### Step 5: Present results
|
||||
|
||||
Read the agent's `optimization-lens-report.md` and present it formatted
|
||||
(markdown tables / grouped sections). Follow the UX rules: never show raw JSON or
|
||||
scanner progress; lead with a one-sentence summary of what was found before the
|
||||
detail. Make clear these are LOW-severity *opportunities*.
|
||||
|
||||
If the agent kept nothing from the candidates (all dropped) but there were
|
||||
deterministic findings, show those; if it kept nothing at all, show the clean
|
||||
result from Step 3.
|
||||
|
||||
### Step 6: Next steps
|
||||
|
||||
End with context-sensitive next steps, explaining WHY each is useful:
|
||||
|
||||
- `/config-audit plan` — turn the kept opportunities into an action plan with
|
||||
backups before you change anything.
|
||||
- `/config-audit feature-gap` — the complementary lens: features you *don't* use
|
||||
yet (this command is about mechanisms you *do* use that could fit better).
|
||||
- Re-run `/config-audit optimize` anytime after editing CLAUDE.md.
|
||||
|
||||
## Notes
|
||||
|
||||
- This command is **agent-driven and not byte-stable** — its output is a
|
||||
human-facing report, deliberately outside the deterministic snapshot suite.
|
||||
- The deterministic half (CA-OPT-001) also rides in the normal orchestrated
|
||||
audit; this command adds the prose-judgment half on top.
|
||||
- No files are modified. To act on a finding, use `/config-audit plan` →
|
||||
`/config-audit implement` (backup + rollback) or edit by hand.
|
||||
114
scanners/lib/lens-prefilter.mjs
Normal file
114
scanners/lib/lens-prefilter.mjs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* lens-prefilter — deterministic, recall-oriented candidate generator for the
|
||||
* v5.7 optimization lens (CA-OPT) hybrid motor, Chunk 2b.
|
||||
*
|
||||
* The OPT *scanner* (Chunk 2a) handles the one mechanism-fit case it can decide
|
||||
* deterministically with high precision (a long numbered procedure → skill). The
|
||||
* other three cases in the register are PROSE-JUDGMENT calls — whether a line is
|
||||
* really lifecycle automation, a path-specific constraint, or an absolute
|
||||
* prohibition depends on reading intent, which a regex cannot settle. So the
|
||||
* hybrid motor splits the work:
|
||||
*
|
||||
* pre-filter (this module, CHEAP, recall-oriented)
|
||||
* → surfaces candidate lines tagged with the register rule they might fit
|
||||
* opus optimization-lens-agent (PRECISION gate)
|
||||
* → reads each candidate in context, keeps only genuine mechanism-fit
|
||||
* opportunities, cites the register rule + source
|
||||
*
|
||||
* Therefore this pre-filter deliberately errs toward recall: a false candidate
|
||||
* costs the agent a moment's judgement, a missed line is never recoverable. It
|
||||
* does, however, avoid the two obvious noise sources — fenced code blocks and
|
||||
* (when the caller passes the parsed body) YAML frontmatter — and it requires an
|
||||
* imperative-looking line for the path-specific class so plain "see docs/x.md"
|
||||
* references don't flood the candidate list.
|
||||
*
|
||||
* The detector names mirror the `lensCheck` fields of the register entries
|
||||
* (knowledge/best-practices.json), so the agent can map each candidate straight
|
||||
* back to its provenance.
|
||||
*
|
||||
* Zero external dependencies. Pure: input text → candidate array.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The three prose-judgment detectors, keyed to their register entries.
|
||||
* `mechanism` is the better-fit mechanism the register recommends.
|
||||
*/
|
||||
export const LENS_DETECTORS = Object.freeze([
|
||||
{ lensCheck: 'claude-md-lifecycle-phrasing', registerId: 'BP-MECH-001', mechanism: 'hook' },
|
||||
{ lensCheck: 'unscoped-path-specific-instruction', registerId: 'BP-MECH-002', mechanism: 'rule' },
|
||||
{ lensCheck: 'never-instruction', registerId: 'BP-MECH-004', mechanism: 'permission' },
|
||||
]);
|
||||
|
||||
// Lifecycle automation phrased as an instruction: "after every commit", "before
|
||||
// each push", "every time you …", "whenever you …", "always run". Recall-first.
|
||||
const LIFECYCLE_RE =
|
||||
/\b(?:after (?:every|each)|before (?:every|each)|on (?:every|each)|every time|each time|always run|whenever)\b/i;
|
||||
|
||||
// Absolute prohibition: a standalone "never" followed by an action word. Kept
|
||||
// permissive (recall); the agent decides whether it is a real hard rule.
|
||||
const NEVER_RE = /\bnever\s+[a-z]/i;
|
||||
|
||||
// A concrete path / glob / known-extension filename anywhere in the line.
|
||||
const PATH_RE =
|
||||
/(?:(?:\.{0,2}\/)?[\w.-]+\/[\w.*/-]+|\*\*?\/[\w.*-]+|\b[\w-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|md|json|ya?ml|toml|go|rs|java|rb|php|c|cpp|h|hpp|sh|sql|css|scss|html|env)\b)/;
|
||||
|
||||
// An imperative / modal verb that marks a line as an instruction rather than a
|
||||
// bare cross-reference. Gates the path-specific class to cut "see foo/bar.md".
|
||||
const INSTRUCTION_RE =
|
||||
/\b(?:use|edit|run|always|must|should|put|place|write|add|modify|update|format|lint|test|name|store|keep|never|generate|build|deploy|commit)\b/i;
|
||||
|
||||
const getDetector = (lensCheck) => LENS_DETECTORS.find((d) => d.lensCheck === lensCheck);
|
||||
|
||||
function candidate(lensCheck, lineNo, lineText) {
|
||||
const d = getDetector(lensCheck);
|
||||
return {
|
||||
lensCheck,
|
||||
registerId: d.registerId,
|
||||
mechanism: d.mechanism,
|
||||
line: lineNo,
|
||||
text: lineText.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan CLAUDE.md text for prose-judgment mechanism-fit candidates.
|
||||
*
|
||||
* Pass the file body (frontmatter stripped) for clean line numbers; the caller
|
||||
* is then responsible for offsetting `line` by the body's start line. Raw text
|
||||
* also works — fenced code is skipped either way.
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {Array<{lensCheck:string, registerId:string, mechanism:string, line:number, text:string}>}
|
||||
*/
|
||||
export function prefilterClaudeMd(text) {
|
||||
const lines = String(text == null ? '' : text).split('\n');
|
||||
const out = [];
|
||||
let inFence = false;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const raw = lines[i];
|
||||
const lineNo = i + 1;
|
||||
|
||||
// Toggle fenced code blocks (``` or ~~~). Fence lines themselves are skipped.
|
||||
if (/^\s*(?:```|~~~)/.test(raw)) {
|
||||
inFence = !inFence;
|
||||
continue;
|
||||
}
|
||||
if (inFence) continue;
|
||||
|
||||
const trimmed = raw.trim();
|
||||
if (trimmed === '') continue;
|
||||
|
||||
if (LIFECYCLE_RE.test(raw)) {
|
||||
out.push(candidate('claude-md-lifecycle-phrasing', lineNo, raw));
|
||||
}
|
||||
if (NEVER_RE.test(raw)) {
|
||||
out.push(candidate('never-instruction', lineNo, raw));
|
||||
}
|
||||
if (PATH_RE.test(raw) && INSTRUCTION_RE.test(raw)) {
|
||||
out.push(candidate('unscoped-path-specific-instruction', lineNo, raw));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
160
scanners/optimize-lens-cli.mjs
Normal file
160
scanners/optimize-lens-cli.mjs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* optimize-lens CLI — feeds the v5.7 optimization lens (CA-OPT) `/config-audit
|
||||
* optimize` command. It produces the two halves of the hybrid motor as one JSON
|
||||
* payload:
|
||||
*
|
||||
* 1. `deterministic` — the OPT scanner's high-precision findings (CA-OPT-001:
|
||||
* a long numbered procedure in CLAUDE.md → skill). Already part of the
|
||||
* orchestrated audit; surfaced here so /optimize is a complete view.
|
||||
* 2. `candidates` — recall-oriented prose-judgment candidates from the
|
||||
* lens-prefilter (lifecycle → hook, unscoped path-specific → rule, "never"
|
||||
* → permission), each stamped with the CONFIRMED register entry it might fit
|
||||
* (claim / recommendation / source / severity). The opus
|
||||
* optimization-lens-agent is the precision gate over these.
|
||||
*
|
||||
* Only CONFIRMED register entries are attached (Verifiseringsplikt); a candidate
|
||||
* whose register rule is missing or unconfirmed is dropped, so the agent never
|
||||
* sees an unverifiable recommendation.
|
||||
*
|
||||
* Usage:
|
||||
* node optimize-lens-cli.mjs [path] [--output-file <path>] [--global]
|
||||
*
|
||||
* Exit codes: 0=ok, 3=unrecoverable error. Zero external dependencies.
|
||||
*/
|
||||
|
||||
import { resolve } from 'node:path';
|
||||
import { writeFile, readFile, stat } from 'node:fs/promises';
|
||||
import { discoverConfigFiles } from './lib/file-discovery.mjs';
|
||||
import { resetCounter } from './lib/output.mjs';
|
||||
import { parseFrontmatter } from './lib/yaml-parser.mjs';
|
||||
import { loadRegister, getEntry } from './lib/best-practices-register.mjs';
|
||||
import { prefilterClaudeMd, LENS_DETECTORS } from './lib/lens-prefilter.mjs';
|
||||
import { scan as optScan } from './optimization-lens-scanner.mjs';
|
||||
|
||||
/** Confirmed register entry for `id`, or null. */
|
||||
function confirmedEntry(register, id) {
|
||||
const e = getEntry(register, id);
|
||||
return e && e.confidence === 'confirmed' ? e : null;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
let targetPath = '.';
|
||||
let outputFile = null;
|
||||
let includeGlobal = false;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--global') includeGlobal = true;
|
||||
else if (args[i] === '--output-file' && args[i + 1]) outputFile = args[++i];
|
||||
else if (!args[i].startsWith('-')) targetPath = args[i];
|
||||
}
|
||||
|
||||
const absPath = resolve(targetPath);
|
||||
try {
|
||||
const s = await stat(absPath);
|
||||
if (!s.isDirectory()) {
|
||||
process.stderr.write(`Error: ${absPath} is not a directory\n`);
|
||||
process.exit(3);
|
||||
}
|
||||
} catch {
|
||||
process.stderr.write(`Error: path does not exist: ${absPath}\n`);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
// Load the register once; tolerate its absence (deterministic half still runs).
|
||||
let register = null;
|
||||
try {
|
||||
register = loadRegister();
|
||||
} catch {
|
||||
register = null;
|
||||
}
|
||||
|
||||
resetCounter();
|
||||
const discovery = await discoverConfigFiles(absPath, { includeGlobal });
|
||||
|
||||
// ── Deterministic half: the OPT scanner (CA-OPT-001) ──
|
||||
const opt = await optScan(absPath, discovery);
|
||||
|
||||
// ── Recall half: prose-judgment candidates from the pre-filter ──
|
||||
const claudeMdFiles = (discovery.files || []).filter((f) => f.type === 'claude-md');
|
||||
const candidates = [];
|
||||
for (const file of claudeMdFiles) {
|
||||
let content;
|
||||
try {
|
||||
content = await readFile(file.absPath, 'utf-8');
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const parsed = parseFrontmatter(content);
|
||||
const body = parsed.body || content;
|
||||
const bodyStartLine = parsed.bodyStartLine || 1;
|
||||
for (const cand of prefilterClaudeMd(body)) {
|
||||
const entry = register ? confirmedEntry(register, cand.registerId) : null;
|
||||
if (!entry) continue; // never surface an unverifiable recommendation
|
||||
candidates.push({
|
||||
file: file.relPath || file.absPath,
|
||||
line: bodyStartLine - 1 + cand.line,
|
||||
lensCheck: cand.lensCheck,
|
||||
mechanism: cand.mechanism,
|
||||
signalText: cand.text,
|
||||
register: {
|
||||
id: entry.id,
|
||||
claim: entry.claim,
|
||||
recommendation: entry.recommendation || null,
|
||||
severity: entry.severity || 'low',
|
||||
source: entry.source,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The CONFIRMED prose-judgment entries, so the agent has full provenance even
|
||||
// for a detector class that produced no candidates this run.
|
||||
const registerEntries = register
|
||||
? LENS_DETECTORS.map((d) => confirmedEntry(register, d.registerId))
|
||||
.filter(Boolean)
|
||||
.map((e) => ({
|
||||
id: e.id,
|
||||
lensCheck: e.lensCheck,
|
||||
claim: e.claim,
|
||||
recommendation: e.recommendation || null,
|
||||
mechanism: e.mechanism || null,
|
||||
severity: e.severity || 'low',
|
||||
source: e.source,
|
||||
}))
|
||||
: [];
|
||||
|
||||
const payload = {
|
||||
status: 'ok',
|
||||
target: absPath,
|
||||
deterministic: opt.findings || [],
|
||||
candidates,
|
||||
register: registerEntries,
|
||||
counts: {
|
||||
deterministic: (opt.findings || []).length,
|
||||
candidates: candidates.length,
|
||||
byLensCheck: candidates.reduce((acc, c) => {
|
||||
acc[c.lensCheck] = (acc[c.lensCheck] || 0) + 1;
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
};
|
||||
|
||||
const json = JSON.stringify(payload, null, 2);
|
||||
if (outputFile) {
|
||||
await writeFile(outputFile, json, 'utf-8');
|
||||
}
|
||||
if (!outputFile) {
|
||||
process.stdout.write(json + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
const isDirectRun = process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname);
|
||||
if (isDirectRun) {
|
||||
main().catch((err) => {
|
||||
process.stderr.write(`Fatal: ${err.message}\n`);
|
||||
process.exit(3);
|
||||
});
|
||||
}
|
||||
132
tests/lib/lens-prefilter.test.mjs
Normal file
132
tests/lib/lens-prefilter.test.mjs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* 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'));
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue