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>
102 lines
4.6 KiB
Markdown
102 lines
4.6 KiB
Markdown
# 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.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`.
|
||
|
||
> `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`),
|
||
- `track` — `ast-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,
|
||
- `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-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 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`. 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>/...`.
|