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

ts-anti-patterns

v1.0.3

Published

CRAP (Change Risk Anti-Patterns) for TypeScript/JavaScript - inspired by minikin/cargo-crap

Downloads

571

Readme

ts-anti-patterns

Token-Aware Refactoring for AI Agents

Every file an agent reads costs tokens. On a 500-file TSX codebase that adds up to millions of input tokens before a single line changes. ts-anti-patterns cuts that bill: it ranks the riskiest functions first so the agent opens only what matters - not the whole tree.

The math

Sample: 300 TSX components averaged ~10k tokens/file (~1 token per 3.2–3.5 chars).

Claude Code meters the same tokens as the API. Claude Sonnet 4.6 (typical coding model): $3 / MTok input, $15 / MTok outputAnthropic pricing. Input costs below; output not included (refactor replies add on top).

| Approach | Input (≈) | vs. full read | Cost (≈) | Saved | |---|---:|---|---:|---:| | Read all 500 files | 5M | 💰 baseline | $15.00 | — | | ts-anti-patterns report → pick targets | ~60k | 83× less 💰 | $0.18 | $14.82 💰 | | Report + 20 worst files (whole) | ~260k | 19× less 💰 | $0.78 | $14.22 💰 | | Report + 20 function slices (file:line) | ~90k | 55× less 💰 | $0.27 | $14.73 💰 |

One function slice ≈ 1.5k tokens. Fat components can hit 30k+ each — CRAP score finds the hotspot inside, line count doesn't.

Profit: one scouting pass with ts-anti-patterns instead of reading every file keeps ~$15 in input cost — before you pay for a single line of agent output. On a 10-pass refactor that's ~$150 of input you never burn just finding where to start.

Why run it before an agent pass

  • Spend tokens on complexity, not volume - fix ranked hotspots, not 500 full files
  • Precise entry points - file:line lets the agent read a 1.5k-token slice, not a 30k-token component
  • Machine-readable output - JSON / Markdown / HTML parsed before the agent opens a single file

What is CRAP?

Change Risk Anti-Patterns combines cyclomatic complexity with test coverage into one score - high CRAP = high complexity + low coverage = exactly where bugs hide.

CRAP(m) = comp(m)² × (1 − cov(m)/100)³ + comp(m)

When no coverage data is provided, ts-anti-patterns falls back to a CC-only mode and uses cyclomatic complexity as the score.

HTML report

Install

npm install -g ts-anti-patterns

Or use npx without installing (recommended for one-off checks and CI):

npx ts-anti-patterns@latest ./src

Using with npx

Run from your project root (where package.json and src/ live). No global install required.

CC-only (default, no coverage)

# Scan ./src (auto-detected if you omit the path)
npx ts-anti-patterns@latest

# Explicit path
npx ts-anti-patterns@latest ./src

# HTML report in the current directory
npx ts-anti-patterns@latest ./src --format html --output crap-report.html

# CI gate (exit 1 if anything is above threshold)
npx ts-anti-patterns@latest ./src --threshold 30 --fail-above --summary

Full CRAP (with coverage)

# One command: try to run tests with coverage, then analyze
npx ts-anti-patterns@latest --full

# Or generate coverage yourself, then analyze
npm run test:coverage
npx ts-anti-patterns@latest --cov

# Explicit coverage file
npx ts-anti-patterns@latest --lcov coverage/lcov.info --format html --output crap-report.html

Custom coverage command (monorepos, pnpm, turbo, etc.)

npx ts-anti-patterns@latest --full --coverage-command "pnpm test -- --coverage"

Pin a version (CI)

npx [email protected] ./src --threshold 30 --fail-above

Install agent skill (Cursor / compatible agents)

ts-anti-patterns ships a bundled SKILL.md so agents know how to run analysis. Install it with npx (no global install needed):

# User-wide (recommended once per machine)
npx ts-anti-patterns@latest skill install

# Only for the current repo (commit .agents/skills/ts-anti-patterns/SKILL.md if you want the team to share it)
npx ts-anti-patterns@latest skill install --project

Other skill commands:

# Show where the skill file would be / is installed
npx ts-anti-patterns@latest skill path
npx ts-anti-patterns@latest skill path --project

