Skip to content

ColumbusLabs/DebtLens

DebtLens

CI npm version License: MIT Node

DebtLens is a maintainability scanner for TypeScript and JavaScript codebases. The first supported rule pack targets React (including React Native, Expo, and Next.js apps), but the core idea applies broadly: catch duplicated logic, bloated modules, weak boundaries, TODO debt, and naming drift before it becomes permanent.

It is not an "AI code detector." It does not try to prove who wrote a line of code. Instead, it finds the patterns that tend to slip into codebases when teams move quickly with coding assistants — duplicated logic, bloated components, state sprawl, overloaded effects, thin abstractions, prop drilling, TODO debt, and naming drift.

See docs/rule-packs.md for how core rules, framework packs, and language-agnostic reporting fit together.

npx debtlens scan
npx debtlens scan src --format markdown
npx debtlens scan --min-severity medium --fail-on high
npx debtlens scan --rules duplicates,state,effects

Example output

$ debtlens scan examples/react --min-severity medium

DebtLens Report
Scanned 3 files with 8 rules in 38ms.
Issues: 4 | high 2 | medium 2 | low 0 | info 0

HIGH (2)
  Prop drilling [prop-drilling]
  src/Dashboard.tsx:13
  Dashboard forwards 7 props across 3 child components.
  confidence 73%
  - ReleaseHero: movie, userId, region, theme, onSelect, onSave
  - ReleaseGrid: movie, userId, region, theme, onSelect, onSave, onShare
  suggestion: Consider colocating the data owner closer to consumers, using a
  composition slot, or extracting a focused context for stable cross-cutting values.

  Duplicate logic [duplicate-logic]
  src/duplicateOne.ts:1
  normalizeMovieRelease is 100% structurally similar to normalizeGameRelease.
  confidence 100%
  - src/duplicateOne.ts:1-18 (18 lines)
  - src/duplicateTwo.ts:1-18 (18 lines)

See docs/showcase-expensify-app.md for a curated run against a large production React Native codebase — one supported target, not the sole identity of the tool.

Why this matters

AI coding assistants make it easier to generate working code quickly. That creates a new maintainer problem: code review must catch duplicated implementations, architectural drift, unnecessary abstractions, and components that quietly absorb too many responsibilities.

That review burden is especially hard for new coders who have not yet built the instinct for what maintainability debt looks like. A beginner can ship something that works and still miss warning signs: repeated logic, overloaded effects, local state scattered everywhere, thin wrappers, or names that drift across a feature.

DebtLens gives maintainers and newer contributors a neutral, explainable report before debt becomes permanent. It is meant to teach what to look for, not just fail a build.

Current rule set

Built-in rules are grouped into a core pack (any TS/JS project) and a react pack (components and hooks). Full taxonomy: docs/rule-packs.md.

Rule Pack What it catches Default severity
duplicate-logic core Near-duplicate functions/components using normalized AST/text similarity Medium
dead-abstraction core Thin wrappers that add little behavior Low
todo-comment core TODO/FIXME/HACK/temporary implementation comments Low
naming-drift core Files with multiple competing names for the same domain concept Info
large-component react React-style components with too many lines, hooks, or branch points Medium
state-sprawl react Components/hooks with many local stateful hooks Medium
effect-complexity react Long or overloaded React effect hooks Medium
prop-drilling react Components that forward many props to children Medium

Performance benchmarks

Synthetic fixtures under tests/benchmarks/fixtures/ exercise small (5 files), medium (30), and large (100) scan sizes.

npm run build
npm run benchmark        # all fixtures + local budget check
npm run benchmark:ci     # small fixture only (used in CI)

Local budgets (generous; CI enforces small < 5000ms only):

Fixture Files Budget
small 5 5s
medium 30 30s
large 100 120s

Per-rule timing is available via --profile in PR #62 once merged.

Install

npm install --save-dev debtlens

or run without installing:

npx debtlens scan

Usage

debtlens init             # write a starter debtlens.config.json (use --force to overwrite)
debtlens init --pack core # starter config using the core rule pack preset
debtlens adopt            # adoption report (dry run; recommends minSeverity)
debtlens packs            # list built-in rule pack presets
debtlens doctor           # inspect resolved config and matched files without scanning
debtlens rules            # list built-in rule ids and descriptions
debtlens explain <rule>   # print rule docs, default thresholds, and false-positive guidance
debtlens suppress --rule <rule> --reason "<why>"   # print a copy-paste inline suppression comment
debtlens scan [target]

Options:

-i, --include <patterns>       comma-separated glob patterns to include
-x, --exclude <patterns>       comma-separated glob patterns to exclude
--min-severity <severity>      info, low, medium, or high
--pack <pack>                  built-in rule pack preset
--rules <rules>                comma-separated rule ids
--threshold <thresholds>       comma-separated key=value threshold overrides
--max-files <count>            maximum files to scan
--format <format>              terminal, json, markdown, pr-comment, or sarif
-o, --output <path>            write the report to a file
--fail-on <severity>           exit 1 when an issue meets this severity
--fail-on-confidence <0-1>     with --fail-on, require at least this confidence to fail
--baseline <path>              report only issues absent from this baseline file
--diff-base <ref>              report only findings introduced since this git ref
--write-baseline [path]        write current issues to a baseline file and exit
--changed [ref]                scan only files changed vs HEAD (or vs <ref> if given)
--staged                       scan only files staged in git
--respect-gitignore            skip files ignored by git
--config <path>                path to debtlens.config.json
--cwd <path>                   working directory
--package <name>               scan a single npm workspace package (MVP: `packages/*` layouts)
--no-color                     disable terminal color
-q, --quiet                    terminal only: suppress per-finding detail
--profile                      print per-rule timing to stderr without changing findings

Examples:

# Scan the current project
debtlens scan

# Scan only app source files
debtlens scan . --include "app/**/*.ts,app/**/*.tsx,src/**/*.ts,src/**/*.tsx"

# Create a Markdown report for a pull request artifact
debtlens scan --format markdown --output debtlens-report.md

# Create a compact grouped PR comment body
debtlens scan --format pr-comment --output debtlens-pr-comment.md

# CI gate: allow low/medium debt but fail high-confidence high-severity debt
debtlens scan --min-severity medium --fail-on high --fail-on-confidence 0.8

# Tune component-size threshold
debtlens scan --threshold "large-component.maxLines=320,state-sprawl.maxStatefulHooks=8"

# Adopt on a legacy repo: record existing debt, then only report newly introduced debt
debtlens scan --write-baseline
debtlens scan --baseline debtlens-baseline.json --fail-on high

# Pull-request scan: only the files this branch changed vs main
debtlens scan --changed origin/main --fail-on high

# Pre-commit scan: only files currently staged in git
debtlens scan --staged --fail-on high

# Opt in to .gitignore filtering in addition to DebtLens exclude globs
debtlens scan --respect-gitignore

# Debug config and file matching without running detectors
debtlens doctor --pack core
debtlens doctor --include "src/**/*.ts,src/**/*.tsx" --changed

# List rule ids for config, CI, or --rules
debtlens rules
debtlens rules --format json

# Quiet terminal output: hide per-finding detail
debtlens scan --quiet

Recommended adoption path

Preview findings and get a minSeverity recommendation before committing config or baseline files:

debtlens adopt --cwd . --rules todo-comment   # dry-run report (default)
debtlens adopt --write-config --write-baseline --force

The second command writes debtlens.config.json and debtlens-baseline.json (baseline write is skipped when zero issues are found). After adoption, use debtlens scan --baseline debtlens-baseline.json --fail-on high in CI to gate only newly introduced debt.

Baseline fingerprints are stable across line shifts, so moving existing code up or down does not resurface already-recorded debt — only genuinely new issues are reported.

When a scan reads zero files, DebtLens prints a stderr warning with likely causes such as include/exclude globs, the target path, --cwd, or an empty git file set from --changed / --staged. The warning is advisory and does not change the exit code for --fail-on.

When duplicate-logic reaches duplicate-logic.maxSnippets, DebtLens warns that duplicate comparisons were capped. JSON output includes the same advisory under summary.warnings.

Inline suppressions

Suppress intentional findings in source with an explicit, auditable reason. Suppressions apply during the scan; baseline and --diff-base filtering run afterward on the remaining issues.

Next-line — hides a finding on the line immediately below the comment:

// debtlens-disable-next-line todo-comment -- tracked in PROJ-123
// TODO: remove after migration ships

File-level — hides all findings for that rule in the file:

// debtlens-disable-file naming-drift -- domain vocabulary is intentional here

Rules:

  • A non-empty reason is required after --. Suppressions without a reason are ignored and emit a warning.
  • Unknown rule ids emit a warning and do not suppress.
  • Only the matching rule (and line, for next-line) is suppressed; other rules on the same line still report.

