# 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 /sgconfig.yml --json=compact ``` - `` 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//...`.