feat: portable cross-runtime code-simplification skill
ast-grep-driven detection + ranking into a JSON ledger, two-track apply (deterministic autofix + model span-edits), configurable report/apply modes. Rule packs for JS/TS, Python, Go, Rust, Java; Svelte/SvelteKit support via project tooling + opt-in grammar. Runtime-neutral SKILL.md (Agent Skills spec). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
---
|
||||
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).
|
||||
license: MIT
|
||||
metadata:
|
||||
author: simplify-code
|
||||
version: "0.1.0"
|
||||
allowed-tools: Read Edit Write Bash(git:*) Bash(ast-grep:*) Bash(sg:*)
|
||||
---
|
||||
|
||||
# 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).
|
||||
|
||||
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.
|
||||
|
||||
> 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## How it works (overview)
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Step 0 — Locate the ast-grep engine
|
||||
|
||||
`ast-grep` is the only required tool and is **bundled** with this skill — no install.
|
||||
Detect platform and pick the binary; fall back gracefully.
|
||||
|
||||
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. **If that file is missing** (fresh clone — binaries aren't committed), fetch the pinned
|
||||
release once into that dir, then 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.
|
||||
|
||||
Refer to the binary below as `AST_GREP`.
|
||||
|
||||
## Step 1 — Scope the candidate files
|
||||
|
||||
Never touch files git ignores.
|
||||
|
||||
- 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.
|
||||
|
||||
## Step 2 — Detect (mechanical, ~0 tokens)
|
||||
|
||||
Run ast-grep with this skill's bundled rules and emit JSON:
|
||||
|
||||
```
|
||||
AST_GREP scan -c <skill-dir>/sgconfig.yml --json <paths>
|
||||
```
|
||||
|
||||
- `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.
|
||||
|
||||
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 a tree-sitter-svelte grammar has been built and enabled in
|
||||
`sgconfig.yml`, the TS/JS packs apply to `<script>` blocks via injection.
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
## Optimization & portability
|
||||
|
||||
- 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`.
|
||||
Reference in New Issue
Block a user