# Print bundled skill content
npx ts-anti-patterns@latest skill show

# Remove installed skill
npx ts-anti-patterns@latest skill uninstall
npx ts-anti-patterns@latest skill uninstall --project

Paths:

| Scope | Location | |---|---| | Global | ~/.agents/skills/ts-anti-patterns/SKILL.md | | Project | ./.agents/skills/ts-anti-patterns/SKILL.md |

Cursor: agents read skills from .agents/skills/. After skill install, open a new chat so the skill is picked up. Some setups also use ~/.cursor/skills/ - if needed, symlink:

ln -s ~/.agents/skills/ts-anti-patterns ~/.cursor/skills/ts-anti-patterns

Local development (this repo)

If you are hacking on ts-anti-patterns itself, use the built CLI:

npm run build
node dist/cli.js ./src --format html --output crap-report.html

Or from another project, point npx at the folder:

npx /path/to/ts-anti-patterns/dist/cli.js ./src

Quick Start - CC-only (no coverage needed)

# Scan a folder
ts-anti-patterns ./src

# Multiple paths (shell glob expansion)
ts-anti-patterns ./src/* ./packages/*/src

# CI gate
ts-anti-patterns ./src --threshold 30 --fail-above

The CLI auto-detects ./src if no path is given. By default, reports show the top 20 worst results; override with --top.

Quick Start - full CRAP mode

One command (auto-generate coverage, then analyze):

ts-anti-patterns --full

Generate coverage with any standard runner - then enable it with --cov for auto-detect (or pass an explicit file): coverage/lcov.info, coverage/coverage-final.json, coverage/coverage-summary.json, coverage/clover.xml, or coverage/cobertura-coverage.xml:

# Vitest
npx vitest run --coverage --coverage.reporter=lcov
# or c8
npx c8 --reporter=lcov npm test
# or Jest
npx jest --coverage --coverageReporters=lcov

# Then run one of:
ts-anti-patterns --cov                        # auto-detects and scores CRAP
ts-anti-patterns --run-coverage --cov         # generate coverage first, then score
ts-anti-patterns --full                       # shorthand: run coverage + CRAP analysis
ts-anti-patterns --full --coverage-command "pnpm test -- --coverage"  # custom command
ts-anti-patterns --lcov coverage/lcov.info    # explicit path (exit 2 if missing)
ts-anti-patterns                              # default CC-only (no coverage)
ts-anti-patterns --source-map auto            # for transpiled coverage (dist → src)

Coverage priority: branch (BRDA) > function (FN/FNDA) > line-range fallback. Each row carries a confidence indicator: ● exact, ◐ range, ○ none. When some functions have no coverage data, the --missing policy chooses how to score them:

| Policy | Effect | | ------------- | -------------------------------------------------- | | pessimistic | unmatched = 0 % (default - punishes blind spots) | | optimistic | unmatched = 100 % (kind to noisy excludes) | | skip | drop unmatched rows from the report |

Options

