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

@eduardbar/drift

v1.0.0

Published

Detect silent technical debt left by AI-generated code

Readme

drift — technical debt detector for AI-generated code

drift

Detect technical debt in AI-generated TypeScript code. One command. Zero config.

npm license TypeScript ts-morph PRs Welcome

Why · Installation · Commands · Rules · Score · Configuration · CI Integration · drift-ignore · Contributing


Why

AI coding tools ship code fast. They also leave behind consistent, predictable structural patterns that accumulate silently: files that grow to 600 lines, catch blocks that swallow errors, exports that nothing imports, functions duplicated across three modules because the model regenerated instead of reusing.

GitClear's 2024 analysis of 211M lines of code found a 39.9% drop in refactoring activity and an 8x increase in duplicated code blocks since AI tools became mainstream. A senior engineer on r/vibecoding put it plainly: "The code looks reviewed. It isn't. Nobody's reading 400-line files the AI dumped in one shot."

drift gives you a 0–100 score per file and project so you know what to look at before it reaches production.

How drift compares to existing tools:

| Tool | What it does | What it misses | |------|--------------|----------------| | ESLint | Correctness and style within a single file | Structural patterns, cross-file dead code, architecture violations | | SonarQube | Enterprise-grade static analysis | Costs money, requires infrastructure, overwhelming for small teams | | drift | Structural debt + AI-specific patterns + cross-file analysis + 0–100 score | Not a linter — does not replace ESLint |


Installation

# Run without installing
npx @eduardbar/drift scan .

# Install globally
npm install -g @eduardbar/drift

# Install as a dev dependency
npm install --save-dev @eduardbar/drift

Commands

drift scan [path]

Scan a directory and print a scored report to stdout.

drift scan .
drift scan ./src
drift scan ./src --output report.md
drift scan ./src --json
drift scan ./src --ai
drift scan ./src --fix
drift scan ./src --min-score 50

Options:

| Flag | Description | |------|-------------| | --output <file> | Write Markdown report to a file instead of stdout | | --json | Output raw DriftReport JSON | | --ai | Output structured JSON optimized for LLM consumption (Claude, GPT, etc.) | | --fix | Print inline fix suggestions for each detected issue | | --min-score <n> | Exit with code 1 if the overall score meets or exceeds this threshold |

Example output:

  drift  —  technical debt detector
  ──────────────────────────────────────────────────

  Score   █████████████░░░░░░░  67/100  HIGH
  4 file(s) with issues  ·  5 errors  ·  12 warnings  ·  3 info  ·  18 files clean

  Top issues:  debug-leftover ×8  ·  any-abuse ×5  ·  no-return-type ×3

  ──────────────────────────────────────────────────

  src/api/users.ts (score 85/100)
    ✖ L1    large-file              File has 412 lines (threshold: 300)
    ▲ L34   debug-leftover          console.log left in production code
    ▲ L89   catch-swallow           Empty catch block silently swallows errors
    ▲ L201  any-abuse               Explicit 'any' type detected

  src/utils/helpers.ts (score 70/100)
    ✖ L12   duplicate-function-name 'formatDate' looks like a duplicate
    ▲ L55   dead-code               Unused import 'debounce'

drift diff [ref]

Compare the current project state against any git ref. Defaults to HEAD~1.

drift diff                # HEAD vs HEAD~1
drift diff HEAD~3         # HEAD vs 3 commits ago
drift diff main           # HEAD vs branch main
drift diff abc1234        # HEAD vs a specific commit
drift diff --json         # Output raw JSON diff

Options:

| Flag | Description | |------|-------------| | --json | Output raw JSON diff |

Shows score delta, issues introduced, and issues resolved since the given ref.


drift report [path]

Generate a self-contained HTML report. No server required — open in any browser.

drift report              # scan current directory
drift report ./src        # scan specific path
drift report ./src --output my-report.html

Options:

| Flag | Description | |------|-------------| | --output <file> | Output path for the HTML file (default: drift-report.html) |

All styles and data are embedded inline in the output file.


drift badge [path]

Generate a badge.svg with the current score, compatible with shields.io style.

drift badge               # writes badge.svg to current directory
drift badge ./src
drift badge ./src --output ./assets/drift-badge.svg

Options:

| Flag | Description | |------|-------------| | --output <file> | Output path for the SVG file (default: badge.svg) |

Add the badge to your README — see README Badge.


drift ci [path]

Emit GitHub Actions annotations and a step summary. Designed to run inside a CI workflow.

drift ci                  # scan current directory
drift ci ./src
drift ci ./src --min-score 60

Options:

| Flag | Description | |------|-------------| | --min-score <n> | Exit with code 1 if the overall score meets or exceeds this threshold |

Outputs ::error and ::warning annotations visible in the PR diff. Writes a markdown summary to $GITHUB_STEP_SUMMARY.


drift trend [period]

Show score evolution over time. period accepts: week, month, quarter, year.

drift trend week
drift trend month
drift trend quarter --since 2025-01-01
drift trend year --until 2025-12-31

Options:

| Flag | Description | |------|-------------| | --since <date> | Start date for the trend window (ISO 8601) | | --until <date> | End date for the trend window (ISO 8601) |


drift blame [target]

Identify which files, rules, or contributors are responsible for the most debt. target accepts: file, rule, overall.

drift blame file          # top files by score
drift blame rule          # top rules by frequency
drift blame overall
drift blame file --top 10

Options:

| Flag | Description | |------|-------------| | --top <n> | Limit output to top N results (default: 5) |


Rules

26 rules across three severity levels. All run automatically unless marked as requiring configuration.

| Rule | Severity | Weight | What it detects | |------|----------|--------|-----------------| | large-file | error | 20 | Files exceeding 300 lines — AI generates monolithic files instead of splitting responsibility | | large-function | error | 15 | Functions exceeding 50 lines — AI avoids decomposing logic into smaller units | | duplicate-function-name | error | 18 | Function names that appear more than once (case-insensitive) — AI regenerates helpers instead of reusing them | | high-complexity | error | 15 | Cyclomatic complexity above 10 — AI produces correct code, not necessarily simple code | | circular-dependency | error | 14 | Circular import chains between modules — AI doesn't reason about module topology | | layer-violation | error | 16 | Imports that cross architectural layers in the wrong direction (e.g., domain importing from infra) — requires drift.config.ts | | debug-leftover | warning | 10 | console.log, console.warn, console.error, and TODO / FIXME / HACK comments — AI leaves scaffolding in place | | dead-code | warning | 8 | Named imports that are never used in the file — AI imports broadly | | any-abuse | warning | 8 | Explicit any type annotations — AI defaults to any when type inference is unclear | | catch-swallow | warning | 10 | Empty catch blocks — AI makes code not throw without handling the error | | comment-contradiction | warning | 12 | Comments that restate what the surrounding code already expresses — AI over-documents the obvious | | deep-nesting | warning | 12 | Control flow nested more than 3 levels deep — results in code that is difficult to follow | | too-many-params | warning | 8 | Functions with more than 4 parameters — AI avoids grouping related arguments into objects | | high-coupling | warning | 10 | Files importing from more than 10 distinct modules — AI imports broadly without encapsulation | | promise-style-mix | warning | 7 | async/await and .then() / .catch() used together in the same file — AI combines styles inconsistently | | unused-export | warning | 8 | Named exports that are never imported anywhere in the project — cross-file dead code ESLint cannot detect | | dead-file | warning | 10 | Files never imported by any other file in the project — invisible dead code | | unused-dependency | warning | 6 | Packages listed in package.json with no corresponding import in source files | | cross-boundary-import | warning | 10 | Imports that cross module boundaries outside the allowed list — requires drift.config.ts | | hardcoded-config | warning | 10 | Hardcoded URLs, IP addresses, secrets, or connection strings — AI skips environment variable abstraction | | inconsistent-error-handling | warning | 8 | Mixed try/catch and .catch() patterns in the same file — AI combines approaches without a consistent strategy | | unnecessary-abstraction | warning | 7 | Wrapper functions or helpers that add no logic over what they wrap — AI over-engineers simple calls | | naming-inconsistency | warning | 6 | Mixed camelCase and snake_case in the same module — AI forgets project conventions mid-generation | | semantic-duplication | warning | 12 | Functions with structurally identical logic despite different names — detected via AST fingerprinting, not text comparison | | no-return-type | info | 5 | Functions missing an explicit return type annotation | | magic-number | info | 3 | Numeric literals used directly in logic without a named constant |


Score

Calculation: For each file, drift sums the weights of all detected issues, capped at 100. The project score is the average across all scanned files.

| Score | Grade | Meaning | |-------|-------|---------| | 0 | CLEAN | No issues found | | 1–19 | LOW | Minor issues — safe to ship | | 20–44 | MODERATE | Worth a review before merging | | 45–69 | HIGH | Significant structural debt detected | | 70–100 | CRITICAL | Review before this goes anywhere near production |


Configuration

drift runs with zero configuration. Architectural rules (layer-violation, cross-boundary-import) require a drift.config.ts (or .js / .json) at your project root:

import type { DriftConfig } from '@eduardbar/drift'

export default {
  layers: [
    { name: 'domain',  patterns: ['src/domain/**'],  canImportFrom: [] },
    { name: 'app',     patterns: ['src/app/**'],     canImportFrom: ['domain'] },
    { name: 'infra',   patterns: ['src/infra/**'],   canImportFrom: ['domain', 'app'] },
  ],
  boundaries: [
    { name: 'auth',    root: 'src/modules/auth',    allowedExternalImports: ['src/shared'] },
    { name: 'billing', root: 'src/modules/billing', allowedExternalImports: ['src/shared'] },
  ],
  exclude: [
    'src/generated/**',
    '**/*.spec.ts',
  ],
  rules: {
    'large-file': { threshold: 400 },   // override default 300
    'magic-number': 'off',              // disable a rule
  },
} satisfies DriftConfig

Without a config file, layer-violation and cross-boundary-import are silently skipped. All other rules run with their defaults.


CI Integration

Basic gate with scan

name: Drift

on: [pull_request]

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Check debt score
        run: npx @eduardbar/drift scan ./src --min-score 60

Exit code is 1 if the score meets or exceeds --min-score. Exit code 0 otherwise.

Annotations and step summary with drift ci

name: Drift

on: [pull_request]

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Run drift
        run: npx @eduardbar/drift ci ./src --min-score 60

drift ci emits ::error and ::warning annotations that appear inline in the PR diff and writes a formatted summary to $GITHUB_STEP_SUMMARY. Use this when you want visibility beyond a pass/fail exit code.


drift-ignore

Suppress a single issue

Add // drift-ignore at the end of the flagged line or on the line immediately above it:

console.log(debugPayload) // drift-ignore
// drift-ignore
const result: any = parse(input)

Suppress an entire file

Add // drift-ignore-file anywhere in the first 10 lines of the file:

// drift-ignore-file
// This file contains intentional console output — not debug leftovers.

When drift-ignore-file is present, analyzeFile() returns an empty report with score 0 for that file. Use this for files like loggers or CLI printers where console.* calls are intentional.


README Badge

Generate a badge from your project score and add it to your README:

drift badge . --output ./assets/drift-badge.svg

Then reference it in your README:

![drift score](./assets/drift-badge.svg)

The badge uses shields.io-compatible styling and color-codes automatically by grade: green for LOW, yellow for MODERATE, orange for HIGH, red for CRITICAL.


Contributing

Open an issue before starting significant work. Check existing issues first — use the bug report or feature request templates.

To add a new detection rule:

  1. Create a branch: git checkout -b feat/rule-name
  2. Add "rule-name": <weight> to RULE_WEIGHTS in src/analyzer.ts
  3. Implement AST detection logic using ts-morph in analyzeFile()
  4. Add a fix_suggestion entry in src/printer.ts
  5. Update the rules table in README.md and AGENTS.md
  6. Open a PR using the template in .github/PULL_REQUEST_TEMPLATE.md

See CODE_OF_CONDUCT.md before participating.


Stack

| Package | Role | |---------|------| | ts-morph | AST traversal and TypeScript analysis | | commander | CLI commands and flags | | kleur | Terminal colors (zero dependencies) |

Runtime: Node.js 18+ · TypeScript 5.x · ES Modules · Supports TypeScript (.ts, .tsx) and JavaScript (.js, .jsx) files


License

MIT © eduardbar