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>
4.6 KiB
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 inconfig.include_tests).--json=compactemits one JSON array of matches. Use--json=streamfor 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 torules/manifest.json).file— path.range.start.line/range.end.line— 0-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.
metadataset in a rule is not emitted in JSON. That is why classification lives inrules/manifest.json, keyed byruleId— read it once during assembly.
Classification (rules/manifest.json)
For every ruleId, the manifest gives:
pattern— catalog key (references/PATTERNS.md),track—ast-grep(a deterministic autofix command exists) orllm(needs a judgment edit); the assembly maps this to the finding'sfix_kind. The skill never applies it,difficulty—mechanical | 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-listrules fire at ≥5 vianthChild; exact count comes from the matched signature text or enrichment. - Nesting depth — the
*-deep-nestingrules 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 0–100. 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. Addfixonly when the rewrite is behavior-preserving for all operand types (→track: ast-grep); otherwise leave it out (→track: llm). - Quote any
patterncontaining:or?(e.g. ternaries) or YAML mis-parses it. - Prefer relational rules (
inside/has+stopBy: end,nthChild) for structural smells; preferpatternfor concrete rewrites. - Add a matching entry to
rules/manifest.jsonand a fixture underfixtures/. - Validate:
AST_GREP scan -c sgconfig.yml --json=compact fixtures/<lang>/....