| Flag | Default | Purpose | |---|---|---| | [...paths] | ./src (auto) | One or more files/dirs to analyze | | --threshold <n> | 30 | Score above which a function is flagged | | --fail-above | off | Exit 1 if any function ≥ severity threshold | | --fail-above-severity <sev> | warning | info | warning | error | | --top <n> | 20 | Show only N worst offenders | | --min <n> | - | Hide entries below this score | | --format <fmt> | human | human | json | html | markdown | github | sarif | pr-comment | | --output <path> | stdout | Write to file instead of stdout | | --exclude <glob> | - | Exclude glob (repeatable, .gitignore-aware) | | --allow <glob> | - | Allow-list glob (repeatable) | | --cov | off | Enable auto-detection from coverage/* | | --full | off | One-command mode: generate coverage then analyze | | --run-coverage | off | Generate coverage before analysis | | --coverage-command <cmd> | auto | Custom command used by --run-coverage / --full | | --lcov <path> | - | Explicit LCOV file (exit 2 if missing) | | --coverage <path> | - | Explicit coverage file (lcov/json-summary/clover/cobertura) | | --source-map <auto\|dir> | - | Translate coverage through source maps (dist/*.js → src/*.ts) | | --missing <policy> | pessimistic | pessimistic | optimistic | skip | | --no-cov | on | Force CC-only mode | | --skip-anonymous | off | Hide <arrow@N> / <fn@N> rows | | --count-nullish-coalescing | off | Count ?? as a branch in CC + cognitive | | --no-cognitive | - | Skip cognitive-complexity computation | | --no-hints | - | Suppress per-function actionable hints | | --baseline <path> | - | Compare against a saved JSON report | | --fail-regression | off | Exit 1 when any regression vs baseline is detected | | --epsilon <n> | 0.01 | Score delta treated as unchanged for --baseline | | --summary | off | Print only the aggregate headline (no table) | | --diagnose <file> | - | Debug one file: every AST function + why it was kept/filtered | | --workspace | off | Scan each package in package.json#workspaces | | --watch | off | Re-render in human format on changes (debounced 200ms) | | --no-cache | - | Disable the .ts-anti-patterns-cache/ AST cache | | --jobs <n> | os.cpus() | Parallel file-analysis concurrency | | --config <path> | discovered | Path to a config file |

Subcommands

| Command | Description | |---|---| | ts-anti-patterns skill <install\|uninstall\|show\|path> | Install bundled agent skill (--project for repo-local). | | ts-anti-patterns init | Create .ts-anti-patterns.json and add an crap script to package.json. Idempotent. | | ts-anti-patterns explain <term> | Print the glossary entry for crap, cc, cognitive, coverage, confidence, severity, missing, or pragma. | | ts-anti-patterns explain | List every glossary term. |

Output Formats

  • human (default) - colorized terminal table with summary, hints, and a sticky Δ since last run line.
  • json - report-v1 envelope: stable shape suitable for --baseline later.
  • html - single self-contained file, no network. Severity colors, threshold slider, search, suppressed/no-coverage toggles, glossary popovers, CSV export, dark mode, print stylesheet.
  • markdown - GFM table with severity emoji. Drops into PR descriptions cleanly.
  • github - Actions annotations (::error, ::warning, ::notice).
  • sarif - SARIF 2.1.0 for GitHub Code Scanning.
  • pr-comment - markdown with <!-- ts-anti-patterns-report --> marker so a bot can update the same comment in place. With --baseline, regressions go on top.

Documentation Index

Programmatic API

import { analyze, renderHtml, computeCrap, GLOSSARY } from "ts-anti-patterns"

const { entries, meta } = await analyze({
  paths: ["src"],
  threshold: 30,
  noCov: true,
})
const html = await renderHtml(entries, meta, { threshold: 30 })

Every renderer (renderHuman, renderJson, renderHtml, renderMarkdown, renderGithub, renderSarif, renderPrComment) and scoring primitive (computeCrap, scoreOf, severityOf) is re-exported from the package root.

CI Quick Start

  • PR gate: ts-anti-patterns --threshold 30 --fail-above --summary
  • Regression gate: ts-anti-patterns --cov --baseline baseline.json --fail-regression --format json
  • Code Scanning: ts-anti-patterns --cov --format sarif --output ts-anti-patterns.sarif
  • PR comment: ts-anti-patterns --cov --baseline baseline.json --format pr-comment --output ts-anti-patterns.md

For complete workflows, copy examples from examples/.

Severity Bands

| Severity | Score | Default behavior | |---|---|---| | ok | ≤ threshold/2 | clean | | info | ≤ threshold | "borderline, watch it" | | warning | ≤ 2 × threshold | triggers --fail-above | | error | > 2 × threshold | triggers --fail-above |

Config File

Discovered via cosmiconfig in this order: .ts-anti-patterns.json, .ts-anti-patterns.yaml, ts-anti-patterns.config.{js,cjs,mjs,json}, or a ts-anti-patterns key in package.json.

{
  "threshold": 30,
  "exclude": ["**/legacy/**"],
  "skipAnonymous": false,
  "countNullishCoalescing": false,
  "failAboveSeverity": "warning"
}

CLI flags always override the config.

cargo-crap - Rust implementation of the CRAP metric.

License

MIT