Terminal output includes inline suppression counts in the filter stats line (for example, 1 inline suppressed). JSON reports expose the same count under summary.filterStats.suppressedByInline.

debtlens suppress prints a ready-to-paste directive so you don't have to remember the syntax:

debtlens suppress --rule todo-comment --reason "tracked in PROJ-123"
# // debtlens-disable-next-line todo-comment -- tracked in PROJ-123

debtlens suppress --rule naming-drift --reason "domain vocabulary is intentional" --file
# // debtlens-disable-file naming-drift -- domain vocabulary is intentional

Prefer baselines for legacy debt, config tuning for false positives, and inline suppressions for rare, documented exceptions. See docs/rules.md for guidance.

Configuration

Create debtlens.config.json:

{
  "$schema": "https://raw.githubusercontent.com/ColumbusLabs/DebtLens/main/schema/debtlens.config.schema.json",
  "include": ["src/**/*.{ts,tsx,js,jsx}"],
  "exclude": ["node_modules/**", "dist/**", "build/**", ".next/**"],
  "minSeverity": "low",
  "respectGitignore": false,
  "rules": [
    "large-component",
    "state-sprawl",
    "effect-complexity",
    "duplicate-logic",
    "dead-abstraction",
    "prop-drilling",
    "todo-comment",
    "naming-drift"
  ],
  "thresholds": {
    "large-component.maxLines": 250,
    "state-sprawl.maxStatefulHooks": 6,
    "effect-complexity.maxLines": 30,
    "duplicate-logic.minSimilarity": 0.86
  },
  "propDrilling": {
    "ignoreComponents": ["DesignSystemCard", "DesignSystemModal"]
  }
}

The stable JSON Schema URL is https://raw.githubusercontent.com/ColumbusLabs/DebtLens/main/schema/debtlens.config.schema.json. debtlens init writes this URL into new config files so editors can provide validation and autocomplete.

Rule packs

Built-in presets select a rule set without hand-picking every rule id. See docs/rule-packs.md.

Pack Rules
core duplicate-logic, dead-abstraction, todo-comment, naming-drift
react core + large-component, state-sprawl, effect-complexity, prop-drilling
react-native same as react
next same as react
{
  "$schema": "https://raw.githubusercontent.com/ColumbusLabs/DebtLens/main/schema/debtlens.config.schema.json",
  "pack": "core",
  "include": ["src/**/*.{ts,tsx,js,jsx}"]
}

Explicit rules in config override the pack. Use debtlens packs to list presets.

Per-rule severities and confidence floors

Tune noisy rules without disabling them. ruleSeverities replaces the severity a rule reports (changing summary counts and --fail-on behavior), and ruleConfidenceFloors hides findings from a rule below a minimum confidence:

{
  "ruleSeverities": {
    "naming-drift": "info"
  },
  "ruleConfidenceFloors": {
    "prop-drilling": 0.8
  }
}

Unknown rule ids in either map emit a warning with a did-you-mean suggestion. Issues hidden by a confidence floor are counted under summary.filterStats.filteredByConfidenceFloor. Both maps accept plugin rule ids when plugins are configured.

Custom naming vocabulary

naming-drift ships with a built-in media/release vocabulary. Add your own domain concepts with vocabulary (concept id → competing terms). Your groups are merged with the built-ins, and a group with the same id overrides the built-in one.

{
  "vocabulary": {
    "commerce-entity": ["product", "sku", "item", "listing"],
    "auth-user": ["user", "account", "member", "profile"]
  }
}

Plugins

Ship custom rules as local ESM modules without forking the CLI. List them in config with the plugin API version, then select them like built-in rules:

{
  "$schema": "https://raw.githubusercontent.com/ColumbusLabs/DebtLens/main/schema/debtlens.config.schema.json",
  "pluginApiVersion": 1,
  "plugins": ["./debtlens-rules/no-console.mjs"],
  "include": ["src/**/*.{ts,tsx,js,jsx}"]
}

Plugin authors import types from the published debtlens/plugin entry point:

import type { Detector, DetectorContext } from "debtlens/plugin";

Besides rules, a plugin's default export may include thresholds (defaults read by context.getThreshold, merged after built-ins so user config and --threshold still override them) and vocabulary (naming-drift concept groups, overridden by user config groups with the same id):

export default {
  rules: [noConsoleDetector],
  thresholds: { "no-console.maxCalls": 0 },
  vocabulary: { logging: ["log", "logger", "console", "debug", "trace"] },
};

