@eduardbar/drift 1.3.0 → 1.4.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 (168) hide show
  1. package/.gga +50 -0
  2. package/.github/actions/drift-review/README.md +60 -0
  3. package/.github/actions/drift-review/action.yml +131 -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/review-pr.yml +34 -41
  7. package/AGENTS.md +75 -251
  8. package/CHANGELOG.md +28 -0
  9. package/README.md +148 -41
  10. package/dist/benchmark.d.ts +1 -1
  11. package/dist/benchmark.js +71 -52
  12. package/dist/cli.js +243 -8
  13. package/dist/config.js +16 -2
  14. package/dist/diff.js +42 -50
  15. package/dist/doctor.d.ts +5 -0
  16. package/dist/doctor.js +133 -0
  17. package/dist/format.d.ts +17 -0
  18. package/dist/format.js +45 -0
  19. package/dist/guard-types.d.ts +57 -0
  20. package/dist/guard-types.js +2 -0
  21. package/dist/guard.d.ts +14 -0
  22. package/dist/guard.js +239 -0
  23. package/dist/index.d.ts +10 -3
  24. package/dist/index.js +4 -1
  25. package/dist/init.d.ts +15 -0
  26. package/dist/init.js +273 -0
  27. package/dist/map-cycles.d.ts +2 -0
  28. package/dist/map-cycles.js +34 -0
  29. package/dist/map-svg.d.ts +19 -0
  30. package/dist/map-svg.js +97 -0
  31. package/dist/map.js +78 -138
  32. package/dist/metrics.js +70 -55
  33. package/dist/output-metadata.d.ts +13 -0
  34. package/dist/output-metadata.js +17 -0
  35. package/dist/plugins-capabilities.d.ts +4 -0
  36. package/dist/plugins-capabilities.js +21 -0
  37. package/dist/plugins-messages.d.ts +10 -0
  38. package/dist/plugins-messages.js +16 -0
  39. package/dist/plugins-rules.d.ts +9 -0
  40. package/dist/plugins-rules.js +137 -0
  41. package/dist/plugins.d.ts +1 -1
  42. package/dist/plugins.js +45 -142
  43. package/dist/reporter-constants.d.ts +16 -0
  44. package/dist/reporter-constants.js +39 -0
  45. package/dist/reporter.d.ts +3 -3
  46. package/dist/reporter.js +35 -55
  47. package/dist/review.d.ts +2 -1
  48. package/dist/review.js +2 -1
  49. package/dist/rules/phase3-configurable.js +23 -15
  50. package/dist/saas/constants.d.ts +15 -0
  51. package/dist/saas/constants.js +48 -0
  52. package/dist/saas/dashboard.d.ts +8 -0
  53. package/dist/saas/dashboard.js +132 -0
  54. package/dist/saas/errors.d.ts +19 -0
  55. package/dist/saas/errors.js +37 -0
  56. package/dist/saas/helpers.d.ts +21 -0
  57. package/dist/saas/helpers.js +110 -0
  58. package/dist/saas/ingest.d.ts +3 -0
  59. package/dist/saas/ingest.js +249 -0
  60. package/dist/saas/organization.d.ts +5 -0
  61. package/dist/saas/organization.js +82 -0
  62. package/dist/saas/plan-change.d.ts +10 -0
  63. package/dist/saas/plan-change.js +15 -0
  64. package/dist/saas/store.d.ts +21 -0
  65. package/dist/saas/store.js +159 -0
  66. package/dist/saas/types.d.ts +191 -0
  67. package/dist/saas/types.js +2 -0
  68. package/dist/saas.d.ts +8 -218
  69. package/dist/saas.js +7 -761
  70. package/dist/sarif.d.ts +74 -0
  71. package/dist/sarif.js +122 -0
  72. package/dist/trust-advanced.d.ts +14 -0
  73. package/dist/trust-advanced.js +65 -0
  74. package/dist/trust-kpi-fs.d.ts +3 -0
  75. package/dist/trust-kpi-fs.js +141 -0
  76. package/dist/trust-kpi-parse.d.ts +7 -0
  77. package/dist/trust-kpi-parse.js +186 -0
  78. package/dist/trust-kpi-types.d.ts +16 -0
  79. package/dist/trust-kpi-types.js +2 -0
  80. package/dist/trust-kpi.d.ts +1 -3
  81. package/dist/trust-kpi.js +6 -266
  82. package/dist/trust-policy.d.ts +32 -0
  83. package/dist/trust-policy.js +160 -0
  84. package/dist/trust-render.d.ts +9 -0
  85. package/dist/trust-render.js +54 -0
  86. package/dist/trust-scoring.d.ts +9 -0
  87. package/dist/trust-scoring.js +208 -0
  88. package/dist/trust.d.ts +4 -32
  89. package/dist/trust.js +29 -432
  90. package/dist/types/app.d.ts +30 -0
  91. package/dist/types/app.js +2 -0
  92. package/dist/types/config.d.ts +25 -0
  93. package/dist/types/config.js +2 -0
  94. package/dist/types/core.d.ts +100 -0
  95. package/dist/types/core.js +2 -0
  96. package/dist/types/diff.d.ts +55 -0
  97. package/dist/types/diff.js +2 -0
  98. package/dist/types/plugin.d.ts +41 -0
  99. package/dist/types/plugin.js +2 -0
  100. package/dist/types/trust.d.ts +120 -0
  101. package/dist/types/trust.js +2 -0
  102. package/dist/types.d.ts +8 -365
  103. package/docs/release-notes-draft.md +40 -0
  104. package/docs/rules-catalog.md +49 -0
  105. package/docs/trust-core-release-checklist.md +37 -5
  106. package/package.json +3 -2
  107. package/packages/vscode-drift/src/code-actions.ts +1 -1
  108. package/schemas/drift-ai-output.v1.json +162 -0
  109. package/schemas/drift-report.v1.json +151 -0
  110. package/schemas/drift-trust.v1.json +131 -0
  111. package/scripts/smoke-repo.mjs +394 -0
  112. package/src/benchmark.ts +75 -53
  113. package/src/cli.ts +285 -13
  114. package/src/config.ts +19 -2
  115. package/src/diff.ts +57 -48
  116. package/src/doctor.ts +173 -0
  117. package/src/format.ts +81 -0
  118. package/src/guard-types.ts +64 -0
  119. package/src/guard.ts +324 -0
  120. package/src/index.ts +35 -0
  121. package/src/init.ts +298 -0
  122. package/src/map-cycles.ts +38 -0
  123. package/src/map-svg.ts +124 -0
  124. package/src/map.ts +111 -142
  125. package/src/metrics.ts +78 -59
  126. package/src/output-metadata.ts +30 -0
  127. package/src/plugins-capabilities.ts +36 -0
  128. package/src/plugins-messages.ts +35 -0
  129. package/src/plugins-rules.ts +296 -0
  130. package/src/plugins.ts +76 -283
  131. package/src/reporter-constants.ts +46 -0
  132. package/src/reporter.ts +64 -65
  133. package/src/review.ts +4 -2
  134. package/src/rules/phase3-configurable.ts +39 -26
  135. package/src/saas/constants.ts +56 -0
  136. package/src/saas/dashboard.ts +172 -0
  137. package/src/saas/errors.ts +45 -0
  138. package/src/saas/helpers.ts +140 -0
  139. package/src/saas/ingest.ts +278 -0
  140. package/src/saas/organization.ts +99 -0
  141. package/src/saas/plan-change.ts +19 -0
  142. package/src/saas/store.ts +172 -0
  143. package/src/saas/types.ts +216 -0
  144. package/src/saas.ts +49 -1031
  145. package/src/sarif.ts +232 -0
  146. package/src/trust-advanced.ts +99 -0
  147. package/src/trust-kpi-fs.ts +169 -0
  148. package/src/trust-kpi-parse.ts +219 -0
  149. package/src/trust-kpi-types.ts +19 -0
  150. package/src/trust-kpi.ts +8 -316
  151. package/src/trust-policy.ts +246 -0
  152. package/src/trust-render.ts +61 -0
  153. package/src/trust-scoring.ts +231 -0
  154. package/src/trust.ts +62 -576
  155. package/src/types/app.ts +30 -0
  156. package/src/types/config.ts +27 -0
  157. package/src/types/core.ts +105 -0
  158. package/src/types/diff.ts +61 -0
  159. package/src/types/plugin.ts +46 -0
  160. package/src/types/trust.ts +134 -0
  161. package/src/types.ts +78 -409
  162. package/tests/cli-sarif.test.ts +92 -0
  163. package/tests/format.test.ts +157 -0
  164. package/tests/new-features.test.ts +10 -2
  165. package/tests/phase1-init-doctor-guard.test.ts +199 -0
  166. package/tests/sarif.test.ts +160 -0
  167. package/tests/trust-kpi.test.ts +31 -4
  168. package/tests/trust.test.ts +18 -0
package/README.md CHANGED
@@ -51,12 +51,61 @@ 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 (`>=18`), `package.json`, ESM mode, `tsconfig.json`, source file count, optional `--low-memory` recommendation, and `drift.config.*` detection.
95
+
96
+ ---
97
+
98
+ ### Repository smoke (local source CLI)
99
+
100
+ Run a non-destructive end-to-end smoke suite against any repository path using local source commands (`node --import tsx ./src/cli.ts ...`).
101
+
102
+ ```bash
103
+ npm run smoke:repo -- ../target-repo --base origin/main
104
+ npm run smoke:repo -- ../target-repo --dry-run
105
+ ```
106
+
107
+ The script writes JSON + Markdown summaries and command logs under `.drift-smoke/<repo>-<timestamp>/`.
108
+
60
109
  ### `drift scan [path]`
61
110
 
62
111
  Scan a directory and print a scored report to stdout.
@@ -77,16 +126,19 @@ drift scan ./src --low-memory --max-file-size-kb 1024
77
126
  | Flag | Description |
78
127
  |------|-------------|
79
128
  | `--output <file>` | Write Markdown report to a file instead of stdout |
129
+ | `--format <type>` | Output format: `console\|json\|markdown\|ai\|sarif` |
80
130
  | `--json` | Output raw `DriftReport` JSON |
81
131
  | `--ai` | Output structured JSON optimized for LLM consumption (Claude, GPT, etc.) |
82
132
  | `--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 |
133
+ | `--min-score <n>` | Exit with code 1 if the overall score strictly exceeds this threshold |
84
134
  | `--low-memory` | Analyze files in bounded chunks to reduce peak RAM |
85
135
  | `--chunk-size <n>` | Files per chunk in low-memory mode (default: 40) |
86
136
  | `--max-files <n>` | Soft cap on analyzed files; extra files are skipped with diagnostics |
87
137
  | `--max-file-size-kb <n>` | Skip oversized files with diagnostics to avoid OOM |
88
138
  | `--with-semantic-duplication` | Keep semantic duplication rule enabled in low-memory mode |
89
139
 
140
+ `--min-score` currently fails when `totalScore > n` (strictly greater than), matching CLI behavior.
141
+
90
142
  **Example output:**
91
143
 
92
144
  ```
@@ -113,6 +165,33 @@ drift scan ./src --low-memory --max-file-size-kb 1024
113
165
 
114
166
  ---
115
167
 
168
+ ### `drift guard [path]`
169
+
170
+ Evaluate drift regression guardrails against a git diff (`--base`) or a baseline file/object.
171
+
172
+ ```bash
173
+ drift guard ./src --base origin/main
174
+ drift guard ./src --baseline drift-baseline.json
175
+ drift guard ./src --base origin/main --budget 3 --by-severity error=0,warning=2,info=5
176
+ drift guard ./src --base origin/main --json
177
+ ```
178
+
179
+ | Flag | Description |
180
+ |------|-------------|
181
+ | `--base <ref>` | Diff mode: compare current state vs git ref |
182
+ | `--baseline <file>` | Baseline mode: compare against baseline JSON (default file name: `drift-baseline.json`) |
183
+ | `--budget <n>` | Maximum allowed score delta |
184
+ | `--by-severity <spec>` | Severity thresholds as CSV `key=value` pairs (`error`, `warning`, `info`) |
185
+ | `--json` | Output full `GuardResult` JSON |
186
+ | `--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 |
187
+
188
+ Behavior:
189
+ - With `--base`, guard enforces no-regression checks for score and total issues, then applies optional budget/severity thresholds.
190
+ - Without `--base`, guard requires a baseline (inline or file) and compares only against available baseline anchors.
191
+ - Exit code is `1` when any guard check fails.
192
+
193
+ ---
194
+
116
195
  ### `drift diff [ref]`
117
196
 
118
197
  Compare the current project state against any git ref. Defaults to `HEAD~1`.
@@ -122,6 +201,7 @@ drift diff # HEAD vs HEAD~1
122
201
  drift diff HEAD~3 # HEAD vs 3 commits ago
123
202
  drift diff main # HEAD vs branch main
124
203
  drift diff abc1234 # HEAD vs a specific commit
204
+ drift diff --format sarif # Output SARIF for code scanning
125
205
  drift diff --json # Output raw JSON diff
126
206
  ```
127
207
 
@@ -129,6 +209,7 @@ drift diff --json # Output raw JSON diff
129
209
 
130
210
  | Flag | Description |
131
211
  |------|-------------|
212
+ | `--format <type>` | Output format: `console\|json\|sarif` |
132
213
  | `--json` | Output raw JSON diff |
133
214
 
134
215
  Shows score delta, issues introduced, and issues resolved since the given ref.
@@ -143,12 +224,14 @@ Review drift against a git base ref and output a PR-ready markdown comment.
143
224
  drift review --base origin/main
144
225
  drift review --base main --comment
145
226
  drift review --base HEAD~3 --json
227
+ drift review --base origin/main --format sarif
146
228
  drift review --base origin/main --fail-on 5
147
229
  ```
148
230
 
149
231
  | Flag | Description |
150
232
  |------|-------------|
151
233
  | `--base <ref>` | Git base ref to compare against (default: `origin/main`) |
234
+ | `--format <type>` | Output format: `console\|json\|markdown\|sarif` |
152
235
  | `--json` | Output structured review JSON |
153
236
  | `--comment` | Print only the markdown body for PR comments |
154
237
  | `--fail-on <n>` | Exit code 1 when score delta is greater than or equal to `n` |
@@ -167,6 +250,7 @@ drift trust ./src
167
250
  drift trust ./src --json
168
251
  drift trust ./src --base origin/main
169
252
  drift trust ./src --base origin/main --markdown
253
+ drift trust ./src --format sarif
170
254
  drift trust ./src --markdown --output trust.md
171
255
  drift trust ./src --min-trust 45
172
256
  drift trust ./src --max-risk HIGH
@@ -180,6 +264,7 @@ drift trust ./src --advanced-trust --history-file ./drift-history.json --markdow
180
264
  | Flag | Description |
181
265
  |------|-------------|
182
266
  | `--base <ref>` | Compare against a git base ref and include deterministic diff-aware penalties/bonuses |
267
+ | `--format <type>` | Output format: `console\|json\|markdown\|sarif` |
183
268
  | `--json` | Output structured trust JSON (`trust_score`, `merge_risk`, `top_reasons`, `fix_priorities`, optional `diff_context`) |
184
269
  | `--markdown` | Output PR-ready markdown trust summary |
185
270
  | `--output <file>` | Write selected trust output format to file |
@@ -308,6 +393,7 @@ Emit GitHub Actions annotations and a step summary. Designed to run inside a CI
308
393
  ```bash
309
394
  drift ci # scan current directory
310
395
  drift ci ./src
396
+ drift ci ./src --format sarif
311
397
  drift ci ./src --min-score 60
312
398
  ```
313
399
 
@@ -315,7 +401,8 @@ drift ci ./src --min-score 60
315
401
 
316
402
  | Flag | Description |
317
403
  |------|-------------|
318
- | `--min-score <n>` | Exit with code 1 if the overall score meets or exceeds this threshold |
404
+ | `--format <type>` | Output format: `console\|json\|sarif` |
405
+ | `--min-score <n>` | Exit with code 1 if the overall score strictly exceeds this threshold |
319
406
 
320
407
  Outputs `::error` and `::warning` annotations visible in the PR diff. Writes a markdown summary to `$GITHUB_STEP_SUMMARY`.
321
408
 
@@ -427,36 +514,28 @@ export default {
427
514
 
428
515
  ## Rules
429
516
 
430
- 26 rules across three severity levels. All run automatically unless marked as requiring configuration.
517
+ 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.
518
+
519
+ For the complete up-to-date catalog (id, severity, weight, phase/origin, note), see [`docs/rules-catalog.md`](./docs/rules-catalog.md).
520
+
521
+ Highlights:
431
522
 
432
523
  | Rule | Severity | Weight | What it detects |
433
524
  |------|----------|--------|-----------------|
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 |
525
+ | `large-file` | error | 20 | Files exceeding 300 lines |
526
+ | `duplicate-function-name` | error | 18 | Same function name repeated across a file |
527
+ | `high-complexity` | error | 15 | Cyclomatic complexity above threshold |
528
+ | `layer-violation` | error | 16 | Invalid layer import direction (requires `layers` config) |
529
+ | `cross-boundary-import` | warning | 10 | Invalid module boundary import (requires `modules`/legacy boundaries config) |
530
+ | `controller-no-db` | warning | 11 | Controller touching DB directly (configurable architecture rule) |
531
+ | `service-no-http` | warning | 11 | Service layer coupled to HTTP concerns (configurable architecture rule) |
532
+ | `max-function-lines` | warning | 9 | Function line cap enforced by `architectureRules.maxFunctionLines` |
533
+ | `semantic-duplication` | warning | 12 | AST-level semantic function duplication |
534
+ | `ai-code-smell` | warning | 12 | Aggregated AI-smell signal from multiple heuristics in a file |
535
+ | `plugin-error` | warning | 4 | Plugin load/contract/runtime failure surfaced as issue |
536
+ | `plugin-warning` | info | 0 | Non-fatal plugin validation warning |
537
+ | `analysis-skip-max-files` | info | 0 | File skipped by `maxFiles` guardrail |
538
+ | `analysis-skip-file-size` | info | 0 | File skipped by `maxFileSizeKb` guardrail |
460
539
 
461
540
  ---
462
541
 
@@ -476,7 +555,7 @@ export default {
476
555
 
477
556
  ## Configuration
478
557
 
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:
558
+ 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
559
 
481
560
  ```typescript
482
561
  import type { DriftConfig } from '@eduardbar/drift'
@@ -509,22 +588,16 @@ export default {
509
588
  { name: 'app', patterns: ['src/app/**'], canImportFrom: ['domain'] },
510
589
  { name: 'infra', patterns: ['src/infra/**'], canImportFrom: ['domain', 'app'] },
511
590
  ],
512
- boundaries: [
591
+ modules: [
513
592
  { name: 'auth', root: 'src/modules/auth', allowedExternalImports: ['src/shared'] },
514
593
  { name: 'billing', root: 'src/modules/billing', allowedExternalImports: ['src/shared'] },
515
594
  ],
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
595
  } satisfies DriftConfig
525
596
  ```
526
597
 
527
- Without a config file, `layer-violation` and `cross-boundary-import` are silently skipped. All other rules run with their defaults.
598
+ For backwards compatibility, `moduleBoundaries` and `boundaries` are normalized to `modules`.
599
+
600
+ 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
601
 
529
602
  ### Memory guardrails (recommended for large repos)
530
603
 
@@ -563,7 +636,7 @@ jobs:
563
636
  run: npx @eduardbar/drift scan ./src --min-score 60
564
637
  ```
565
638
 
566
- Exit code is `1` if the score meets or exceeds `--min-score`. Exit code `0` otherwise.
639
+ Exit code is `1` if the score is strictly greater than `--min-score`. Exit code `0` otherwise.
567
640
 
568
641
  ### Annotations and step summary with `drift ci`
569
642
 
@@ -595,11 +668,45 @@ The repository includes `.github/workflows/review-pr.yml`, which:
595
668
  - enforces a trust baseline gate with `drift trust-gate drift-trust.json --min-trust 45 --max-risk HIGH`
596
669
  - uploads `drift trust --json` as `drift-trust-json-pr-<PR_NUMBER>-run-<RUN_ATTEMPT>` for manual KPI tracking
597
670
  - publishes a compact trust KPI summary in `$GITHUB_STEP_SUMMARY` (score, merge risk, new/resolved issues when diff context is available)
671
+ - 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
672
+
673
+ Quick local SARIF export:
674
+
675
+ ```bash
676
+ drift scan ./src --format sarif > drift.sarif
677
+ ```
678
+
679
+ Example GitHub Code Scanning upload with SARIF:
680
+
681
+ ```yaml
682
+ - name: Generate drift SARIF
683
+ run: npx @eduardbar/drift scan ./src --format sarif > drift.sarif
684
+
685
+ - name: Upload SARIF to Code Scanning
686
+ uses: github/codeql-action/upload-sarif@v3
687
+ with:
688
+ sarif_file: drift.sarif
689
+ ```
598
690
 
599
691
  Default gate behavior in this repo:
600
692
  - fail when trust is below 45
601
693
  - fail when merge risk is above `HIGH` (that means `CRITICAL` is blocked)
602
694
 
695
+ ### GitHub Action contract (v2)
696
+
697
+ This repo also ships reusable local composite actions:
698
+
699
+ - `./.github/actions/drift-scan`: score-focused scan gate using `npx @eduardbar/drift@<version>` (no global install).
700
+ - `./.github/actions/drift-review`: trust/review/gate flow for PRs, aligned with `.github/workflows/review-pr.yml`.
701
+
702
+ `drift-scan` exposes machine-friendly outputs for CI composition:
703
+ - `score`, `grade`, `errors`, `warnings`, `infos`, `total-issues`, `files-affected`, `top-rules`
704
+
705
+ `drift-review` exposes:
706
+ - `trust-score`, `merge-risk`, `new-issues`, `resolved-issues`, `trust-json`, `trust-markdown`, `review-markdown`
707
+
708
+ See `.github/actions/drift-scan/README.md` and `.github/actions/drift-review/README.md` for usage details.
709
+
603
710
  ---
604
711
 
605
712
  ## drift-ignore
@@ -1,2 +1,2 @@
1
- export {};
1
+ export declare function runBenchmarkCli(argv?: string[]): Promise<void>;
2
2
  //# sourceMappingURL=benchmark.d.ts.map
package/dist/benchmark.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
3
4
  import { analyzeProject } from './analyzer.js';
4
5
  import { loadConfig } from './config.js';
5
6
  import { buildReport } from './reporter.js';
@@ -7,6 +8,30 @@ import { generateReview } from './review.js';
7
8
  import { buildTrustReport } from './trust.js';
8
9
  import { cleanupTempDir, extractFilesAtRef } from './git.js';
9
10
  import { computeDiff } from './diff.js';
11
+ const DEFAULT_SCAN_PATH = '.';
12
+ const DEFAULT_REVIEW_PATH = '.';
13
+ const DEFAULT_TRUST_PATH = '.';
14
+ const DEFAULT_BASE_REF = 'HEAD~1';
15
+ const DEFAULT_WARMUP_RUNS = 1;
16
+ const DEFAULT_MEASURED_RUNS = 5;
17
+ const TABLE_WIDTHS = {
18
+ task: 10,
19
+ warmup: 8,
20
+ runs: 6,
21
+ median: 13,
22
+ mean: 11,
23
+ min: 10,
24
+ max: 10,
25
+ };
26
+ const TABLE_COLUMNS = [
27
+ { key: 'task', header: 'task' },
28
+ { key: 'warmup', header: 'warmup' },
29
+ { key: 'runs', header: 'runs' },
30
+ { key: 'median', header: 'median(ms)' },
31
+ { key: 'mean', header: 'mean(ms)' },
32
+ { key: 'min', header: 'min(ms)' },
33
+ { key: 'max', header: 'max(ms)' },
34
+ ];
10
35
  function parseNumberFlag(value, flagName) {
11
36
  const parsed = Number(value);
12
37
  if (!Number.isFinite(parsed) || parsed < 0) {
@@ -16,52 +41,29 @@ function parseNumberFlag(value, flagName) {
16
41
  }
17
42
  function parseOptions(argv) {
18
43
  const options = {
19
- scanPath: '.',
20
- reviewPath: '.',
21
- trustPath: '.',
22
- baseRef: 'HEAD~1',
23
- warmupRuns: 1,
24
- measuredRuns: 5,
44
+ scanPath: DEFAULT_SCAN_PATH,
45
+ reviewPath: DEFAULT_REVIEW_PATH,
46
+ trustPath: DEFAULT_TRUST_PATH,
47
+ baseRef: DEFAULT_BASE_REF,
48
+ warmupRuns: DEFAULT_WARMUP_RUNS,
49
+ measuredRuns: DEFAULT_MEASURED_RUNS,
25
50
  };
26
- for (let i = 0; i < argv.length; i += 1) {
51
+ const handlers = {
52
+ '--scan-path': (value) => { options.scanPath = value; },
53
+ '--review-path': (value) => { options.reviewPath = value; },
54
+ '--trust-path': (value) => { options.trustPath = value; },
55
+ '--base': (value) => { options.baseRef = value; },
56
+ '--warmup': (value) => { options.warmupRuns = parseNumberFlag(value, '--warmup'); },
57
+ '--runs': (value) => { options.measuredRuns = parseNumberFlag(value, '--runs'); },
58
+ '--json-out': (value) => { options.jsonOut = value; },
59
+ };
60
+ for (let i = 0; i < argv.length; i += 2) {
27
61
  const arg = argv[i];
28
62
  const next = argv[i + 1];
29
- if (arg === '--scan-path' && next) {
30
- options.scanPath = next;
31
- i += 1;
32
- continue;
33
- }
34
- if (arg === '--review-path' && next) {
35
- options.reviewPath = next;
36
- i += 1;
37
- continue;
38
- }
39
- if (arg === '--trust-path' && next) {
40
- options.trustPath = next;
41
- i += 1;
42
- continue;
43
- }
44
- if (arg === '--base' && next) {
45
- options.baseRef = next;
46
- i += 1;
47
- continue;
48
- }
49
- if (arg === '--warmup' && next) {
50
- options.warmupRuns = parseNumberFlag(next, '--warmup');
51
- i += 1;
52
- continue;
53
- }
54
- if (arg === '--runs' && next) {
55
- options.measuredRuns = parseNumberFlag(next, '--runs');
56
- i += 1;
57
- continue;
58
- }
59
- if (arg === '--json-out' && next) {
60
- options.jsonOut = next;
61
- i += 1;
62
- continue;
63
- }
64
- throw new Error(`Unknown or incomplete argument: ${arg}`);
63
+ const handler = handlers[arg];
64
+ if (!handler || !next)
65
+ throw new Error(`Unknown or incomplete argument: ${arg}`);
66
+ handler(next);
65
67
  }
66
68
  if (options.measuredRuns < 1) {
67
69
  throw new Error('--runs must be at least 1');
@@ -107,8 +109,8 @@ async function runTask(name, warmupRuns, measuredRuns, task) {
107
109
  };
108
110
  }
109
111
  function printTable(results) {
110
- const headers = ['task', 'warmup', 'runs', 'median(ms)', 'mean(ms)', 'min(ms)', 'max(ms)'];
111
- const widths = [10, 8, 6, 13, 11, 10, 10];
112
+ const headers = TABLE_COLUMNS.map((column) => column.header);
113
+ const widths = TABLE_COLUMNS.map((column) => TABLE_WIDTHS[column.key]);
112
114
  const row = (values) => values
113
115
  .map((value, index) => value.padEnd(widths[index], ' '))
114
116
  .join(' ');
@@ -153,11 +155,14 @@ async function runTrust(trustPath, baseRef) {
153
155
  }
154
156
  buildTrustReport(report, { diff });
155
157
  }
156
- async function main() {
157
- const options = parseOptions(process.argv.slice(2));
158
+ async function runReview(reviewPath, baseRef) {
159
+ await generateReview(reviewPath, baseRef);
160
+ }
161
+ async function main(argv) {
162
+ const options = parseOptions(argv);
158
163
  const results = [
159
164
  await runTask('scan', options.warmupRuns, options.measuredRuns, () => runScan(options.scanPath)),
160
- await runTask('review', options.warmupRuns, options.measuredRuns, () => generateReview(options.reviewPath, options.baseRef).then(() => undefined)),
165
+ await runTask('review', options.warmupRuns, options.measuredRuns, () => runReview(options.reviewPath, options.baseRef)),
161
166
  await runTask('trust', options.warmupRuns, options.measuredRuns, () => runTrust(options.trustPath, options.baseRef)),
162
167
  ];
163
168
  const output = {
@@ -178,8 +183,22 @@ async function main() {
178
183
  process.stdout.write(`\nSaved benchmark JSON to ${options.jsonOut}\n`);
179
184
  }
180
185
  }
181
- main().catch((error) => {
182
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
183
- process.exit(1);
184
- });
186
+ function isExecutedAsEntryPoint() {
187
+ const entryArg = process.argv[1];
188
+ if (!entryArg)
189
+ return false;
190
+ return import.meta.url === pathToFileURL(path.resolve(entryArg)).href;
191
+ }
192
+ export async function runBenchmarkCli(argv = process.argv.slice(2)) {
193
+ try {
194
+ await main(argv);
195
+ }
196
+ catch (error) {
197
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
198
+ process.exit(1);
199
+ }
200
+ }
201
+ if (isExecutedAsEntryPoint()) {
202
+ void runBenchmarkCli();
203
+ }
185
204
  //# sourceMappingURL=benchmark.js.map