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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fc7f825268
commit
81b33fff26
@@ -4,15 +4,16 @@ A portable [Agent Skill](https://agentskills.io/specification) that simplifies a
|
||||
codebase **safely and incrementally** from any agent runtime — Claude Code, GitHub
|
||||
Copilot (CLI / VS Code), pi, Codex CLI, Gemini CLI, Cursor.
|
||||
|
||||
It reduces complexity, nesting, duplication, magic numbers, dead code, and long
|
||||
functions **while preserving behavior** — detecting opportunities mechanically with a
|
||||
bundled [`ast-grep`](https://ast-grep.github.io/) engine, ranking the biggest wins,
|
||||
and applying fixes one at a time with build/test verification.
|
||||
It finds complexity, nesting, duplication, magic numbers, dead code, long functions,
|
||||
duplicated enums/types, near-duplicate functions, and reinvented utilities — detecting
|
||||
mechanically with bundled engines ([`ast-grep`](https://ast-grep.github.io/), cpd, scc,
|
||||
biome, ruff), ranking the biggest wins, and writing **one actionable report** for an agent
|
||||
to act on. It does **not** modify code.
|
||||
|
||||
## Two passes
|
||||
## Two analysis passes (both report-only)
|
||||
|
||||
- **Pass A — structural simplification** (per-node): nesting, magic numbers, booleans,
|
||||
params, dead branches — apply or report, verified per change.
|
||||
- **Pass A — structural** (per-node): nesting, magic numbers, booleans, params, dead
|
||||
branches — each emitted as a finding with a concrete `action`.
|
||||
- **Pass B — consolidation & anti-pattern analysis** (cross-file, **report-only**):
|
||||
duplicate enums/types, near-duplicate functions, untyped repeated DTOs, reinvented
|
||||
utilities, plus lint/metrics enrichment. Targets the measured AI failure mode
|
||||
@@ -24,14 +25,20 @@ All engines are bundled single static binaries (ast-grep, cpd, scc, biome, ruff
|
||||
`bin/manifest.json`); nothing is assumed about the target environment, and Pass B never
|
||||
writes into the project.
|
||||
|
||||
## Report-only — it never modifies your code
|
||||
|
||||
This skill **does not edit, fix, or format anything**. It analyzes and writes two files
|
||||
under `simplify/`: `findings.json` (machine-actionable ledger) and a ranked
|
||||
`*-report.md` action queue. The calling **agent** reads the report and acts on it; the
|
||||
skill has no apply/verify/modify step and no modes.
|
||||
|
||||
## Why it stays fast and cheap
|
||||
|
||||
- **Codebase out of context.** `ast-grep` finds + ranks candidates mechanically; the
|
||||
model reads only the flagged spans, from a small JSON ledger on disk.
|
||||
- **Two-track apply.** Mechanical fixes are applied by deterministic `ast-grep`
|
||||
autofix rules (zero model tokens, zero hallucination); only genuinely semantic
|
||||
refactors go to the model.
|
||||
- **Resumable.** Progress is a `status` field in `simplify/findings.json`; reruns skip
|
||||
- **Codebase out of context.** Bundled binaries find + rank candidates mechanically; the
|
||||
model reads only flagged spans, and is used only to *judge* cross-file candidates.
|
||||
- **Actionable findings.** Each finding carries an explicit `action` (what to change) and
|
||||
`verify` (how to confirm after acting) so a finding is independently executable.
|
||||
- **Resumable.** The acting agent flips a `status` field in `findings.json`; reruns keep
|
||||
finished work.
|
||||
|
||||
## Design constraints (deliberate)
|
||||
@@ -52,7 +59,7 @@ writes into the project.
|
||||
SKILL.md entry point (read this first)
|
||||
sgconfig.yml ast-grep project config → rules/
|
||||
bin/ bundled ast-grep binary per platform
|
||||
rules/<lang>/ ast-grep rule packs (detect + autofix)
|
||||
rules/<lang>/ ast-grep rule packs (detection + suggested fixes for the report)
|
||||
references/ PATTERNS · DETECTION · OPTIMIZATION · PORTABILITY
|
||||
assets/ ledger.schema.json
|
||||
fixtures/ messy sample code for validating the skill
|
||||
@@ -61,8 +68,9 @@ fixtures/ messy sample code for validating the skill
|
||||
## Install
|
||||
|
||||
Copy or symlink this directory into your runtime's skills location — see
|
||||
`references/PORTABILITY.md` for per-OS, per-runtime instructions. Then ask your agent
|
||||
to "simplify this codebase" (preview) or "simplify and apply".
|
||||
`references/PORTABILITY.md` for per-OS, per-runtime instructions. Then ask your agent to
|
||||
"simplify this codebase" / "find duplication" — it writes `simplify/findings.json` + a
|
||||
report, which the agent (or you) then acts on.
|
||||
|
||||
## Languages
|
||||
|
||||
|
||||
@@ -1,302 +1,161 @@
|
||||
---
|
||||
name: simplify-code
|
||||
description: >-
|
||||
Simplify and refactor a codebase safely and incrementally — reduce complexity,
|
||||
nesting, duplication, magic numbers, dead code, and long functions while
|
||||
preserving behavior. Detects opportunities mechanically with ast-grep, ranks
|
||||
the biggest wins, and applies fixes one at a time with build/test verification.
|
||||
Use when asked to simplify, clean up, refactor, reduce complexity, remove dead
|
||||
code, or improve readability of code in any language (JS/TS, Python, Go, Rust,
|
||||
Java, Svelte/SvelteKit, and more).
|
||||
Analyze a codebase and produce a single actionable report of simplification &
|
||||
consolidation opportunities — complexity, nesting, duplication, magic numbers, dead
|
||||
code, long functions, duplicated enums/types, near-duplicate functions, reinvented
|
||||
utilities — for an agent to then act on. Detects mechanically with bundled tools
|
||||
(ast-grep, cpd, scc, biome, ruff) and ranks the biggest wins. This skill NEVER modifies
|
||||
code: it only writes a report. Use when asked to simplify, clean up, refactor, reduce
|
||||
complexity, de-duplicate, remove dead code, find reuse, or review anti-patterns in any
|
||||
language (JS/TS, Python, Go, Rust, Java, Svelte/SvelteKit, and more).
|
||||
license: MIT
|
||||
metadata:
|
||||
author: simplify-code
|
||||
version: "0.1.0"
|
||||
allowed-tools: Read Edit Write Bash(git:*) Bash(ast-grep:*) Bash(sg:*)
|
||||
version: "0.2.0"
|
||||
allowed-tools: Read Write Bash(git:*) Bash(ast-grep:*) Bash(sg:*) Bash(cpd:*) Bash(scc:*) Bash(biome:*) Bash(ruff:*)
|
||||
---
|
||||
|
||||
# Simplify Code
|
||||
|
||||
Systematic, behavior-preserving code simplification that runs the same way in any
|
||||
agent runtime (Claude Code, GitHub Copilot CLI/VS Code, pi, Codex, Gemini CLI).
|
||||
A **report generator**, not an editor. It analyzes a codebase and writes one actionable
|
||||
report; **it never modifies source files**. The calling agent reads the report and decides
|
||||
what to act on. Runs the same way in any runtime (Claude Code, GitHub Copilot CLI/VS Code,
|
||||
pi, Codex, Gemini CLI).
|
||||
|
||||
The skill keeps the codebase **out of your context**: a bundled `ast-grep` binary
|
||||
finds and ranks candidates mechanically, you assemble a small JSON **ledger** on
|
||||
disk, and then you touch only the flagged spans — one fix at a time, verified.
|
||||
It keeps the codebase **out of your context**: bundled binaries find and rank candidates
|
||||
mechanically; you assemble a small JSON ledger on disk; the model is used only to *judge*
|
||||
cross-file candidates, never to edit.
|
||||
|
||||
> Throughout, "run" means execute via your shell/terminal tool; "read the span"
|
||||
> means read only the indicated line range; "edit" means your native file-edit
|
||||
> tool. These map to whatever each runtime calls them.
|
||||
> "run" = execute via your shell/terminal tool; "read the span" = read only the indicated
|
||||
> line range. There is no edit step — this skill does not change code.
|
||||
|
||||
## When to use
|
||||
|
||||
Activate when the user wants to simplify / clean up / refactor / de-duplicate /
|
||||
reduce complexity / remove dead code / improve readability — for a file, a
|
||||
directory, or a whole repository.
|
||||
When the user wants to simplify / clean up / refactor / de-duplicate / reduce complexity /
|
||||
remove dead code / find reuse / review anti-patterns — for a file, directory, or repo. The
|
||||
output is a report the user (or the agent) then acts on separately.
|
||||
|
||||
## What it produces (the output contract)
|
||||
|
||||
Always exactly two files under `simplify/` in the target repo — nothing else is written:
|
||||
|
||||
1. **`simplify/findings.json`** — the machine-actionable ledger. Every finding MUST carry:
|
||||
`id`, `kind` (`structural|lint|metric|consolidation`), `detector`, `title`,
|
||||
`members` (`[{file,line}]`), `severity` (0–100), `confidence` (0–1),
|
||||
**`action`** (a concrete, executable instruction — what to change), **`verify`** (the
|
||||
command(s) to confirm it after acting), `status` (`pending` initially), and for
|
||||
consolidation: `verdict` (`consolidate|partial|keep-separate`). Schema + example:
|
||||
`assets/ledger.schema.json`.
|
||||
2. **`simplify/<report>.md`** — a ranked, human/agent-readable **action queue** built from
|
||||
the ledger, plus a "kept separate (and why)" section.
|
||||
|
||||
Every reported finding is **independently executable**: an agent can take one finding, do
|
||||
its `action`, run its `verify`, and mark `status: applied` — without re-running this skill.
|
||||
|
||||
## How it works (overview)
|
||||
|
||||
Two complementary passes:
|
||||
- **Pass A — structural simplification** (Steps 0–7 below): per-node fixes via ast-grep
|
||||
(nesting, magic numbers, booleans, params…), with apply/report modes.
|
||||
- **Pass B — consolidation & anti-pattern analysis** (see "Pass B" section): cross-file,
|
||||
AI-typical problems (duplication, failure to reuse, reinvented utilities) — **report-only**.
|
||||
Trigger when the user mentions duplication / consolidate / reuse / DRY / "duplicated
|
||||
enums or types" / anti-patterns, or asks for a deeper review. Full method:
|
||||
`references/CONSOLIDATION.md`; taxonomy: `references/ANTIPATTERNS.md`.
|
||||
|
||||
```
|
||||
Pass A: detect (ast-grep, 0 tokens) → assemble ledger (cheap) → infer build/test/lint (once)
|
||||
→ baseline gate → apply top findings (Track A: ast-grep autofix | Track B: span edit)
|
||||
→ verify each change → report
|
||||
locate engines → scope files → DETECT (Pass A structural + Pass B consolidation/lint/metrics)
|
||||
→ infer build/test (for the `verify` fields) → assemble findings.json (each with action+verify)
|
||||
→ JUDGE cross-file candidates (cheap model + AHA guardrails) → write report. No edits, ever.
|
||||
```
|
||||
|
||||
Two modes (default **report**):
|
||||
- **report** — dry-run: produce proposed diffs + `simplification-report.md`, change nothing.
|
||||
- **apply** — edit in place, re-running build/test after every change.
|
||||
|
||||
Pick mode from the user's words ("dry run / preview / report" → report; "apply / do it
|
||||
/ fix it" → apply). If unclear, do **report** first and offer to apply.
|
||||
No modes, no flags, no apply step. The skill analyzes and reports; acting is the agent's job.
|
||||
|
||||
---
|
||||
|
||||
## Step 0 — Locate the ast-grep engine
|
||||
## Step 0 — Locate the engines
|
||||
|
||||
`ast-grep` is the only required tool and is **bundled** with this skill — no install.
|
||||
Detect platform and pick the binary; fall back gracefully.
|
||||
Bundled per platform (`bin/manifest.json`) — no install. Detect `<os>-<arch>`:
|
||||
- OS: Linux→`linux`, macOS→`darwin`, Windows→`win32`. Arch: x86_64→`x64`, arm64→`arm64`.
|
||||
|
||||
1. Determine `<os>-<arch>`:
|
||||
- OS: Linux→`linux`, macOS/Darwin→`darwin`, Windows→`win32`.
|
||||
- Arch: x86_64/amd64→`x64`, arm64/aarch64→`arm64`.
|
||||
2. Use `<skill-dir>/bin/<os>-<arch>/ast-grep` (`ast-grep.exe` on Windows). Make it
|
||||
executable if needed (`chmod +x` on Unix).
|
||||
3. The binaries are bundled via **git-lfs**, so a normal clone has them. **If the file is
|
||||
an LFS pointer or missing** (lfs not installed / not pulled), run `git lfs pull`, or
|
||||
fetch the pinned release once into that dir and verify against `bin/checksums.txt`:
|
||||
the version is in `bin/VERSION`; releases are at
|
||||
`https://github.com/ast-grep/ast-grep/releases/download/<VERSION>/app-<triple>.zip`
|
||||
(triples: `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`,
|
||||
`x86_64-apple-darwin`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc`). Or just use a
|
||||
PATH `ast-grep` / install it (next step).
|
||||
5. Verify: run `ast-grep --version`.
|
||||
6. **Fallbacks**, in order, if the bundled binary won't run (rare arch, or macOS
|
||||
Gatekeeper / Windows SmartScreen blocking an unsigned binary):
|
||||
a. an `ast-grep` (or `sg`) already on `PATH`;
|
||||
b. if none, tell the user the one-line install for their OS
|
||||
(`npm i -g @ast-grep/cli` · `brew install ast-grep` · `cargo install ast-grep`
|
||||
· `scoop install ast-grep`) and offer to continue with **LLM-heuristic
|
||||
detection** (grep + reading suspicious files) — slower, less precise.
|
||||
Required: **ast-grep** at `<skill-dir>/bin/<os>-<arch>/ast-grep` (`.exe` on Windows;
|
||||
`chmod +x` on Unix). Aux (for Pass B): **cpd, scc, biome, ruff** in the same dir. Verify
|
||||
`ast-grep --version`. Binaries are git-lfs; if a file is an LFS pointer/missing, run
|
||||
`git lfs pull`, else fetch the asset from `bin/manifest.json` and verify against
|
||||
`bin/checksums.txt`, else use a PATH copy, else degrade (that detector is skipped with a
|
||||
note). Absence of any **aux** tool never blocks the run. Refer to the main binary as
|
||||
`AST_GREP`.
|
||||
|
||||
Refer to the binary below as `AST_GREP`.
|
||||
## Step 1 — Scope
|
||||
|
||||
**Aux engines for Pass B** (optional; same bundle/fetch/verify mechanism): `cpd`
|
||||
(duplication), `scc` (metrics), `biome` (JS/TS lint), `ruff` (Python lint) — see
|
||||
`bin/manifest.json` for per-platform assets + `bin/checksums.txt`. Each is selected the same
|
||||
way (`bin/<os>-<arch>/<tool>`), and absence of any aux tool never blocks a run (the relevant
|
||||
detector just degrades or is skipped with a note).
|
||||
Read-only scoping; never touch git-ignored files.
|
||||
- Git repo: candidates = `git ls-files` + `git ls-files --others --exclude-standard`.
|
||||
- Not a git repo: ask which paths to include.
|
||||
- Honor any path the user named (intersect with the above).
|
||||
- **Test files are excluded** (their literals/structure are usually intentional). Filter:
|
||||
```
|
||||
\.(test|spec)\.[mc]?[jt]sx?$ · (^|/)(__tests__|tests?|e2e|__mocks__|__snapshots__)/
|
||||
_test\.go$ · (^|/)test_[^/]*\.py$|_test\.py$|conftest\.py · (^|/)src/test/|Tests?\.java$
|
||||
```
|
||||
Record `config.include_tests: false`. (Widen only if the user explicitly asks to include tests.)
|
||||
|
||||
## Step 1 — Scope the candidate files
|
||||
## Step 2 — Detect (mechanical, ~0 model tokens)
|
||||
|
||||
Never touch files git ignores.
|
||||
**Pass A — structural** (`AST_GREP scan -c <skill-dir>/sgconfig.yml --json=compact <paths>`):
|
||||
redundant booleans, magic numbers, deep nesting, long params, etc. (`references/DETECTION.md`).
|
||||
For Svelte `.svelte`, see `references/SVELTE.md` (project tooling / opt-in grammar).
|
||||
|
||||
- In a git repo: candidates = `git ls-files` plus
|
||||
`git ls-files --others --exclude-standard` (tracked + untracked-but-unignored).
|
||||
- Not a git repo: ask the user which paths to include before doing anything.
|
||||
- Honor any path the user named (a file, dir, or glob) by intersecting with the above.
|
||||
**Pass B — consolidation & anti-patterns** (`references/CONSOLIDATION.md` for the full method):
|
||||
- *Duplicate enums/labels/unions* — `AST_GREP` extracts enum/union/`as const` defs → member-set cluster.
|
||||
- *Near-duplicate functions* — `cpd <paths> --min-tokens 50 --reporters json --output simplify/cpd --silent` (exact) **+** `AST_GREP` skeleton extraction (renamed/structural).
|
||||
- *Untyped repeated DTOs* — `AST_GREP` extracts object literals → key-set cluster; keep only shapes repeated ≥3× with **no** matching interface.
|
||||
- *Reinventing-the-wheel* — `AST_GREP` function-signature index → name/signature similarity.
|
||||
- *Lint (defer, don't reimplement)* — bundled `biome lint --reporter=json <paths>` (JS/TS) and `ruff check --output-format=json <paths>` (Python), read-only. Deeper/type-aware/other-language → optional project toolchain only-if-present.
|
||||
- *Metrics* — `scc --by-file -f csv --no-cocomo <paths>` (+ ast-grep-derived nesting/length/params) → flag god/long functions.
|
||||
|
||||
**Exclude test files BY DEFAULT.** Test literals/structure are usually intentional, so
|
||||
filter the candidate list (e.g. `git ls-files | grep -vE '<pattern>'`) unless the user
|
||||
opts in. Record the choice in the ledger `config.include_tests`. Default patterns:
|
||||
## Step 3 — Infer build / test / lint (for the report's `verify` fields)
|
||||
|
||||
```
|
||||
\.(test|spec)\.[mc]?[jt]sx?$ # foo.test.ts, foo.spec.jsx, ...
|
||||
(^|/)(__tests__|tests?|e2e|__mocks__|__snapshots__)/ # test dirs (any language)
|
||||
_test\.go$ # Go
|
||||
(^|/)test_[^/]*\.py$|_test\.py$|(^|/)conftest\.py$ # Python
|
||||
(^|/)src/test/|Tests?\.java$ # Java
|
||||
```
|
||||
Infer from manifests (don't hardcode) and record in `config`; these are written into each
|
||||
finding's `verify` so the agent can confirm its own changes later. NOT run by this skill.
|
||||
- Node `package.json` scripts; Python `pytest`/ruff; Go `go build/test`; Rust `cargo
|
||||
build/test`; Java Maven/Gradle. Set `null` if absent.
|
||||
|
||||
**Switch to include tests:** if the user says "include tests / also tests /
|
||||
--include-tests / with tests", skip the filter and set `config.include_tests: true`.
|
||||
## Step 4 — Assemble findings + judge cross-file candidates
|
||||
|
||||
## Step 2 — Detect (mechanical, ~0 tokens)
|
||||
Write `simplify/findings.json` (use a cheap/fast model). For each candidate produce a
|
||||
finding with the **full output contract** (id, kind, detector, title, members, severity,
|
||||
confidence, **action**, **verify**, status:pending; verdict for consolidation).
|
||||
- **Structural / lint / metric** findings: `action` = the concrete fix (for a mechanical
|
||||
ast-grep rule, the suggested rewrite or `AST_GREP scan --rule <id> --update-all`; for a
|
||||
semantic one, the pattern from `references/PATTERNS.md` + the span). `verify` = `config`
|
||||
build/test (+lint).
|
||||
- **Consolidation (Pass B)** findings are **mandatory-judged** before inclusion: give the
|
||||
cheap model the real spans and apply the judge contract + AHA guardrails in
|
||||
`references/CONSOLIDATION.md`. Only include `verdict != keep-separate` with
|
||||
`confidence ≥ 0.6`. Record rejected clusters under `kept_separate` (with the reason) so
|
||||
reruns don't re-flag. Fan out one judge per cluster where the runtime supports sub-agents.
|
||||
|
||||
Run ast-grep with this skill's bundled rules and emit JSON:
|
||||
Rank by `severity` (biggest win first). **Resumable:** if `findings.json` exists, keep
|
||||
entries already `status: applied` and refresh the rest.
|
||||
|
||||
```
|
||||
AST_GREP scan -c <skill-dir>/sgconfig.yml --json <paths>
|
||||
```
|
||||
## Step 5 — Write the report
|
||||
|
||||
- `sgconfig.yml` points at `rules/` (per-language packs: detection + autofix).
|
||||
- Rules cover: redundant boolean/ternary, magic numbers, dead/unreachable code,
|
||||
deep nesting, long parameter lists, simplifiable APIs, duplicate-ish patterns.
|
||||
- **Optional enrichment, only if already installed** (never install, never block on
|
||||
absence): `scc`, `gocyclo`, `staticcheck`, `cargo clippy`, `knip`, `jscpd`, `pmd`.
|
||||
Fold their output into the ledger as extra `signal`/findings.
|
||||
Write `simplify/consolidation-report.md`: a header (mode, scope, `config` commands), a
|
||||
**ranked action queue** (one block per finding: id · verdict/kind · confidence · title ·
|
||||
**Where** · **Do** (the action) · **Verify** · Why), a **"Kept separate (do NOT
|
||||
consolidate)"** section, a **latent drift/bugs** callout, and short Pass-A/lint/metric
|
||||
summaries. See the example in `assets/ledger.schema.json` and `references/CONSOLIDATION.md`.
|
||||
|
||||
See `references/DETECTION.md` for rule authoring and the full enrichment matrix.
|
||||
|
||||
**Svelte / SvelteKit (`.svelte`):** ast-grep has no built-in Svelte grammar, so handle
|
||||
these specially (full guide: `references/SVELTE.md`):
|
||||
- Detect primarily with the project's own Svelte tooling **if present** — run
|
||||
`svelte-check` (machine output), the project's `eslint` (eslint-plugin-svelte) on
|
||||
`**/*.svelte`, and/or `svelte/compiler` warnings; fold results into the ledger.
|
||||
- Optionally, if the tree-sitter-svelte grammar is built (one command, see SVELTE.md),
|
||||
scan `.svelte` with `sgconfig.svelte.yml` — TS/JS packs then apply to `<script>` blocks
|
||||
via injection, plus `rules-svelte/` template rules.
|
||||
- Otherwise do model-driven refactors (runes migration, store→`$state`, etc.) — and
|
||||
**obey the SvelteKit guardrails in `references/SVELTE.md`** (never break
|
||||
`+page`/`+layout`/`load`/form-action conventions). Verify with `svelte-check` too.
|
||||
|
||||
## Step 3 — Assemble the ledger (`findings.json`)
|
||||
|
||||
Read ast-grep's JSON and write `simplify/findings.json` (create the `simplify/` dir).
|
||||
Use a **cheap/fast model** for this step if your runtime lets you choose one.
|
||||
|
||||
For each match, compute and record a finding (schema: `assets/ledger.schema.json`):
|
||||
- location (`file`, `start_line`, `end_line`), `lang`, `detector` (rule id),
|
||||
- `signal` (metric values — e.g. nesting depth, param count, function length derived
|
||||
from the match range; plus any enrichment metrics),
|
||||
- `suggested_pattern` (from the catalog in `references/PATTERNS.md`),
|
||||
- `apply_track`: `ast-grep` if the rule carries a `fix:`, else `llm`,
|
||||
- `difficulty`: `mechanical` (has fix) | `semantic` (needs judgment) | `hard` (multi-file/risky),
|
||||
- `confidence` (0–1), `severity` (0–100), `status: pending`.
|
||||
|
||||
Rank biggest-win-first:
|
||||
`severity = max(0, cyclomatic-10)*(lines/20) + unused_params*10 + dup_blocks*50 + ast_grep_hits*5`
|
||||
(tune as needed; see DETECTION.md).
|
||||
|
||||
Write a top-level `config:` block too (filled in Step 4) so everything lives in one file.
|
||||
|
||||
**Resumability:** if `findings.json` already exists, load it and process only
|
||||
`status: pending` items — do not re-scan-and-overwrite completed work.
|
||||
|
||||
## Step 4 — Infer build / test / lint commands (once, cached)
|
||||
|
||||
These are project-specific and must NOT be hardcoded. Infer them from the project's
|
||||
manifests/structure, then cache in `findings.json` → `config:`:
|
||||
|
||||
```json
|
||||
"config": {
|
||||
"build": "<cmd or null>",
|
||||
"test": "<cmd or null>",
|
||||
"lint": "<cmd or null>",
|
||||
"format": "<cmd or null>",
|
||||
"detected_from": "package.json|pyproject.toml|go.mod|Cargo.toml|pom.xml|Makefile|...",
|
||||
"runtime": "node|python|go|rust|java|...",
|
||||
"inferred_at": "<commit-or-marker>"
|
||||
}
|
||||
```
|
||||
|
||||
Hints by ecosystem (verify they exist before trusting them):
|
||||
- Node: `package.json` scripts (`build`/`test`/`lint`), pnpm/yarn/npm.
|
||||
- Python: `pyproject.toml`/`tox.ini`/`pytest.ini` → `pytest`; ruff/black for lint/format.
|
||||
- Go: `go build ./...`, `go test ./...`, `gofmt`, `go vet`.
|
||||
- Rust: `cargo build`, `cargo test`, `cargo fmt`, `cargo clippy`.
|
||||
- Java: Maven (`mvn -q test`) / Gradle (`./gradlew test`).
|
||||
|
||||
Re-infer only if `config` is missing or the manifest changed. If a command can't be
|
||||
found, set it `null` and tell the user; treat a missing test command as "no automatic
|
||||
verification available" (be more conservative; prefer report mode).
|
||||
|
||||
## Step 5 — Baseline gate
|
||||
|
||||
Run `config.build` then `config.test`. If either FAILS, **STOP** and report — never
|
||||
simplify on top of a red baseline. (If both are `null`, warn and proceed cautiously.)
|
||||
|
||||
## Step 6 — Apply (process the ledger top-down)
|
||||
|
||||
Take the top-N `pending` findings (start small, e.g. N=10; respect any user/token budget).
|
||||
For each, **one change at a time**:
|
||||
|
||||
### Track A — mechanical (ast-grep autofix, 0 LLM tokens)
|
||||
The rule carries a `fix:`.
|
||||
- **apply mode:** `AST_GREP scan -c sgconfig.yml --update-all` (or `--rule <file>` to
|
||||
scope to one rule).
|
||||
- **report mode:** capture the diff with `AST_GREP scan -c sgconfig.yml --json` /
|
||||
dry-run; do not write.
|
||||
|
||||
### Track B — semantic (LLM span edit)
|
||||
- Read **only** the flagged span (± a few context lines), not the whole file.
|
||||
- Apply the catalog pattern (`references/PATTERNS.md`) via your native edit tool
|
||||
(a search/replace style edit — keep the surrounding code identical). For a large
|
||||
rewrite, replace the whole function/block.
|
||||
- Keep behavior unchanged. Do not invent abstractions. One pattern per step.
|
||||
|
||||
### After each change → verify
|
||||
Run cached `config.build` + `config.test` (+ `config.lint` if cheap):
|
||||
- **pass** → mark finding `status: applied`, record `model_used`; continue.
|
||||
- **fail** → revert that one change, mark `status: failed` with the diagnostic, then
|
||||
retry up to 3× with more context. Still failing → if your runtime allows a stronger
|
||||
model, escalate it for that finding; otherwise mark `failed` and move on.
|
||||
|
||||
Never batch unrelated changes into a single verify step. Update `findings.json` as you go.
|
||||
|
||||
## Step 7 — Report
|
||||
|
||||
Write `simplify/simplification-report.md`:
|
||||
- summary (counts by status, by pattern, by language),
|
||||
- per-finding: file, pattern, track, result, and (report mode) the proposed diff /
|
||||
(apply mode) the applied diff,
|
||||
- anything skipped/failed with the reason and rollback guidance,
|
||||
- remaining `pending` findings for a future run.
|
||||
|
||||
In **report mode** also emit proposed changes as `simplify/patches/*.patch` (e.g.
|
||||
`git diff` of a throwaway application, or ast-grep diffs) so a human can apply them.
|
||||
|
||||
---
|
||||
|
||||
## Pass B — consolidation & anti-pattern analysis (report-only)
|
||||
|
||||
Run when asked to de-duplicate / consolidate / find reuse / DRY / review anti-patterns, or
|
||||
as a deeper second pass. **Pass B never edits files** — it produces a report a human (or a
|
||||
later explicit apply) acts on. Full method + the mandatory judge contract + AHA guardrails:
|
||||
`references/CONSOLIDATION.md`. Taxonomy of what each anti-pattern maps to:
|
||||
`references/ANTIPATTERNS.md`.
|
||||
|
||||
Scope = the same git-visible, test-excluded set as Step 1. Every detector follows
|
||||
**extract (0 tokens) → cluster → JUDGE (mandatory, cheap model + AHA guardrails) → report**;
|
||||
only report `verdict != keep-separate` with `confidence ≥ 0.6`.
|
||||
|
||||
- **F1 Consolidation core**
|
||||
- *Duplicate enums/labels/unions*: `AST_GREP` extracts enum/union/`as const` defs →
|
||||
member-set cluster → judge (merge vs derive intentional subset vs keep-separate).
|
||||
- *Near-duplicate functions*: `cpd` for exact copy-paste (`cpd <paths> --min-tokens 50
|
||||
--reporters json --output simplify/cpd --silent`) **and** `AST_GREP` skeleton extraction
|
||||
for renamed/structural near-dups → judge (unify / partial / keep-separate).
|
||||
- *Untyped repeated DTOs*: `AST_GREP` extracts object literals → key-set cluster → report
|
||||
only shapes repeated ≥3× with **no** matching interface → judge.
|
||||
- **F2 Reinventing-the-wheel**: `AST_GREP` builds a function-signature index → name/signature
|
||||
similarity → judge whether new code re-implements an existing util (recommend reuse).
|
||||
- **F3 Lint enrichment (defer, don't reimplement)**: run bundled `biome` (JS/TS) and `ruff`
|
||||
(Python) **read-only** (no config needed, nothing written): e.g.
|
||||
`biome lint --reporter=json <paths>`, `ruff check --output-format=json <paths>`. Map each
|
||||
diagnostic into the ledger (`detector: "biome:<rule>"`); a tool-fixable rule → mechanical.
|
||||
Deeper/type-aware/other-language lint and cross-file dead code: optional project toolchain
|
||||
only-if-present (tsc/eslint, knip, vulture, staticcheck, clippy) — never assume it.
|
||||
- **F4 Complexity/metrics**: `scc` (file-level complexity/LOC) + `AST_GREP`-derived
|
||||
per-function nesting/length/params → flag god/long functions; feeds ranking. Report.
|
||||
|
||||
Output: `simplify/consolidation-report.md` (clusters + verdict/why/risk/recommendation; a
|
||||
"kept separate (and why)" section; a "latent drift/bugs" callout) + ledger findings with
|
||||
`kind: "consolidation"`.
|
||||
Then stop. The skill's job is done; the agent acts on `findings.json` / the report.
|
||||
|
||||
---
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Never modify git-ignored files. Never change public/observable behavior unless the
|
||||
user explicitly asks. No speculative abstractions, no drive-by rewrites.
|
||||
- Prefer Track A (deterministic) over Track B whenever a vetted `fix:` exists.
|
||||
- If classification is uncertain, treat the finding as Track B + rely on the verify gate.
|
||||
- Keep your own context lean: work from the ledger and span reads, not whole files.
|
||||
- Stop and ask if the baseline is red, the repo isn't under version control, or a
|
||||
change would alter an API/exported symbol.
|
||||
- **This skill never modifies source files** — no edits, no autofix, no formatting. It only
|
||||
writes `simplify/findings.json` and `simplify/*.md`.
|
||||
- Never read or report on git-ignored files. Test files excluded by default.
|
||||
- Pass B is judge-gated: report only real, confident findings; preserve intentional
|
||||
duplication (AHA — rule of three, don't merge across layers/bounded contexts).
|
||||
- Keep your own context lean: work from tool output + the ledger, not whole files.
|
||||
- If not a git repo, ask for scope before analyzing.
|
||||
|
||||
## Optimization & portability
|
||||
## References
|
||||
|
||||
- Token/model tactics (span reads, cheap-model triage, optional sub-agent fan-out,
|
||||
cache-friendly prefixes): `references/OPTIMIZATION.md`.
|
||||
- Per-runtime install locations, invocation, and model-override notes:
|
||||
`references/PORTABILITY.md`.
|
||||
- Svelte / SvelteKit handling, the opt-in grammar, and guardrails: `references/SVELTE.md`.
|
||||
- Detection rules & enrichment: `references/DETECTION.md` · Pattern catalog: `references/PATTERNS.md`
|
||||
- Consolidation pipeline + judge contract + AHA guardrails: `references/CONSOLIDATION.md`
|
||||
- AI anti-pattern taxonomy → treatment: `references/ANTIPATTERNS.md`
|
||||
- Token/model tactics: `references/OPTIMIZATION.md` · Per-runtime notes: `references/PORTABILITY.md`
|
||||
- Svelte / SvelteKit: `references/SVELTE.md` · Bundled engines: `bin/manifest.json`
|
||||
|
||||
+35
-19
@@ -34,11 +34,14 @@
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "file", "start_line", "end_line", "lang", "detector", "apply_track", "difficulty", "severity", "status"],
|
||||
"required": ["id", "kind", "detector", "title", "members", "severity", "confidence", "action", "verify", "status"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "description": "stable id, e.g. f0001" },
|
||||
"kind": { "enum": ["structural", "lint", "metric", "consolidation"], "description": "structural=Pass A rule; lint=Biome/ruff; metric=scc/derived; consolidation=Pass B (report-only)" },
|
||||
"verdict": { "enum": ["consolidate", "partial", "keep-separate"], "description": "Pass B only: the judge's verdict" },
|
||||
"id": { "type": "string", "description": "stable id, e.g. c01 / f0001" },
|
||||
"kind": { "enum": ["structural", "lint", "metric", "consolidation"], "description": "structural=Pass A rule; lint=Biome/ruff; metric=scc/derived; consolidation=Pass B" },
|
||||
"title": { "type": "string", "description": "REQUIRED: one-line summary of the finding (what + where, human-scannable)" },
|
||||
"action": { "type": "string", "description": "REQUIRED: the concrete, executable change the agent should make (not a vague suggestion). The skill itself never performs it." },
|
||||
"verify": { "type": "string", "description": "REQUIRED: command(s) to confirm the change after the agent applies it (usually config.build/test/typecheck)" },
|
||||
"verdict": { "enum": ["consolidate", "partial", "keep-separate"], "description": "Pass B (consolidation) only: the judge's verdict" },
|
||||
"members": { "type": "array", "items": { "type": "object", "properties": { "file": {"type": "string"}, "line": {"type": "integer"} } }, "description": "Pass B only: the cluster's member locations" },
|
||||
"recommendation": { "type": "string", "description": "Pass B only: the concrete suggested change (never auto-applied)" },
|
||||
"file": { "type": "string" },
|
||||
@@ -58,12 +61,11 @@
|
||||
"dup_blocks": { "type": "number" }
|
||||
}
|
||||
},
|
||||
"apply_track": { "enum": ["ast-grep", "llm"] },
|
||||
"fix_kind": { "enum": ["deterministic", "edit", "manual"], "description": "informational only (skill never applies): deterministic=an ast-grep autofix command exists; edit=a span edit the agent makes; manual=needs human judgment" },
|
||||
"difficulty": { "enum": ["mechanical", "semantic", "hard"] },
|
||||
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
||||
"severity": { "type": "number", "minimum": 0, "maximum": 100, "description": "ranking score; biggest-win-first" },
|
||||
"status": { "enum": ["pending", "applied", "skipped", "failed"] },
|
||||
"model_used": { "type": ["string", "null"] },
|
||||
"status": { "enum": ["pending", "applied", "skipped"], "description": "pending initially; the ACTING agent updates it after acting (not this skill)" },
|
||||
"notes": { "type": "string" }
|
||||
}
|
||||
}
|
||||
@@ -72,24 +74,38 @@
|
||||
"examples": [
|
||||
{
|
||||
"config": {
|
||||
"build": "npm run build", "test": "npm test", "lint": "npm run lint", "format": "npx prettier -w .",
|
||||
"detected_from": "package.json", "runtime": "node", "inferred_at": "a1b2c3d"
|
||||
"build": "pnpm -r build", "test": "vitest run", "lint": null, "typecheck": "pnpm -r typecheck",
|
||||
"format": null, "detected_from": "package.json", "runtime": "node",
|
||||
"include_tests": false, "passes": ["A", "B"]
|
||||
},
|
||||
"engine": { "ast_grep_version": "0.44.0", "binary": "linux-x64", "enrichment": ["knip"] },
|
||||
"engine": { "ast_grep": "0.44.0", "cpd": "5.0.11", "scc": "3.7.0", "biome": "2.5.0", "binary": "linux-x64" },
|
||||
"summary": { "passA_structural": 180, "passB_consolidation": 9, "lint": 149, "kept_separate": 3 },
|
||||
"findings": [
|
||||
{
|
||||
"id": "f0001", "file": "src/cart.ts", "start_line": 4, "end_line": 4, "lang": "typescript",
|
||||
"detector": "ts-prefer-includes", "suggested_pattern": "prefer-includes",
|
||||
"apply_track": "ast-grep", "difficulty": "mechanical", "confidence": 0.99,
|
||||
"severity": 5, "status": "pending", "model_used": null, "notes": ""
|
||||
"id": "c01", "kind": "consolidation", "detector": "dup-function", "verdict": "consolidate",
|
||||
"title": "Response helpers jsonErr()/json() copy-pasted across 21+ SvelteKit routes",
|
||||
"members": [
|
||||
{ "file": "dashboard/src/routes/api/projects/+server.ts", "line": 6 },
|
||||
{ "file": "dashboard/src/routes/api/stories/[id]/abandon/+server.ts", "line": 6 }
|
||||
],
|
||||
"fix_kind": "edit", "difficulty": "semantic", "confidence": 0.95, "severity": 80,
|
||||
"action": "Create dashboard/src/lib/response.ts exporting json(data,status=200) and jsonErr(status,error)=json({error},status); replace the ~29 local definitions with `import { json, jsonErr } from \"$lib/response\"`.",
|
||||
"verify": "pnpm -r typecheck && vitest run",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"id": "f0002", "file": "src/cart.ts", "start_line": 20, "end_line": 38, "lang": "typescript",
|
||||
"detector": "ts-deep-nesting", "suggested_pattern": "guard-clauses",
|
||||
"signal": { "nesting": 3, "lines": 18 },
|
||||
"apply_track": "llm", "difficulty": "semantic", "confidence": 0.7,
|
||||
"severity": 38, "status": "pending", "model_used": null, "notes": ""
|
||||
"id": "f0002", "kind": "structural", "detector": "ts-deep-nesting",
|
||||
"title": "Deeply nested conditional in dispatch.ts — guard clauses",
|
||||
"members": [ { "file": "core/orchestrator/src/dispatch.ts", "line": 222 } ],
|
||||
"signal": { "nesting": 3 }, "fix_kind": "edit", "difficulty": "semantic",
|
||||
"confidence": 0.7, "severity": 22,
|
||||
"action": "Flatten with early returns (see references/PATTERNS.md → guard-clauses); read lines 218-236 and invert the outer conditions.",
|
||||
"verify": "pnpm -r typecheck && vitest run",
|
||||
"status": "pending"
|
||||
}
|
||||
],
|
||||
"kept_separate": [
|
||||
{ "detector": "dup-function", "title": "dead-letter.ts vs run-monitor.ts", "why": "Same skeleton, different concerns (Redis delivery-count vs DB elapsed-time); not duplication." }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
+22
-137
@@ -1,146 +1,31 @@
|
||||
{
|
||||
"config": {
|
||||
"build": "npm run build",
|
||||
"test": "npm test",
|
||||
"lint": "npm run lint",
|
||||
"format": "npx prettier -w .",
|
||||
"detected_from": "package.json",
|
||||
"runtime": "node",
|
||||
"inferred_at": "<commit>"
|
||||
},
|
||||
"engine": {
|
||||
"ast_grep_version": "0.44.0",
|
||||
"binary": "linux-x64",
|
||||
"enrichment": []
|
||||
"build": "npm run build", "test": "npm test", "lint": null, "typecheck": "tsc --noEmit",
|
||||
"format": null, "detected_from": "package.json", "runtime": "node",
|
||||
"include_tests": false, "passes": ["A", "B"]
|
||||
},
|
||||
"engine": { "ast_grep": "0.44.0", "cpd": "5.0.11", "scc": "3.7.0", "biome": "2.5.0", "binary": "linux-x64" },
|
||||
"summary": { "passA_structural": 2, "passB_consolidation": 0, "lint": 0, "kept_separate": 0 },
|
||||
"findings": [
|
||||
{
|
||||
"id": "f0006",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 31,
|
||||
"end_line": 36,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-deep-nesting",
|
||||
"suggested_pattern": "guard-clauses",
|
||||
"apply_track": "llm",
|
||||
"difficulty": "semantic",
|
||||
"confidence": 0.7,
|
||||
"severity": 20.3,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
"id": "f0001", "kind": "structural", "detector": "ts-prefer-includes",
|
||||
"title": "Use .includes() instead of indexOf()!==-1 in fixtures/ts/messy.ts",
|
||||
"members": [ { "file": "fixtures/ts/messy.ts", "line": 4 } ],
|
||||
"fix_kind": "deterministic", "difficulty": "mechanical", "confidence": 0.99, "severity": 5,
|
||||
"action": "Replace `tags.indexOf(tag) !== -1` with `tags.includes(tag)` (or run `ast-grep scan -c sgconfig.yml --rule ts-prefer-includes --update-all`).",
|
||||
"verify": "npm run build && npm test",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"id": "f0008",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 33,
|
||||
"end_line": 35,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-deep-nesting",
|
||||
"suggested_pattern": "guard-clauses",
|
||||
"apply_track": "llm",
|
||||
"difficulty": "semantic",
|
||||
"confidence": 0.7,
|
||||
"severity": 20.1,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "f0005",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 20,
|
||||
"end_line": 27,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-long-param-list",
|
||||
"suggested_pattern": "parameter-object",
|
||||
"apply_track": "llm",
|
||||
"difficulty": "semantic",
|
||||
"confidence": 0.7,
|
||||
"severity": 10.4,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "f0001",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 4,
|
||||
"end_line": 4,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-prefer-includes",
|
||||
"suggested_pattern": "prefer-includes",
|
||||
"apply_track": "ast-grep",
|
||||
"difficulty": "mechanical",
|
||||
"confidence": 0.95,
|
||||
"severity": 5,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "f0002",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 8,
|
||||
"end_line": 8,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-prefer-not-includes",
|
||||
"suggested_pattern": "prefer-includes",
|
||||
"apply_track": "ast-grep",
|
||||
"difficulty": "mechanical",
|
||||
"confidence": 0.95,
|
||||
"severity": 5,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "f0003",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 12,
|
||||
"end_line": 12,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-ternary-to-boolean",
|
||||
"suggested_pattern": "simplify-boolean",
|
||||
"apply_track": "ast-grep",
|
||||
"difficulty": "mechanical",
|
||||
"confidence": 0.95,
|
||||
"severity": 5,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "f0004",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 16,
|
||||
"end_line": 16,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-ternary-negate",
|
||||
"suggested_pattern": "simplify-boolean",
|
||||
"apply_track": "ast-grep",
|
||||
"difficulty": "mechanical",
|
||||
"confidence": 0.95,
|
||||
"severity": 5,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "f0007",
|
||||
"file": "fixtures/ts/messy.ts",
|
||||
"start_line": 33,
|
||||
"end_line": 33,
|
||||
"lang": "typescript",
|
||||
"detector": "ts-magic-number",
|
||||
"suggested_pattern": "magic-number",
|
||||
"apply_track": "llm",
|
||||
"difficulty": "semantic",
|
||||
"confidence": 0.7,
|
||||
"severity": 3.0,
|
||||
"status": "pending",
|
||||
"model_used": null,
|
||||
"notes": ""
|
||||
"id": "f0002", "kind": "structural", "detector": "ts-deep-nesting",
|
||||
"title": "Deeply nested conditional in price() — guard clauses",
|
||||
"members": [ { "file": "fixtures/ts/messy.ts", "line": 29 } ],
|
||||
"signal": { "nesting": 3 }, "fix_kind": "edit", "difficulty": "semantic",
|
||||
"confidence": 0.7, "severity": 22,
|
||||
"action": "Flatten with early returns (references/PATTERNS.md -> guard-clauses); read the span and invert the outer conditions.",
|
||||
"verify": "npm run build && npm test",
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
],
|
||||
"kept_separate": []
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@ For each candidate cluster, give the model the real code spans and ask for exact
|
||||
Only report findings with `verdict != keep-separate` AND `confidence >= 0.6`. For
|
||||
`keep-separate`, record the reason in the report so re-runs don't re-flag it.
|
||||
|
||||
When you write the finding into `findings.json`, the judge's `recommendation` becomes the
|
||||
required **`action`** (a concrete, executable instruction) and you must add a **`verify`**
|
||||
(the `config` build/test/typecheck command) and a one-line **`title`** — these three fields
|
||||
are mandatory for every finding (see `assets/ledger.schema.json`) so the acting agent can
|
||||
execute it without re-running this skill.
|
||||
|
||||
### AHA guardrails (put these in the judge prompt — they prevent harmful merges)
|
||||
- **Rule of three**: 2 occurrences usually isn't worth abstracting; prefer ≥3.
|
||||
- **"The wrong abstraction is worse than duplication"** (Sandi Metz). If unifying would
|
||||
|
||||
@@ -32,7 +32,8 @@ Each match object includes (ast-grep 0.44):
|
||||
|
||||
For every `ruleId`, the manifest gives:
|
||||
- `pattern` — catalog key (`references/PATTERNS.md`),
|
||||
- `track` — `ast-grep` (rule carries a `fix:`, deterministic Track A) or `llm` (Track B),
|
||||
- `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`).
|
||||
|
||||
+19
-18
@@ -5,12 +5,13 @@ apply as many as your runtime supports. All are optional accelerators — the sk
|
||||
correct without any of them.
|
||||
|
||||
## 1. Push work out of the model
|
||||
- Detection is the bundled `ast-grep` binary — **zero model tokens**.
|
||||
- Mechanical fixes are Track A (`ast-grep --update-all`) — the model never reads them.
|
||||
- Goal: the model only ever touches `difficulty: semantic | hard` findings.
|
||||
- Detection is the bundled binaries — **zero model tokens**.
|
||||
- Mechanical findings just record a deterministic autofix command in their `action` — the
|
||||
model never reads their code.
|
||||
- Goal: the model only *judges* `difficulty: semantic | hard` cross-file candidates.
|
||||
|
||||
## 2. Span-scoped reads
|
||||
Never read a whole file to make one edit. From the ledger you already have
|
||||
Never read a whole file. From the ledger you already have
|
||||
`file` + `start_line`/`end_line`; read only that range plus a few context lines (~95% fewer
|
||||
read tokens on large files). The codebase enters context only as flagged spans.
|
||||
|
||||
@@ -20,22 +21,22 @@ Use a small/fast model (Haiku / GPT-5-mini / Gemini Flash) for:
|
||||
- **classification / triage** — mechanical vs semantic, confidence;
|
||||
- **verification reading** — interpreting build/test output (pass/fail + which finding).
|
||||
|
||||
## 4. Model tiering (capability-detected)
|
||||
- Semantic apply → a mid model (Sonnet).
|
||||
- `hard` / multi-file / low-confidence / post-retry → escalate to a frontier model (Opus)
|
||||
**only then**. Confidence-based cascading typically cuts cost ~50–85% at ~95% quality.
|
||||
## 4. Model tiering for the JUDGE (capability-detected)
|
||||
The model is used only to *judge* cross-file candidates (Pass B), never to edit.
|
||||
- Cluster + judge on a cheap model.
|
||||
- `hard` / multi-file / low-confidence → escalate to a frontier model **only then**.
|
||||
Confidence-based cascading typically cuts cost ~50–85% at ~95% quality.
|
||||
- **How to route**, by runtime:
|
||||
- Claude Code — sub-agents with a per-agent `model:` override.
|
||||
- pi — provider-agnostic; pick the model per sub-agent/step.
|
||||
- Copilot / others without per-step model choice — run on the invoked model and use
|
||||
`confidence`/`difficulty` to order work and decide attempt-vs-defer (defer `hard` ones
|
||||
to report mode for a human).
|
||||
`confidence` to decide which findings to include vs leave out (low-confidence clusters
|
||||
simply aren't reported).
|
||||
|
||||
## 5. Optional sub-agent fan-out
|
||||
On runtimes with sub-agents (Claude Code, pi), process independent findings/files in
|
||||
parallel isolated contexts; each returns a compact JSON status row to the parent. Keeps the
|
||||
orchestrator context clean. Falls back to a sequential ledger loop everywhere else. Keep
|
||||
each change's verify gate inside its own sub-agent so failures isolate cleanly.
|
||||
On runtimes with sub-agents (Claude Code, pi), run one **judge** per cluster in parallel
|
||||
isolated contexts; each returns a compact JSON verdict to the parent. Keeps the
|
||||
orchestrator context clean. Falls back to a sequential judge loop everywhere else.
|
||||
|
||||
## 6. Cache-friendly prefix
|
||||
Keep static context (the pattern catalog, the manifest, the ledger summary) front-loaded
|
||||
@@ -48,7 +49,7 @@ span) after it. Don't reorder the stable prefix between findings.
|
||||
Don't re-scan-and-overwrite a ledger that still has `pending` work.
|
||||
|
||||
## Rough budget intuition
|
||||
detect = free (binary). assembly + triage = cheap model, one pass over compact JSON.
|
||||
apply = one mid-model span edit per semantic finding + one verify read. Mechanical findings
|
||||
= free. So cost scales with the number of *semantic* findings you choose to process
|
||||
(top-N), not with repo size.
|
||||
detect = free (binaries). assembly = cheap model, one pass over compact JSON. judging =
|
||||
one cheap-model verdict per cross-file cluster (mechanical/structural findings need no
|
||||
judging — free). So cost scales with the number of *cross-file clusters* to judge, not with
|
||||
repo size. The skill performs no edits, so there is no apply/verify token cost.
|
||||
|
||||
+21
-17
@@ -1,15 +1,19 @@
|
||||
# Simplification pattern catalog
|
||||
|
||||
The refactoring patterns this skill applies. Each entry: what it is, which rule(s) detect
|
||||
it, the apply track, a before/after, and **when NOT to apply**. Behavior preservation is
|
||||
non-negotiable — when in doubt, downgrade to Track B (model edit) and rely on the verify
|
||||
gate.
|
||||
The refactoring patterns this skill **recommends in its report** (it never applies them —
|
||||
the acting agent does). Each entry: what it is, which rule(s) detect it, the fix nature, a
|
||||
before/after, and **when NOT to do it**. The labels below describe the *kind of action*
|
||||
the report tells the agent to take:
|
||||
- **deterministic** — a vetted, behavior-preserving rewrite (an ast-grep autofix command
|
||||
the agent may run; the finding's `action` names it). Safe for all operand types.
|
||||
- **judgment** — needs the agent to read the span and edit with care.
|
||||
|
||||
`suggested_pattern` keys in the ledger map to the headings here.
|
||||
`suggested_pattern` keys in the ledger map to the headings here. Behavior preservation is
|
||||
non-negotiable — when in doubt, mark it judgment and include a `verify` command.
|
||||
|
||||
---
|
||||
|
||||
## simplify-boolean — Track A (mechanical)
|
||||
## simplify-boolean — deterministic
|
||||
Redundant boolean expressions.
|
||||
Detectors: `ts/js-ternary-to-boolean`, `*-ternary-negate`, `go/rs/java-eq-true`, `*-eq-false`.
|
||||
|
||||
@@ -23,7 +27,7 @@ Detectors: `ts/js-ternary-to-boolean`, `*-ternary-negate`, `go/rs/java-eq-true`,
|
||||
on a non-boolean (we only rewrite the `? true : false` form there, which is always safe;
|
||||
`== true` rewrites are restricted to statically-typed Go/Rust/Java).
|
||||
|
||||
## prefer-includes — Track A (mechanical)
|
||||
## prefer-includes — deterministic
|
||||
Detectors: `ts/js-prefer-includes`, `*-prefer-not-includes`.
|
||||
|
||||
```diff
|
||||
@@ -33,7 +37,7 @@ Detectors: `ts/js-prefer-includes`, `*-prefer-not-includes`.
|
||||
**Not when:** the index value is used afterwards, or `NaN` membership matters (`includes`
|
||||
finds `NaN`, `indexOf` doesn't).
|
||||
|
||||
## identity-compare / membership / truthiness — Track A (Python)
|
||||
## identity-compare / membership / truthiness — deterministic (Python)
|
||||
Pattern keys: `identity-compare`, `membership`, `truthiness` (this one section covers all three).
|
||||
Detectors: `py-is-none`, `py-is-not-none`, `py-not-in`, `py-not-is`, `py-empty-seq-falsy`.
|
||||
|
||||
@@ -48,7 +52,7 @@ Detectors: `py-is-none`, `py-is-not-none`, `py-not-in`, `py-not-is`, `py-empty-s
|
||||
where `len()` doesn't apply / `0` is meaningful (for the truthiness rewrite — the rule
|
||||
notes this).
|
||||
|
||||
## guard-clauses — Track B (semantic)
|
||||
## guard-clauses — judgment
|
||||
Detector: `*-deep-nesting` (≥3 nested conditionals).
|
||||
Flatten with early returns / continues instead of deep nesting.
|
||||
|
||||
@@ -66,13 +70,13 @@ Flatten with early returns / continues instead of deep nesting.
|
||||
**Not when:** branches have `else` side effects, cleanup must run on all paths, or
|
||||
inverting conditions changes short-circuit evaluation. Read the whole block first.
|
||||
|
||||
## parameter-object — Track B (semantic)
|
||||
## parameter-object — judgment
|
||||
Detector: `*-long-param-list` (≥5 params).
|
||||
Group related params into an object/struct/dataclass.
|
||||
**Not when:** params are unrelated, the function is a hot path where an allocation matters,
|
||||
or it's a public API signature you may not change.
|
||||
|
||||
## magic-number — Track B (semantic)
|
||||
## magic-number — judgment
|
||||
Detector: `*-magic-number`.
|
||||
Replace an unexplained literal with a named constant.
|
||||
|
||||
@@ -84,20 +88,20 @@ Replace an unexplained literal with a named constant.
|
||||
**Not when:** the literal is self-evident (array index, `0`/`1`), or already adjacent to a
|
||||
clear name.
|
||||
|
||||
## extract-method — Track B (semantic)
|
||||
## extract-method — judgment
|
||||
No detector rule (so no `manifest.json` entry) — triggered by long functions (length from
|
||||
ledger `signal.lines`) or repeated blocks. Pull a cohesive chunk into a well-named helper.
|
||||
**Not when:** the chunk shares many locals (would need a long param list — reconsider), or
|
||||
extraction would hurt readability.
|
||||
|
||||
## remove-dead-code — Track B (semantic), or tool-applied
|
||||
## remove-dead-code — judgment (or tool-reported)
|
||||
No detector rule (no `manifest.json` entry) — sourced from enrichment (`knip`, `vulture`,
|
||||
`staticcheck`, `cargo-machete`) or unreachable-branch findings. Delete unused
|
||||
exports/vars/imports/branches.
|
||||
**Not when:** the symbol is part of a public API, used via reflection/dynamic import, or
|
||||
referenced only in files outside the scanned set — verify first.
|
||||
|
||||
## Svelte patterns — Track B (opt-in grammar / enrichment)
|
||||
## Svelte patterns — judgment (opt-in grammar / enrichment)
|
||||
Full catalog + SvelteKit guardrails: `SVELTE.md`. Pattern keys used in the ledger:
|
||||
- `svelte-event-property` — detector `svelte-event-directive-to-property`: `on:click={h}`
|
||||
→ `onclick={h}` (model edits the span; the event name varies, so it's not a fixed rule).
|
||||
@@ -106,7 +110,7 @@ Full catalog + SvelteKit guardrails: `SVELTE.md`. Pattern keys used in the ledge
|
||||
|
||||
---
|
||||
|
||||
### Applying a pattern (Track B)
|
||||
### How the ACTING agent applies one (this skill only writes the finding)
|
||||
1. Read only the finding's span (± a few context lines).
|
||||
2. Apply exactly one pattern with the native edit tool; keep surrounding code byte-identical.
|
||||
3. Verify (build/test). Pass → `applied`; fail → revert, retry ≤3, then escalate/defer.
|
||||
2. Make exactly the change in the finding's `action`; keep surrounding code byte-identical.
|
||||
3. Run the finding's `verify`. Pass → set `status: applied`; fail → revert, retry ≤3, defer.
|
||||
|
||||
@@ -39,8 +39,8 @@ clean up / refactor / reduce complexity / remove dead code". You can also name i
|
||||
explicitly (e.g. `/simplify-code` where slash-skills are supported, or "use the
|
||||
simplify-code skill").
|
||||
|
||||
Choose a mode in plain words: "preview / dry-run / report" → **report**; "apply / fix it /
|
||||
do it" → **apply**. Default is report.
|
||||
There are no modes — the skill always just writes `simplify/findings.json` + a report.
|
||||
Acting on it is the calling agent's job, separate from this skill.
|
||||
|
||||
## The bundled engine
|
||||
|
||||
@@ -51,9 +51,9 @@ rare arch, it falls back to a PATH `ast-grep`, then to LLM-heuristic detection.
|
||||
|
||||
## Model routing per runtime
|
||||
|
||||
See `OPTIMIZATION.md` §4. Summary:
|
||||
- **Claude Code** — sub-agents accept a `model:` override; route triage→Haiku,
|
||||
apply→Sonnet, hard→Opus.
|
||||
The model is used only to judge cross-file candidates. See `OPTIMIZATION.md` §4. Summary:
|
||||
- **Claude Code** — sub-agents accept a `model:` override; route judging→Haiku,
|
||||
hard/low-confidence→a stronger model.
|
||||
- **pi** — provider-agnostic; set the model per step/sub-agent.
|
||||
- **Copilot / others** — no per-step model pick; run on the invoked model and use the
|
||||
ledger's `confidence`/`difficulty` to order and defer.
|
||||
|
||||
@@ -101,5 +101,6 @@ Refactor the *insides* freely; never violate these conventions:
|
||||
`action="?/name"`. Don't rename one without the other.
|
||||
- Never touch generated/`$app/*`/`$env/*` wiring or `.svelte-kit/`.
|
||||
|
||||
After any Svelte change, the verify gate should run the project's `svelte-check` and
|
||||
Each Svelte finding's `verify` should include the project's `svelte-check` (so the acting
|
||||
agent confirms after it makes the change), alongside
|
||||
build/test from the ledger `config`, not just a generic build.
|
||||
|
||||
Reference in New Issue
Block a user