@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/src/types.ts CHANGED
@@ -1,409 +1,78 @@
1
- import type { SourceFile } from 'ts-morph'
2
-
3
- export interface DriftIssue {
4
- rule: string
5
- severity: 'error' | 'warning' | 'info'
6
- message: string
7
- line: number
8
- column: number
9
- snippet: string
10
- }
11
-
12
- export interface FileReport {
13
- path: string
14
- issues: DriftIssue[]
15
- score: number // 0–100, higher = more drift
16
- }
17
-
18
- export interface DriftReport {
19
- scannedAt: string
20
- targetPath: string
21
- files: FileReport[]
22
- totalIssues: number
23
- totalScore: number
24
- totalFiles: number
25
- summary: {
26
- errors: number
27
- warnings: number
28
- infos: number
29
- byRule: Record<string, number>
30
- }
31
- quality: RepoQualityScore
32
- maintenanceRisk: MaintenanceRiskMetrics
33
- }
34
-
35
- export interface RepoQualityScore {
36
- overall: number
37
- dimensions: {
38
- architecture: number
39
- complexity: number
40
- 'ai-patterns': number
41
- testing: number
42
- }
43
- }
44
-
45
- export interface RiskHotspot {
46
- file: string
47
- driftScore: number
48
- complexityIssues: number
49
- hasNearbyTests: boolean
50
- changeFrequency: number
51
- risk: number
52
- reasons: string[]
53
- }
54
-
55
- export interface MaintenanceRiskMetrics {
56
- score: number
57
- level: 'low' | 'medium' | 'high' | 'critical'
58
- hotspots: RiskHotspot[]
59
- signals: {
60
- highComplexityFiles: number
61
- filesWithoutNearbyTests: number
62
- frequentChangeFiles: number
63
- }
64
- }
65
-
66
- export interface AIOutput {
67
- summary: {
68
- score: number
69
- grade: string
70
- total_issues: number
71
- files_affected: number
72
- files_clean: number
73
- ai_likelihood: number
74
- ai_code_smell_score: number
75
- }
76
- files_suspected: Array<{ path: string; ai_likelihood: number; triggers: string[] }>
77
- priority_order: AIIssue[]
78
- maintenance_risk: MaintenanceRiskMetrics
79
- quality: RepoQualityScore
80
- context_for_ai: {
81
- project_type: string
82
- scan_path: string
83
- rules_detected: string[]
84
- recommended_action: string
85
- }
86
- }
87
-
88
- export interface AIIssue {
89
- rank: number
90
- file: string
91
- line: number
92
- rule: string
93
- severity: string
94
- message: string
95
- snippet: string
96
- fix_suggestion: string
97
- effort: 'low' | 'medium' | 'high'
98
- }
99
-
100
- export type MergeRiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
101
-
102
- export interface TrustGatePolicyPreset {
103
- branch: string
104
- enabled?: boolean
105
- minTrust?: number
106
- maxRisk?: MergeRiskLevel
107
- }
108
-
109
- export interface TrustGatePolicyPack {
110
- enabled?: boolean
111
- minTrust?: number
112
- maxRisk?: MergeRiskLevel
113
- }
114
-
115
- export interface TrustGatePolicyConfig {
116
- enabled?: boolean
117
- minTrust?: number
118
- maxRisk?: MergeRiskLevel
119
- presets?: TrustGatePolicyPreset[]
120
- policyPacks?: Record<string, TrustGatePolicyPack>
121
- }
122
-
123
- export interface TrustReason {
124
- label: string
125
- detail: string
126
- impact: number
127
- }
128
-
129
- export interface TrustFixPriority {
130
- rank: number
131
- rule: string
132
- severity: DriftIssue['severity']
133
- occurrences: number
134
- estimated_trust_gain: number
135
- effort: 'low' | 'medium' | 'high'
136
- suggestion: string
137
- confidence?: 'low' | 'medium' | 'high'
138
- explanation?: string
139
- systemic?: boolean
140
- }
141
-
142
- export interface TrustAdvancedComparison {
143
- source: 'previous-trust-json' | 'snapshot-history'
144
- trend: 'improving' | 'regressing' | 'stable'
145
- summary: string
146
- trust_delta?: number
147
- previous_trust_score?: number
148
- previous_merge_risk?: MergeRiskLevel
149
- snapshot_score_delta?: number
150
- snapshot_label?: string
151
- snapshot_timestamp?: string
152
- }
153
-
154
- export interface TrustAdvancedContext {
155
- comparison?: TrustAdvancedComparison
156
- team_guidance: string[]
157
- }
158
-
159
- export interface TrustDiffContext {
160
- baseRef: string
161
- status: 'improved' | 'regressed' | 'neutral'
162
- scoreDelta: number
163
- newIssues: number
164
- resolvedIssues: number
165
- filesChanged: number
166
- penalty: number
167
- bonus: number
168
- netImpact: number
169
- }
170
-
171
- export interface DriftTrustReport {
172
- scannedAt: string
173
- targetPath: string
174
- trust_score: number
175
- merge_risk: MergeRiskLevel
176
- top_reasons: TrustReason[]
177
- fix_priorities: TrustFixPriority[]
178
- diff_context?: TrustDiffContext
179
- advanced_context?: TrustAdvancedContext
180
- }
181
-
182
- export interface TrustKpiDiagnostic {
183
- level: 'warning' | 'error'
184
- code: 'path-not-found' | 'path-not-supported' | 'read-failed' | 'parse-failed' | 'invalid-shape' | 'invalid-diff-context'
185
- message: string
186
- file?: string
187
- }
188
-
189
- export interface TrustScoreStats {
190
- average: number | null
191
- median: number | null
192
- min: number | null
193
- max: number | null
194
- }
195
-
196
- export interface TrustDiffTrendSummary {
197
- available: boolean
198
- samples: number
199
- statusDistribution: {
200
- improved: number
201
- regressed: number
202
- neutral: number
203
- }
204
- scoreDelta: {
205
- average: number | null
206
- median: number | null
207
- }
208
- issues: {
209
- newTotal: number
210
- resolvedTotal: number
211
- netNew: number
212
- }
213
- }
214
-
215
- export interface TrustKpiReport {
216
- generatedAt: string
217
- input: string
218
- files: {
219
- matched: number
220
- parsed: number
221
- malformed: number
222
- }
223
- prsEvaluated: number
224
- mergeRiskDistribution: Record<MergeRiskLevel, number>
225
- trustScore: TrustScoreStats
226
- highRiskRatio: number | null
227
- diffTrend: TrustDiffTrendSummary
228
- diagnostics: TrustKpiDiagnostic[]
229
- }
230
-
231
- // ---------------------------------------------------------------------------
232
- // Configuration
233
- // ---------------------------------------------------------------------------
234
-
235
- /**
236
- * Layer definition for architectural boundary enforcement.
237
- */
238
- export interface LayerDefinition {
239
- name: string
240
- patterns: string[]
241
- canImportFrom: string[]
242
- }
243
-
244
- /**
245
- * Module boundary definition for cross-boundary enforcement.
246
- */
247
- export interface ModuleBoundary {
248
- name: string
249
- root: string
250
- allowedExternalImports?: string[]
251
- }
252
-
253
- /**
254
- * Optional project-level configuration for drift.
255
- * Place in drift.config.ts (or .js / .json) at the project root.
256
- */
257
- export interface DriftConfig {
258
- layers?: LayerDefinition[]
259
- modules?: ModuleBoundary[]
260
- plugins?: string[]
261
- performance?: DriftPerformanceConfig
262
- architectureRules?: {
263
- controllerNoDb?: boolean
264
- serviceNoHttp?: boolean
265
- maxFunctionLines?: number
266
- }
267
- saas?: {
268
- freeUserThreshold?: number
269
- maxRunsPerWorkspacePerMonth?: number
270
- maxReposPerWorkspace?: number
271
- retentionDays?: number
272
- strictActorEnforcement?: boolean
273
- maxWorkspacesPerOrganizationByPlan?: {
274
- free?: number
275
- sponsor?: number
276
- team?: number
277
- business?: number
278
- }
279
- }
280
- trustGate?: TrustGatePolicyConfig
281
- }
282
-
283
- export interface DriftPerformanceConfig {
284
- lowMemory?: boolean
285
- chunkSize?: number
286
- maxFiles?: number
287
- maxFileSizeKb?: number
288
- includeSemanticDuplication?: boolean
289
- }
290
-
291
- export interface DriftAnalysisOptions {
292
- lowMemory?: boolean
293
- chunkSize?: number
294
- maxFiles?: number
295
- maxFileSizeKb?: number
296
- includeSemanticDuplication?: boolean
297
- }
298
-
299
- export interface PluginRuleContext {
300
- projectRoot: string
301
- filePath: string
302
- config?: DriftConfig
303
- }
304
-
305
- export interface DriftPluginRule {
306
- id?: string
307
- name: string
308
- severity?: DriftIssue['severity']
309
- weight?: number
310
- detect: (file: SourceFile, context: PluginRuleContext) => DriftIssue[]
311
- fix?: (issue: DriftIssue, file: SourceFile, context: PluginRuleContext) => DriftIssue | void
312
- }
313
-
314
- export interface DriftPlugin {
315
- name: string
316
- apiVersion?: number
317
- capabilities?: Record<string, string | number | boolean>
318
- rules: DriftPluginRule[]
319
- }
320
-
321
- export interface LoadedPlugin {
322
- id: string
323
- plugin: DriftPlugin
324
- }
325
-
326
- export interface PluginLoadError {
327
- pluginId: string
328
- pluginName?: string
329
- ruleId?: string
330
- code?: string
331
- message: string
332
- }
333
-
334
- export interface PluginLoadWarning {
335
- pluginId: string
336
- pluginName?: string
337
- ruleId?: string
338
- code?: string
339
- message: string
340
- }
341
-
342
- // ---------------------------------------------------------------------------
343
- // Diff
344
- // ---------------------------------------------------------------------------
345
-
346
- export interface FileDiff {
347
- path: string // path relativo al project root
348
- scoreBefore: number
349
- scoreAfter: number
350
- scoreDelta: number // positivo = empeoró (más deuda), negativo = mejoró
351
- newIssues: DriftIssue[]
352
- resolvedIssues: DriftIssue[]
353
- }
354
-
355
- export interface DriftDiff {
356
- baseRef: string // git ref del baseline (e.g. "HEAD~1", "main")
357
- projectPath: string // path absoluto del proyecto
358
- scannedAt: string // ISO timestamp
359
- files: FileDiff[] // solo archivos con cambios (delta != 0 o issues diff != 0)
360
- totalScoreBefore: number
361
- totalScoreAfter: number
362
- totalDelta: number // positivo = más deuda, negativo = menos deuda
363
- newIssuesCount: number
364
- resolvedIssuesCount: number
365
- }
366
-
367
- /** Historical analysis data for a single commit */
368
- export interface HistoricalAnalysis {
369
- commitHash: string;
370
- commitDate: Date;
371
- author: string;
372
- message: string;
373
- files: FileReport[];
374
- totalScore: number;
375
- averageScore: number;
376
- }
377
-
378
- /** Trend data point for score evolution */
379
- export interface TrendDataPoint {
380
- date: Date;
381
- score: number;
382
- fileCount: number;
383
- avgIssuesPerFile: number;
384
- }
385
-
386
- /** Blame attribution data */
387
- export interface BlameAttribution {
388
- author: string;
389
- email: string;
390
- commits: number;
391
- linesChanged: number;
392
- issuesIntroduced: number;
393
- avgScoreImpact: number;
394
- }
395
-
396
- /** Extended DriftReport with historical context */
397
- export interface DriftTrendReport extends DriftReport {
398
- trend: TrendDataPoint[];
399
- regression: {
400
- slope: number;
401
- intercept: number;
402
- r2: number;
403
- };
404
- }
405
-
406
- /** Extended DriftReport with blame data */
407
- export interface DriftBlameReport extends DriftReport {
408
- blame: BlameAttribution[];
409
- }
1
+ // drift-ignore-file
2
+ export type {
3
+ DriftIssue,
4
+ FileReport,
5
+ RepoQualityScore,
6
+ RiskHotspot,
7
+ MaintenanceRiskMetrics,
8
+ AIIssue,
9
+ AIOutput,
10
+ AIOutputJson,
11
+ DriftReport,
12
+ DriftReportJson,
13
+ DriftOutputMetadata,
14
+ } from './types/core.js'
15
+
16
+ export type {
17
+ MergeRiskLevel,
18
+ TrustGatePolicyPreset,
19
+ TrustGatePolicyPack,
20
+ TrustGatePolicyConfig,
21
+ TrustReason,
22
+ TrustFixPriority,
23
+ TrustAdvancedComparison,
24
+ TrustAdvancedContext,
25
+ TrustDiffContext,
26
+ DriftTrustReport,
27
+ DriftTrustReportJson,
28
+ TrustKpiDiagnostic,
29
+ TrustScoreStats,
30
+ TrustDiffTrendSummary,
31
+ TrustKpiReport,
32
+ } from './types/trust.js'
33
+
34
+ export type {
35
+ LayerDefinition,
36
+ ModuleBoundary,
37
+ DriftPerformanceConfig,
38
+ DriftAnalysisOptions,
39
+ } from './types/config.js'
40
+
41
+ export type { DriftConfig } from './types/app.js'
42
+
43
+ export type {
44
+ PluginRuleContext,
45
+ DriftPluginRule,
46
+ DriftPlugin,
47
+ LoadedPlugin,
48
+ PluginLoadError,
49
+ PluginLoadWarning,
50
+ } from './types/plugin.js'
51
+
52
+ export type {
53
+ FileDiff,
54
+ DriftDiff,
55
+ HistoricalAnalysis,
56
+ TrendDataPoint,
57
+ BlameAttribution,
58
+ DriftTrendReport,
59
+ DriftBlameReport,
60
+ } from './types/diff.js'
61
+
62
+ export type {
63
+ GuardBaseline,
64
+ GuardThresholds,
65
+ GuardOptions,
66
+ GuardMetrics,
67
+ GuardCheck,
68
+ GuardEvaluation,
69
+ GuardResult,
70
+ } from './guard-types.js'
71
+
72
+ export type {
73
+ SarifLevel,
74
+ DriftSarifRule,
75
+ DriftSarifResult,
76
+ DriftSarifRun,
77
+ DriftSarifLog,
78
+ } from './sarif.js'
@@ -0,0 +1,92 @@
1
+ import { afterEach, describe, expect, it } from 'vitest'
2
+ import { spawnSync } from 'node:child_process'
3
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { tmpdir } from 'node:os'
6
+
7
+ type CliResult = {
8
+ status: number | null
9
+ stdout: string
10
+ stderr: string
11
+ }
12
+
13
+ type SarifOutput = {
14
+ version: string
15
+ runs: Array<{
16
+ tool: {
17
+ driver: {
18
+ name: string
19
+ }
20
+ }
21
+ results?: Array<{
22
+ ruleId?: string
23
+ message?: {
24
+ text?: string
25
+ }
26
+ }>
27
+ }>
28
+ }
29
+
30
+ function runCli(args: string[]): CliResult {
31
+ const result = spawnSync(process.execPath, ['--import', 'tsx', 'src/cli.ts', ...args], {
32
+ cwd: process.cwd(),
33
+ encoding: 'utf8',
34
+ })
35
+
36
+ return {
37
+ status: result.status,
38
+ stdout: result.stdout,
39
+ stderr: result.stderr,
40
+ }
41
+ }
42
+
43
+ function expectValidSarifFrom(result: CliResult): SarifOutput {
44
+ expect(result.status).toBe(0)
45
+ expect(result.stderr).not.toContain('Error:')
46
+
47
+ const sarif = JSON.parse(result.stdout) as SarifOutput
48
+ expect(sarif.version).toBe('2.1.0')
49
+ expect(Array.isArray(sarif.runs)).toBe(true)
50
+ expect(sarif.runs.length).toBeGreaterThan(0)
51
+ expect(sarif.runs[0]?.tool.driver.name).toBe('drift')
52
+ expect(Array.isArray(sarif.runs[0]?.results)).toBe(true)
53
+
54
+ return sarif
55
+ }
56
+
57
+ describe('cli sarif output', () => {
58
+ let tmpDir = ''
59
+
60
+ afterEach(() => {
61
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true })
62
+ tmpDir = ''
63
+ })
64
+
65
+ it('serializes scan --format sarif output as SARIF JSON', () => {
66
+ tmpDir = mkdtempSync(join(tmpdir(), 'drift-cli-sarif-scan-'))
67
+ writeFileSync(join(tmpDir, 'sample.ts'), 'console.log("debug")\n')
68
+
69
+ const result = runCli(['scan', tmpDir, '--format', 'sarif'])
70
+ const sarif = expectValidSarifFrom(result)
71
+ expect(sarif.runs[0]?.results?.some((entry) => entry.ruleId === 'debug-leftover')).toBe(true)
72
+ })
73
+
74
+ it('serializes ci --format sarif output as SARIF JSON', () => {
75
+ tmpDir = mkdtempSync(join(tmpdir(), 'drift-cli-sarif-ci-'))
76
+ writeFileSync(join(tmpDir, 'sample.ts'), 'console.log("debug")\n')
77
+
78
+ const result = runCli(['ci', tmpDir, '--format', 'sarif'])
79
+ const sarif = expectValidSarifFrom(result)
80
+ expect(sarif.runs[0]?.results?.some((entry) => entry.ruleId === 'debug-leftover')).toBe(true)
81
+ })
82
+
83
+ it('serializes trust --format sarif output as SARIF JSON without requiring git base', () => {
84
+ tmpDir = mkdtempSync(join(tmpdir(), 'drift-cli-sarif-trust-'))
85
+ writeFileSync(join(tmpDir, 'sample.ts'), 'console.log("debug")\n')
86
+
87
+ const result = runCli(['trust', tmpDir, '--format', 'sarif'])
88
+ const sarif = expectValidSarifFrom(result)
89
+ expect(sarif.runs[0]?.results?.some((entry) => entry.ruleId === 'debug-leftover')).toBe(true)
90
+ })
91
+
92
+ })