Your AI wrote spaghetti. This eats it.
Desloppify finds the mess that AI coding agents leave behind — banner comments nobody asked for, placeholder variables named data2, empty catch blocks, god files with 1200 lines, forEach(async ...) that silently drops promises, and the classic placeholder TODO that shipped to prod.
The CLI scans. Your agent (or you) decides what to fix. Fixes run on isolated git worktrees so nothing breaks.
# 1) Install the agent skill
npx skills add FasalZein/desloppify
# 2) Install repo-local hooks
bunx desloppify install-hooks
# 3) Print the guided first-run setup
bunx desloppify setup
# 4) Run the scanner
bunx desloppify check-tools .
bunx desloppify scan . --pack js-ts
bunx desloppify report .
bunx desloppify score . --pack js-ts
# or pick another first-party pack:
bunx desloppify scan . --pack python
bunx desloppify scan . --pack rust
bunx desloppify scan . --pack go
bunx desloppify scan . --pack rubyFor large JS/TS repos, the default scan keeps madge off the main path. Add --with-madge or --category circular-deps only when you explicitly want circular dependency analysis.
Desloppify is skill-driven. Install it as an agent skill and say "desloppify" or "clean up this code" — your agent handles the rest.
# Print the canonical install command
bunx desloppify install-skill --print
# Run the install directly from the CLI
bunx desloppify install-skillThe canonical underlying command is:
npx skills add FasalZein/desloppifyThe skill teaches your agent the full workflow: scan, triage, read saved artifacts, prepare isolated fix worktrees when needed, merge, and verify. You don't need to memorize commands — the skill drives everything.
Desloppify now exposes a stable root import for Bun/TS consumers so you do not have to depend on internal source-path imports.
import { scanProject, scanProjectSummary, summarizeScanReport, calculateScore, compareScanReports } from "desloppify"
const report = await scanProject({ path: ".", pack: "js-ts" })
const summary = await scanProjectSummary({ path: ".", pack: "js-ts" })Current root exports focus on the stable scan/report contract:
scanProject(...)scanProjectSummary(...)summarizeScanReport(report)calculateScore(...)compareScanReports(...)
The full canonical findings artifact is still richer than the compact summary transport view.
Internal desloppify/src/* imports are not part of the supported package surface.
# Global CLI install
bun install -g desloppify
# Clone and link
git clone https://github.com/FasalZein/desloppify.git
cd desloppify && bun install && bun linkDesloppify scans for anti-patterns across categories like:
- AI Slop — banner comments, narration, hedging, placeholder data,
console.log("here") - Complexity — god files, barrel exports, mixed concerns, deep nesting, monolith routes
- Security — hardcoded secrets, SQL injection, eval/exec, pickle loads
- Test Quality — empty tests, weak assertions, sleepy tests, skipped tests
- Async Correctness —
forEach(async), blocking requests, sequential independent awaits - Dead Code — unused exports, functions, files, dependencies
- Weak Types —
any,as any,object,Function,@ts-ignore - Runtime Validation — unvalidated
req.body,JSON.parsecasts, fetch response casts - Accessibility — interactive divs, missing alt text, inputs without labels
- Naming & Semantics — numeric suffixes, generic bucket files, Python builtin shadowing
...and more. Run desloppify rules to see the full list.
You (or your agent) says "desloppify"
↓
Skill takes over
↓
CLI scans → finds issues → scores codebase
↓
Agent triages: fix / skip / flag
↓
You can prepare git worktrees and spawn fix agents in parallel
↓
Merge → re-scan → confirm score improved
The CLI handles detection deterministically. The agent handles judgment — is this try-catch necessary? Is this duplication intentional? Is this comment helpful or AI narration?
The current worktrees command prints the setup commands; your agent or shell orchestrates the actual fix runs.
The shipped skill now also supports an optional overhaul-review workflow for users who want more than a quick scan/fix pass.
This is a skill-layer mode, not a new CLI command.
Use it when the user wants:
- a systematic repo audit
- a phased cleanup plan
- an architecture / tests / performance review tied back to real scan evidence
Mode choices:
- Surgical — one theme only
- Systematic — section-by-section review
- Full audit — broad phased roadmap
The workflow still starts from real desloppify evidence (scan, saved artifacts, report, delta) and ends with:
- impact/effort buckets
- explicit out-of-scope items
- likely failure modes
- ordered remediation steps
The detailed instructions live in skills/desloppify/reference/overhaul-review.md.
Keep the README workflow-oriented. For the fuller agent-facing command matrix, see skills/desloppify/reference/commands.md.
desloppify setup # guided first-run setup
desloppify install-skill --print # print the canonical skill install command
desloppify install-skill # run npx skills add FasalZein/desloppify
desloppify install-hooks --print # print the hook scaffold + git config script
desloppify install-hooks # scaffold repo-local pre-commit / pre-push hooks and enable them
desloppify scan [path] --pack js-ts # detect issues and save report artifacts locally
desloppify scan [path] --pack python # Python pack
desloppify scan [path] --pack rust # Rust pack
desloppify scan [path] --pack go # Go pack
desloppify scan [path] --pack ruby # Ruby pack
desloppify scan [path] --json --pack js-ts # machine-readable normalized findings output
desloppify scan [path] --json --summary --pack js-ts
# compact machine-readable summary without full findings payload
desloppify scan [path] --markdown --pack js-ts # readable markdown report
desloppify scan [path] --wiki --project <project> --pack js-ts
desloppify scan [path] --handoff --project <project> --slice <slice-id> --pack js-ts
desloppify scan [path] --category complexity --pack js-ts
desloppify scan [path] --architecture modular-monolith --pack js-ts
desloppify scan [path] --staged --pack js-ts # staged git changes only
desloppify scan [path] --changed --pack js-ts # current branch diff only
desloppify scan [path] --with-madge --pack js-ts
# opt into whole-repo circular dependency analysis
desloppify report [path] # normalized metrics + path hotspots from latest saved scan
desloppify report [path] --json # emit the saved findings report JSON directly
desloppify report [path] --json --summary # compact summary JSON from the saved report
desloppify benchmark fetch --manifest <file> # clone/fetch pinned benchmark checkouts from a manifest
desloppify benchmark snapshot --manifest <file> # build benchmark snapshot JSON from a benchmark manifest
desloppify benchmark report --manifest <file> # render markdown cohort report from that snapshot
desloppify score [path] --pack js-ts # weighted quality grade (A+ to F)
desloppify score [path] --pack python # weighted quality grade for Python scans
desloppify score [path] --pack rust # weighted quality grade for Rust scans
desloppify score [path] --pack go # weighted quality grade for Go scans
desloppify score [path] --pack ruby # weighted quality grade for Ruby scans
desloppify score [path] --with-madge --pack js-ts
# include circular dependency analysis in score runs
desloppify delta [base] [head] --json # compare saved findings reports across two repos or report paths
desloppify delta [base] [head] --category complexity --fail-on added,worsened
desloppify delta [base] [head] --path '**/routes/*.ts' --fail-on any
desloppify delta [base] [head] --severity high,critical --fail-on added,worsened
desloppify delta [base] [head] --markdown # regressions-only markdown + saved latest.delta.md artifact
desloppify delta [base] [head] --comment --max-findings 8
# compact PR/CI comment + saved latest.delta.comment.md
desloppify delta [base] [head] # human delta with category + path hotspots
desloppify rules # list all detection rules after config overrides
desloppify score [path] # score also respects desloppify.config.json
desloppify rules --pack python # python-specific rule bundle
desloppify rules --pack rust # rust-specific rule bundle
desloppify rules --pack go # go-specific rule bundle
desloppify rules --pack ruby # ruby-specific rule bundle
desloppify rules --architecture modular-monolith # active architecture bundle only
desloppify fix [path] --safe # auto-fix safe mechanical issues
desloppify fix [path] --confident # add AST-validated fixes
desloppify fix [path] --all # include cross-file fixes
desloppify check-tools [path] --json # show available analyzers plus available/suggested packs as JSON
desloppify worktrees [path] # print worktree setup commandsUse --project, --slice, --prd, and --feature with --wiki or --handoff when you want wiki-native output with concrete project context.
desloppify install-hooks
# or, if you want the raw command:
desloppify install-hooks --printThis scaffolds repo-local hooks under .githooks/ and sets git config core.hooksPath .githooks for the current repo. The installed hooks resolve desloppify from ./node_modules/.bin, bunx, or your PATH. Existing unmanaged hook files are left alone instead of being overwritten, and the installer refuses to replace another active hook chain from .git/hooks, .husky, or a worktree-specific hooks path.
pre-commit→ scans--stagedpre-push→ scans--changed
By default the hooks scan only current changes and block only on HIGH/CRITICAL findings.
They auto-pick the repo's suggested pack when that choice is unambiguous, otherwise they fall back to js-ts.
Set DESLOPPIFY_PACK=<pack> if you need a different pack in hook runs.
Set DESLOPPIFY_HOOK_SCOPE=repo if hook runs should scan the whole repo instead of the current diff.
A normal desloppify scan ... run writes artifacts to:
.desloppify/reports/latest.findings.json(canonical scan JSON, now including normalized metrics + path hotspots).desloppify/reports/latest.report.md.desloppify/reports/latest.wiki.json.desloppify/reports/latest.handoff.md.desloppify/reports/latest.delta.md(fromdesloppify delta --markdown).desloppify/reports/latest.delta.comment.md(fromdesloppify delta --comment)
The CLI also prints these paths after the scan so agents and humans know exactly what to read next. The first artifact-writing scan also auto-adds .desloppify/ to .gitignore in git repos so this working state stays local by default. Use desloppify report . when you want a compact normalized summary from the latest saved scan. Use --json --summary when a full findings payload would be too large for an agent or automation consumer. The same split now exists programmatically: scanProject(...) returns the full report, while scanProjectSummary(...) returns the compact view.
For cross-repo comparisons, use a benchmark manifest and run:
desloppify benchmark fetch --manifest ./benchmarks/manifest.json
desloppify benchmark snapshot --manifest ./benchmarks/manifest.json
desloppify benchmark report --manifest ./benchmarks/manifest.jsonPretty scan mode also shows:
- the current score / grade
- issue severity summary
- concrete next actions
Recommended follow-up order:
latest.findings.jsonfor machine decisionslatest.report.mdfor human reviewlatest.wiki.jsonorlatest.handoff.mdfor workflow handoff
Example benchmark manifest:
{
"schemaVersion": 1,
"id": "local-cohort",
"name": "Local cohort",
"description": "Compare one explicit-AI repo against one mature OSS repo.",
"artifacts": {
"checkoutsDir": "./checkouts",
"snapshotPath": "./artifacts/benchmark.snapshot.json",
"reportPath": "./artifacts/benchmark.report.md"
},
"repos": [
{
"id": "ai",
"repo": "example/ai-repo",
"url": "https://github.com/example/ai-repo.git",
"ref": "<pinned-sha>",
"cohort": "explicit-ai",
"pack": "js-ts"
},
{
"id": "oss",
"repo": "example/oss-repo",
"url": "https://github.com/example/oss-repo.git",
"ref": "<pinned-sha>",
"cohort": "mature-oss",
"pack": "js-ts"
}
],
"pairings": [
{ "aiRepoId": "ai", "solidRepoId": "oss" }
]
}If you already have local repos instead of pinned remotes, you can still use path entries and skip benchmark fetch.
Each issue deducts points based on severity and category weight. Security issues hit harder than AI slop. Per-category cap prevents one noisy category from tanking your score.
Grades: A+ (95-100) A (85-94) B (70-84) C (50-69) D (30-49) F (0-29)
Optional (auto-detected, improves coverage):
knip— dead code detection (JS/TS)madge— circular dependency detection (JS/TS, opt-in for full scans via--with-madgeor--category circular-deps)tsc— implicit any detection
Desloppify ships explicit first-party packs.
| Pack | Status | Notes |
|---|---|---|
js-ts |
Available | JavaScript / TypeScript / React-oriented analyzer bundle |
python |
Available | Python-scoped grep, ast-grep, and Ruff integration |
rust |
Available | Rust-scoped rules with ast-grep and cargo clippy integration |
go |
Available | Go-focused rules with staticcheck and golangci-lint integration |
ruby |
Available | Ruby-focused rules with RuboCop integration |
Run desloppify check-tools . before your first scan to see the available packs for the repo and the suggested pack when the choice is unambiguous.
More packs can be added without changing the core scan/report contract.
Current tool adapters include:
- JS/TS:
knip,madge,ast-grep,tsc,eslint,biome,oxlint - Python:
ast-grep,ruff - Rust:
ast-grep,cargo clippy - Go: built-in grep rules,
staticcheck,golangci-lint - Ruby: built-in grep rules,
rubocop - Recommendations only today:
oxfmt,mypy,vulture
The external-tool layer is intentionally best-effort: if a tool or config is missing, desloppify skips it instead of failing the whole scan. Repo-local binaries in node_modules/.bin are resolved automatically when you scan or run fixes against another checkout.
For large JS/TS repos, circular dependency analysis is intentionally opt-in. Full scans skip madge unless you pass --with-madge, and --category circular-deps still runs only the circular-deps pass. On monorepos with package.json workspaces like apps/* and packages/*, the madge pass is scoped per workspace package instead of one repo-wide graph.
desloppify now supports repo-local config discovery:
desloppify.config.jsondesloppify.config.cjsdesloppify.config.js.desloppifyrc.desloppifyrc.json.desloppifyrc.cjs.desloppifyrc.js
Current support is intentionally small and deterministic:
extendsplugins.<namespace>for local JSON/module rule packs or installed package pluginsplugin:<namespace>/<config>preset extendsrules.<id>.enabledrules.<id>.severityrules.<id>.weightrules.<id>.options.<name>for plugin rule option valuesoverrides[].filesoverrides[].rules.<id>.enabledoverrides[].rules.<id>.severityoverrides[].rules.<id>.weightoverrides[].rules.<id>.options.<name>
Example:
{
"extends": ["./desloppify.base.json"],
"plugins": {
"local": "./desloppify.plugin.cjs"
},
"rules": {
"CONSOLE_LOG": { "enabled": false },
"LONG_FILE": { "severity": "HIGH", "weight": 1.5 },
"local/contains-token": {
"options": {
"token": "ACME",
"replacement": "safeToken"
}
}
},
"overrides": [
{
"files": ["src/rules/**"],
"rules": {
"LONG_FILE": { "enabled": false },
"local/contains-token": {
"options": {
"token": "RULE_TEST_TOKEN"
}
}
}
}
]
}A plugin can be a local JSON file, a local module, or an installed package. Module plugins should use the public helper from desloppify/plugin-api and declare metadata.
desloppify.config.js / .cjs and .desloppifyrc.js / .cjs are also supported when you want computed config.
const { definePlugin, PLUGIN_API_VERSION } = require("desloppify/plugin-api")
module.exports = definePlugin({
meta: {
name: "local-plugin",
namespace: "local",
apiVersion: PLUGIN_API_VERSION
},
rules: [
{
id: "contains-token",
category: "ai-slop",
severity: "MEDIUM",
message: "Contains {{token}}",
description: "Contains {{token}} marker",
fix: "Replace {{token}} with {{replacement}}",
pattern: "{{token}}",
files: ["src/**"],
identityGroup: 0,
options: {
token: {
type: "string",
default: "ACME",
description: "Token literal to flag"
},
replacement: {
type: "string",
default: "safeToken"
}
}
}
],
configs: {
recommended: {
rules: {
"local/contains-token": {
weight: 1.5,
options: {
token: "BETA"
}
}
}
}
}
})Packaged plugins work too:
{
"plugins": { "acme": "desloppify-plugin-acme" },
"extends": ["plugin:acme/recommended"]
}Then reference a preset with:
{
"plugins": { "local": "./desloppify.plugin.cjs" },
"extends": ["plugin:local/recommended"]
}Module plugin validation now checks apiVersion, namespace mismatches, duplicate/non-local rule ids, and option default type mismatches. Plugin rule options are scalar values (string, number, boolean) resolved from rule defaults, then top-level config, then matching file overrides. {{optionName}} placeholders can be used in plugin pattern, flags, message, description, fix, and files. Plugin regex rules also derive stable delta identities from their match text (or a selected identityGroup capture), and can now attach fix text just like built-in findings, so line shifts do not churn delta reports and custom rules surface better remediation. This keeps local plugins deterministic and gives better failure messages during scan/rules/score.
This lets you tune built-in rules without forking the tool. It is the first step toward reference-repo-style config/plugin extensibility.
.desloppify/ is intentionally local working state, not a committed artifact surface. If you need committed outputs, export them explicitly instead of tracking the hidden working directory.
desloppify fix also uses repo-local formatter tooling as a cleanup pass after safe rewrites:
- JS/TS:
biome format,oxfmt - Python:
ruff format
- CI pins Bun to
1.3.10for reproducible test runs bun run release:checkverifies the npm tarball payload locally- GitHub Actions
Releaseworkflow bumpspatch/minor/major, syncs versioned source files, commits, and tagsvX.Y.Z - GitHub Actions
Publishworkflow publishes the tagged release to npm and creates a GitHub release
# Inline: suppress a specific rule
console.log("intentional"); // desloppify:ignore CONSOLE_LOG
# Project-level: .desloppifyignore (gitignore syntax)
dist/
coverage/
*.gen.tsMIT