Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ All notable changes to DebtLens are documented here. This project adheres to
### Added

- `debtlens history record` / `history show` with `.debtlens/history.jsonl` ledger and timeline reports.
- Payoff ranking via `--sort payoff`, JSON `payoffScore`, and top-payoff report sections.
- `debtlens calibrate` for percentile-based threshold suggestions with optional `--write`.
- Interactive `debtlens triage` for keep/baseline/suppress workflows.
- Payoff ranking via `--sort payoff`, JSON `payoffScore`, and top-payoff report sections
([#251](https://github.com/ColumbusLabs/DebtLens/issues/251)).
- `debtlens calibrate` for percentile-based threshold suggestions with optional `--write`
([#256](https://github.com/ColumbusLabs/DebtLens/issues/256)).
- Interactive `debtlens triage` for keep/baseline/suppress workflows
([#255](https://github.com/ColumbusLabs/DebtLens/issues/255)).
- `debtlens fix` dry-run autofix allowlist for duplicated literals.
- Opt-in `feature-flags` pack with `stale-feature-flag` detector.
- `--concurrency` and `--cache-dir` scan controls for large-repo performance.
Expand All @@ -22,7 +25,8 @@ All notable changes to DebtLens are documented here. This project adheres to
([#260](https://github.com/ColumbusLabs/DebtLens/issues/260),
[#261](https://github.com/ColumbusLabs/DebtLens/issues/261),
[#262](https://github.com/ColumbusLabs/DebtLens/issues/262)).
- Config `budgets` block and `debtlens scan --budget-report` for per-area debt SLO gating.
- Config `budgets` block and `debtlens scan --budget-report` for per-area debt SLO gating
([#252](https://github.com/ColumbusLabs/DebtLens/issues/252)).
- `debtlens scan --format badge` emits a self-contained SVG badge plus shields.io endpoint JSON
([#265](https://github.com/ColumbusLabs/DebtLens/issues/265)).
- Scan results now warn when matched files exceed `maxFiles`; terminal output prints the
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ debtlens init --pack core # starter config using the core rule pack preset
debtlens init --policy @org/debtlens-policy # starter config from a shared policy package
debtlens init --from-eslint eslint.config.json # print a migration suggestion without writing
debtlens adopt # adoption report (dry run; recommends minSeverity)
debtlens calibrate . # suggest percentile-based threshold overrides
debtlens triage . # interactively keep, baseline, or suppress findings
debtlens watch examples/react --rules todo-comment # rescan on file changes
debtlens completions zsh # print shell completions
debtlens mcp # stdio MCP server for Cursor/Claude-style agents
Expand Down Expand Up @@ -343,6 +345,39 @@ The second command writes `debtlens.config.json` and `debtlens-baseline.json` (b

For serious open-source repositories and broad monorepos, treat adoption as a scoped rollout instead of a whole-repo gate on day one. Run `adopt` first, then start with `--changed origin/main` or a maintained source subdirectory, add `--package` for workspaces, keep generated/dependency outputs excluded, and narrow expensive or noisy rules with `--rules`, thresholds, baselines, or confidence floors before widening coverage. If the default 2,000-file cap appears, either raise it with `--max-files` for a deliberate full scan or make the target more precise with `--package`, `--include`, `--exclude`, `--changed`, or `--respect-gitignore`.

### Tune, prioritize, triage, and budget

After `adopt` picks rules, use these commands to finish a low-noise rollout:

```bash
# 1. Tune numeric thresholds to your repo's distributions
debtlens calibrate . --percentile 90
debtlens calibrate . --percentile 90 --write # merge into debtlens.config.json

# 2. Review highest-ROI findings first (enable hotspots when git history is available)
debtlens scan . --sort payoff --hotspots

# 3. Walk the backlog interactively (requires a TTY; use --dry-run to preview)
debtlens triage .

# 4. Protect cleaned areas with per-path budgets in debtlens.config.json
debtlens scan . --budget-report
debtlens scan . --fail-on high # budget breaches also fail the gate
```

Example `budgets` block:

```json
{
"budgets": {
"src/payments": { "maxIssues": 20, "maxHigh": 0 },
"src/legacy": { "maxIssues": 250 }
}
}
```

See [`docs/prioritization.md`](./docs/prioritization.md) for the payoff formula, `priority` weights, and budget glob matching.

Named quality-gate presets give teams a shared rollout vocabulary:

| Preset | Use when | Default behavior |
Expand Down
29 changes: 29 additions & 0 deletions docs/false-positives.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,35 @@ adding inline suppressions.
| Low-confidence finding family | `ruleConfidenceFloors` or `--fail-on-confidence` | Keeps findings visible while preventing weak gates. |
| One documented exception | Inline suppression with a reason | Auditable, local, and visible in JSON/SARIF output. |

## Calibrate thresholds to your repo

Default thresholds are deliberately generic. On a large legacy repo they can be noisy; on a small repo they can miss real debt. After `debtlens adopt` picks rules and severity, run calibration to tune the numbers:

```bash
# Preview percentile-based threshold suggestions (default p90)
debtlens calibrate .

# Tune aggressiveness and merge suggestions into debtlens.config.json
debtlens calibrate . --percentile 85 --write
```

Calibration temporarily lowers supported numeric trigger thresholds so the sample
includes below-threshold code, not just existing findings. The report also lists
selected policy floors, boolean switches, similarity controls, and safety caps
under **Not calibrated** when they cannot be inferred honestly from a distribution.

Calibration scans the target, collects observed metrics for threshold-driven rules (function length, branch counts, and similar), and suggests values at the chosen percentile so roughly the worst N% is flagged. Review the printed config snippet before using `--write`; unrelated config keys are preserved.

Pair calibration with payoff ranking and triage for a low-noise first rollout:

```bash
debtlens calibrate . --percentile 90
debtlens scan . --sort payoff --hotspots
debtlens triage .
```

See [`docs/prioritization.md`](./prioritization.md) for payoff scoring and per-area budgets.

## Baseline before suppressing

```bash
Expand Down
90 changes: 90 additions & 0 deletions docs/prioritization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Payoff prioritization

DebtLens can rank findings by **payoff score** so teams fix the debt that costs the most first instead of working through a flat severity list.

## When scores appear

Each issue gets a `payoffScore` in JSON output when payoff ranking is enabled:

- pass `--sort payoff` on `debtlens scan`, or
- enable git churn hotspots with `--hotspots` on the CLI, or
- pass `--blame-age` to include age in the score.

Terminal, Markdown, and HTML reports include a **Top payoff targets** section when any issue carries a score.

## Scoring formula

Payoff combines signals already present on each finding:

```
payoffScore = severityWeight × confidence × churnFactor × ageFactor
```

| Factor | Default behavior |
| --- | --- |
| `severityWeight` | high 16, medium 8, low 3, info 1 |
| `confidence` | issue confidence, floored at 0.35 |
| `churnFactor` | `1 + log2(1 + churnMetric) × churnWeight` when hotspot data exists; otherwise 1 |
| `ageFactor` | `1 + min(introducedDaysAgo / 365, 2) × ageWeight` when blame age is available; otherwise 1 |

Churn metrics come from [`src/core/hotspots.ts`](../src/core/hotspots.ts). Age uses optional `introducedDaysAgo` from `--blame-age`.

## CLI usage

```bash
# Sort findings by payoff and show top targets in the terminal report
debtlens scan . --sort payoff --hotspots

# JSON consumers read payoffScore and a bounded top-target shortlist
debtlens scan . --sort payoff --format json
```

When payoff scores are enabled, JSON output also includes
`summary.topPayoffTargets`. It contains at most 10 compact targets ordered
deterministically by score, file, line, rule, and fingerprint. Full issue details
remain in `issues`.

Use `--hotspots` when git history is available so churn boosts files that change often. In CI, check out enough history (`fetch-depth: 0` or a bounded `--churn-range`).

## Config weights

Tune weights under the `priority` block in `debtlens.config.json`:

```json
{
"priority": {
"severity": { "high": 20, "medium": 10, "low": 4, "info": 1 },
"churn": 1.2,
"age": 0.75
}
}
```

Higher `churn` and `age` weights amplify those factors. Severity weights follow the same keys as built-in severities.

## Adoption workflow

Payoff ranking fits between calibration and triage:

1. `debtlens calibrate` — tune thresholds to your repo (see [`docs/false-positives.md`](./false-positives.md)).
2. `debtlens scan . --sort payoff --hotspots` — review the highest-ROI findings first.
3. `debtlens triage` — baseline or suppress the backlog interactively.
4. Add `budgets` — cap debt per directory after cleanup (see below).

## Per-area budgets

After triage, protect cleaned areas with path budgets:

```json
{
"budgets": {
"src/payments": { "maxIssues": 20, "maxHigh": 0 },
"src/legacy": { "maxIssues": 250 }
}
}
```

- Budget breaches fail the scan gate (exit code 1) like `--fail-on`.
- `debtlens scan --budget-report` prints used/budget/headroom without failing — useful in CI dashboards.

Keys are path globs (`src/payments/**`, `src/**/*.ts`, or exact prefixes).
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"chokidar": "^4.0.3",
"commander": "^15.0.0",
"fast-glob": "^3.3.3",
"micromatch": "^4.0.8",
"ts-morph": "^28.0.0",
"yaml": "^2.9.0"
},
Expand Down
67 changes: 67 additions & 0 deletions schema/debtlens.scan-result.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,73 @@
"type": "integer",
"minimum": 0
},
"topPayoffTargets": {
"type": "array",
"maxItems": 10,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"fingerprint",
"ruleId",
"file",
"severity",
"payoffScore"
],
"properties": {
"id": {
"type": "string"
},
"fingerprint": {
"type": "string"
},
"ruleId": {
"type": "string"
},
"file": {
"type": "string"
},
"severity": {
"enum": [
"info",
"low",
"medium",
"high"
]
},
"payoffScore": {
"type": "number",
"minimum": 0
},
"location": {
"type": "object",
"additionalProperties": false,
"required": [
"startLine"
],
"properties": {
"startLine": {
"type": "integer",
"minimum": 1
},
"startColumn": {
"type": "integer",
"minimum": 1
},
"endLine": {
"type": "integer",
"minimum": 1
},
"endColumn": {
"type": "integer",
"minimum": 1
}
}
}
}
}
},
"warnings": {
"type": "array",
"items": {
Expand Down
Loading