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

@sebassdc/crap4ts

v0.1.1

Published

CRAP metric CLI for TypeScript: combines cyclomatic complexity and test coverage to find risky code, with a bundled cross-agent AI skill.

Readme

crap4ts

CRAP (Change Risk Anti-Pattern) metric for TypeScript projects.

Combines cyclomatic complexity with test coverage to identify functions that are both complex and under-tested — the riskiest code to change.

Quick Start

Install from npm:

npm install -g @sebassdc/crap4ts

Or from source:

git clone https://github.com/sebassdc/crap4ts.git
cd crap4ts
npm install
npm run build
npm install -g .

Configure your test runner to emit Istanbul JSON coverage:

Vitest (vitest.config.ts):

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',          // or 'istanbul'
      reporter: ['text', 'json'],
    },
  },
});

Jest (jest.config.ts):

export default {
  coverageReporters: ['text', 'json'],
};

Run from your project root (where src/ lives):

crap4ts

crap4ts automatically deletes stale coverage data, runs your test suite with coverage, and prints the report.

Output

CRAP Report
===========
Function                       Module                              CC   Cov%     CRAP
-------------------------------------------------------------------------------------
complexFn                      my.module                          12  45.0%    130.2
simpleFn                       my.module                           1 100.0%      1.0

CLI Options

crap4ts --help              # show usage and available options
crap4ts --version           # print version number
crap4ts --src lib           # analyze from lib/ instead of src/
crap4ts --exclude dist      # exclude paths containing "dist"
crap4ts --timeout 120       # set analysis timeout to 120 seconds

Configuration File

Instead of passing flags every time, create a crap4ts.config.json (or .crap4tsrc.json) in your project root:

{
  "src": "lib",
  "exclude": ["dist", "fixtures"],
  "output": "json",
  "failOnCrap": 30,
  "timeout": 120
}

File Discovery

crap4ts looks for config files in the current working directory in this order:

  1. crap4ts.config.json (preferred)
  2. .crap4tsrc.json (fallback)

The first file found is used. If neither exists, all options use their defaults.

To load a config file from a custom path, use the --config flag:

crap4ts --config configs/crap4ts.json

CLI Override Precedence

CLI flags always take precedence over config file values. For example, if your config file sets "src": "lib" but you run crap4ts --src app, the app directory is used.

Supported Keys

| Key | Type | Description | Default | |----------------------|------------|--------------------------------------------------|---------| | src | string | Source directory to analyze | "src" | | exclude | string[] | Exclude paths containing these patterns | [] | | output | string | Output format: "text", "json", "markdown", or "csv" | "text"| | runner | string | Test runner: "vitest" or "jest" | auto | | coverageCommand | string | Custom shell command to generate coverage | none | | failOnCrap | number | Fail if any CRAP score >= this value | none | | failOnComplexity | number | Fail if any cyclomatic complexity >= this value | none | | failOnCoverageBelow| number | Fail if any function coverage < this % (0-100) | none | | top | number | Show only the top N entries | all | | timeout | number | Analysis timeout in seconds | 600 |

Unknown keys are silently ignored, so config files are forward-compatible with future versions.

Programmatic API

crap4ts can be used as a library in your own tools and scripts. The API assumes coverage data already exists (run your test suite with coverage first).

import { generateReport, crapScore, extractFunctions } from '@sebassdc/crap4ts';

// High-level: analyze an entire source tree against existing coverage
const { entries } = generateReport({
  srcDir: 'src',
  coverageDir: 'coverage',
});

entries.forEach(e => console.log(`${e.name}: CRAP ${e.crap}`));

generateReport(options)

Finds source files, parses coverage, analyzes each file, and returns entries sorted by CRAP score. This does not run your test suite -- it reads from an existing coverage-final.json.

| Option | Type | Description | Default | |---------------|------------|--------------------------------------------------|---------| | srcDir | string | Source directory to scan for .ts files | -- | | coverageDir | string | Directory containing coverage-final.json | -- | | filters | string[] | Only include files matching these substrings | [] | | excludes | string[] | Exclude files whose path contains these substrings| [] |

Low-level exports

For fine-grained control, individual functions are also exported:

import {
  extractFunctions,    // parse a TS source string into FunctionInfo[]
  parseCoverage,       // read coverage-final.json from a directory
  coverageForRange,    // get coverage % for a line range
  sourceToModule,      // convert file path to dotted module name
  crapScore,           // compute CRAP score from complexity and coverage
  sortByCrap,          // sort CrapEntry[] by CRAP descending
  formatReport,        // render text table from CrapEntry[]
  formatJsonReport,    // render JSON string from CrapEntry[]
  formatMarkdownReport, // render markdown table from CrapEntry[]
  formatCsvReport,     // render CSV string from CrapEntry[]
  findSourceFiles,     // find all .ts files in a directory
  filterSources,       // filter file list by substring patterns
  analyzeFile,         // analyze a single file against coverage data
} from '@sebassdc/crap4ts';

TypeScript types CrapEntry, FunctionInfo, CoverageData, and FileCoverageData are also exported.

CI Integration

Use threshold flags to fail CI when code quality drops below acceptable levels:

# Fail if any function has CRAP >= 30 or coverage below 70%
crap4ts --fail-on-crap 30 --fail-on-coverage-below 70

# Fail if any function has complexity >= 15, show only top 10
crap4ts --fail-on-complexity 15 --top 10

Multiple thresholds can be combined. The report is always printed before any failure.

The --top flag limits displayed entries but all entries are evaluated against thresholds.

Exit Codes

| Code | Meaning | |------|---------| | 0 | Pass -- no threshold violations | | 1 | Threshold violated or runtime error | | 2 | Usage error (invalid flags or arguments) |

Output Formats

crap4ts supports four output formats:

crap4ts                      # default text table
crap4ts --json               # JSON (shorthand for --output json)
crap4ts --output markdown    # Markdown table
crap4ts --output csv         # CSV

Text (default)

CRAP Report
===========
Function                       Module                              CC   Cov%     CRAP
-------------------------------------------------------------------------------------
complexFn                      my.module                          12  45.0%    130.2
simpleFn                       my.module                           1 100.0%      1.0

JSON

{
  "tool": "crap4ts",
  "entries": [
    {
      "name": "complexFn",
      "module": "my.module",
      "complexity": 12,
      "coverage": 45,
      "crap": 130.2
    }
  ]
}

Markdown

# CRAP Report

| Function | Module | CC | Cov% | CRAP |
|---|---|---:|---:|---:|
| complexFn | my.module | 12 | 45.0% | 130.2 |
| simpleFn | my.module | 1 | 100.0% | 1.0 |

CSV

Function,Module,CC,Coverage,CRAP
complexFn,my.module,12,45.0,130.2
simpleFn,my.module,1,100.0,1.0

Text output is the default. Use --json as a shorthand or --output <format> for any format.

Excluding Paths

Use --exclude to filter out files whose path contains a given substring. The flag is repeatable:

# Skip dist and fixtures directories
crap4ts --exclude dist --exclude fixtures

# Analyze lib/ but skip generated code
crap4ts --src lib --exclude __generated__

# Combine with other options
crap4ts --src packages/core/src --exclude __mocks__ --exclude .stories --json

Filtering

Pass module path fragments as arguments to filter:

crap4ts parser validator   # only files matching those strings

CRAP Formula

CRAP(fn) = CC² × (1 - coverage)³ + CC
  • CC = cyclomatic complexity (decision points + 1)
  • coverage = fraction of statements covered by tests

| Score | Risk | |-------|------| | 1–5 | Low — clean code | | 5–30 | Moderate — refactor or add tests | | 30+ | High — complex and under-tested |

What It Counts

Decision points that increase cyclomatic complexity:

  • if / ternary (c ? a : b)
  • else if (each adds 1)
  • for / for...of / for...in
  • while / do...while
  • catch clauses (each adds 1)
  • case clauses in switch (each case adds 1; default does not)
  • && / || / ?? operators (each operator adds 1)

Nested functions and class bodies are skipped — only the enclosing function's body is analyzed.

Compatibility

| Layout | Status | Notes | |--------|--------|-------| | Standard (src/) | Supported | Default, no config needed | | Custom source dir | Supported | Use --src <dir> | | Monorepo workspace | Supported | Point --src to package source | | Multiple src dirs | Supported | Use --exclude to filter | | Windows paths | Supported | Normalized internally | | Istanbul JSON coverage | Required | Other formats not supported | | Branch coverage | Not used | Statement coverage only |

Limitations

  • Only TypeScript (.ts) files are analyzed — .tsx, .js, and .jsx files are ignored.
  • Only functions found within the configured source directory (default: src/) are scanned.
  • Coverage data must be in Istanbul JSON format (coverage-final.json). Other coverage formats are not supported.
  • Runner detection is heuristic: crap4ts checks for Vitest config files first, then Jest config files, then falls back to the scripts field in package.json. Use --runner vitest|jest to override.
  • Nested functions are attributed to their enclosing function rather than being extracted as separate symbols.
  • Dynamic or computed method names (e.g., [Symbol.iterator]() or ["methodName"]()) are not extracted.
  • Only statement coverage is used when computing the coverage fraction — branch and function coverage are ignored.
  • Coverage is calculated using statement-to-function overlap: a statement is attributed to a function if its line range overlaps the function's line range. This is an approximation; a multi-line statement that spans a function boundary may be counted for both the enclosing and the adjacent function.

For advanced usage patterns, see docs/advanced-usage.md.

Extracted Symbols

  • Top-level function declarations
  • Top-level const f = () => {} and const f = function() {}
  • Class constructor, methods, getters, and setters (named as ClassName.methodName)
  • Object literal methods, getters, and setters in top-level variable declarations (named as varName.methodName or varName['string-key'])

Nested functions (functions defined inside other functions, methods, or arrows) are intentionally excluded. They are not extracted as separate symbols; their complexity is attributed to the enclosing function.

Cross-Agent Skill

crap4ts ships a bundled SKILL.md that you can install into the cross-agent skill directory consumed by Claude Code, Codex, Pi, and any harness that reads .agents/skills/.

# Global install for the current user (~/.agents/skills/crap4ts/SKILL.md)
crap4ts skill install

# Project-local install (./.agents/skills/crap4ts/SKILL.md)
crap4ts skill install --project

# Print the bundled skill
crap4ts skill show

# Print where the skill is (or would be) installed
crap4ts skill path
crap4ts skill path --project

# Remove
crap4ts skill uninstall
crap4ts skill uninstall --project

The bundled skill lives inside the published package at src/skill/SKILL.md and is shipped via the files field in package.json.

Claude Code

Claude Code reads skills from ~/.claude/skills/, not ~/.agents/skills/. After installing, symlink the skill so both directories stay in sync:

crap4ts skill install
ln -s ~/.agents/skills/crap4ts ~/.claude/skills/crap4ts

For project-local installs, symlink into .claude/skills/ at the repo root:

crap4ts skill install --project
ln -s .agents/skills/crap4ts .claude/skills/crap4ts

Runner Configuration

crap4ts supports three ways to run your test suite for coverage, applied in this order of precedence:

1. --coverage-command (highest priority)

Run an arbitrary shell command instead of the built-in runner logic. The command is executed with shell: true, so pipes, environment variables, and shell syntax all work.

# Monorepo: run tests only for a specific package
crap4ts --coverage-command "npm run test:api -- --coverage"

# Custom script with environment variables
crap4ts --coverage-command "CI=1 yarn test --coverage --coverageReporters=json"

# Turborepo / Nx workspace
crap4ts --coverage-command "npx turbo run test -- --coverage"

The command must produce a coverage/coverage-final.json file in Istanbul JSON format.

2. --runner vitest|jest (skip auto-detection)

Use the built-in runner invocation for Vitest or Jest, but skip the config-file heuristic:

# Force Jest even if a vitest.config.ts exists
crap4ts --runner jest

# Force Vitest in a project without a vitest.config file
crap4ts --runner vitest

3. Auto-detection (default)

When neither flag is provided, crap4ts detects the runner automatically:

  1. If any vitest.config.* file exists, use Vitest.
  2. If any jest.config.* file exists, use Jest.
  3. If package.json lists jest as a dependency, use Jest.
  4. Otherwise, default to Vitest.

Troubleshooting

| Error | Fix | |-------|-----| | Source directory 'src' not found | Use --src <dir> to point to your source directory | | No TypeScript files found | Verify your source directory contains .ts files | | No files match the filters | Check your filter arguments match actual file paths | | Unable to parse package.json | Fix your package.json or use --runner vitest\|jest | | Coverage run failed | Ensure your test suite passes independently before running crap4ts | | No coverage-final.json found | Configure your test runner to output Istanbul JSON coverage (see Quick Start) | | Coverage run timed out | Increase timeout with --timeout <seconds> |

Development

npm install
npm test          # run tests
npm run build     # compile to dist/
npm run coverage  # run tests with coverage

Inspiration

This project was inspired by crap4clj by Uncle Bob.

License

MIT