diff --git a/CHANGELOG.md b/CHANGELOG.md index 91bf936..053958d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to DebtLens are documented here. This project adheres to [Semantic Versioning](https://semver.org/). +## [0.3.0] - 2026-06-09 + +### Added + +- **`debtlens adopt`** first-run adoption workflow with dry-run recommendations and optional + config/baseline writes. +- **`debtlens doctor`** command to inspect resolved config and matched files without scanning. +- **Rule packs** (`core`, `react`, `react-native`, `next`) as config presets via `--pack` and + `debtlens init --pack`. +- **Configurable `todo-comment` markers** with custom patterns, disabled defaults, and + `replaceDefaults`. +- **Inline suppressions** via `debtlens-disable-next-line` and `debtlens-disable-file` + comments with required reasons after `--`. +- **`--fail-on-confidence`** CLI flag, config field, and GitHub Action input for + confidence-aware CI exit codes. +- **`--diff-base`** mode to report findings introduced since a git ref. +- **`--package`** monorepo scanning MVP for `packages/*` workspace layouts. +- **`--profile`** per-rule timing output without changing findings. +- **Filter stats** in scan summaries (`suppressedByBaseline`, `filteredByMinSeverity`, + `suppressedByInline`) across terminal, JSON, Markdown, and PR-comment reporters. +- **GitHub Action** step summary output, PR comment upsert mode, and `fail-on-confidence` input. +- **Calibrated quality fixtures** for representative app shapes. +- **Performance benchmark suite** for scan fixtures. +- **Rule fix guidance** and refactor prompts in Markdown reports. +- **Plugin API RFC** in `docs/plugin-api-rfc.md`. + +### Changed + +- **`large-component`** now recognizes `memo`, `forwardRef`, and class components. +- **`naming-drift`** is quieter on domain-rich apps via `disableBuiltInVocabulary` and + calibrated defaults. +- **`todo-comment`** skips `debtlens-disable-*` directive lines so suppression comments do + not self-trigger findings. +- Contributor docs refreshed for completed roadmap; good-first issue queue is now historical. + ## [0.2.0] - 2026-06-06 ### Added diff --git a/README.md b/README.md index f28a0ea..d8df72b 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,33 @@ When a scan reads zero files, DebtLens prints a stderr warning with likely cause 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: + +```ts +// 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: + +```ts +// 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`. + +Prefer baselines for legacy debt, config tuning for false positives, and inline suppressions for rare, documented exceptions. See [`docs/rules.md`](./docs/rules.md#suppressing-findings) for guidance. + ## Configuration Create `debtlens.config.json`: @@ -386,14 +413,16 @@ steps: Want to help make DebtLens better? Start with the [first-PR guide](./docs/contributing-first-pr.md), the -[rule pack taxonomy](./docs/rule-packs.md), the -[good first issues list](./docs/good-first-issues.md), and the -[contributor roadmap](https://github.com/ColumbusLabs/DebtLens/projects). Use -[Discussions](https://github.com/ColumbusLabs/DebtLens/discussions) for open-ended -ideas, rule proposals, and usage questions. +[rule pack taxonomy](./docs/rule-packs.md), and +[CONTRIBUTING.md](./CONTRIBUTING.md). The v0.3 contributor roadmap batch is complete; +see [`docs/good-first-issues.md`](./docs/good-first-issues.md) for a historical index of +shipped tasks. Propose new work in +[Discussions](https://github.com/ColumbusLabs/DebtLens/discussions), via the rule request +template, or the [plugin API RFC](./docs/plugin-api-rfc.md). Contribution paths: **core TS/JS rules**, **React pack rules**, **framework packs** -(Next.js, RN, Node), **scanner/CI** (baselines, monorepos), and **reporters**. +(Next.js, RN, Node), **scanner/CI** (baselines, monorepos, inline suppressions), and +**reporters**. ## Development @@ -409,10 +438,15 @@ node dist/cli/index.js scan examples/react --min-severity info ## Project status -DebtLens is currently in the v0.2 release line. The architecture is 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`](./ROADMAP.md) and [`docs/rule-packs.md`](./docs/rule-packs.md). +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`](./ROADMAP.md) and +[`docs/rule-packs.md`](./docs/rule-packs.md). ## License diff --git a/ROADMAP.md b/ROADMAP.md index cbd2735..936acea 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -24,17 +24,22 @@ the core vs pack taxonomy, future languages, and contribution paths. - Add `--changed` mode for pull request scanning. - Add `--sarif` output for GitHub code scanning. -## v0.3 — Maintainer workflow integrations +## v0.3 — Maintainer workflow integrations (shipped) -- GitHub Action. +- GitHub Action with SARIF, step summary, PR comment upsert, and `fail-on-confidence`. - PR comment mode with Markdown annotations grouped by file. -- Suggested refactor prompts for each issue. -- Rule config schema and generated docs. -- Monorepo/package-aware scanning. +- Suggested refactor prompts and rule fix guidance in reports. +- Rule config schema, rule packs, and `debtlens doctor`. +- Monorepo/package-aware scanning (`--package` MVP). +- First-run adoption workflow (`debtlens adopt`). +- Inline suppressions with required reasons. +- Filter stats in scan summaries (baseline, min-severity, inline suppressions). +- `--diff-base` for branch-introduced findings. +- Scan profiling (`--profile`) and calibrated quality fixtures. ## v0.4 — Ecosystem expansion -- Optional rule packs in config (`core`, `react`, `react-native`, `next`, `node`). +- Optional rule packs in config (`core`, `react`, `react-native`, `next`, `node`) — **partially shipped in v0.3**. - Vue/Svelte detectors where applicable. - **First non-JS language pack: Python** — duplicate/dead-abstraction/TODO/naming rules; same `ScanResult` and SARIF contract as TS/JS. - Configurable domain vocabulary for naming drift. diff --git a/docs/contributing-first-pr.md b/docs/contributing-first-pr.md index a973b5a..938265a 100644 --- a/docs/contributing-first-pr.md +++ b/docs/contributing-first-pr.md @@ -30,13 +30,19 @@ Use a focused test while iterating, then run the full commands before opening a - `tests/` mirrors the production areas with focused `node:test` coverage. - `examples/` contains small projects used for smoke tests and report fixtures. -## Pick an issue +## Pick a contribution -Start with [`docs/good-first-issues.md`](./good-first-issues.md), then comment on the -GitHub issue before taking medium or large work. Small docs and test-only fixes usually -do not need a long design thread. +The original good-first issue queue is complete as of v0.3.0. Use +[`docs/good-first-issues.md`](./good-first-issues.md) as a historical index of shipped +work, then propose new changes through +[Discussions](https://github.com/ColumbusLabs/DebtLens/discussions), the +[rule request template](../.github/ISSUE_TEMPLATE/rule_request.md), or the plugin API RFC +([`docs/plugin-api-rfc.md`](./plugin-api-rfc.md)). -Good starter areas by layer: +Comment before starting medium or large work so maintainers can help shape the approach. +Small docs and test-only fixes usually do not need a long design thread. + +Good contribution areas by layer: - **Core rules** — duplication, abstractions, naming, TODO markers - **React pack** — component size, hooks, prop drilling, effects diff --git a/docs/good-first-issues.md b/docs/good-first-issues.md index 8d20864..1ee6a95 100644 --- a/docs/good-first-issues.md +++ b/docs/good-first-issues.md @@ -7,14 +7,22 @@ contribution layers. **Labels:** `good first issue` (general) · `good-first-rule` (detector/rule work) -Statuses reflect the current repository surface. **Done** means the GitHub issue is closed. +## Roadmap status (v0.3.0) + +The original contributor roadmap batch is **complete**. There are no open good-first +implementation issues. New work should start in +[Discussions](https://github.com/ColumbusLabs/DebtLens/discussions), the +[rule request template](../.github/ISSUE_TEMPLATE/rule_request.md), or the plugin API RFC +([`docs/plugin-api-rfc.md`](./plugin-api-rfc.md)). + +Statuses below are historical. **Done** means the GitHub issue is closed. ## Core rules (any TS/JS project) | # | Task | Issue | Status | | --- | --- | --- | --- | -| 3 | Reduce `naming-drift` false positives on domain-rich apps | [#3](https://github.com/ColumbusLabs/DebtLens/issues/3) | Open | -| 4 | Configurable markers for `todo-comment` | [#4](https://github.com/ColumbusLabs/DebtLens/issues/4) | Open | +| 3 | Reduce `naming-drift` false positives on domain-rich apps | [#3](https://github.com/ColumbusLabs/DebtLens/issues/3) | **Done** | +| 4 | Configurable markers for `todo-comment` | [#4](https://github.com/ColumbusLabs/DebtLens/issues/4) | **Done** | | 27 | Warn when `duplicate-logic` hits `maxSnippets` cap | [#27](https://github.com/ColumbusLabs/DebtLens/issues/27) | **Done** | ## React pack rules @@ -22,7 +30,7 @@ Statuses reflect the current repository surface. **Done** means the GitHub issue | # | Task | Issue | Status | | --- | --- | --- | --- | | 1 | Make the `prop-drilling` host-component list configurable | [#1](https://github.com/ColumbusLabs/DebtLens/issues/1) | **Done** | -| 2 | Teach `large-component` to recognize `memo`, `forwardRef`, and class components | [#2](https://github.com/ColumbusLabs/DebtLens/issues/2) | Open | +| 2 | Teach `large-component` to recognize `memo`, `forwardRef`, and class components | [#2](https://github.com/ColumbusLabs/DebtLens/issues/2) | **Done** | | 15 | Extend `effect-complexity` to `useLayoutEffect` / `useInsertionEffect` | [#15](https://github.com/ColumbusLabs/DebtLens/issues/15) | **Done** | ### 1. Make the `prop-drilling` host-component list configurable — [#1](https://github.com/ColumbusLabs/DebtLens/issues/1) (closed) @@ -30,29 +38,21 @@ Statuses reflect the current repository surface. **Done** means the GitHub issue Implemented via `propDrilling.ignoreComponents`, config/schema support, and detector tests covering custom ignored components. -### 2. Teach `large-component` to recognize more component forms — [#2](https://github.com/ColumbusLabs/DebtLens/issues/2) - -Today it only classifies PascalCase function/arrow components -([`src/utils/ast.ts`](../src/utils/ast.ts) `collectFunctionLikes`). It misses -`memo(function X(){})`, `forwardRef(...)`, and class components. - -- Verify: fixtures for each form in `tests/detectors/largeComponent.test.ts`. - -### 3. Reduce `naming-drift` false positives on domain-rich apps — [#3](https://github.com/ColumbusLabs/DebtLens/issues/3) +### 2. Teach `large-component` to recognize more component forms — [#2](https://github.com/ColumbusLabs/DebtLens/issues/2) (closed) -The built-in media vocabulary treats distinct domain entities (e.g. `movie` vs `show`) -as "competing names." Options: raise the default `minVariants`, add a config switch to -disable the built-in pack, or require co-occurrence in the same identifier. +`memo`, `forwardRef`, and class components are classified in +[`src/utils/ast.ts`](../src/utils/ast.ts) with fixtures in +`tests/detectors/largeComponent.test.ts`. -- Touch: [`src/detectors/namingDrift.ts`](../src/detectors/namingDrift.ts). -- Verify: a media-style fixture that should NOT fire by default. +### 3. Reduce `naming-drift` false positives on domain-rich apps — [#3](https://github.com/ColumbusLabs/DebtLens/issues/3) (closed) -### 4. Configurable markers for `todo-comment` — [#4](https://github.com/ColumbusLabs/DebtLens/issues/4) +`namingDrift.disableBuiltInVocabulary` and calibrated media fixtures reduce noise on +domain-rich apps. See [`src/detectors/namingDrift.ts`](../src/detectors/namingDrift.ts). -Allow projects to add/replace the marker patterns -([`src/detectors/todoComment.ts`](../src/detectors/todoComment.ts)) via config. +### 4. Configurable markers for `todo-comment` — [#4](https://github.com/ColumbusLabs/DebtLens/issues/4) (closed) -- Verify: a custom marker fires; a removed default does not. +Custom markers, disabled defaults, and `replaceDefaults` are supported via config and +documented in the schema. See [`src/detectors/todoComment.ts`](../src/detectors/todoComment.ts). ### 15. Extend `effect-complexity` to layout/insertion effects — [#15](https://github.com/ColumbusLabs/DebtLens/issues/15) (closed) @@ -176,12 +176,20 @@ Expose `write-baseline`, `thresholds`, and `max-files` in `action.yml`. Scan `examples/react-native` and `examples/next` in `.github/workflows/ci.yml`. -## Roadmap / larger work +## Adoption and CI (v0.3) -Framework packs, monorepo support, and plugin loading are multi-PR efforts. Read the -issue body before starting and comment if you plan to own one. - -| # | Task | Layer | Issue | +| # | Task | Issue | Status | | --- | --- | --- | --- | -| 23 | Monorepo and package-aware scanning | scanner / CI | [#23](https://github.com/ColumbusLabs/DebtLens/issues/23) | -| 26 | Plugin API for third-party rules | scanner / extensibility | [#26](https://github.com/ColumbusLabs/DebtLens/issues/26) | +| 23 | Monorepo and package-aware scanning | [#23](https://github.com/ColumbusLabs/DebtLens/issues/23) | **Done** | +| 33 | Inline suppressions with required reasons | [#33](https://github.com/ColumbusLabs/DebtLens/issues/33) | **Done** | +| 37 | `debtlens doctor` for config debugging | [#37](https://github.com/ColumbusLabs/DebtLens/issues/37) | **Done** | +| 38 | First-run adoption wizard | [#38](https://github.com/ColumbusLabs/DebtLens/issues/38) | **Done** | +| 39 | Confidence-aware exit-code policy | [#39](https://github.com/ColumbusLabs/DebtLens/issues/39) | **Done** | + +## Forward-looking work + +Multi-PR or RFC efforts for future releases: + +| # | Task | Layer | Issue | Status | +| --- | --- | --- | --- | --- | +| 26 | Plugin API for third-party rules | scanner / extensibility | [#26](https://github.com/ColumbusLabs/DebtLens/issues/26) | RFC closed — see [`docs/plugin-api-rfc.md`](./plugin-api-rfc.md) | diff --git a/docs/rules.md b/docs/rules.md index f31ad2c..34357a8 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -17,6 +17,25 @@ Every finding includes a **confidence** score from 0 to 1. Confidence reflects h Use confidence for triage and CI policy (for example, `--fail-on high --fail-on-confidence 0.8`) when you want to gate on high-severity findings that are also well-supported. +## Suppressing findings + +DebtLens supports three layers for managing noise. Pick the narrowest tool that fits: + +| Approach | Best for | +| --- | --- | +| **Config / thresholds** | Project-wide false positives (ignore components, custom TODO markers, naming vocabulary) | +| **Baseline** | Legacy debt you accept today but want to block newly introduced issues in CI | +| **Inline suppression** | Rare, file-local exceptions with an auditable reason | + +Inline suppressions use comment directives: + +```ts +// debtlens-disable-next-line -- +// debtlens-disable-file -- +``` + +The reason after `--` is required. Unknown rule ids and missing reasons produce warnings and do not suppress. Suppressions run inside `scan()` before baseline or `--diff-base` filtering, so baselines still track only the issues that remain after inline suppressions. + ## `large-component` Flags React-style PascalCase functions, `memo`/`forwardRef` wrappers, and class components diff --git a/package-lock.json b/package-lock.json index fffdebf..3a307fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "debtlens", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "debtlens", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "dependencies": { "commander": "^15.0.0", diff --git a/package.json b/package.json index e1e00d4..b7debf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "debtlens", - "version": "0.2.0", + "version": "0.3.0", "description": "Maintainability scanner for TypeScript and JavaScript codebases; React rule pack is the first supported target.", "type": "module", "homepage": "https://github.com/ColumbusLabs/DebtLens#readme", diff --git a/src/detectors/todoComment.ts b/src/detectors/todoComment.ts index 331825d..9e9aafa 100644 --- a/src/detectors/todoComment.ts +++ b/src/detectors/todoComment.ts @@ -43,6 +43,7 @@ export const todoCommentDetector: Detector = { for (let index = 0; index < lines.length; index += 1) { const line = lines[index] ?? ""; if (!line.includes("//") && !line.includes("/*") && !line.includes("*")) continue; + if (/debtlens-disable-(?:next-line|file)/i.test(line)) continue; const match = patterns.find((pattern) => pattern.regex.test(line)); if (!match) continue; diff --git a/tests/cli/scan.test.ts b/tests/cli/scan.test.ts index deb1682..36a332b 100644 --- a/tests/cli/scan.test.ts +++ b/tests/cli/scan.test.ts @@ -187,6 +187,84 @@ describe("debtlens scan fail-on confidence", () => { }); }); +describe("debtlens scan inline suppressions", () => { + function withTempProject(run: (dir: string) => void) { + const dir = mkdtempSync(join(tmpdir(), "debtlens-cli-suppress-")); + try { + mkdirSync(join(dir, "src"), { recursive: true }); + run(dir); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + } + + it("suppresses a matching next-line finding when a reason is provided", () => { + withTempProject((dir) => { + writeFileSync(join(dir, "src", "Control.ts"), "// TODO remove after launch\n"); + writeFileSync( + join(dir, "src", "Widget.ts"), + "// debtlens-disable-next-line todo-comment -- tracked in PROJ-1\n// TODO remove after launch\n", + ); + + const control = JSON.parse( + runScan(["src/Control.ts", "--cwd", dir, "--rules", "todo-comment", "--format", "json"]).stdout, + ); + const suppressed = JSON.parse( + runScan(["src/Widget.ts", "--cwd", dir, "--rules", "todo-comment", "--format", "json"]).stdout, + ); + + assert.equal(control.summary.totalIssues, 1); + assert.equal(suppressed.summary.totalIssues, 0); + assert.equal(suppressed.summary.filterStats?.suppressedByInline, 1); + }); + }); + + it("does not suppress when the reason is missing", () => { + withTempProject((dir) => { + writeFileSync( + join(dir, "src", "Widget.ts"), + "// debtlens-disable-next-line todo-comment\n// TODO remove after launch\n", + ); + + const result = runScan([".", "--cwd", dir, "--rules", "todo-comment", "--format", "json"]); + const parsed = JSON.parse(result.stdout); + + assert.equal(result.status, 0); + assert.equal(parsed.summary.totalIssues, 1); + assert.match(result.stderr, /reason is missing/); + }); + }); + + it("does not suppress when the rule id does not match", () => { + withTempProject((dir) => { + writeFileSync( + join(dir, "src", "Widget.ts"), + "// debtlens-disable-next-line naming-drift -- wrong rule\n// TODO remove after launch\n", + ); + + const result = runScan([".", "--cwd", dir, "--rules", "todo-comment", "--format", "json"]); + const parsed = JSON.parse(result.stdout); + + assert.equal(parsed.summary.totalIssues, 1); + }); + }); + + it("suppresses all matching file-level findings for the configured rule", () => { + withTempProject((dir) => { + writeFileSync( + join(dir, "src", "Widget.ts"), + "// debtlens-disable-file todo-comment -- legacy rollout debt\n// TODO one\n// TODO two\n", + ); + + const result = runScan([".", "--cwd", dir, "--rules", "todo-comment", "--format", "json"]); + const parsed = JSON.parse(result.stdout); + + assert.equal(parsed.summary.totalIssues, 0); + assert.equal(parsed.summary.filterStats?.suppressedByInline, 2); + }); + }); +}); + describe("debtlens scan diff-base", () => { it("rejects --diff-base and --baseline together", () => { const result = runScan(["examples/react", "--diff-base", "HEAD~1", "--baseline", "baseline.json"]); diff --git a/tests/core/suppressions.test.ts b/tests/core/suppressions.test.ts index 087fbe8..7bae1b7 100644 --- a/tests/core/suppressions.test.ts +++ b/tests/core/suppressions.test.ts @@ -59,4 +59,15 @@ describe("inline suppressions", () => { assert.equal(result.issues.length, 0); assert.equal(result.suppressedByInline, 1); }); + + it("does not suppress a different rule on the suppressed line", () => { + const files = [file("src/a.ts", "// debtlens-disable-next-line todo-comment -- tracked in JIRA-1\nconst movie = 1;\n")]; + const result = applyInlineSuppressions([ + issue({ ruleId: "todo-comment", location: { startLine: 2 } }), + issue({ ruleId: "naming-drift", ruleName: "Naming drift", location: { startLine: 2 } }), + ], files, validRuleIds); + assert.equal(result.issues.length, 1); + assert.equal(result.issues[0]?.ruleId, "naming-drift"); + assert.equal(result.suppressedByInline, 1); + }); });