feat(opt): optimization lens CA-OPT-001 (procedure→skill) — v5.7 Fase 1 Chunk 2a
First detector of the 'is the config OPTIMAL?' axis (vs the existing 'correct?' scanners). New orchestrated scanner family CA-OPT (count 14->15), the deterministic half of the hybrid optimization lens. CA-OPT-001 (low, Missed opportunity): a multi-step procedure in CLAUDE.md (>=6 consecutive numbered steps) that belongs in a skill. Reads recommendation + provenance from the best-practices register (BP-MECH-003). Conservative by design; the negative corpus proves null false-positives. 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 (OST,OPT), SC-5 regenerated under hermetic HOME (additive OPT entry only). 10 new tests; suite 1045->1055, self-audit A/A, readmeCheck passed (all verified with a clean HOME). Note: the pre-existing TOK test reads the real ~/.claude (non-hermetic) -> run the suite with a clean HOME for deterministic results. Tracked as a separate follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
55f83a3c99
commit
e7833b65fc
14 changed files with 350 additions and 38 deletions
16
CLAUDE.md
16
CLAUDE.md
|
|
@ -101,7 +101,7 @@ Default: auto-detects scope from git context. Override with `/config-audit full|
|
|||
```
|
||||
|
||||
### Finding ID Format
|
||||
`CA-{SCANNER}-{NNN}` — e.g. `CA-CML-001`, `CA-SET-003`, `CA-HKV-002`, `CA-RUL-005`, `CA-TOK-005`, `CA-CPS-001`, `CA-DIS-001`, `CA-COL-001`, `CA-SKL-001`, `CA-OST-001`
|
||||
`CA-{SCANNER}-{NNN}` — e.g. `CA-CML-001`, `CA-SET-003`, `CA-HKV-002`, `CA-RUL-005`, `CA-TOK-005`, `CA-CPS-001`, `CA-DIS-001`, `CA-COL-001`, `CA-SKL-001`, `CA-OST-001`, `CA-OPT-001`
|
||||
|
||||
## Testing
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ Default: auto-detects scope from git context. Override with `/config-audit full|
|
|||
node --test 'tests/**/*.test.mjs'
|
||||
```
|
||||
|
||||
1045 tests across 58 test files (18 lib + 30 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`.
|
||||
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`.
|
||||
|
||||
### active-config-reader — load-pattern model + rule/agent/output-style enumeration (v5.6 Foundation)
|
||||
|
||||
|
|
@ -360,6 +360,18 @@ file in `knowledge/` (the `*.md` stay human-only); source of truth for the v5.7
|
|||
JSON, **not** YAML — `yaml-parser.mjs` can't do arrays-of-objects). Byte-stable until a scanner
|
||||
consumes it (Chunk 2). Full design: `docs/v5.7-optimization-lens-plan.md`.
|
||||
|
||||
### OPT scanner — optimization lens / mechanism-fit (`CA-OPT`, v5.7 Fase 1 Chunk 2a, count 14→15)
|
||||
|
||||
First detector of the «optimal?» axis (vs «correct?»). `optimization-lens-scanner.mjs` reads the
|
||||
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).
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Session directories accumulate — use `/config-audit cleanup` to manage
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -8,14 +8,14 @@
|
|||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
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. 14 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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ Claude Code reads instructions from at least 7 different file types across multi
|
|||
|
||||
This plugin provides three layers of configuration intelligence:
|
||||
|
||||
- **Health** — 14 deterministic scanners verify correctness across every configuration file, catching broken imports, deprecated settings, conflicting rules, format errors, permission contradictions, prompt-cache token waste, cache-prefix instability, dead tool grants, cross-plugin skill collisions, and output styles that silently strip Claude Code's coding instructions
|
||||
- **Health** — 15 deterministic scanners verify correctness across every configuration file, catching broken imports, deprecated settings, conflicting rules, format errors, permission contradictions, prompt-cache token waste, cache-prefix instability, dead tool grants, cross-plugin skill collisions, output styles that silently strip Claude Code's coding instructions, and procedures in CLAUDE.md that would fit better as a skill
|
||||
- **Opportunities** — context-aware recommendations for Claude Code features that could benefit your specific project, backed by Anthropic's official guidance
|
||||
- **Action** — auto-fix with mandatory backups, syntax validation, rollback support, and a human-in-the-loop workflow for anything non-trivial
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ By default, `/config-audit` auto-detects scope from your git context. Override w
|
|||
|
||||
## Deterministic Scanners
|
||||
|
||||
14 Node.js scanners that perform structural analysis an LLM cannot reliably do: schema validation, circular reference detection, import resolution, conflict detection across scopes, prompt-cache-aware token-cost analysis, cache-prefix stability, dead-tool detection, cross-plugin skill collisions, and output-style validation. Plus a standalone plugin-health scanner. Zero external dependencies.
|
||||
15 Node.js scanners that perform structural analysis an LLM cannot reliably do: schema validation, circular reference detection, import resolution, conflict detection across scopes, prompt-cache-aware token-cost analysis, cache-prefix stability, dead-tool detection, cross-plugin skill collisions, output-style validation, and a best-practice optimization lens (mechanism-fit). Plus a standalone plugin-health scanner. Zero external dependencies.
|
||||
|
||||
**Why deterministic?** LLMs are powerful at understanding intent and context. But they cannot reliably validate JSON schemas, detect circular `@import` chains, or catch that your global `settings.json` contradicts your project-level one. These scanners fill that gap — fast, repeatable, and zero false positives on structural issues.
|
||||
|
||||
|
|
@ -325,6 +325,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 |
|
||||
|
||||
> **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
|
||||
|
|
@ -524,7 +525,7 @@ node scanners/posture.mjs examples/optimal-setup/
|
|||
|
||||
### Self-Audit: Scanning the Scanner
|
||||
|
||||
The plugin runs all 14 scanners + the standalone plugin-health scanner on itself via `self-audit.mjs`. Test fixtures and example files are automatically excluded from scoring — a configuration plugin that ships deliberately broken examples shouldn't fail its own audit. Use `--check-readme` to verify badge counts are in sync with the filesystem.
|
||||
The plugin runs all 15 scanners + the standalone plugin-health scanner on itself via `self-audit.mjs`. Test fixtures and example files are automatically excluded from scoring — a configuration plugin that ships deliberately broken examples shouldn't fail its own audit. Use `--check-readme` to verify badge counts are in sync with the filesystem.
|
||||
|
||||
```bash
|
||||
node scanners/self-audit.mjs
|
||||
|
|
@ -597,7 +598,7 @@ Markdown files remain the human-readable mirror. Loaded and validated by
|
|||
node --test 'tests/**/*.test.mjs'
|
||||
```
|
||||
|
||||
1045 tests across 58 test files (18 lib + 30 scanner + 1 hook + 1 agent + 3 commands + 1 knowledge + 4 top-level). Test fixtures in `tests/fixtures/`. Requires Node.js 18+ (`node:test`).
|
||||
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/`. Requires Node.js 18+ (`node:test`).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
48
STATE.md
48
STATE.md
|
|
@ -2,36 +2,38 @@
|
|||
|
||||
_Current state-of-play for DENNE pluginen. Auto-injisert ved sesjonsstart. Overskrives (ikke append) ved sesjonsslutt. Historikk → git (STATE.md er tracked). Stier relative til plugin-mappa._
|
||||
|
||||
## Aktiv kontekst (v5.7 Fase 1 Chunk 1 DONE — neste: Chunk 2 = optimerings-linsen `CA-OPT`)
|
||||
v5.6.0 released. **v5.7 Fase 1 Chunk 1 (register-fundament) ferdig & verifisert** denne økten. Suite **1045**, scanner **14**, self-audit **A/A** (plugin 100 / config 93), readmeCheck passed.
|
||||
## Aktiv kontekst (v5.7 Fase 1 Chunk 2a DONE — neste: Chunk 2b = opus-analyzer)
|
||||
v5.6.0 released. **v5.7 Fase 1 Chunk 1 (register) + Chunk 2a (linse `CA-OPT-001`) ferdig & verifisert.** Suite **1055** (hermetisk HOME), scanner **15** (`CA-OPT`), self-audit **A/A** (plugin 100 / config 94, hermetisk HOME), readmeCheck passed. Plan: `docs/v5.7-optimization-lens-plan.md`.
|
||||
|
||||
Visjons-diskusjonen (2026-06-20) ga GO-ready plan → **`docs/v5.7-optimization-lens-plan.md`** (les FØR videre arbeid). Skifte: korrekt? → optimal? («F1-tuning»). 4 byggeklosser, 2 faser. **Fase 1 = blokk 1+2; Fase 2 = blokk 3+4 (utsatt, eget GO).**
|
||||
## v5.7 Fase 1 — låste beslutninger (2026-06-20)
|
||||
- **Rekkefølge:** Fase 1 (linse + kunnskapsbase) FØRST, så Fase 2 (kampanje + backlog, eget GO).
|
||||
- **Kunnskaps-format:** strukturert **JSON**-register (zero-dep avgjorde); markdown som speil.
|
||||
- **Linse-motor:** ny familie `CA-OPT` + hybrid motor. **Chunk 2 splittet i 2a (determ.) + 2b (agent)** av chunk-sizing + arkitektur («scannere emitter byte-stabilt; agenter beriker»).
|
||||
- **OPT-scoring → `'CLAUDE.md'`-area** (eksisterende → ingen ny posture-rad → byte-stabil; unngår GAP-utilization-konflikt som `'Feature Coverage'` ville gitt). Humanizer-kategori `'Missed opportunity'` (mulighet, ikke feil).
|
||||
|
||||
## Låste beslutninger (2026-06-20)
|
||||
- **Rekkefølge:** Fase 1 (linse + kunnskapsbase) FØRST, så Fase 2 (kampanje + backlog).
|
||||
- **Kunnskaps-format:** strukturert **JSON**-register (zero-dep avgjorde YAML vs JSON; `yaml-parser.mjs` kan ikke arrays-of-objects); markdown som speil.
|
||||
- **Linse-motor:** ny familie (`CA-OPT`) + hybrid motor (determ. pre-filter → opus-analyzer), presisjons-gated.
|
||||
|
||||
## Chunking (ett GO'd-sesjon hver)
|
||||
1. **Chunk 1 — Register-fundament: ✅ DONE.** `knowledge/best-practices.json` (13 entries: V-rader + blogg-regler, kildefestet) + `scanners/lib/best-practices-register.mjs` (loadRegister/validateRegister/getEntry) + `tests/lib/best-practices-register.test.mjs` (22 tester). Byte-stabil (ingen scanner konsumerer det ennå).
|
||||
2. **Chunk 2 — Linsen (`CA-OPT`) — NESTE, KREVER GO:** determ. pre-filter + opus-analyzer-agent som leser registeret + dømmer mekanisme-fit + humanizer/scoring-wiring + fixtures (positiv + negativ, null false-positive) + byte-stab strip (følg CA-OST-presedens). lensCheck-feltene i seedet peker allerede på detektorene som skal bygges.
|
||||
3. **Chunk 3 — `knowledge-refresh`:** semi-auto poller (`--dry-run`, stale/candidate, human-godkjente skriv).
|
||||
## Chunking
|
||||
1. **Chunk 1 — Register-fundament: ✅ DONE** (`55f83a3`). best-practices.json + register-modul + 22 tester.
|
||||
2. **Chunk 2a — Linsen, determ. del: ✅ DONE** (denne økten). `CA-OPT-001`: ≥6-stegs nummerert prosedyre i CLAUDE.md → skill (BP-MECH-003), konservativ, negativt korpus = null FP. Full wiring (orchestrator/humanizer/scoring/strip/SC-5 regenerert). 10 nye tester.
|
||||
3. **Chunk 2b — Linsen, opus-analyzer — NESTE, KREVER GO:** ikke-deterministisk agent (søster av feature-gap-agent) for prose-judgment-tilfellene linsen bevisst hopper over: lifecycle→hook (BP-MECH-001), unscoped path-spesifikk→rule (BP-MECH-002), «never»→permission (BP-MECH-004). Leser registeret + CA-OPT-001-kandidatene. Ikke i byte-stabil suite (egen kommando). lensCheck-feltene i registeret navngir detektorene.
|
||||
4. **Chunk 3 — `knowledge-refresh`:** semi-auto poller (`--dry-run`, stale/candidate, human-godkjente skriv).
|
||||
|
||||
## Åpne tråder / neste steg (prioritert)
|
||||
1. **Chunk 2 (CA-OPT-linsen) — KREVER GO.** Les plan-doc-en + registeret først.
|
||||
2. **Fase 2 (kampanje + backlog) — utsatt, eget GO** etter linse-validering på ett repo.
|
||||
3. **Åpne design-detaljer** (i planen): endelig ID-prefix (`CA-OPT`?), refresh-kilder/kadens, `/repo-init`-synergi (linsen som kvalitetsmåler).
|
||||
4. **llm-security KRITISK** (annet repo) + 3 aktive plugins + STATE-sveip — uendret. Egne GO.
|
||||
1. **Chunk 2b (opus-analyzer) — KREVER GO.** Les plan-doc + registeret (BP-MECH-001/002/004).
|
||||
2. **TOK-test mangler HOME-isolasjon (pre-eksisterende, OUT-OF-SCOPE for 2a):** `tests/scanners/token-hotspots.test.mjs` `runScanner` overstyrer ikke HOME → TOK leser ekte `~/.claude` → «emits zero findings»-asserten er miljøavhengig (rød på ekte HOME, grønn på ren HOME). Kjør suiten med ren HOME for determinisme. **Foreslått liten egen fix (eget GO):** hermetisk HOME i `runScanner`.
|
||||
3. **Fase 2 (kampanje + backlog) — utsatt, eget GO** etter linse-validering.
|
||||
4. **Åpne design-detaljer** (i planen): refresh-kilder/kadens, `/repo-init`-synergi.
|
||||
5. **llm-security KRITISK** (annet repo) + 3 aktive plugins — uendret. Egne GO.
|
||||
|
||||
## Gotchas (UFRAVIKELIG)
|
||||
- **CLAUDE.md-lengde drar config-grade.** Lange architecture-tillegg i pluginens egen CLAUDE.md tipper CML «getting long»-gradienten (A93→B89 sett denne økten). Hold tillegg lean; full detalj → `docs/`. Mål alltid self-audit mot baseline FØR commit.
|
||||
- **Scanner-/familie-tillegg = strip, IKKE re-seed (POST-B2).** Bevar frozen v5.0.0 via strip-helpere; re-seed KUN SC-5. `CA-OPT` (Chunk 2) følger CA-OST-presedensen (bumper `scanners_ok` → strip).
|
||||
- **Release-cut = 3 filer + annotert tag + catalog.** plugin.json + README (badge + 1 version-history-rad) + CHANGELOG; så `git tag -a`; så `../catalog` ref-bump. tests-badge = `ℹ tests N` (`countTestCases`), `readmeCheck.passed:true`. Nå: **1045**, scanner **14**. Knowledge-badge teller kun `.md` (JSON-registeret påvirker den ikke).
|
||||
- **Persistens (verifisert):** sesjoner i `~/.claude/config-audit/sessions/` ligger UTENFOR plugin-mappa → overlever reinstall/oppgradering.
|
||||
- Push-vindu: hverdag 20–23, helg fritt. docs-gate: `feat:` krever ≥3 ikke-ws-linjer i README+CLAUDE.md.
|
||||
- **Verifiser suiten med ren HOME.** Flere tester (TOK + dermed SC-5/orchestrator via TOK) leser ekte `~/.claude` → ikke-hermetiske. `HOME=$(mktemp -d) node --test 'tests/**/*.test.mjs'`. Regenerer ALDRI SC-5 med ekte HOME (baker inn sesjons-drift) — bruk ren HOME.
|
||||
- **CLAUDE.md-lengde drar config-grade.** Hold architecture-tillegg i pluginens egen CLAUDE.md lean; full detalj → `docs/`. Mål self-audit mot baseline (ren HOME) FØR commit. Nå: 379 linjer / 25.8k chars (under 500/40k).
|
||||
- **Scanner-/familie-tillegg = strip, IKKE re-seed.** `strip-added-scanner.mjs` ADDED_SCANNERS = {OST, OPT}; re-seed KUN SC-5 (ren HOME). Hardkodet scanner-count i `scan-orchestrator-humanizer.test.mjs` (`scanners.length`).
|
||||
- **Release-cut = 3 filer + annotert tag + catalog.** tests-badge `ℹ tests N`, scanner-badge, `readmeCheck.passed`. Nå: **1055**, scanner **15**. Knowledge-badge teller kun `.md`.
|
||||
- **Persistens (verifisert):** sesjoner i `~/.claude/config-audit/sessions/` overlever reinstall/oppgradering.
|
||||
- Push-vindu: hverdag 20–23, helg fritt. Auto-commit per chunk + push når naturlig (operatør-beslutning, se memory `chunk-commit-push-flow`).
|
||||
|
||||
## Scope-gjerde
|
||||
Chunk 1 komplett, verifisert, committet. **Chunk 2 (CA-OPT) krever eget GO.** Ingen annen kode-oppgave åpen.
|
||||
Chunk 1 + 2a komplett, verifisert (ren HOME), committet. **Chunk 2b (opus-analyzer) + TOK-fix + Fase 2 = egne GO.**
|
||||
|
||||
## Kontinuitet
|
||||
Tre lag: STATE.md (tracked) + auto-memory (`config-audit-vision` — låste beslutninger) + CLAUDE.md. Plan-detalj → `docs/v5.7-optimization-lens-plan.md`. Oppdater DENNE som siste handling.
|
||||
Tre lag: STATE.md + auto-memory (`config-audit-vision`, `chunk-commit-push-flow`) + CLAUDE.md. Plan → `docs/v5.7-optimization-lens-plan.md`. Oppdater DENNE som siste handling.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ Scanner CLI: `node scanners/scan-orchestrator.mjs <path> [--global] [--full-mach
|
|||
| `collision-scanner.mjs` | COL | Cross-plugin skill name collisions (low); user-vs-plugin overlaps (medium); `details.namespaces` payload |
|
||||
| `skill-listing-scanner.mjs` | SKL | (1) `CA-SKL-001` (medium): active skill descriptions over the verified 1,536-char listing cap (CC 2.1.105) → silently truncated in the model's skill listing. (2) `CA-SKL-002` (low): sum of active descriptions (each counted up to the cap) over the listing budget (~2% of context, CC 2.1.32), anchored on a conservative 200k window with a calibration note that the budget scales 5× on 1M-context models — leads with the measured sum, an estimate not telemetry. HOME-scoped (all user + plugin skills). Remediation surfaces `disableBundledSkills` / `skillOverrides` / trim. Distinct lens from TOK pattern F (project-local 500-char bloat heuristic) |
|
||||
| `output-style-scanner.mjs` | OST | (1) `CA-OST-001` (medium): a user/project custom output style not setting `keep-coding-instructions: true` (defaults false) → silently strips Claude Code's built-in software-engineering instructions when active (V10). (2) `CA-OST-002` (low): a **plugin** style with `force-for-plugin: true` auto-applies and overrides the user's `outputStyle` (V11; plugin-styles-only per docs). (3) `CA-OST-003` (medium): a settings `outputStyle` matching no built-in (Default/Explanatory/Learning/Proactive, case-insensitive) nor discovered custom style → dead config (CC falls back to default). Reads each style's frontmatter via `parseFrontmatter`; fixture-gated (silent with no output styles). New scanner family in v5.6 C (count 13→14) |
|
||||
| `optimization-lens-scanner.mjs` | OPT | `CA-OPT-001` (low, *Missed opportunity*): a CLAUDE.md procedure (≥6 consecutive numbered steps) that belongs in a skill (mechanism-fit, `BP-MECH-003`). Reads the machine-readable best-practices register (`best-practices-register.mjs`) for recommendation + provenance. Conservative — negative corpus proves null false-positive; prose-judgment cases (lifecycle→hook, "never"→permission) deferred to the Chunk 2b opus analyzer. Scoring area `CLAUDE.md` (existing → byte-stable). New scanner family in v5.7 Fase 1 Chunk 2a (count 14→15) |
|
||||
|
||||
## Scanner Lib (`scanners/lib/`)
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ Scanner CLI: `node scanners/scan-orchestrator.mjs <path> [--global] [--full-mach
|
|||
| `active-config-reader.mjs` | Read-only inventory: readActiveConfig(), detectGitRoot(), walkClaudeMdCascade(), readClaudeJsonProjectSlice() (longest-prefix match), enumeratePlugins(), enumerateSkills(), readActiveHooks(), readActiveMcpServers() (with cache → package.json tool-count fallback), estimateTokens() (v5: `'mcp'` kind = 500 + toolCount × 200) |
|
||||
| `tokenizer-api.mjs` | Anthropic `count_tokens` wrapper for `--accurate-tokens` (v5 N5); 5s AbortController timeout, exponential 429 backoff, key masking |
|
||||
| `humanizer.mjs` | Plain-language output translator (v5.1.0): `humanizeFinding`, `humanizeFindings`, `humanizeEnvelope`, `computeRelevanceContext`. Pure functions; never mutate inputs. Adds `userImpactCategory`, `userActionLanguage`, `relevanceContext` fields and replaces title/description/recommendation when a translation exists. Bypassed by `--raw` and `--json` paths. |
|
||||
| `humanizer-data.mjs` | TRANSLATIONS table for 15 scanner prefixes (CML/SET/HKV/RUL/MCP/IMP/CNF/COL/TOK/CPS/DIS/GAP/PLH/SKL/OST). Three-step lookup: exact title → regex pattern → `_default` → fall through to original |
|
||||
| `humanizer-data.mjs` | TRANSLATIONS table for 16 scanner prefixes (CML/SET/HKV/RUL/MCP/IMP/CNF/COL/TOK/CPS/DIS/GAP/PLH/SKL/OST/OPT). Three-step lookup: exact title → regex pattern → `_default` → fall through to original |
|
||||
|
||||
## Action Engines (`scanners/`)
|
||||
|
||||
|
|
|
|||
|
|
@ -850,4 +850,24 @@ export const TRANSLATIONS = {
|
|||
recommendation: 'See the details for which output style to adjust.',
|
||||
},
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// OPT — Optimization Lens (mechanism-fit)
|
||||
// Category: Missed opportunity
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
OPT: {
|
||||
static: {
|
||||
'A multi-step procedure in CLAUDE.md belongs in a skill': {
|
||||
title: 'A long checklist in CLAUDE.md could be a skill instead',
|
||||
description: 'Your CLAUDE.md has a multi-step procedure that loads on every turn, costing tokens whether or not you\'re doing that task. Procedures fit better as a skill, whose steps load only when you actually run them.',
|
||||
recommendation: 'Move the steps into a skill under `.claude/skills/`. Keep CLAUDE.md for facts Claude should always know, not step-by-step procedures.',
|
||||
},
|
||||
},
|
||||
patterns: [],
|
||||
_default: {
|
||||
title: 'Your setup could fit Claude Code a little better',
|
||||
description: 'A check found a setup that works but where a different mechanism would fit the job better.',
|
||||
recommendation: 'See the details for the suggested change.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ const SCANNER_TO_CATEGORY = {
|
|||
GAP: 'Missed opportunity',
|
||||
PLH: 'Configuration mistake',
|
||||
OST: 'Configuration mistake',
|
||||
OPT: 'Missed opportunity',
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ const SCANNER_AREA_MAP = {
|
|||
DIS: 'Settings',
|
||||
COL: 'Plugin Hygiene',
|
||||
OST: 'Settings',
|
||||
OPT: 'CLAUDE.md',
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
146
scanners/optimization-lens-scanner.mjs
Normal file
146
scanners/optimization-lens-scanner.mjs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* OPT Scanner — Optimization Lens / mechanism-fit (v5.7 Fase 1 Chunk 2a)
|
||||
*
|
||||
* The first detector of the "is the config OPTIMAL?" axis (vs. the existing
|
||||
* "is it CORRECT?" scanners). It reads the machine-readable best-practices
|
||||
* register (knowledge/best-practices.json) and flags config that works but uses
|
||||
* a mechanism a better one would fit — the deterministic half of the hybrid
|
||||
* motor (the opus analyzer for prose-judgment cases is Chunk 2b).
|
||||
*
|
||||
* CA-OPT-001 A multi-step procedure in CLAUDE.md should be a SKILL (BP-MECH-003).
|
||||
* CLAUDE.md is for facts Claude holds every turn; a procedure there
|
||||
* costs always-loaded tokens whether or not you run it, and a skill's
|
||||
* body loads only on invoke. Detection is deliberately CONSERVATIVE
|
||||
* (a run of >= 6 consecutive numbered steps) to keep precision high —
|
||||
* the negative corpus in the tests proves null false-positives.
|
||||
* Framed as a Missed opportunity (humanizer), severity LOW.
|
||||
*
|
||||
* Provenance: the recommendation + claim come from the register entry (only a
|
||||
* `confirmed` entry is used user-facing — Verifiseringsplikt); an inline default
|
||||
* is the graceful fallback if the register is unavailable. Fixture-gated: the
|
||||
* marketplace-medium CLAUDE.md has no numbered lists, so it emits nothing (SC-5
|
||||
* byte-stable; the additive OPT scanner entry is stripped from frozen baselines).
|
||||
*
|
||||
* Zero external dependencies.
|
||||
*/
|
||||
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { finding, scannerResult } from './lib/output.mjs';
|
||||
import { SEVERITY } from './lib/severity.mjs';
|
||||
import { parseFrontmatter } from './lib/yaml-parser.mjs';
|
||||
import { loadRegister, getEntry } from './lib/best-practices-register.mjs';
|
||||
|
||||
const SCANNER = 'OPT';
|
||||
const STEP_THRESHOLD = 6;
|
||||
const STEP_RE = /^\s*\d+\.\s+\S/;
|
||||
const PROCEDURE_TITLE = 'A multi-step procedure in CLAUDE.md belongs in a skill';
|
||||
|
||||
// Graceful fallback if the register is missing/unreadable (the register is the
|
||||
// source of truth; this keeps the scanner working without it).
|
||||
const DEFAULT_MECH_003 = {
|
||||
claim:
|
||||
'A multi-step procedure in CLAUDE.md should be a skill — CLAUDE.md is for facts Claude ' +
|
||||
'should hold all the time; procedures belong in skills.',
|
||||
recommendation:
|
||||
'Extract the procedure into .claude/skills/; its body then loads only on invoke instead ' +
|
||||
'of every turn.',
|
||||
};
|
||||
|
||||
/** Return the confirmed register entry for `id`, or null (→ caller uses default). */
|
||||
function confirmedEntry(id) {
|
||||
try {
|
||||
const e = getEntry(loadRegister(), id);
|
||||
return e && e.confidence === 'confirmed' ? e : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Longest run of consecutive numbered-list items. Blank lines and indented
|
||||
* continuation lines neither extend nor break a run; any other non-step line
|
||||
* breaks it. Conservative by design (a wrapped, non-indented step line ends the
|
||||
* run → undercount, never overcount).
|
||||
* @param {string} text
|
||||
* @returns {{count:number, startIndex:number, firstStep:string}}
|
||||
*/
|
||||
function longestNumberedRun(text) {
|
||||
const lines = String(text).split('\n');
|
||||
let maxRun = 0;
|
||||
let maxStartIdx = 0;
|
||||
let maxFirstStep = '';
|
||||
let run = 0;
|
||||
let runStartIdx = 0;
|
||||
let runFirstStep = '';
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (STEP_RE.test(line)) {
|
||||
if (run === 0) {
|
||||
runStartIdx = i;
|
||||
runFirstStep = line.trim();
|
||||
}
|
||||
run++;
|
||||
if (run > maxRun) {
|
||||
maxRun = run;
|
||||
maxStartIdx = runStartIdx;
|
||||
maxFirstStep = runFirstStep;
|
||||
}
|
||||
} else if (line.trim() === '' || /^\s+\S/.test(line)) {
|
||||
// blank or indented continuation — part of the list, neither step nor break
|
||||
} else {
|
||||
run = 0;
|
||||
}
|
||||
}
|
||||
return { count: maxRun, startIndex: maxStartIdx, firstStep: maxFirstStep };
|
||||
}
|
||||
|
||||
export async function scan(targetPath, discovery) {
|
||||
const start = Date.now();
|
||||
const findings = [];
|
||||
const claudeMdFiles = ((discovery && discovery.files) || []).filter((f) => f.type === 'claude-md');
|
||||
|
||||
const entry = confirmedEntry('BP-MECH-003');
|
||||
const claim = (entry && entry.claim) || DEFAULT_MECH_003.claim;
|
||||
const recommendation = (entry && entry.recommendation) || DEFAULT_MECH_003.recommendation;
|
||||
|
||||
let filesScanned = 0;
|
||||
for (const file of claudeMdFiles) {
|
||||
let content;
|
||||
try {
|
||||
content = await readFile(file.absPath, 'utf-8');
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
filesScanned++;
|
||||
|
||||
const parsed = parseFrontmatter(content);
|
||||
const body = parsed.body || content;
|
||||
const bodyStartLine = parsed.bodyStartLine || 1;
|
||||
const run = longestNumberedRun(body);
|
||||
|
||||
if (run.count >= STEP_THRESHOLD) {
|
||||
findings.push(
|
||||
finding({
|
||||
scanner: SCANNER,
|
||||
severity: SEVERITY.low,
|
||||
title: PROCEDURE_TITLE,
|
||||
description: claim,
|
||||
file: file.relPath || file.absPath,
|
||||
line: bodyStartLine + run.startIndex,
|
||||
evidence: run.firstStep,
|
||||
recommendation,
|
||||
category: 'mechanism-fit',
|
||||
details: {
|
||||
mechanism: 'skill',
|
||||
steps: run.count,
|
||||
register: entry ? entry.id : null,
|
||||
source: entry ? entry.source.url : null,
|
||||
confidence: entry ? entry.confidence : null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return scannerResult(SCANNER, 'ok', findings, filesScanned, Date.now() - start);
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import { scan as scanDisabledInSchema } from './disabled-in-schema-scanner.mjs';
|
|||
import { scan as scanCollision } from './collision-scanner.mjs';
|
||||
import { scan as scanSkillListing } from './skill-listing-scanner.mjs';
|
||||
import { scan as scanOutputStyle } from './output-style-scanner.mjs';
|
||||
import { scan as scanOptimizationLens } from './optimization-lens-scanner.mjs';
|
||||
|
||||
// Directory names that identify test fixture / example directories
|
||||
const FIXTURE_DIR_NAMES = ['tests', 'examples', '__tests__', 'test-fixtures'];
|
||||
|
|
@ -66,6 +67,7 @@ const SCANNERS = [
|
|||
{ name: 'COL', fn: scanCollision, label: 'Plugin Skill Collision' },
|
||||
{ name: 'SKL', fn: scanSkillListing, label: 'Skill-Listing Budget' },
|
||||
{ name: 'OST', fn: scanOutputStyle, label: 'Output-Style Validation' },
|
||||
{ name: 'OPT', fn: scanOptimizationLens, label: 'Optimization Lens' },
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* return stripAddedScanners(stripHotspotLoadPattern(out));
|
||||
*/
|
||||
|
||||
const ADDED_SCANNERS = new Set(['OST']);
|
||||
const ADDED_SCANNERS = new Set(['OST', 'OPT']);
|
||||
|
||||
function stripFromEnvelope(env) {
|
||||
if (!env || typeof env !== 'object' || !Array.isArray(env.scanners)) return;
|
||||
|
|
@ -58,5 +58,5 @@ export function stripAddedScanners(payload) {
|
|||
*/
|
||||
export function stripAddedScannerStderr(text) {
|
||||
if (typeof text !== 'string') return text;
|
||||
return text.replace(/^[ \t]*\[OST\][^\n]*\n/gm, '');
|
||||
return text.replace(/^[ \t]*\[(OST|OPT)\][^\n]*\n/gm, '');
|
||||
}
|
||||
|
|
|
|||
112
tests/scanners/optimization-lens-scanner.test.mjs
Normal file
112
tests/scanners/optimization-lens-scanner.test.mjs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
|
|
@ -103,7 +103,7 @@ describe('scan-orchestrator humanizer wiring (Step 5)', () => {
|
|||
assert.ok(actual.meta, 'meta present');
|
||||
assert.ok(Array.isArray(actual.scanners), 'scanners array present');
|
||||
assert.ok(actual.aggregate, 'aggregate present');
|
||||
assert.equal(actual.scanners.length, 14, 'all 14 scanners present');
|
||||
assert.equal(actual.scanners.length, 15, 'all 15 scanners present');
|
||||
});
|
||||
|
||||
it('preserves scanner shape (scanner/status/findings/counts)', async () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"kind": "text",
|
||||
"payload": "`[CML] CLAUDE.md Linter`: 1 finding(s) (0ms)\n `[SET] Settings Validator`: 0 finding(s) (0ms)\n `[HKV] Hook Validator`: 0 finding(s) (0ms)\n `[RUL] Rules Validator`: 0 finding(s) (0ms)\n `[MCP] MCP Config Validator`: 0 finding(s) (0ms)\n `[IMP] Import Resolver`: 0 finding(s) (0ms)\n `[CNF] Conflict Detector`: 0 finding(s) (0ms)\n `[GAP] Feature Gap Scanner`: 17 finding(s) (0ms)\n `[TOK] Token Hotspots`: 1 finding(s) (0ms)\n `[CPS] Cache-Prefix Stability`: 0 finding(s) (0ms)\n `[DIS] Disabled-In-Schema`: 0 finding(s) (0ms)\n `[COL] Plugin Skill Collision`: 0 finding(s) (0ms)\n `[SKL] Skill-Listing Budget`: 0 finding(s) (0ms)\n `[OST] Output-Style Validation`: 0 finding(s) (0ms)\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Configuration health\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n Health: A (98/100) — Healthy setup, only minor polish needed\n 9 areas reviewed\n\n Area scores\n ───────────\n `CLAUDE.md` ........... A (90) `Settings` ............ A (100)\n `Hooks` ............... A (100) `Rules` ............... A (100)\n `MCP` ................. A (100) `Imports` ............. A (100)\n `Conflicts` ........... A (100) `Token Efficiency` .... A (90)\n `Plugin Hygiene` ...... A (100)\n\n 17 ways you could get more out of Claude Code — see /config-audit feature-gap\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
"payload": "`[CML] CLAUDE.md Linter`: 1 finding(s) (0ms)\n `[SET] Settings Validator`: 0 finding(s) (0ms)\n `[HKV] Hook Validator`: 0 finding(s) (0ms)\n `[RUL] Rules Validator`: 0 finding(s) (0ms)\n `[MCP] MCP Config Validator`: 0 finding(s) (0ms)\n `[IMP] Import Resolver`: 0 finding(s) (0ms)\n `[CNF] Conflict Detector`: 0 finding(s) (0ms)\n `[GAP] Feature Gap Scanner`: 17 finding(s) (0ms)\n `[TOK] Token Hotspots`: 1 finding(s) (0ms)\n `[CPS] Cache-Prefix Stability`: 0 finding(s) (0ms)\n `[DIS] Disabled-In-Schema`: 0 finding(s) (0ms)\n `[COL] Plugin Skill Collision`: 0 finding(s) (0ms)\n `[SKL] Skill-Listing Budget`: 0 finding(s) (0ms)\n `[OST] Output-Style Validation`: 0 finding(s) (0ms)\n `[OPT] Optimization Lens`: 0 finding(s) (0ms)\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Configuration health\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n Health: A (98/100) — Healthy setup, only minor polish needed\n 9 areas reviewed\n\n Area scores\n ───────────\n `CLAUDE.md` ........... A (90) `Settings` ............ A (100)\n `Hooks` ............... A (100) `Rules` ............... A (100)\n `MCP` ................. A (100) `Imports` ............. A (100)\n `Conflicts` ........... A (100) `Token Efficiency` .... A (90)\n `Plugin Hygiene` ...... A (100)\n\n 17 ways you could get more out of Claude Code — see /config-audit feature-gap\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -582,6 +582,20 @@
|
|||
"low": 0,
|
||||
"info": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"scanner": "OPT",
|
||||
"status": "ok",
|
||||
"files_scanned": 1,
|
||||
"duration_ms": 0,
|
||||
"findings": [],
|
||||
"counts": {
|
||||
"critical": 0,
|
||||
"high": 0,
|
||||
"medium": 0,
|
||||
"low": 0,
|
||||
"info": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"aggregate": {
|
||||
|
|
@ -596,7 +610,7 @@
|
|||
"risk_score": 11,
|
||||
"risk_band": "Medium",
|
||||
"verdict": "PASS",
|
||||
"scanners_ok": 13,
|
||||
"scanners_ok": 14,
|
||||
"scanners_error": 0,
|
||||
"scanners_skipped": 1
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue