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:
Giancarmine Salucci
2026-06-23 00:54:40 +02:00
co-authored by Claude Opus 4.8
commit 3f6f642f6b
36 changed files with 1776 additions and 0 deletions
+233
View File
@@ -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`.