Files
simplify-code/SKILL.md
T
Giancarmine SalucciandClaude Opus 4.8 3f6f642f6b 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>
2026-06-23 00:54:40 +02:00

11 KiB
Raw Blame History

name, description, license, metadata, allowed-tools
name description license metadata allowed-tools
simplify-code 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). MIT
author version
simplify-code 0.1.0
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).
  4. Verify: run ast-grep --version.
  5. 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::

"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.