npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

desloppify

v1.0.2

Published

Your AI wrote spaghetti. This eats it. The definitive AI slop detector for any codebase.

Downloads

539

Readme

Desloppify

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.

Quick start

# 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 ruby

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

Install as a skill

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-skill

The canonical underlying command is:

npx skills add FasalZein/desloppify

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

Programmatic API

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.

Other install methods

# Global CLI install
bun install -g desloppify

# Clone and link
git clone https://github.com/FasalZein/desloppify.git
cd desloppify && bun install && bun link

What it catches

Desloppify 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 CorrectnessforEach(async), blocking requests, sequential independent awaits
  • Dead Code — unused exports, functions, files, dependencies
  • Weak Typesany, as any, object, Function, @ts-ignore
  • Runtime Validation — unvalidated req.body, JSON.parse casts, 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.

How it works

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.

Optional overhaul review mode

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.

Commands

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 commands

Use --project, --slice, --prd, and --feature with --wiki or --handoff when you want wiki-native output with concrete project context.

Git hooks for current changes by default

desloppify install-hooks
# or, if you want the raw command:
desloppify install-hooks --print

This 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 --staged
  • pre-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.

Saved report artifacts

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 (from desloppify delta --markdown)
  • .desloppify/reports/latest.delta.comment.md (from desloppify 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.json

Pretty scan mode also shows:

  • the current score / grade
  • issue severity summary
  • concrete next actions

Recommended follow-up order:

  1. latest.findings.json for machine decisions
  2. latest.report.md for human review
  3. latest.wiki.json or latest.handoff.md for 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.

Scoring

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)

Requirements

Optional (auto-detected, improves coverage):

  • knip — dead code detection (JS/TS)
  • madge — circular dependency detection (JS/TS, opt-in for full scans via --with-madge or --category circular-deps)
  • tsc — implicit any detection

Packs

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.

Configuration

desloppify now supports repo-local config discovery:

  • desloppify.config.json
  • desloppify.config.cjs
  • desloppify.config.js
  • .desloppifyrc
  • .desloppifyrc.json
  • .desloppifyrc.cjs
  • .desloppifyrc.js

Current support is intentionally small and deterministic:

  • extends
  • plugins.<namespace> for local JSON/module rule packs or installed package plugins
  • plugin:<namespace>/<config> preset extends
  • rules.<id>.enabled
  • rules.<id>.severity
  • rules.<id>.weight
  • rules.<id>.options.<name> for plugin rule option values
  • overrides[].files
  • overrides[].rules.<id>.enabled
  • overrides[].rules.<id>.severity
  • overrides[].rules.<id>.weight
  • overrides[].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

Release

  • CI pins Bun to 1.3.10 for reproducible test runs
  • bun run release:check verifies the npm tarball payload locally
  • GitHub Actions Release workflow bumps patch / minor / major, syncs versioned source files, commits, and tags vX.Y.Z
  • GitHub Actions Publish workflow publishes the tagged release to npm and creates a GitHub release

False positives

# Inline: suppress a specific rule
console.log("intentional"); // desloppify:ignore CONSOLE_LOG

# Project-level: .desloppifyignore (gitignore syntax)
dist/
coverage/
*.gen.ts

License

MIT