@eduardbar/drift 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,15 +7,36 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.9.1] — 2026-02-25
11
+
12
+ ### Fixed
13
+ - `drift trend`: `analyzeSingleCommit` now analyses the full project snapshot at each commit instead of only the files changed in the diff. Uses `git ls-tree -r <hash> --name-only` to enumerate all tracked `.ts/.tsx` files, writes them to a temp directory via `git show <hash>:<file>`, runs `analyzeProject` on the snapshot, then cleans up. Score in each `TrendDataPoint` now reflects the total project health, not just the files touched in that commit.
14
+ - `drift trend`: added sampling to `analyzeHistoricalCommits` — selects at most 10 commits distributed evenly across the period (configurable via `maxSamples`). Prevents timeouts on repos with 100+ commits.
15
+ - `drift trend` / `drift blame`: propagate `DriftConfig` through the full call chain (`analyzeTrend` → `analyzeHistoricalCommits` → `analyzeSingleCommit` → `analyzeProject`) so custom rule configs are respected in historical analysis.
16
+
17
+ ---
18
+
10
19
  ## [Unreleased]
11
20
 
21
+ ---
22
+
23
+ ## [0.9.0] - 2026-02-24
24
+
12
25
  ### Added
13
26
 
14
- - **Phase 6: Static HTML report + README badge** — output commands for visibility
27
+ - **Phase 4: Historical drift analysis**
28
+ - `drift trend [path]` — `TrendAnalyzer` reads git log and computes drift score per commit over time; outputs a score-over-time table and detects score regressions across the project history
29
+ - `drift blame [path]` — `BlameAnalyzer` maps each detected issue to the git commit and author that introduced it using `git blame`; output includes author, commit hash, date, and issue description per line
30
+ - **Phase 6: Static HTML report + README badge + CI annotations**
15
31
  - `drift report [path]` — generates a self-contained `drift-report.html` with score, per-file breakdown, collapsible issue list, and fix suggestions
16
- - `drift badge [path]` — generates a `badge.svg` with the current drift score for your README
32
+ - `drift badge [path]` — generates a `badge.svg` with the current drift score for embedding in a README
17
33
  - `drift ci [path]` — emits GitHub Actions workflow annotations inline on PR diffs and writes a step summary; supports `--min-score` to gate PRs
18
34
 
35
+ ### Fixed
36
+
37
+ - `VERSION` is now read dynamically from `package.json` at runtime — no longer hardcoded as a string constant in `cli.ts`
38
+ - Added missing `program.parse()` call in `cli.ts` — subcommands (`scan`, `diff`, `trend`, `blame`, `report`, `badge`, `ci`) were registered but never executed when the CLI was invoked
39
+
19
40
  ---
20
41
 
21
42
  ## [0.8.0] - 2026-02-24
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
- ![drift — vibe coding debt detector](./assets/og.svg)
1
+ ![drift — technical debt detector for AI-generated code](./assets/og.png)
2
2
 
3
3
  # drift
4
4
 
5
- Detect silent technical debt left by AI-generated code. One command. Zero config.
6
-
7
- _Vibe coding ships fast. drift tells you what it left behind._
5
+ Detect technical debt in AI-generated TypeScript code. One command. Zero config.
8
6
 
9
7
  ![npm](https://img.shields.io/npm/v/@eduardbar/drift?color=6366f1&label=npm)
10
8
  ![license](https://img.shields.io/badge/license-MIT-green.svg)
@@ -12,20 +10,73 @@ _Vibe coding ships fast. drift tells you what it left behind._
12
10
  ![ts-morph](https://img.shields.io/badge/powered%20by-ts--morph-6366f1.svg)
13
11
  ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)
14
12
 
15
- [Installation](#-installation) [Usage](#-usage) [Rules](#-what-it-detects) [CI Integration](#-ci-integration) [Score](#-score) [Contributing](#-contributing)
13
+ [Why](#why) · [Installation](#installation) · [Commands](#commands) · [Rules](#rules) · [Score](#score) · [Configuration](#configuration) · [CI Integration](#ci-integration) · [drift-ignore](#drift-ignore) · [Contributing](#contributing)
14
+
15
+ ---
16
+
17
+ ## Why
18
+
19
+ 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.
20
+
21
+ 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."_
22
+
23
+ drift gives you a 0–100 score per file and project so you know what to look at before it reaches production.
24
+
25
+ **How drift compares to existing tools:**
26
+
27
+ | Tool | What it does | What it misses |
28
+ |------|--------------|----------------|
29
+ | ESLint | Correctness and style within a single file | Structural patterns, cross-file dead code, architecture violations |
30
+ | SonarQube | Enterprise-grade static analysis | Costs money, requires infrastructure, overwhelming for small teams |
31
+ | drift | Structural debt + AI-specific patterns + cross-file analysis + 0–100 score | Not a linter — does not replace ESLint |
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ # Run without installing
39
+ npx @eduardbar/drift scan .
40
+
41
+ # Install globally
42
+ npm install -g @eduardbar/drift
43
+
44
+ # Install as a dev dependency
45
+ npm install --save-dev @eduardbar/drift
46
+ ```
16
47
 
17
48
  ---
18
49
 
19
- ## 🎯 Why?
50
+ ## Commands
20
51
 
21
- You reviewed the AI-generated code today. Huge files, unused functions, empty catch blocks, duplicate helpers, `console.log` everywhere. It ran fine in dev. It will bite you in prod.
52
+ ### `drift scan [path]`
22
53
 
23
- drift scans your TypeScript/JavaScript codebase for the specific patterns AI tools leave behind and gives you a score so you know where to look first.
54
+ Scan a directory and print a scored report to stdout.
24
55
 
25
56
  ```bash
26
- $ npx @eduardbar/drift scan ./src
57
+ drift scan .
58
+ drift scan ./src
59
+ drift scan ./src --output report.md
60
+ drift scan ./src --json
61
+ drift scan ./src --ai
62
+ drift scan ./src --fix
63
+ drift scan ./src --min-score 50
64
+ ```
27
65
 
28
- drift — vibe coding debt detector
66
+ **Options:**
67
+
68
+ | Flag | Description |
69
+ |------|-------------|
70
+ | `--output <file>` | Write Markdown report to a file instead of stdout |
71
+ | `--json` | Output raw `DriftReport` JSON |
72
+ | `--ai` | Output structured JSON optimized for LLM consumption (Claude, GPT, etc.) |
73
+ | `--fix` | Print inline fix suggestions for each detected issue |
74
+ | `--min-score <n>` | Exit with code 1 if the overall score meets or exceeds this threshold |
75
+
76
+ **Example output:**
77
+
78
+ ```
79
+ drift — technical debt detector
29
80
  ──────────────────────────────────────────────────
30
81
 
31
82
  Score █████████████░░░░░░░ 67/100 HIGH
@@ -48,156 +99,181 @@ $ npx @eduardbar/drift scan ./src
48
99
 
49
100
  ---
50
101
 
51
- ## 📦 Installation
102
+ ### `drift diff [ref]`
103
+
104
+ Compare the current project state against any git ref. Defaults to `HEAD~1`.
52
105
 
53
106
  ```bash
54
- # Run without installing
55
- npx @eduardbar/drift scan ./src
107
+ drift diff # HEAD vs HEAD~1
108
+ drift diff HEAD~3 # HEAD vs 3 commits ago
109
+ drift diff main # HEAD vs branch main
110
+ drift diff abc1234 # HEAD vs a specific commit
111
+ drift diff --json # Output raw JSON diff
112
+ ```
56
113
 
57
- # Install globally
58
- npm install -g @eduardbar/drift
59
- drift scan ./src
114
+ **Options:**
60
115
 
61
- # Install as dev dependency
62
- npm install --save-dev @eduardbar/drift
63
- ```
116
+ | Flag | Description |
117
+ |------|-------------|
118
+ | `--json` | Output raw JSON diff |
119
+
120
+ Shows score delta, issues introduced, and issues resolved since the given ref.
64
121
 
65
122
  ---
66
123
 
67
- ## 🚀 Usage
124
+ ### `drift report [path]`
125
+
126
+ Generate a self-contained HTML report. No server required — open in any browser.
68
127
 
69
128
  ```bash
70
- # Recommended no install needed
71
- npx @eduardbar/drift scan .
72
- npx @eduardbar/drift scan ./src
73
- npx @eduardbar/drift scan ./src --output report.md
74
- npx @eduardbar/drift scan ./src --json
75
- npx @eduardbar/drift scan ./src --ai
76
- npx @eduardbar/drift scan ./src --fix
77
- npx @eduardbar/drift scan ./src --min-score 50
78
-
79
- # Install globally if you want the short 'drift' command
80
- npm install -g @eduardbar/drift
81
- drift scan .
129
+ drift report # scan current directory
130
+ drift report ./src # scan specific path
131
+ drift report ./src --output my-report.html
82
132
  ```
83
133
 
84
- ### Options
134
+ **Options:**
85
135
 
86
136
  | Flag | Description |
87
137
  |------|-------------|
88
- | `--output <file>` | Write Markdown report to a file |
89
- | `--json` | Output raw JSON instead of console output |
90
- | `--ai` | Output AI-optimized JSON for LLM consumption (Claude, GPT, etc.) |
91
- | `--fix` | Show fix suggestions for each detected issue |
92
- | `--min-score <n>` | Exit with code 1 if overall score exceeds threshold |
138
+ | `--output <file>` | Output path for the HTML file (default: `drift-report.html`) |
93
139
 
94
- ### `drift diff [ref]`
140
+ All styles and data are embedded inline in the output file.
95
141
 
96
- Compare the current state of your project against any git ref:
142
+ ---
143
+
144
+ ### `drift badge [path]`
145
+
146
+ Generate a `badge.svg` with the current score, compatible with shields.io style.
97
147
 
98
148
  ```bash
99
- drift diff # HEAD vs HEAD~1 (default)
100
- drift diff HEAD~3 # HEAD vs 3 commits ago
101
- drift diff main # HEAD vs branch main
102
- drift diff abc1234 # HEAD vs specific commit
103
- drift diff --json # Output raw JSON diff
149
+ drift badge # writes badge.svg to current directory
150
+ drift badge ./src
151
+ drift badge ./src --output ./assets/drift-badge.svg
104
152
  ```
105
153
 
106
- Shows score delta, new issues introduced, and issues resolved per file.
107
-
108
- ### `drift report [path]`
154
+ **Options:**
109
155
 
110
- Generate a self-contained `drift-report.html` — open in any browser:
156
+ | Flag | Description |
157
+ |------|-------------|
158
+ | `--output <file>` | Output path for the SVG file (default: `badge.svg`) |
111
159
 
112
- ```bash
113
- drift report # scan current directory
114
- drift report ./src # scan specific path
115
- ```
160
+ Add the badge to your README — see [README Badge](#readme-badge).
116
161
 
117
- No server needed. The file embeds all styles and data inline.
162
+ ---
118
163
 
119
- ### `drift badge [path]`
164
+ ### `drift ci [path]`
120
165
 
121
- Generate a `badge.svg` with the current score for your README:
166
+ Emit GitHub Actions annotations and a step summary. Designed to run inside a CI workflow.
122
167
 
123
168
  ```bash
124
- drift badge # writes badge.svg to current directory
125
- drift badge ./src # scan specific path
169
+ drift ci # scan current directory
170
+ drift ci ./src
171
+ drift ci ./src --min-score 60
126
172
  ```
127
173
 
128
- Drop the generated file in your repo and reference it as a local badge.
174
+ **Options:**
129
175
 
130
- ### `drift ci [path]`
176
+ | Flag | Description |
177
+ |------|-------------|
178
+ | `--min-score <n>` | Exit with code 1 if the overall score meets or exceeds this threshold |
179
+
180
+ Outputs `::error` and `::warning` annotations visible in the PR diff. Writes a markdown summary to `$GITHUB_STEP_SUMMARY`.
181
+
182
+ ---
183
+
184
+ ### `drift trend [period]`
131
185
 
132
- Emit GitHub Actions annotations and step summary:
186
+ Show score evolution over time. `period` accepts: `week`, `month`, `quarter`, `year`.
133
187
 
134
188
  ```bash
135
- drift ci # scan current directory
136
- drift ci ./src # scan specific path
137
- drift ci --min-score 60 # exit code 1 if score exceeds threshold
189
+ drift trend week
190
+ drift trend month
191
+ drift trend quarter --since 2025-01-01
192
+ drift trend year --until 2025-12-31
138
193
  ```
139
194
 
140
- Outputs inline annotations visible in the PR diff. Use `--min-score` to gate merges.
195
+ **Options:**
141
196
 
142
- ### AI Integration
197
+ | Flag | Description |
198
+ |------|-------------|
199
+ | `--since <date>` | Start date for the trend window (ISO 8601) |
200
+ | `--until <date>` | End date for the trend window (ISO 8601) |
201
+
202
+ ---
143
203
 
144
- Use `--ai` to get structured output that LLMs can consume:
204
+ ### `drift blame [target]`
205
+
206
+ Identify which files, rules, or contributors are responsible for the most debt. `target` accepts: `file`, `rule`, `overall`.
145
207
 
146
208
  ```bash
147
- npx @eduardbar/drift scan ./src --ai
209
+ drift blame file # top files by score
210
+ drift blame rule # top rules by frequency
211
+ drift blame overall
212
+ drift blame file --top 10
148
213
  ```
149
214
 
150
- Output includes:
151
- - Priority-ordered issues (by severity and effort)
152
- - Fix suggestions for each issue
153
- - Recommended action for quick wins
215
+ **Options:**
154
216
 
155
- Use `--fix` to see concrete fix suggestions in terminal:
217
+ | Flag | Description |
218
+ |------|-------------|
219
+ | `--top <n>` | Limit output to top N results (default: 5) |
156
220
 
157
- ```bash
158
- npx @eduardbar/drift scan ./src --fix
159
- ```
221
+ ---
222
+
223
+ ## Rules
224
+
225
+ 26 rules across three severity levels. All run automatically unless marked as requiring configuration.
226
+
227
+ | Rule | Severity | Weight | What it detects |
228
+ |------|----------|--------|-----------------|
229
+ | `large-file` | error | 20 | Files exceeding 300 lines — AI generates monolithic files instead of splitting responsibility |
230
+ | `large-function` | error | 15 | Functions exceeding 50 lines — AI avoids decomposing logic into smaller units |
231
+ | `duplicate-function-name` | error | 18 | Function names that appear more than once (case-insensitive) — AI regenerates helpers instead of reusing them |
232
+ | `high-complexity` | error | 15 | Cyclomatic complexity above 10 — AI produces correct code, not necessarily simple code |
233
+ | `circular-dependency` | error | 14 | Circular import chains between modules — AI doesn't reason about module topology |
234
+ | `layer-violation` | error | 16 | Imports that cross architectural layers in the wrong direction (e.g., domain importing from infra) — requires `drift.config.ts` |
235
+ | `debug-leftover` | warning | 10 | `console.log`, `console.warn`, `console.error`, and `TODO` / `FIXME` / `HACK` comments — AI leaves scaffolding in place |
236
+ | `dead-code` | warning | 8 | Named imports that are never used in the file — AI imports broadly |
237
+ | `any-abuse` | warning | 8 | Explicit `any` type annotations — AI defaults to `any` when type inference is unclear |
238
+ | `catch-swallow` | warning | 10 | Empty `catch` blocks — AI makes code not throw without handling the error |
239
+ | `comment-contradiction` | warning | 12 | Comments that restate what the surrounding code already expresses — AI over-documents the obvious |
240
+ | `deep-nesting` | warning | 12 | Control flow nested more than 3 levels deep — results in code that is difficult to follow |
241
+ | `too-many-params` | warning | 8 | Functions with more than 4 parameters — AI avoids grouping related arguments into objects |
242
+ | `high-coupling` | warning | 10 | Files importing from more than 10 distinct modules — AI imports broadly without encapsulation |
243
+ | `promise-style-mix` | warning | 7 | `async/await` and `.then()` / `.catch()` used together in the same file — AI combines styles inconsistently |
244
+ | `unused-export` | warning | 8 | Named exports that are never imported anywhere in the project — cross-file dead code ESLint cannot detect |
245
+ | `dead-file` | warning | 10 | Files never imported by any other file in the project — invisible dead code |
246
+ | `unused-dependency` | warning | 6 | Packages listed in `package.json` with no corresponding import in source files |
247
+ | `cross-boundary-import` | warning | 10 | Imports that cross module boundaries outside the allowed list — requires `drift.config.ts` |
248
+ | `hardcoded-config` | warning | 10 | Hardcoded URLs, IP addresses, secrets, or connection strings — AI skips environment variable abstraction |
249
+ | `inconsistent-error-handling` | warning | 8 | Mixed `try/catch` and `.catch()` patterns in the same file — AI combines approaches without a consistent strategy |
250
+ | `unnecessary-abstraction` | warning | 7 | Wrapper functions or helpers that add no logic over what they wrap — AI over-engineers simple calls |
251
+ | `naming-inconsistency` | warning | 6 | Mixed `camelCase` and `snake_case` in the same module — AI forgets project conventions mid-generation |
252
+ | `semantic-duplication` | warning | 12 | Functions with structurally identical logic despite different names — detected via AST fingerprinting, not text comparison |
253
+ | `no-return-type` | info | 5 | Functions missing an explicit return type annotation |
254
+ | `magic-number` | info | 3 | Numeric literals used directly in logic without a named constant |
160
255
 
161
256
  ---
162
257
 
163
- ## 🔍 What it detects
164
-
165
- | Rule | Severity | What it catches |
166
- |------|----------|-----------------|
167
- | `large-file` | error | Files over 300 lines — AI dumps everything into one place |
168
- | `large-function` | error | Functions over 50 lines — AI avoids splitting logic |
169
- | `duplicate-function-name` | error | Near-identical function names — AI regenerates instead of reusing |
170
- | `high-complexity` | error | Cyclomatic complexity > 10 AI generates correct code, not simple code |
171
- | `circular-dependency` | error | Circular import chains between modules |
172
- | `debug-leftover` | warning | `console.log`, `TODO`, `FIXME`, `HACK` comments |
173
- | `dead-code` | warning | Unused imports AI imports more than it uses |
174
- | `any-abuse` | warning | Explicit `any` type — AI defaults to `any` when it can't infer |
175
- | `catch-swallow` | warning | Empty catch blocks — AI makes code "not throw" |
176
- | `comment-contradiction` | warning | Comments that restate what the code already says — AI documents the obvious |
177
- | `deep-nesting` | warning | Nesting depth > 3 — if inside for inside if inside try = unreadable |
178
- | `too-many-params` | warning | Functions with more than 4 parameters — AI avoids options objects |
179
- | `high-coupling` | warning | Files importing from more than 10 modules — AI imports broadly |
180
- | `promise-style-mix` | warning | `async/await` and `.then()` mixed in the same file |
181
- | `unused-export` | warning | Named exports never imported anywhere in the project — cross-file dead code |
182
- | `dead-file` | warning | Files never imported by any other file — invisible dead code |
183
- | `unused-dependency` | warning | Packages in `package.json` never imported in source code |
184
- | `no-return-type` | info | Missing explicit return types on functions |
185
- | `magic-number` | info | Numeric literals used directly in logic — extract to named constants |
186
- | `layer-violation` | error | Layer imports a layer it's not allowed to (requires `drift.config.ts`) |
187
- | `cross-boundary-import` | warning | Module imports from another module outside allowed boundaries (requires `drift.config.ts`) |
188
- | `over-commented` | info | Functions where comments exceed 40% of lines — AI over-documents the obvious |
189
- | `hardcoded-config` | warning | Hardcoded URLs, IPs, or connection strings — AI skips environment variables |
190
- | `inconsistent-error-handling` | warning | Mixed `try/catch` and `.catch()` in the same file — AI combines styles randomly |
191
- | `unnecessary-abstraction` | warning | Single-method interfaces or abstract classes with no reuse — AI over-engineers |
192
- | `naming-inconsistency` | warning | Mixed camelCase and snake_case in the same scope — AI forgets project conventions |
258
+ ## Score
259
+
260
+ **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.
261
+
262
+ | Score | Grade | Meaning |
263
+ |-------|-------|---------|
264
+ | 0 | CLEAN | No issues found |
265
+ | 1–19 | LOW | Minor issuessafe to ship |
266
+ | 20–44 | MODERATE | Worth a review before merging |
267
+ | 45–69 | HIGH | Significant structural debt detected |
268
+ | 70–100 | CRITICAL | Review before this goes anywhere near production |
193
269
 
194
270
  ---
195
271
 
196
- ## ⚙️ Configuration (optional)
272
+ ## Configuration
197
273
 
198
- Architectural rules (`layer-violation`, `cross-boundary-import`) require a `drift.config.ts` at your project root:
274
+ drift runs with zero configuration. Architectural rules (`layer-violation`, `cross-boundary-import`) require a `drift.config.ts` (or `.js` / `.json`) at your project root:
199
275
 
200
- ```ts
276
+ ```typescript
201
277
  import type { DriftConfig } from '@eduardbar/drift'
202
278
 
203
279
  export default {
@@ -206,117 +282,146 @@ export default {
206
282
  { name: 'app', patterns: ['src/app/**'], canImportFrom: ['domain'] },
207
283
  { name: 'infra', patterns: ['src/infra/**'], canImportFrom: ['domain', 'app'] },
208
284
  ],
209
- modules: [
285
+ boundaries: [
210
286
  { name: 'auth', root: 'src/modules/auth', allowedExternalImports: ['src/shared'] },
211
287
  { name: 'billing', root: 'src/modules/billing', allowedExternalImports: ['src/shared'] },
212
288
  ],
289
+ exclude: [
290
+ 'src/generated/**',
291
+ '**/*.spec.ts',
292
+ ],
293
+ rules: {
294
+ 'large-file': { threshold: 400 }, // override default 300
295
+ 'magic-number': 'off', // disable a rule
296
+ },
213
297
  } satisfies DriftConfig
214
298
  ```
215
299
 
216
- Without a config file, these two rules are silently skipped. All other rules run automatically with no configuration needed.
300
+ Without a config file, `layer-violation` and `cross-boundary-import` are silently skipped. All other rules run with their defaults.
217
301
 
218
302
  ---
219
303
 
220
- ## ⚙️ CI / GitHub Actions
304
+ ## CI Integration
221
305
 
222
- Add drift to your PR workflow to gate on score and get inline annotations:
306
+ ### Basic gate with `scan`
223
307
 
224
308
  ```yaml
225
- - name: Run drift
226
- run: npx @eduardbar/drift ci --min-score 60
309
+ name: Drift
310
+
311
+ on: [pull_request]
312
+
313
+ jobs:
314
+ drift:
315
+ runs-on: ubuntu-latest
316
+ steps:
317
+ - uses: actions/checkout@v4
318
+ - uses: actions/setup-node@v4
319
+ with:
320
+ node-version: 20
321
+ - name: Check debt score
322
+ run: npx @eduardbar/drift scan ./src --min-score 60
227
323
  ```
228
324
 
229
- This will:
230
- - Emit inline annotations on the exact lines with issues (visible in the PR diff)
231
- - Write a summary to the GitHub Actions step summary
232
- - Exit with code 1 if the score exceeds the threshold
325
+ Exit code is `1` if the score meets or exceeds `--min-score`. Exit code `0` otherwise.
233
326
 
234
- If you only need a pass/fail gate without annotations, `scan` works too:
327
+ ### Annotations and step summary with `drift ci`
235
328
 
236
329
  ```yaml
237
- - name: Check for vibe coding drift
238
- run: npx @eduardbar/drift scan ./src --min-score 60
330
+ name: Drift
331
+
332
+ on: [pull_request]
333
+
334
+ jobs:
335
+ drift:
336
+ runs-on: ubuntu-latest
337
+ steps:
338
+ - uses: actions/checkout@v4
339
+ - uses: actions/setup-node@v4
340
+ with:
341
+ node-version: 20
342
+ - name: Run drift
343
+ run: npx @eduardbar/drift ci ./src --min-score 60
239
344
  ```
240
345
 
241
- Exit code `1` if score exceeds `--min-score`. Exit code `0` otherwise.
346
+ `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.
242
347
 
243
348
  ---
244
349
 
245
- ## 📊 Score
350
+ ## drift-ignore
246
351
 
247
- | Score | Grade | Meaning |
248
- |-------|-------|---------|
249
- | 0 | CLEAN | No issues found |
250
- | 1–19 | LOW | Minor issues, safe to ship |
251
- | 20–44 | MODERATE | Worth a review before merging |
252
- | 45–69 | HIGH | Significant structural debt detected |
253
- | 70–100 | CRITICAL | Review before this goes anywhere near production |
352
+ ### Suppress a single issue
254
353
 
255
- ---
354
+ Add `// drift-ignore` at the end of the flagged line or on the line immediately above it:
256
355
 
257
- ## 🗂️ Project structure
356
+ ```typescript
357
+ console.log(debugPayload) // drift-ignore
358
+ ```
258
359
 
360
+ ```typescript
361
+ // drift-ignore
362
+ const result: any = parse(input)
259
363
  ```
260
- src/
261
- ├── types.ts — DriftIssue, FileReport, DriftReport interfaces
262
- ├── analyzer.ts — AST analysis with ts-morph, 15 detection rules
263
- ├── reporter.ts — buildReport() + Markdown formatter
264
- ├── printer.ts — Console output with color (kleur)
265
- ├── index.ts — Public API re-exports
266
- └── cli.ts — CLI entry point (Commander.js)
364
+
365
+ ### Suppress an entire file
366
+
367
+ Add `// drift-ignore-file` anywhere in the first 10 lines of the file:
368
+
369
+ ```typescript
370
+ // drift-ignore-file
371
+ // This file contains intentional console output — not debug leftovers.
267
372
  ```
268
373
 
374
+ 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.
375
+
269
376
  ---
270
377
 
271
- ## 🧪 Run on yourself
378
+ ## README Badge
272
379
 
273
- drift passes its own scan with a MODERATE score the `console.log` calls in `printer.ts` are intentional CLI output, not debug leftovers. We eat our own dog food.
380
+ Generate a badge from your project score and add it to your README:
274
381
 
275
382
  ```bash
276
- git clone https://github.com/eduardbar/drift
277
- cd drift
278
- npm install
279
- npm run build
280
- node dist/cli.js scan ./src
383
+ drift badge . --output ./assets/drift-badge.svg
281
384
  ```
282
385
 
283
- Or without cloning:
386
+ Then reference it in your README:
284
387
 
285
- ```bash
286
- npx @eduardbar/drift scan .
388
+ ```markdown
389
+ ![drift score](./assets/drift-badge.svg)
287
390
  ```
288
391
 
392
+ 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.
393
+
289
394
  ---
290
395
 
291
- ## 🤝 Contributing
396
+ ## Contributing
292
397
 
293
- PRs are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.
398
+ Open an issue before starting significant work. Check [existing issues](https://github.com/eduardbar/drift/issues) first — use the bug report or feature request templates.
294
399
 
295
- **Adding a new detection rule:**
400
+ **To add a new detection rule:**
296
401
 
297
- 1. Fork the repo and create a branch: `git checkout -b feat/rule-name`
298
- 2. Add the rule weight to `RULE_WEIGHTS` in `src/analyzer.ts`
299
- 3. Implement the AST detection logic using ts-morph
300
- 4. Add a `fix_suggestion` for the rule in `src/printer.ts`
402
+ 1. Create a branch: `git checkout -b feat/rule-name`
403
+ 2. Add `"rule-name": <weight>` to `RULE_WEIGHTS` in `src/analyzer.ts`
404
+ 3. Implement AST detection logic using ts-morph in `analyzeFile()`
405
+ 4. Add a `fix_suggestion` entry in `src/printer.ts`
301
406
  5. Update the rules table in `README.md` and `AGENTS.md`
302
- 6. Open a PR use the [PR template](./.github/PULL_REQUEST_TEMPLATE.md)
303
-
304
- Before opening an issue, check [existing issues](https://github.com/eduardbar/drift/issues). Use the [bug report](./.github/ISSUE_TEMPLATE/bug_report.md) or [feature request](./.github/ISSUE_TEMPLATE/feature_request.md) templates.
407
+ 6. Open a PR using the template in `.github/PULL_REQUEST_TEMPLATE.md`
305
408
 
306
- Please read [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before participating.
409
+ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before participating.
307
410
 
308
411
  ---
309
412
 
310
- ## 🧱 Stack
413
+ ## Stack
414
+
415
+ | Package | Role |
416
+ |---------|------|
417
+ | [`ts-morph`](https://github.com/dsherret/ts-morph) | AST traversal and TypeScript analysis |
418
+ | [`commander`](https://github.com/tj/commander.js) | CLI commands and flags |
419
+ | [`kleur`](https://github.com/lukeed/kleur) | Terminal colors (zero dependencies) |
311
420
 
312
- TypeScript · ts-morph · commander · kleur
421
+ **Runtime:** Node.js 18+ · TypeScript 5.x · ES Modules
313
422
 
314
423
  ---
315
424
 
316
- ## 📄 License
425
+ ## License
317
426
 
318
427
  MIT © [eduardbar](https://github.com/eduardbar)
319
-
320
- ---
321
-
322
- _Built with mate by a developer who got tired of reviewing the same AI-generated patterns every week._