@eduardbar/drift 1.3.0 → 1.5.0

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.
Files changed (198) hide show
  1. package/.gga +50 -0
  2. package/.github/actions/drift-review/README.md +62 -0
  3. package/.github/actions/drift-review/action.yml +148 -0
  4. package/.github/actions/drift-scan/README.md +28 -32
  5. package/.github/actions/drift-scan/action.yml +78 -14
  6. package/.github/workflows/publish-vscode.yml +1 -3
  7. package/.github/workflows/publish.yml +8 -0
  8. package/.github/workflows/quality.yml +15 -0
  9. package/.github/workflows/reusable-quality-checks.yml +95 -0
  10. package/.github/workflows/review-pr.yml +33 -41
  11. package/AGENTS.md +75 -251
  12. package/CHANGELOG.md +41 -0
  13. package/README.md +177 -43
  14. package/benchmarks/fixtures/critical/drift.config.ts +21 -0
  15. package/benchmarks/fixtures/critical/src/app/user-service.ts +30 -0
  16. package/benchmarks/fixtures/critical/src/domain/entities.ts +19 -0
  17. package/benchmarks/fixtures/critical/src/domain/policies.ts +22 -0
  18. package/benchmarks/fixtures/critical/src/index.ts +10 -0
  19. package/benchmarks/fixtures/critical/src/infra/memory-user-repo.ts +14 -0
  20. package/benchmarks/perf-budget.v1.json +27 -0
  21. package/dist/benchmark.d.ts +1 -1
  22. package/dist/benchmark.js +83 -52
  23. package/dist/cli.js +243 -8
  24. package/dist/config.js +16 -2
  25. package/dist/diff.js +42 -50
  26. package/dist/doctor.d.ts +26 -0
  27. package/dist/doctor.js +140 -0
  28. package/dist/format.d.ts +17 -0
  29. package/dist/format.js +45 -0
  30. package/dist/guard-baseline.d.ts +12 -0
  31. package/dist/guard-baseline.js +57 -0
  32. package/dist/guard-metrics.d.ts +6 -0
  33. package/dist/guard-metrics.js +39 -0
  34. package/dist/guard-types.d.ts +58 -0
  35. package/dist/guard-types.js +2 -0
  36. package/dist/guard.d.ts +16 -0
  37. package/dist/guard.js +178 -0
  38. package/dist/index.d.ts +10 -3
  39. package/dist/index.js +4 -1
  40. package/dist/init.d.ts +15 -0
  41. package/dist/init.js +273 -0
  42. package/dist/map-cycles.d.ts +2 -0
  43. package/dist/map-cycles.js +34 -0
  44. package/dist/map-svg.d.ts +19 -0
  45. package/dist/map-svg.js +97 -0
  46. package/dist/map.js +78 -138
  47. package/dist/metrics.js +70 -55
  48. package/dist/output-metadata.d.ts +15 -0
  49. package/dist/output-metadata.js +19 -0
  50. package/dist/plugins-capabilities.d.ts +4 -0
  51. package/dist/plugins-capabilities.js +21 -0
  52. package/dist/plugins-messages.d.ts +10 -0
  53. package/dist/plugins-messages.js +16 -0
  54. package/dist/plugins-rules.d.ts +9 -0
  55. package/dist/plugins-rules.js +137 -0
  56. package/dist/plugins.d.ts +1 -1
  57. package/dist/plugins.js +45 -142
  58. package/dist/reporter-constants.d.ts +16 -0
  59. package/dist/reporter-constants.js +39 -0
  60. package/dist/reporter.d.ts +3 -3
  61. package/dist/reporter.js +35 -55
  62. package/dist/review.d.ts +2 -1
  63. package/dist/review.js +2 -1
  64. package/dist/rules/phase3-configurable.js +23 -15
  65. package/dist/saas/constants.d.ts +15 -0
  66. package/dist/saas/constants.js +48 -0
  67. package/dist/saas/dashboard.d.ts +8 -0
  68. package/dist/saas/dashboard.js +132 -0
  69. package/dist/saas/errors.d.ts +19 -0
  70. package/dist/saas/errors.js +37 -0
  71. package/dist/saas/helpers.d.ts +21 -0
  72. package/dist/saas/helpers.js +110 -0
  73. package/dist/saas/ingest.d.ts +3 -0
  74. package/dist/saas/ingest.js +249 -0
  75. package/dist/saas/organization.d.ts +5 -0
  76. package/dist/saas/organization.js +82 -0
  77. package/dist/saas/plan-change.d.ts +10 -0
  78. package/dist/saas/plan-change.js +15 -0
  79. package/dist/saas/store.d.ts +21 -0
  80. package/dist/saas/store.js +159 -0
  81. package/dist/saas/types.d.ts +191 -0
  82. package/dist/saas/types.js +2 -0
  83. package/dist/saas.d.ts +8 -218
  84. package/dist/saas.js +7 -761
  85. package/dist/sarif.d.ts +74 -0
  86. package/dist/sarif.js +122 -0
  87. package/dist/trust-advanced.d.ts +14 -0
  88. package/dist/trust-advanced.js +65 -0
  89. package/dist/trust-kpi-fs.d.ts +3 -0
  90. package/dist/trust-kpi-fs.js +141 -0
  91. package/dist/trust-kpi-parse.d.ts +7 -0
  92. package/dist/trust-kpi-parse.js +186 -0
  93. package/dist/trust-kpi-types.d.ts +16 -0
  94. package/dist/trust-kpi-types.js +2 -0
  95. package/dist/trust-kpi.d.ts +1 -3
  96. package/dist/trust-kpi.js +6 -266
  97. package/dist/trust-policy.d.ts +32 -0
  98. package/dist/trust-policy.js +160 -0
  99. package/dist/trust-render.d.ts +9 -0
  100. package/dist/trust-render.js +54 -0
  101. package/dist/trust-scoring.d.ts +9 -0
  102. package/dist/trust-scoring.js +208 -0
  103. package/dist/trust.d.ts +5 -32
  104. package/dist/trust.js +29 -432
  105. package/dist/types/app.d.ts +30 -0
  106. package/dist/types/app.js +2 -0
  107. package/dist/types/config.d.ts +25 -0
  108. package/dist/types/config.js +2 -0
  109. package/dist/types/core.d.ts +100 -0
  110. package/dist/types/core.js +2 -0
  111. package/dist/types/diff.d.ts +55 -0
  112. package/dist/types/diff.js +2 -0
  113. package/dist/types/plugin.d.ts +41 -0
  114. package/dist/types/plugin.js +2 -0
  115. package/dist/types/trust.d.ts +120 -0
  116. package/dist/types/trust.js +2 -0
  117. package/dist/types.d.ts +8 -365
  118. package/docs/AGENTS.md +1 -1
  119. package/docs/release-notes-draft.md +40 -0
  120. package/docs/rules-catalog.md +49 -0
  121. package/docs/trust-core-release-checklist.md +37 -5
  122. package/package.json +11 -4
  123. package/packages/vscode-drift/src/code-actions.ts +1 -1
  124. package/schemas/drift-ai-output.v1.json +162 -0
  125. package/schemas/drift-doctor.v1.json +57 -0
  126. package/schemas/drift-guard.v1.json +298 -0
  127. package/schemas/drift-report.v1.json +151 -0
  128. package/schemas/drift-trust.v1.json +131 -0
  129. package/scripts/check-docs-drift.mjs +154 -0
  130. package/scripts/check-performance-budget.mjs +360 -0
  131. package/scripts/check-runtime-policy.mjs +66 -0
  132. package/scripts/smoke-repo.mjs +394 -0
  133. package/src/benchmark.ts +92 -53
  134. package/src/cli.ts +285 -13
  135. package/src/config.ts +19 -2
  136. package/src/diff.ts +57 -48
  137. package/src/doctor.ts +185 -0
  138. package/src/format.ts +81 -0
  139. package/src/guard-baseline.ts +74 -0
  140. package/src/guard-metrics.ts +52 -0
  141. package/src/guard-types.ts +66 -0
  142. package/src/guard.ts +248 -0
  143. package/src/index.ts +36 -0
  144. package/src/init.ts +298 -0
  145. package/src/map-cycles.ts +38 -0
  146. package/src/map-svg.ts +124 -0
  147. package/src/map.ts +111 -142
  148. package/src/metrics.ts +78 -59
  149. package/src/output-metadata.ts +32 -0
  150. package/src/plugins-capabilities.ts +36 -0
  151. package/src/plugins-messages.ts +35 -0
  152. package/src/plugins-rules.ts +296 -0
  153. package/src/plugins.ts +76 -283
  154. package/src/reporter-constants.ts +46 -0
  155. package/src/reporter.ts +64 -65
  156. package/src/review.ts +4 -2
  157. package/src/rules/phase3-configurable.ts +39 -26
  158. package/src/saas/constants.ts +56 -0
  159. package/src/saas/dashboard.ts +172 -0
  160. package/src/saas/errors.ts +45 -0
  161. package/src/saas/helpers.ts +140 -0
  162. package/src/saas/ingest.ts +278 -0
  163. package/src/saas/organization.ts +99 -0
  164. package/src/saas/plan-change.ts +19 -0
  165. package/src/saas/store.ts +172 -0
  166. package/src/saas/types.ts +216 -0
  167. package/src/saas.ts +49 -1031
  168. package/src/sarif.ts +232 -0
  169. package/src/trust-advanced.ts +99 -0
  170. package/src/trust-kpi-fs.ts +169 -0
  171. package/src/trust-kpi-parse.ts +219 -0
  172. package/src/trust-kpi-types.ts +19 -0
  173. package/src/trust-kpi.ts +8 -316
  174. package/src/trust-policy.ts +246 -0
  175. package/src/trust-render.ts +61 -0
  176. package/src/trust-scoring.ts +231 -0
  177. package/src/trust.ts +62 -576
  178. package/src/types/app.ts +30 -0
  179. package/src/types/config.ts +27 -0
  180. package/src/types/core.ts +105 -0
  181. package/src/types/diff.ts +61 -0
  182. package/src/types/plugin.ts +46 -0
  183. package/src/types/trust.ts +134 -0
  184. package/src/types.ts +79 -409
  185. package/tests/ci-quality-matrix.test.ts +37 -0
  186. package/tests/ci-smoke-gate.test.ts +26 -0
  187. package/tests/ci-version-alignment.test.ts +93 -0
  188. package/tests/cli-sarif.test.ts +92 -0
  189. package/tests/docs-drift-check.test.ts +115 -0
  190. package/tests/format.test.ts +157 -0
  191. package/tests/new-features.test.ts +11 -3
  192. package/tests/perf-budget-check.test.ts +146 -0
  193. package/tests/phase1-init-doctor-guard.test.ts +301 -0
  194. package/tests/runtime-policy-alignment.test.ts +46 -0
  195. package/tests/sarif.test.ts +160 -0
  196. package/tests/trust-kpi.test.ts +31 -4
  197. package/tests/trust.test.ts +18 -0
  198. package/vitest.config.ts +2 -0
package/README.md CHANGED
@@ -51,12 +51,63 @@ npm install --save-dev @eduardbar/drift
51
51
 
52
52
  - Product requirements and roadmap: [`docs/PRD.md`](./docs/PRD.md)
53
53
  - Trust core release checklist: [`docs/trust-core-release-checklist.md`](./docs/trust-core-release-checklist.md)
54
+ - Rules catalog (source-of-truth snapshot): [`docs/rules-catalog.md`](./docs/rules-catalog.md)
54
55
  - Contributor/agent workflow guide: [`docs/AGENTS.md`](./docs/AGENTS.md)
55
56
 
56
57
  ---
57
58
 
58
59
  ## Commands
59
60
 
61
+ ### `drift init`
62
+
63
+ Scaffold first-run setup for drift in an existing repository.
64
+
65
+ ```bash
66
+ drift init
67
+ drift init --preset node-backend
68
+ drift init --preset react-app --ci --baseline
69
+ ```
70
+
71
+ | Flag | Description |
72
+ |------|-------------|
73
+ | `--preset <type>` | Generate `drift.config.ts` using one of: `node-backend`, `react-app`, `hexagonal`, `monorepo` |
74
+ | `--ci` | Generate `.github/workflows/drift-review.yml` |
75
+ | `--baseline` | Generate `drift-baseline.json` from the current scan |
76
+
77
+ `drift init` is non-destructive for pre-existing targets: drift skips generation when an output file already exists and prints a warning.
78
+
79
+ ---
80
+
81
+ ### `drift doctor`
82
+
83
+ Run environment diagnostics for Node/runtime and project readiness.
84
+
85
+ ```bash
86
+ drift doctor
87
+ drift doctor --json
88
+ ```
89
+
90
+ | Flag | Description |
91
+ |------|-------------|
92
+ | `--json` | Output structured doctor report JSON |
93
+
94
+ Checks include Node major version support (`>=20`), `package.json`, ESM mode, `tsconfig.json`, source file count, optional `--low-memory` recommendation, and `drift.config.*` detection.
95
+
96
+ When `--json` is enabled, output includes `$schema` and `toolVersion` metadata and follows [`schemas/drift-doctor.v1.json`](./schemas/drift-doctor.v1.json).
97
+
98
+ ---
99
+
100
+ ### Repository smoke (local source CLI)
101
+
102
+ Run a non-destructive end-to-end smoke suite against any repository path using local source commands (`node --import tsx ./src/cli.ts ...`).
103
+
104
+ ```bash
105
+ npm run smoke:repo -- ../target-repo --base origin/main
106
+ npm run smoke:repo -- ../target-repo --dry-run
107
+ ```
108
+
109
+ The script writes JSON + Markdown summaries and command logs under `.drift-smoke/<repo>-<timestamp>/`.
110
+
60
111
  ### `drift scan [path]`
61
112
 
62
113
  Scan a directory and print a scored report to stdout.
@@ -77,16 +128,19 @@ drift scan ./src --low-memory --max-file-size-kb 1024
77
128
  | Flag | Description |
78
129
  |------|-------------|
79
130
  | `--output <file>` | Write Markdown report to a file instead of stdout |
131
+ | `--format <type>` | Output format: `console\|json\|markdown\|ai\|sarif` |
80
132
  | `--json` | Output raw `DriftReport` JSON |
81
133
  | `--ai` | Output structured JSON optimized for LLM consumption (Claude, GPT, etc.) |
82
134
  | `--fix` | Print inline fix suggestions for each detected issue |
83
- | `--min-score <n>` | Exit with code 1 if the overall score meets or exceeds this threshold |
135
+ | `--min-score <n>` | Exit with code 1 if the overall score strictly exceeds this threshold |
84
136
  | `--low-memory` | Analyze files in bounded chunks to reduce peak RAM |
85
137
  | `--chunk-size <n>` | Files per chunk in low-memory mode (default: 40) |
86
138
  | `--max-files <n>` | Soft cap on analyzed files; extra files are skipped with diagnostics |
87
139
  | `--max-file-size-kb <n>` | Skip oversized files with diagnostics to avoid OOM |
88
140
  | `--with-semantic-duplication` | Keep semantic duplication rule enabled in low-memory mode |
89
141
 
142
+ `--min-score` currently fails when `totalScore > n` (strictly greater than), matching CLI behavior.
143
+
90
144
  **Example output:**
91
145
 
92
146
  ```
@@ -113,6 +167,35 @@ drift scan ./src --low-memory --max-file-size-kb 1024
113
167
 
114
168
  ---
115
169
 
170
+ ### `drift guard [path]`
171
+
172
+ Evaluate drift regression guardrails against a git diff (`--base`) or a baseline file/object.
173
+
174
+ ```bash
175
+ drift guard ./src --base origin/main
176
+ drift guard ./src --baseline drift-baseline.json
177
+ drift guard ./src --base origin/main --budget 3 --by-severity error=0,warning=2,info=5
178
+ drift guard ./src --base origin/main --json
179
+ ```
180
+
181
+ | Flag | Description |
182
+ |------|-------------|
183
+ | `--base <ref>` | Diff mode: compare current state vs git ref |
184
+ | `--baseline <file>` | Baseline mode: compare against baseline JSON (default file name: `drift-baseline.json`) |
185
+ | `--budget <n>` | Maximum allowed score delta |
186
+ | `--by-severity <spec>` | Severity thresholds as CSV `key=value` pairs (`error`, `warning`, `info`) |
187
+ | `--json` | Output full `GuardResult` JSON |
188
+ | `--low-memory` / `--chunk-size <n>` / `--max-files <n>` / `--max-file-size-kb <n>` / `--with-semantic-duplication` | Analysis resource controls shared with scan/diff/report/badge/ci/trust |
189
+
190
+ Behavior:
191
+ - With `--base`, guard enforces no-regression checks for score and total issues, then applies optional budget/severity thresholds.
192
+ - Without `--base`, guard requires a baseline (inline or file) and compares only against available baseline anchors.
193
+ - Exit code is `1` when any guard check fails.
194
+
195
+ When `--json` is enabled, output includes `$schema` and `toolVersion` metadata and follows [`schemas/drift-guard.v1.json`](./schemas/drift-guard.v1.json).
196
+
197
+ ---
198
+
116
199
  ### `drift diff [ref]`
117
200
 
118
201
  Compare the current project state against any git ref. Defaults to `HEAD~1`.
@@ -122,6 +205,7 @@ drift diff # HEAD vs HEAD~1
122
205
  drift diff HEAD~3 # HEAD vs 3 commits ago
123
206
  drift diff main # HEAD vs branch main
124
207
  drift diff abc1234 # HEAD vs a specific commit
208
+ drift diff --format sarif # Output SARIF for code scanning
125
209
  drift diff --json # Output raw JSON diff
126
210
  ```
127
211
 
@@ -129,6 +213,7 @@ drift diff --json # Output raw JSON diff
129
213
 
130
214
  | Flag | Description |
131
215
  |------|-------------|
216
+ | `--format <type>` | Output format: `console\|json\|sarif` |
132
217
  | `--json` | Output raw JSON diff |
133
218
 
134
219
  Shows score delta, issues introduced, and issues resolved since the given ref.
@@ -143,12 +228,14 @@ Review drift against a git base ref and output a PR-ready markdown comment.
143
228
  drift review --base origin/main
144
229
  drift review --base main --comment
145
230
  drift review --base HEAD~3 --json
231
+ drift review --base origin/main --format sarif
146
232
  drift review --base origin/main --fail-on 5
147
233
  ```
148
234
 
149
235
  | Flag | Description |
150
236
  |------|-------------|
151
237
  | `--base <ref>` | Git base ref to compare against (default: `origin/main`) |
238
+ | `--format <type>` | Output format: `console\|json\|markdown\|sarif` |
152
239
  | `--json` | Output structured review JSON |
153
240
  | `--comment` | Print only the markdown body for PR comments |
154
241
  | `--fail-on <n>` | Exit code 1 when score delta is greater than or equal to `n` |
@@ -167,11 +254,12 @@ drift trust ./src
167
254
  drift trust ./src --json
168
255
  drift trust ./src --base origin/main
169
256
  drift trust ./src --base origin/main --markdown
257
+ drift trust ./src --format sarif
170
258
  drift trust ./src --markdown --output trust.md
171
259
  drift trust ./src --min-trust 45
172
260
  drift trust ./src --max-risk HIGH
173
261
  drift trust ./src --branch main
174
- drift trust ./src --branch release/v1.4.0 --policy-pack strict --explain-policy
262
+ drift trust ./src --branch release/v1.5.0 --policy-pack strict --explain-policy
175
263
  drift trust ./src --advanced-trust
176
264
  drift trust ./src --advanced-trust --previous-trust ./artifacts/prev-trust.json
177
265
  drift trust ./src --advanced-trust --history-file ./drift-history.json --markdown
@@ -180,6 +268,7 @@ drift trust ./src --advanced-trust --history-file ./drift-history.json --markdow
180
268
  | Flag | Description |
181
269
  |------|-------------|
182
270
  | `--base <ref>` | Compare against a git base ref and include deterministic diff-aware penalties/bonuses |
271
+ | `--format <type>` | Output format: `console\|json\|markdown\|sarif` |
183
272
  | `--json` | Output structured trust JSON (`trust_score`, `merge_risk`, `top_reasons`, `fix_priorities`, optional `diff_context`) |
184
273
  | `--markdown` | Output PR-ready markdown trust summary |
185
274
  | `--output <file>` | Write selected trust output format to file |
@@ -308,6 +397,7 @@ Emit GitHub Actions annotations and a step summary. Designed to run inside a CI
308
397
  ```bash
309
398
  drift ci # scan current directory
310
399
  drift ci ./src
400
+ drift ci ./src --format sarif
311
401
  drift ci ./src --min-score 60
312
402
  ```
313
403
 
@@ -315,7 +405,8 @@ drift ci ./src --min-score 60
315
405
 
316
406
  | Flag | Description |
317
407
  |------|-------------|
318
- | `--min-score <n>` | Exit with code 1 if the overall score meets or exceeds this threshold |
408
+ | `--format <type>` | Output format: `console\|json\|sarif` |
409
+ | `--min-score <n>` | Exit with code 1 if the overall score strictly exceeds this threshold |
319
410
 
320
411
  Outputs `::error` and `::warning` annotations visible in the PR diff. Writes a markdown summary to `$GITHUB_STEP_SUMMARY`.
321
412
 
@@ -427,36 +518,28 @@ export default {
427
518
 
428
519
  ## Rules
429
520
 
430
- 26 rules across three severity levels. All run automatically unless marked as requiring configuration.
521
+ drift currently defines **35 rule IDs** in `RULE_WEIGHTS` (`src/analyzer.ts`): core detections, configurable architecture checks, AI/meta aggregation, plugin diagnostics, and analysis guardrail diagnostics.
522
+
523
+ For the complete up-to-date catalog (id, severity, weight, phase/origin, note), see [`docs/rules-catalog.md`](./docs/rules-catalog.md).
524
+
525
+ Highlights:
431
526
 
432
527
  | Rule | Severity | Weight | What it detects |
433
528
  |------|----------|--------|-----------------|
434
- | `large-file` | error | 20 | Files exceeding 300 lines — AI generates monolithic files instead of splitting responsibility |
435
- | `large-function` | error | 15 | Functions exceeding 50 lines AI avoids decomposing logic into smaller units |
436
- | `duplicate-function-name` | error | 18 | Function names that appear more than once (case-insensitive) — AI regenerates helpers instead of reusing them |
437
- | `high-complexity` | error | 15 | Cyclomatic complexity above 10 AI produces correct code, not necessarily simple code |
438
- | `circular-dependency` | error | 14 | Circular import chains between modules AI doesn't reason about module topology |
439
- | `layer-violation` | error | 16 | Imports that cross architectural layers in the wrong direction (e.g., domain importing from infra) — requires `drift.config.ts` |
440
- | `debug-leftover` | warning | 10 | `console.log`, `console.warn`, `console.error`, and `TODO` / `FIXME` / `HACK` comments — AI leaves scaffolding in place |
441
- | `dead-code` | warning | 8 | Named imports that are never used in the file — AI imports broadly |
442
- | `any-abuse` | warning | 8 | Explicit `any` type annotations — AI defaults to `any` when type inference is unclear |
443
- | `catch-swallow` | warning | 10 | Empty `catch` blocks — AI makes code not throw without handling the error |
444
- | `comment-contradiction` | warning | 12 | Comments that restate what the surrounding code already expresses — AI over-documents the obvious |
445
- | `deep-nesting` | warning | 12 | Control flow nested more than 3 levels deep — results in code that is difficult to follow |
446
- | `too-many-params` | warning | 8 | Functions with more than 4 parameters — AI avoids grouping related arguments into objects |
447
- | `high-coupling` | warning | 10 | Files importing from more than 10 distinct modules — AI imports broadly without encapsulation |
448
- | `promise-style-mix` | warning | 7 | `async/await` and `.then()` / `.catch()` used together in the same file — AI combines styles inconsistently |
449
- | `unused-export` | warning | 8 | Named exports that are never imported anywhere in the project — cross-file dead code ESLint cannot detect |
450
- | `dead-file` | warning | 10 | Files never imported by any other file in the project — invisible dead code |
451
- | `unused-dependency` | warning | 6 | Packages listed in `package.json` with no corresponding import in source files |
452
- | `cross-boundary-import` | warning | 10 | Imports that cross module boundaries outside the allowed list — requires `drift.config.ts` |
453
- | `hardcoded-config` | warning | 10 | Hardcoded URLs, IP addresses, secrets, or connection strings — AI skips environment variable abstraction |
454
- | `inconsistent-error-handling` | warning | 8 | Mixed `try/catch` and `.catch()` patterns in the same file — AI combines approaches without a consistent strategy |
455
- | `unnecessary-abstraction` | warning | 7 | Wrapper functions or helpers that add no logic over what they wrap — AI over-engineers simple calls |
456
- | `naming-inconsistency` | warning | 6 | Mixed `camelCase` and `snake_case` in the same module — AI forgets project conventions mid-generation |
457
- | `semantic-duplication` | warning | 12 | Functions with structurally identical logic despite different names — detected via AST fingerprinting, not text comparison |
458
- | `no-return-type` | info | 5 | Functions missing an explicit return type annotation |
459
- | `magic-number` | info | 3 | Numeric literals used directly in logic without a named constant |
529
+ | `large-file` | error | 20 | Files exceeding 300 lines |
530
+ | `duplicate-function-name` | error | 18 | Same function name repeated across a file |
531
+ | `high-complexity` | error | 15 | Cyclomatic complexity above threshold |
532
+ | `layer-violation` | error | 16 | Invalid layer import direction (requires `layers` config) |
533
+ | `cross-boundary-import` | warning | 10 | Invalid module boundary import (requires `modules`/legacy boundaries config) |
534
+ | `controller-no-db` | warning | 11 | Controller touching DB directly (configurable architecture rule) |
535
+ | `service-no-http` | warning | 11 | Service layer coupled to HTTP concerns (configurable architecture rule) |
536
+ | `max-function-lines` | warning | 9 | Function line cap enforced by `architectureRules.maxFunctionLines` |
537
+ | `semantic-duplication` | warning | 12 | AST-level semantic function duplication |
538
+ | `ai-code-smell` | warning | 12 | Aggregated AI-smell signal from multiple heuristics in a file |
539
+ | `plugin-error` | warning | 4 | Plugin load/contract/runtime failure surfaced as issue |
540
+ | `plugin-warning` | info | 0 | Non-fatal plugin validation warning |
541
+ | `analysis-skip-max-files` | info | 0 | File skipped by `maxFiles` guardrail |
542
+ | `analysis-skip-file-size` | info | 0 | File skipped by `maxFileSizeKb` guardrail |
460
543
 
461
544
  ---
462
545
 
@@ -476,7 +559,7 @@ export default {
476
559
 
477
560
  ## Configuration
478
561
 
479
- drift runs with zero configuration. Architectural rules (`layer-violation`, `cross-boundary-import`) require a `drift.config.ts` (or `.js` / `.json`) at your project root:
562
+ drift runs with zero configuration. Architectural rules (`layer-violation`, `cross-boundary-import`) and configurable architecture checks use `drift.config.ts` (or `.js` / `.json`) at project root:
480
563
 
481
564
  ```typescript
482
565
  import type { DriftConfig } from '@eduardbar/drift'
@@ -509,22 +592,16 @@ export default {
509
592
  { name: 'app', patterns: ['src/app/**'], canImportFrom: ['domain'] },
510
593
  { name: 'infra', patterns: ['src/infra/**'], canImportFrom: ['domain', 'app'] },
511
594
  ],
512
- boundaries: [
595
+ modules: [
513
596
  { name: 'auth', root: 'src/modules/auth', allowedExternalImports: ['src/shared'] },
514
597
  { name: 'billing', root: 'src/modules/billing', allowedExternalImports: ['src/shared'] },
515
598
  ],
516
- exclude: [
517
- 'src/generated/**',
518
- '**/*.spec.ts',
519
- ],
520
- rules: {
521
- 'large-file': { threshold: 400 }, // override default 300
522
- 'magic-number': 'off', // disable a rule
523
- },
524
599
  } satisfies DriftConfig
525
600
  ```
526
601
 
527
- Without a config file, `layer-violation` and `cross-boundary-import` are silently skipped. All other rules run with their defaults.
602
+ For backwards compatibility, `moduleBoundaries` and `boundaries` are normalized to `modules`.
603
+
604
+ Without a config file, `layer-violation`, `cross-boundary-import`, and configurable architecture checks (`controller-no-db`, `service-no-http`, `max-function-lines`) are skipped. All other rules run with defaults.
528
605
 
529
606
  ### Memory guardrails (recommended for large repos)
530
607
 
@@ -563,7 +640,7 @@ jobs:
563
640
  run: npx @eduardbar/drift scan ./src --min-score 60
564
641
  ```
565
642
 
566
- Exit code is `1` if the score meets or exceeds `--min-score`. Exit code `0` otherwise.
643
+ Exit code is `1` if the score is strictly greater than `--min-score`. Exit code `0` otherwise.
567
644
 
568
645
  ### Annotations and step summary with `drift ci`
569
646
 
@@ -586,6 +663,29 @@ jobs:
586
663
 
587
664
  `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.
588
665
 
666
+ ### Required quality checks for merge and release
667
+
668
+ This repository enforces `.github/workflows/quality.yml` on `pull_request` and `push` to `main`/`master`.
669
+
670
+ The workflow runs a required Node.js matrix (`20`, `22`) and executes these checks in each matrix job:
671
+ - `npm ci`
672
+ - `npm run check:runtime-policy`
673
+ - `npm run check:docs-drift`
674
+ - `npm test`
675
+ - `npm run test:coverage`
676
+ - `npm run build`
677
+ - `npm run smoke:repo -- --base HEAD --out .drift-smoke/ci-node-<node>`
678
+
679
+ Additionally, Node 20 runs a deterministic performance regression gate:
680
+ - `npm run check:perf-budget -- --out .drift-perf/ci-node-20/benchmark-latest.json`
681
+ - Budget source of truth: [`benchmarks/perf-budget.v1.json`](./benchmarks/perf-budget.v1.json)
682
+
683
+ `check:docs-drift` enforces documentation source-of-truth contracts: package version claims come from `package.json` and rule catalog/count claims come from `RULE_WEIGHTS` in `src/analyzer.ts`.
684
+
685
+ Coverage artifacts and smoke E2E artifacts are uploaded from each matrix run to support traceability and CI debugging. The perf gate also uploads `.drift-perf/...` artifacts on Node 20.
686
+
687
+ `publish.yml` now runs `release-verify` before publish, reusing the same quality workflow contract. `publish` only runs after `release-verify` passes.
688
+
589
689
  ### Auto PR comment with `drift review`
590
690
 
591
691
  The repository includes `.github/workflows/review-pr.yml`, which:
@@ -595,11 +695,45 @@ The repository includes `.github/workflows/review-pr.yml`, which:
595
695
  - enforces a trust baseline gate with `drift trust-gate drift-trust.json --min-trust 45 --max-risk HIGH`
596
696
  - uploads `drift trust --json` as `drift-trust-json-pr-<PR_NUMBER>-run-<RUN_ATTEMPT>` for manual KPI tracking
597
697
  - publishes a compact trust KPI summary in `$GITHUB_STEP_SUMMARY` (score, merge risk, new/resolved issues when diff context is available)
698
+ - generates `drift.sarif` via `drift scan --format sarif`, uploads it to GitHub Code Scanning on non-fork PRs, and stores the SARIF file as workflow artifact for traceability
699
+
700
+ Quick local SARIF export:
701
+
702
+ ```bash
703
+ drift scan ./src --format sarif > drift.sarif
704
+ ```
705
+
706
+ Example GitHub Code Scanning upload with SARIF:
707
+
708
+ ```yaml
709
+ - name: Generate drift SARIF
710
+ run: npx @eduardbar/drift scan ./src --format sarif > drift.sarif
711
+
712
+ - name: Upload SARIF to Code Scanning
713
+ uses: github/codeql-action/upload-sarif@v3
714
+ with:
715
+ sarif_file: drift.sarif
716
+ ```
598
717
 
599
718
  Default gate behavior in this repo:
600
719
  - fail when trust is below 45
601
720
  - fail when merge risk is above `HIGH` (that means `CRITICAL` is blocked)
602
721
 
722
+ ### GitHub Action contract (v2)
723
+
724
+ This repo also ships reusable local composite actions:
725
+
726
+ - `./.github/actions/drift-scan`: score-focused scan gate using `npx @eduardbar/drift@<version>` (no global install).
727
+ - `./.github/actions/drift-review`: trust/review/gate flow for PRs, aligned with `.github/workflows/review-pr.yml`.
728
+
729
+ `drift-scan` exposes machine-friendly outputs for CI composition:
730
+ - `score`, `grade`, `errors`, `warnings`, `infos`, `total-issues`, `files-affected`, `top-rules`
731
+
732
+ `drift-review` exposes:
733
+ - `trust-score`, `merge-risk`, `new-issues`, `resolved-issues`, `trust-json`, `trust-markdown`, `review-markdown`
734
+
735
+ See `.github/actions/drift-scan/README.md` and `.github/actions/drift-review/README.md` for usage details.
736
+
603
737
  ---
604
738
 
605
739
  ## drift-ignore
@@ -673,7 +807,7 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before participating.
673
807
  | [`commander`](https://github.com/tj/commander.js) | CLI commands and flags |
674
808
  | [`kleur`](https://github.com/lukeed/kleur) | Terminal colors (zero dependencies) |
675
809
 
676
- **Runtime:** Node.js 18+ · TypeScript 5.x · ES Modules · Supports TypeScript (`.ts`, `.tsx`) and JavaScript (`.js`, `.jsx`) files
810
+ **Runtime:** Node.js 20.x and 22.x (LTS) · TypeScript 5.x · ES Modules · Supports TypeScript (`.ts`, `.tsx`) and JavaScript (`.js`, `.jsx`) files
677
811
 
678
812
  ---
679
813
 
@@ -0,0 +1,21 @@
1
+ const config = {
2
+ layers: [
3
+ { name: 'domain', patterns: ['src/domain/**'], canImportFrom: [] },
4
+ { name: 'app', patterns: ['src/app/**'], canImportFrom: ['domain'] },
5
+ { name: 'infra', patterns: ['src/infra/**'], canImportFrom: ['domain', 'app'] },
6
+ ],
7
+ architectureRules: {
8
+ controllerNoDb: true,
9
+ serviceNoHttp: true,
10
+ maxFunctionLines: 80,
11
+ },
12
+ performance: {
13
+ lowMemory: false,
14
+ chunkSize: 40,
15
+ maxFiles: 200,
16
+ maxFileSizeKb: 512,
17
+ includeSemanticDuplication: false,
18
+ },
19
+ }
20
+
21
+ export default config
@@ -0,0 +1,30 @@
1
+ import { canAccessBilling, normalizeEmail, type User } from '../domain/entities.js'
2
+
3
+ export interface UserRepository {
4
+ findByEmail(email: string): Promise<User | undefined>
5
+ save(user: User): Promise<void>
6
+ }
7
+
8
+ export class UserService {
9
+ constructor(private readonly repo: UserRepository) {}
10
+
11
+ async ensureUser(email: string): Promise<User> {
12
+ const normalizedEmail = normalizeEmail(email)
13
+ const existing = await this.repo.findByEmail(normalizedEmail)
14
+ if (existing) return existing
15
+
16
+ const created: User = {
17
+ id: `u-${normalizedEmail}`,
18
+ email: normalizedEmail,
19
+ active: true,
20
+ }
21
+
22
+ await this.repo.save(created)
23
+ return created
24
+ }
25
+
26
+ async canAccessFeature(email: string): Promise<boolean> {
27
+ const user = await this.ensureUser(email)
28
+ return canAccessBilling(user)
29
+ }
30
+ }
@@ -0,0 +1,19 @@
1
+ export type UserId = string
2
+
3
+ export interface User {
4
+ id: UserId
5
+ email: string
6
+ active: boolean
7
+ }
8
+
9
+ export function normalizeEmail(email: string): string {
10
+ return email.trim().toLowerCase()
11
+ }
12
+
13
+ export function isCompanyEmail(email: string): boolean {
14
+ return normalizeEmail(email).endsWith('@example.com')
15
+ }
16
+
17
+ export function canAccessBilling(user: User): boolean {
18
+ return user.active && isCompanyEmail(user.email)
19
+ }
@@ -0,0 +1,22 @@
1
+ export interface AccessPolicyInput {
2
+ score: number
3
+ isInternal: boolean
4
+ isTrial: boolean
5
+ hasPaymentIssue: boolean
6
+ }
7
+
8
+ export function computeTrustTier(input: AccessPolicyInput): 'low' | 'medium' | 'high' {
9
+ if (input.hasPaymentIssue) return 'low'
10
+ if (input.score >= 80 && input.isInternal) return 'high'
11
+ if (input.score >= 60 && !input.isTrial) return 'medium'
12
+ return 'low'
13
+ }
14
+
15
+ export function canRunExpensiveChecks(input: AccessPolicyInput): boolean {
16
+ const tier = computeTrustTier(input)
17
+ return tier === 'high' || (tier === 'medium' && input.score >= 70)
18
+ }
19
+
20
+ export function shouldNotifyOps(input: AccessPolicyInput): boolean {
21
+ return input.hasPaymentIssue || (!input.isInternal && input.score < 45)
22
+ }
@@ -0,0 +1,10 @@
1
+ import { UserService } from './app/user-service.js'
2
+ import { MemoryUserRepository } from './infra/memory-user-repo.js'
3
+
4
+ const service = new UserService(new MemoryUserRepository())
5
+
6
+ export async function runFixtureFlow(): Promise<boolean> {
7
+ return service.canAccessFeature('Demo.User@example.com')
8
+ }
9
+
10
+ void runFixtureFlow()
@@ -0,0 +1,14 @@
1
+ import type { User } from '../domain/entities.js'
2
+ import type { UserRepository } from '../app/user-service.js'
3
+
4
+ export class MemoryUserRepository implements UserRepository {
5
+ private readonly users = new Map<string, User>()
6
+
7
+ async findByEmail(email: string): Promise<User | undefined> {
8
+ return this.users.get(email)
9
+ }
10
+
11
+ async save(user: User): Promise<void> {
12
+ this.users.set(user.email, user)
13
+ }
14
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "schemaVersion": "drift-perf-budget/v1",
3
+ "budgetVersion": "2026-03-26",
4
+ "benchmark": {
5
+ "fixturePath": "benchmarks/fixtures/critical",
6
+ "warmupRuns": 1,
7
+ "measuredRuns": 3
8
+ },
9
+ "tolerance": {
10
+ "runtimePct": 30,
11
+ "memoryPct": 25
12
+ },
13
+ "tasks": {
14
+ "scan": {
15
+ "maxMedianMs": 3800,
16
+ "maxRssMb": 700
17
+ },
18
+ "review": {
19
+ "maxMedianMs": 6500,
20
+ "maxRssMb": 1300
21
+ },
22
+ "trust": {
23
+ "maxMedianMs": 6500,
24
+ "maxRssMb": 1300
25
+ }
26
+ }
27
+ }
@@ -1,2 +1,2 @@
1
- export {};
1
+ export declare function runBenchmarkCli(argv?: string[]): Promise<void>;
2
2
  //# sourceMappingURL=benchmark.d.ts.map