See the reference plugin in examples/plugin/ and the full contract in docs/plugin-api-rfc.md. Plugin paths must stay within the config file's directory tree, rule ids must not collide with built-ins, and CI pipelines scanning untrusted repos can set DEBTLENS_DISABLE_PLUGINS=1 to skip plugin loading entirely (see SECURITY.md).

Output formats

Terminal output is designed for local development. JSON is designed for integrations. Markdown is designed for release notes and maintainer handoffs. pr-comment is compact Markdown grouped by file for GitHub pull request comments. SARIF (2.1.0) is designed for GitHub code scanning and other security/quality dashboards.

debtlens scan --format json
debtlens scan --format markdown --output reports/debtlens.md
debtlens scan --format pr-comment --output debtlens-pr-comment.md
debtlens scan --format sarif --output debtlens.sarif

GitHub Action

Run DebtLens on pull requests and surface findings as code-scanning annotations:

name: DebtLens
on: pull_request

permissions:
  contents: read
  security-events: write   # required to upload SARIF

jobs:
  debtlens:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0     # needed for --changed to diff against the base branch
      - uses: ColumbusLabs/debtlens@v0
        with:
          changed: origin/${{ github.base_ref }}
          format: sarif
          output: debtlens.sarif
          thresholds: large-component.maxLines=300
          quiet: true
          fail-on: high
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: debtlens.sarif

Inputs: target, min-severity, rules, fail-on, fail-on-confidence, format, output, changed, respect-gitignore, baseline, config, write-baseline, thresholds, max-files, working-directory, quiet, step-summary, comment. Each maps to the matching scan flag. write-baseline and baseline are mutually exclusive. With fail-on, a qualifying issue fails the job (gating the merge); if: always() still uploads the SARIF so annotations appear even on a failing run.

Set step-summary: true to append a compact Markdown rollup to the job's GitHub Actions step summary (useful alongside SARIF or terminal output):

- uses: ColumbusLabs/debtlens@v0
  with:
    changed: origin/${{ github.base_ref }}
    format: sarif
    output: debtlens.sarif
    step-summary: true
    quiet: true
    fail-on: high

Set comment: true to upsert a stable pull request comment (requires pull-requests: write):

permissions:
  contents: read
  pull-requests: write

- uses: ColumbusLabs/debtlens@v0
  with:
    changed: origin/${{ github.base_ref }}
    comment: true
    fail-on: high

To post a grouped PR comment manually instead, write the pr-comment output and post it with actions/github-script:

permissions:
  contents: read
  pull-requests: write

steps:
  - uses: actions/checkout@v4
    with:
      fetch-depth: 0
  - uses: ColumbusLabs/debtlens@v0
    with:
      changed: origin/${{ github.base_ref }}
      format: pr-comment
      output: debtlens-pr-comment.md
      fail-on: high
  - uses: actions/github-script@v7
    if: always() && github.event_name == 'pull_request'
    with:
      script: |
        const fs = require('node:fs');
        const body = fs.readFileSync('debtlens-pr-comment.md', 'utf8');
        await github.rest.issues.createComment({
          owner: context.repo.owner,
          repo: context.repo.repo,
          issue_number: context.issue.number,
          body,
        });

Contributing

Want to help make DebtLens better? Start with the first-PR guide, the rule pack taxonomy, and CONTRIBUTING.md. The v0.3 contributor roadmap batch is complete; see docs/good-first-issues.md for a historical index of shipped tasks. Propose new work in Discussions, via the rule request template, or the plugin API RFC.

Contribution paths: core TS/JS rules, React pack rules, framework packs (Next.js, RN, Node), scanner/CI (baselines, monorepos, inline suppressions), and reporters.

Development

npm install
npm run typecheck
npm test          # node:test suite (run via tsx)
npm run test:all  # typecheck + tests
npm run build
npm run dev
node dist/cli/index.js scan examples/react --min-severity info

Project status

DebtLens is in the v0.3 release line. Recent capabilities include debtlens adopt and debtlens doctor, rule packs, inline suppressions with required reasons, confidence-aware --fail-on, monorepo --package scanning, GitHub Action step summaries and PR comment upsert, and --diff-base branch comparisons.

The architecture stays intentionally simple: a language-agnostic scan and reporting layer with pluggable rule packs on top. React is the first serious pack; React Native, Next.js, and broader TS/JS rules expand from there. See ROADMAP.md and docs/rule-packs.md.

License

MIT

About

Maintainability scanner for TypeScript and JavaScript codebases; React rule pack is the first supported target.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors