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

@icaruswings138/crap4ts

v0.4.0

Published

CRAP scores for TypeScript and TSX using cyclomatic complexity and test coverage.

Readme

crap4ts

This document follows Simplified Technical English.

CRAP means Change Risk Anti-Pattern. The score combines cyclomatic complexity with measured test coverage to identify TypeScript and TSX functions that are risky to change.

Complex functions have more execution paths. Low coverage gives you less evidence that a change preserves behavior. A high CRAP score means that one or both risks need attention.

crap4ts reports these risks without changing source files or tests. It is an independent implementation inspired by Uncle Bob's crap4clj. It is not a port or an official TypeScript version of crap4clj.

Why CRAP scores matter for AI agents

Prompt instructions can lose influence as an agent's context grows. A deterministic check applies the same rule each time the workflow runs it.

Tools such as CRAP scoring and mutation testing can enforce measurable quality requirements without relying on the agent to remember them. crap4ts provides CRAP scoring. It does not provide mutation testing.

A workflow can rerun crap4ts after each change. The agent must then respond to current code and coverage data. It can add focused tests, reduce complexity, or explain why a function needs an exception.

A low score does not prove that the system has good names, module boundaries, or architecture. Use CRAP scoring as one quality check, not as a complete definition of maintainable code.

Quick start

The CLI requires Node.js 20.19 or later.

Install from npm. No registry login is required to install this public package.

Install the CLI globally:

npm install --global @icaruswings138/crap4ts

Or install it as a development dependency in the project that you want to analyze:

npm install --save-dev @icaruswings138/crap4ts
npx crap4ts --help

The examples below use the global crap4ts command. For a local installation, use npx crap4ts or run crap4ts from a package script.

Run the remaining commands from the project that you want to analyze. Create crap4ts.config.json in that project root:

{
  "sourceRoots": ["src"],
  "coverageCommand": "pnpm coverage",
  "coveragePath": "coverage/coverage-final.json",
  "coverageFormat": "istanbul",
  "coverageDirectory": "coverage"
}

Run the configured coverage command by itself first. Confirm that it creates the declared artifact:

pnpm coverage

Then generate a fresh report:

crap4ts

The report lists the highest CRAP scores first.

Understand the score

The score uses cyclomatic complexity C and coverage percentage p:

CRAP = C^2 * (1 - p / 100)^3 + C

Full coverage removes the coverage penalty, so the score equals the function's complexity. Low coverage increases the penalty for complex functions.

| Complexity | Coverage | CRAP | | ---: | ---: | ---: | | 2 | 0% | 6 | | 5 | 0% | 30 | | 10 | 50% | 22.5 | | 10 | 100% | 10 |

A high score gives you two possible responses. Add focused tests when behavior lacks coverage. Simplify control flow when complexity drives the score. Some functions need both changes.

Use Uncle Bob's published bands as guidance:

| CRAP score | Interpretation | | --- | --- | | 1-5 | Low. The function is simple or well covered. | | 5-30 | Moderate. Consider focused tests or refactoring. | | 30+ | High. The function is complex and under-tested. |

The published bands overlap at 5 and 30. Treat them as approximate guidance. Use an explicit threshold to enforce a maximum acceptable score.

Recommended workflow

Use this loop before or during work on a risky module:

  1. Run a baseline report.
  2. Resolve N/A entries and coverage diagnostics before trusting the scores.
  3. Filter the report to the module that you plan to change.
  4. Start with the highest-scoring function in that module.
  5. Add characterization tests when existing tests do not establish current behavior.
  6. Reduce complex branching when complexity drives the score.
  7. Run the project tests and crap4ts again.
  8. Continue until the result meets the project's agreed limit.

The agent skill reports by default. Give the agent a separate instruction when you want it to edit tests or source files.

Common CLI commands

Generate fresh coverage and analyze all configured source roots:

crap4ts

Filter the report to matching source paths:

crap4ts orders billing

Analyze an existing LCOV artifact:

crap4ts --use-existing-coverage \
  --coverage coverage/lcov.info \
  --coverage-format lcov

Analyze an existing Istanbul artifact:

crap4ts --use-existing-coverage \
  --coverage coverage/coverage-final.json \
  --coverage-format istanbul

Write structured output for an agent or another program:

crap4ts --json

Configure a project

The CLI reads crap4ts.config.json from the current project directory. It does not search parent directories.

| Field | Required | Meaning | | --- | --- | --- | | sourceRoots | Yes | Project-relative TypeScript source directories. | | exclude | No | Array of project-relative source exclusion globs. Defaults to []. | | threshold | No | Maximum acceptable CRAP score per function. A finite, non-negative number; omitted by default. | | coverageCommand | Generated mode | Command that creates the coverage artifact. | | coveragePath | Yes | Project-relative Istanbul or LCOV artifact path. | | coverageFormat | Yes | istanbul or lcov. | | coverageDirectory | No | Disposable directory that generated mode may remove. |

Set coverageDirectory only when the complete directory contains disposable coverage output.

Generate and analyze coverage

Generated mode is the default. The CLI removes the configured artifact, runs the project coverage command, and analyzes the new artifact.

crap4ts

In text mode, the coverage command inherits the terminal input and output streams. In JSON mode, the CLI routes coverage stdout to stderr so report stdout stays parseable.

A failed coverage command stops the analysis with exit code 1.

The CLI does not guess a test runner, command, path, or format. Run the project coverage command yourself when the setup fails. Confirm that it creates the configured artifact.

Analyze existing coverage

Use --use-existing-coverage to skip cleanup and command execution.

For LCOV line coverage:

crap4ts --use-existing-coverage \
  --coverage coverage/lcov.info \
  --coverage-format lcov

For Istanbul statement coverage:

crap4ts --use-existing-coverage \
  --coverage coverage/coverage-final.json \
  --coverage-format istanbul

The command reads the selected artifact as UTF-8 and leaves it unchanged.

Filter source paths

Positional values filter project-relative source paths by substring. Multiple filters use OR matching.

crap4ts orders billing

This command includes a source path when it contains orders or billing.

Exclude test files and generated code

Add exclude to crap4ts.config.json to omit files from source analysis:

{
  "sourceRoots": ["src"],
  "exclude": [
    "**/*.test.ts",
    "**/*.test.tsx",
    "**/*.spec.ts",
    "**/*.spec.tsx",
    "**/__tests__/**",
    "src/generated/**"
  ],
  "coverageCommand": "pnpm coverage",
  "coveragePath": "coverage/coverage-final.json",
  "coverageFormat": "istanbul"
}

Patterns match the complete project-relative path, using / separators on every operating system. Use * within a path segment, ** across directories, ? for one character, [ab] for character classes, and braces for alternatives such as **/*.{test,spec}.{ts,tsx}. Patterns also match dot files and dot directories. Absolute paths, parent (..) segments, backslash separators, and leading ! negation are rejected. Use directory/** to exclude a directory's contents.

Add temporary exclusions with a repeatable CLI option. Quote each glob so the shell passes it unchanged:

crap4ts --exclude '**/*.test.ts' --exclude '**/__tests__/**'

CLI exclusions append to configured exclusions. A file must pass the source-root and positional-filter selection, then match none of the exclusion patterns. With no exclusions, test files remain eligible for analysis. If no files remain, the CLI reports the roots, filters, and exclusions and exits with code 2.

Exclusions affect source analysis, not the coverage command or which tests execute. Coverage records whose project-relative paths match an exclusion do not produce UNMATCHED_COVERAGE_FILE diagnostics. Other unmatched records are still reported. Source discovery still traverses the configured roots, so exclusions do not bypass directory permission errors.

Library callers can pass exclude in analyzeProject options, or as the optional fourth argument to findSourceFiles.

Fail on high CRAP scores

Set an inclusive maximum with --threshold:

crap4ts --threshold 5

A score of 5 passes; 5.1 or 6 fails. The comparison uses the full, unrounded score, so 5.01 also fails even if the text table displays 5.0. Scores belong to individual functions: any function above the limit in any analyzed file fails the run.

To persist the limit, add "threshold": 5 to crap4ts.config.json. The CLI option overrides the configured value. Without either setting, the run remains report-only.

The complete report is printed before the command exits with code 3 for a threshold breach. Each offending function produces a CRAP_THRESHOLD_EXCEEDED diagnostic with its source location, score, and threshold. Text diagnostics go to stderr; JSON diagnostics remain inside the report so stdout stays parseable.

The check applies only to functions selected by source roots, filters, and exclusions. It works with generated or existing coverage. N/A scores do not breach the threshold; their existing coverage diagnostics remain visible. A passing threshold check therefore does not guarantee complete coverage evidence.

CLI options

| Option | Meaning | | --- | --- | | --source-root <path> | Replace configured source roots. Repeat the option to add roots. | | --exclude <glob> | Append a source exclusion glob. Repeat to add more. | | --threshold <number> | Fail with exit code 3 when any function's CRAP score is greater than this limit. | | --coverage-command <command> | Replace the configured command for generated mode. | | --coverage <path> | Replace the project-relative coverage artifact path. | | --coverage-format <format> | Select istanbul or lcov. | | --coverage-directory <path> | Name an explicit project-relative directory that generated mode may remove. | | --use-existing-coverage | Read the current artifact without cleanup or command execution. | | --json | Write one JSON object instead of the text table. | | --no-color | Disable colours in the CRAP report. | | --help | Print usage information. |

Except for additive --exclude patterns, command-line values replace matching configuration values. Repeated --source-root values replace the complete configured list.

Read the reports

Text output uses a table with function, module, cyclomatic complexity, coverage, and CRAP columns. Numbers are right-aligned, with coverage and CRAP rounded to one decimal place. A summary counts analyzed functions, high-risk functions (CRAP >30), and functions missing coverage.

CRAP Report
===========
+------------+------------+----+----------+------+
| Function   | Module     | CC | Coverage | CRAP |
+------------+------------+----+----------+------+
| placeOrder | src/orders | 12 |    45.0% | 36.0 |
| receipt    | src/orders |  2 |   100.0% |  2.0 |
+------------+------------+----+----------+------+

Functions: 2
High risk (>30): 1
Missing coverage: 0

Interactive terminals colour numeric cells using these display bands:

| Metric | Green | Yellow | Red | | --- | --- | --- | --- | | CRAP | ≤5 | >5 to 30 | >30 | | Coverage | ≥80% | 50% to <80% | <50% |

N/A is grey. Colours and summary counts use the unrounded values. These bands are visual guidance; they do not enforce thresholds or affect exit codes. Complexity values are not colour-coded.

The table adapts to the terminal width and marks truncated names or modules with …. Terminals narrower than 60 columns use wrapped, labelled records; the minimum layout width is 20 columns. Redirected output and default library output are plain text with full names. JSON retains full names and values in every environment.

Use --no-color, set NO_COLOR (including an empty value), or use TERM=dumb to disable report colour. Redirecting stdout also disables colour automatically. These settings affect this report; the project's coverage command controls its own output.

crap4ts --no-color
crap4ts --json > report.json

Library callers can pass { color: true, columns: 80 } as the second argument to formatTextReport. Without options, its output is deterministic, uncoloured, and not width-limited.

The report sorts numeric CRAP scores from highest to lowest. Source path and source position break ties. Entries with N/A scores come after numeric scores.

N/A means that the analyzer lacks coverage evidence for that function. It does not mean 0 percent coverage. Text mode writes the reason to stderr.

JSON mode keeps full numeric precision. Each entry exposes its source position through top-level start and end objects. The report also includes tool metadata, coverage metadata, and structured diagnostics. Stdout contains exactly one JSON object, so another program can parse it directly.

crap4ts --json

Counted syntax

Every function starts at complexity 1. The analyzer adds 1 for each of these decisions inside that function:

  • if
  • conditional expressions with ? :
  • for, for...in, and for...of
  • while and do...while
  • catch
  • each non-default case
  • &&, ||, and ??

The analyzer does not add complexity for else, default, try, finally, optional chaining, or default parameters. A nested function has its own score, so its decisions do not increase the parent function score.

The extractor recognizes function declarations, function expressions, arrow functions, methods, constructors, accessors, class field functions, and TSX arrow components. It ignores declarations without an executable body.

Coverage mapping and limitations

Istanbul input uses statement locations and statement counters. The analyzer does not replace missing statement data with function or branch counters.

LCOV input uses line hits. Coverage is ambiguous when any two functions in one source file share a tracked physical line, including sibling and nested functions. The report retains the stable LCOV_NESTED_LINE_OVERLAP code for every affected function. The shared line still counts once within each function measurement.

The analyzer reports N/A when no coverage file matches a source or no tracked location belongs to a function. The diagnostics identify unmatched paths and unused coverage records.

Cleanup accepts project-relative descendants only. The CLI rejects the project root, parent paths, absolute paths, and symbolic links that escape the project.

The analyzer supports .ts and .tsx files, Istanbul JSON, and LCOV. High scores change the exit code only when a threshold is configured.

Exit code 0 means analysis completed without a threshold breach, even when coverage diagnostics or N/A values exist. Exit code 1 means coverage generation, reading, parsing, mapping, or analysis failed. Exit code 2 means arguments or configuration are invalid. Exit code 3 means at least one function's CRAP score exceeded the configured threshold.

Install the agent skill

The repository includes an Agent Skills-compatible SKILL.md. The skill teaches agents how to configure the project, run the CLI, interpret diagnostics, and report risky functions.

Install the CLI first. The skill does not provide the crap4ts executable.

From the crap4ts checkout, link the skill into your local agent directories:

scripts/link-skill.sh

The script creates these links:

  • ~/.agents/skills/crap4ts for Codex and compatible agents
  • ~/.claude/skills/crap4ts for Claude Code

The script refuses to replace a real directory at either path. A later git pull updates the linked skill. Rebuild the package when the CLI implementation changes.

Ask for a report without edits:

Set up and run a CRAP report for this project. Do not edit tests or source files.

Authorize changes separately:

Work through the highest CRAP scores in src/orders. Preserve behavior, run the tests, and rerun crap4ts after each change.

Project checks

See CONTRIBUTING.md for development setup, quality requirements, and pull request guidance.

Run the normal verification before a push:

pnpm verify

This command runs the complete test suite, builds the package, generates coverage, and prints this repository's CRAP report.

Run mutation testing as part of the slower full verification:

pnpm verify:full

You can also run each check separately:

pnpm test
pnpm build
pnpm exec tsc -p tsconfig.json --noEmit
pnpm coverage
pnpm self-check
pnpm mutation

pnpm self-check builds the package and runs crap4ts against this repository. Mutation testing covers the scorer, complexity code, and coverage adapters. Run pnpm verify:full with Node.js 22.18 or later in Node.js 22, or Node.js 24.11 or later.

License

crap4ts is available under the MIT License.