Files
simplify-code/references/DETECTION.md
T
Giancarmine SalucciandClaude Opus 4.8 81b33fff26 refactor: report-only skill — actionable findings, no apply/modes
The skill no longer modifies code. It emits exactly two artifacts under simplify/:
findings.json (machine-actionable ledger) + a ranked report. Every finding now REQUIRES
title/action/verify (locked in ledger.schema.json) so each is independently executable by
the calling agent. Removed: apply/report modes, Track A autofix execution, baseline gate,
verify-each-change loop, the include-tests switch (tests excluded by default). Reframed
SKILL.md, README, PATTERNS (deterministic/judgment), DETECTION, OPTIMIZATION (judge-only
model tiering), PORTABILITY, SVELTE, CONSOLIDATION accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 02:53:19 +02:00

4.6 KiB
Raw Blame History

Detection, ranking & the ledger

How candidates are found mechanically, scored, classified, and recorded — so the model works from a small file on disk instead of the whole codebase.

The scan command

AST_GREP scan -c <skill-dir>/sgconfig.yml --json=compact <paths>
  • <paths> is the git-visible candidate set (SKILL.md Step 1), with test files excluded by default (overridable via the include-tests switch; recorded in config.include_tests).
  • --json=compact emits one JSON array of matches. Use --json=stream for very large repos (newline-delimited; process incrementally).

JSON fields you can rely on

Each match object includes (ast-grep 0.44):

  • ruleId — the rule that matched (join key to rules/manifest.json).
  • file — path.
  • range.start.line / range.end.line0-indexed; add 1 for human/editor lines.
  • range.start.column / range.end.column, plus byte offsets.
  • lines — the matched source text.
  • metaVariables — captured $VARS (single + $$$ multi).
  • severity, message, note.

metadata set in a rule is not emitted in JSON. That is why classification lives in rules/manifest.json, keyed by ruleId — read it once during assembly.

Classification (rules/manifest.json)

For every ruleId, the manifest gives:

  • pattern — catalog key (references/PATTERNS.md),
  • trackast-grep (a deterministic autofix command exists) or llm (needs a judgment edit); the assembly maps this to the finding's fix_kind. The skill never applies it,
  • difficultymechanical | semantic | hard,
  • weight — contribution to the ranking score,
  • optional requires — a capability gate (e.g. svelte-grammar).

Every manifest pattern resolves to a heading in PATTERNS.md. The reverse isn't required: a few catalog patterns (extract-method, remove-dead-code) have no detector rule — they come from metrics/enrichment, so they carry no manifest entry.

If a ruleId is missing from the manifest, default to track: llm, difficulty: semantic, weight: 5, and note it.

Metrics that aren't single nodes

ast-grep matches nodes, not measurements. Derive these during assembly:

  • Function length — from a finding's range (end start lines), or from enrichment.
  • Param count — the *-long-param-list rules fire at ≥5 via nthChild; exact count comes from the matched signature text or enrichment.
  • Nesting depth — the *-deep-nesting rules fire at ≥3 nested conditionals.
  • Cyclomatic complexity — only via enrichment tools (below); otherwise approximate from nesting + branch counts.

Optional enrichment tools (only if already installed)

Never install these; use them when present and fold output into signal/extra findings.

Language Metrics Dead code Duplication
any scc (size), lizard (CC, multi-lang) jscpd
JS/TS eslint (+sonarjs cognitive complexity) knip jscpd
Python radon (CC/MI) vulture jscpd
Go gocyclo staticcheck
Rust rust-code-analysis, cargo clippy cargo-machete (deps)
Java PMD (CC + dead + CPD) PMD PMD CPD
Svelte svelte-check, eslint-plugin-svelte, svelte/compiler warnings eslint

Ranking (biggest-win-first)

Per finding, base score from the manifest weight, scaled by available metrics:

severity = manifest.weight
         + max(0, cyclomatic - 10) * (lines / 20)
         + unused_params * 10
         + dup_blocks * 50

Clamp to 0100. Process highest first, capped at a small top-N per run (default 10) plus any user/token budget. Tune the weights against fixtures/.

The ledger (simplify/findings.json)

Full schema + example: ../assets/ledger.schema.json. Two blocks: config (Step 4) and findings. Key rule: status is the source of truth — on rerun, load the ledger and process only status: pending; never blindly re-scan-and-overwrite.

Authoring new rules

  • One YAML doc per rule (separate docs in a file with ---). Required: id, language, rule. Add fix only when the rewrite is behavior-preserving for all operand types (→ track: ast-grep); otherwise leave it out (→ track: llm).
  • Quote any pattern containing : or ? (e.g. ternaries) or YAML mis-parses it.
  • Prefer relational rules (inside/has + stopBy: end, nthChild) for structural smells; prefer pattern for concrete rewrites.
  • Add a matching entry to rules/manifest.json and a fixture under fixtures/.
  • Validate: AST_GREP scan -c sgconfig.yml --json=compact fixtures/<lang>/....