@eduardbar/drift 1.2.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 (195) 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/publish-vscode.yml +3 -3
  7. package/.github/workflows/publish.yml +3 -3
  8. package/.github/workflows/review-pr.yml +94 -9
  9. package/AGENTS.md +75 -245
  10. package/CHANGELOG.md +28 -0
  11. package/README.md +308 -51
  12. package/ROADMAP.md +6 -5
  13. package/dist/analyzer.d.ts +2 -2
  14. package/dist/analyzer.js +420 -159
  15. package/dist/benchmark.d.ts +2 -0
  16. package/dist/benchmark.js +204 -0
  17. package/dist/cli.js +693 -67
  18. package/dist/config.js +16 -2
  19. package/dist/diff.js +66 -10
  20. package/dist/doctor.d.ts +5 -0
  21. package/dist/doctor.js +133 -0
  22. package/dist/format.d.ts +17 -0
  23. package/dist/format.js +45 -0
  24. package/dist/git.js +12 -0
  25. package/dist/guard-types.d.ts +57 -0
  26. package/dist/guard-types.js +2 -0
  27. package/dist/guard.d.ts +14 -0
  28. package/dist/guard.js +239 -0
  29. package/dist/index.d.ts +12 -3
  30. package/dist/index.js +6 -1
  31. package/dist/init.d.ts +15 -0
  32. package/dist/init.js +273 -0
  33. package/dist/map-cycles.d.ts +2 -0
  34. package/dist/map-cycles.js +34 -0
  35. package/dist/map-svg.d.ts +19 -0
  36. package/dist/map-svg.js +97 -0
  37. package/dist/map.js +78 -138
  38. package/dist/metrics.js +70 -55
  39. package/dist/output-metadata.d.ts +13 -0
  40. package/dist/output-metadata.js +17 -0
  41. package/dist/plugins-capabilities.d.ts +4 -0
  42. package/dist/plugins-capabilities.js +21 -0
  43. package/dist/plugins-messages.d.ts +10 -0
  44. package/dist/plugins-messages.js +16 -0
  45. package/dist/plugins-rules.d.ts +9 -0
  46. package/dist/plugins-rules.js +137 -0
  47. package/dist/plugins.d.ts +2 -1
  48. package/dist/plugins.js +80 -28
  49. package/dist/printer.js +4 -0
  50. package/dist/reporter-constants.d.ts +16 -0
  51. package/dist/reporter-constants.js +39 -0
  52. package/dist/reporter.d.ts +3 -3
  53. package/dist/reporter.js +35 -55
  54. package/dist/review.d.ts +2 -1
  55. package/dist/review.js +4 -3
  56. package/dist/rules/comments.js +2 -2
  57. package/dist/rules/complexity.js +2 -7
  58. package/dist/rules/nesting.js +3 -13
  59. package/dist/rules/phase0-basic.js +10 -10
  60. package/dist/rules/phase3-configurable.js +23 -15
  61. package/dist/rules/shared.d.ts +2 -0
  62. package/dist/rules/shared.js +27 -3
  63. package/dist/saas/constants.d.ts +15 -0
  64. package/dist/saas/constants.js +48 -0
  65. package/dist/saas/dashboard.d.ts +8 -0
  66. package/dist/saas/dashboard.js +132 -0
  67. package/dist/saas/errors.d.ts +19 -0
  68. package/dist/saas/errors.js +37 -0
  69. package/dist/saas/helpers.d.ts +21 -0
  70. package/dist/saas/helpers.js +110 -0
  71. package/dist/saas/ingest.d.ts +3 -0
  72. package/dist/saas/ingest.js +249 -0
  73. package/dist/saas/organization.d.ts +5 -0
  74. package/dist/saas/organization.js +82 -0
  75. package/dist/saas/plan-change.d.ts +10 -0
  76. package/dist/saas/plan-change.js +15 -0
  77. package/dist/saas/store.d.ts +21 -0
  78. package/dist/saas/store.js +159 -0
  79. package/dist/saas/types.d.ts +191 -0
  80. package/dist/saas/types.js +2 -0
  81. package/dist/saas.d.ts +8 -82
  82. package/dist/saas.js +7 -320
  83. package/dist/sarif.d.ts +74 -0
  84. package/dist/sarif.js +122 -0
  85. package/dist/trust-advanced.d.ts +14 -0
  86. package/dist/trust-advanced.js +65 -0
  87. package/dist/trust-kpi-fs.d.ts +3 -0
  88. package/dist/trust-kpi-fs.js +141 -0
  89. package/dist/trust-kpi-parse.d.ts +7 -0
  90. package/dist/trust-kpi-parse.js +186 -0
  91. package/dist/trust-kpi-types.d.ts +16 -0
  92. package/dist/trust-kpi-types.js +2 -0
  93. package/dist/trust-kpi.d.ts +7 -0
  94. package/dist/trust-kpi.js +185 -0
  95. package/dist/trust-policy.d.ts +32 -0
  96. package/dist/trust-policy.js +160 -0
  97. package/dist/trust-render.d.ts +9 -0
  98. package/dist/trust-render.js +54 -0
  99. package/dist/trust-scoring.d.ts +9 -0
  100. package/dist/trust-scoring.js +208 -0
  101. package/dist/trust.d.ts +37 -0
  102. package/dist/trust.js +168 -0
  103. package/dist/types/app.d.ts +30 -0
  104. package/dist/types/app.js +2 -0
  105. package/dist/types/config.d.ts +25 -0
  106. package/dist/types/config.js +2 -0
  107. package/dist/types/core.d.ts +100 -0
  108. package/dist/types/core.js +2 -0
  109. package/dist/types/diff.d.ts +55 -0
  110. package/dist/types/diff.js +2 -0
  111. package/dist/types/plugin.d.ts +41 -0
  112. package/dist/types/plugin.js +2 -0
  113. package/dist/types/trust.d.ts +120 -0
  114. package/dist/types/trust.js +2 -0
  115. package/dist/types.d.ts +8 -211
  116. package/docs/PRD.md +187 -109
  117. package/docs/plugin-contract.md +61 -0
  118. package/docs/release-notes-draft.md +40 -0
  119. package/docs/rules-catalog.md +49 -0
  120. package/docs/trust-core-release-checklist.md +87 -0
  121. package/package.json +6 -3
  122. package/packages/vscode-drift/src/code-actions.ts +1 -1
  123. package/schemas/drift-ai-output.v1.json +162 -0
  124. package/schemas/drift-report.v1.json +151 -0
  125. package/schemas/drift-trust.v1.json +131 -0
  126. package/scripts/smoke-repo.mjs +394 -0
  127. package/src/analyzer.ts +484 -155
  128. package/src/benchmark.ts +266 -0
  129. package/src/cli.ts +840 -85
  130. package/src/config.ts +19 -2
  131. package/src/diff.ts +84 -10
  132. package/src/doctor.ts +173 -0
  133. package/src/format.ts +81 -0
  134. package/src/git.ts +16 -0
  135. package/src/guard-types.ts +64 -0
  136. package/src/guard.ts +324 -0
  137. package/src/index.ts +83 -0
  138. package/src/init.ts +298 -0
  139. package/src/map-cycles.ts +38 -0
  140. package/src/map-svg.ts +124 -0
  141. package/src/map.ts +111 -142
  142. package/src/metrics.ts +78 -59
  143. package/src/output-metadata.ts +30 -0
  144. package/src/plugins-capabilities.ts +36 -0
  145. package/src/plugins-messages.ts +35 -0
  146. package/src/plugins-rules.ts +296 -0
  147. package/src/plugins.ts +148 -27
  148. package/src/printer.ts +4 -0
  149. package/src/reporter-constants.ts +46 -0
  150. package/src/reporter.ts +64 -65
  151. package/src/review.ts +6 -4
  152. package/src/rules/comments.ts +2 -2
  153. package/src/rules/complexity.ts +2 -7
  154. package/src/rules/nesting.ts +3 -13
  155. package/src/rules/phase0-basic.ts +11 -12
  156. package/src/rules/phase3-configurable.ts +39 -26
  157. package/src/rules/shared.ts +31 -3
  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 -433
  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 +210 -0
  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 +260 -0
  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 +78 -238
  185. package/tests/cli-sarif.test.ts +92 -0
  186. package/tests/diff.test.ts +124 -0
  187. package/tests/format.test.ts +157 -0
  188. package/tests/new-features.test.ts +80 -1
  189. package/tests/phase1-init-doctor-guard.test.ts +199 -0
  190. package/tests/plugins.test.ts +219 -0
  191. package/tests/rules.test.ts +23 -1
  192. package/tests/saas-foundation.test.ts +358 -1
  193. package/tests/sarif.test.ts +160 -0
  194. package/tests/trust-kpi.test.ts +147 -0
  195. package/tests/trust.test.ts +602 -0
@@ -0,0 +1,266 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs'
2
+ import * as path from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+ import { analyzeProject } from './analyzer.js'
5
+ import { loadConfig } from './config.js'
6
+ import { buildReport } from './reporter.js'
7
+ import { generateReview } from './review.js'
8
+ import { buildTrustReport } from './trust.js'
9
+ import { cleanupTempDir, extractFilesAtRef } from './git.js'
10
+ import { computeDiff } from './diff.js'
11
+
12
+ interface BenchmarkOptions {
13
+ scanPath: string
14
+ reviewPath: string
15
+ trustPath: string
16
+ baseRef: string
17
+ warmupRuns: number
18
+ measuredRuns: number
19
+ jsonOut?: string
20
+ }
21
+
22
+ interface TaskResult {
23
+ name: 'scan' | 'review' | 'trust'
24
+ warmupRuns: number
25
+ measuredRuns: number
26
+ samplesMs: number[]
27
+ medianMs: number
28
+ meanMs: number
29
+ minMs: number
30
+ maxMs: number
31
+ }
32
+
33
+ interface BenchmarkOutput {
34
+ meta: {
35
+ scannedAt: string
36
+ node: string
37
+ platform: NodeJS.Platform
38
+ cwd: string
39
+ }
40
+ options: BenchmarkOptions
41
+ results: TaskResult[]
42
+ }
43
+
44
+ const DEFAULT_SCAN_PATH = '.'
45
+ const DEFAULT_REVIEW_PATH = '.'
46
+ const DEFAULT_TRUST_PATH = '.'
47
+ const DEFAULT_BASE_REF = 'HEAD~1'
48
+ const DEFAULT_WARMUP_RUNS = 1
49
+ const DEFAULT_MEASURED_RUNS = 5
50
+
51
+ const TABLE_WIDTHS = {
52
+ task: 10,
53
+ warmup: 8,
54
+ runs: 6,
55
+ median: 13,
56
+ mean: 11,
57
+ min: 10,
58
+ max: 10,
59
+ } as const
60
+
61
+ const TABLE_COLUMNS = [
62
+ { key: 'task', header: 'task' },
63
+ { key: 'warmup', header: 'warmup' },
64
+ { key: 'runs', header: 'runs' },
65
+ { key: 'median', header: 'median(ms)' },
66
+ { key: 'mean', header: 'mean(ms)' },
67
+ { key: 'min', header: 'min(ms)' },
68
+ { key: 'max', header: 'max(ms)' },
69
+ ] as const
70
+
71
+ function parseNumberFlag(value: string, flagName: string): number {
72
+ const parsed = Number(value)
73
+ if (!Number.isFinite(parsed) || parsed < 0) {
74
+ throw new Error(`${flagName} must be a non-negative number, received '${value}'`)
75
+ }
76
+ return Math.floor(parsed)
77
+ }
78
+
79
+ function parseOptions(argv: string[]): BenchmarkOptions {
80
+ const options: BenchmarkOptions = {
81
+ scanPath: DEFAULT_SCAN_PATH,
82
+ reviewPath: DEFAULT_REVIEW_PATH,
83
+ trustPath: DEFAULT_TRUST_PATH,
84
+ baseRef: DEFAULT_BASE_REF,
85
+ warmupRuns: DEFAULT_WARMUP_RUNS,
86
+ measuredRuns: DEFAULT_MEASURED_RUNS,
87
+ }
88
+
89
+ const handlers: Record<string, (value: string) => void> = {
90
+ '--scan-path': (value) => { options.scanPath = value },
91
+ '--review-path': (value) => { options.reviewPath = value },
92
+ '--trust-path': (value) => { options.trustPath = value },
93
+ '--base': (value) => { options.baseRef = value },
94
+ '--warmup': (value) => { options.warmupRuns = parseNumberFlag(value, '--warmup') },
95
+ '--runs': (value) => { options.measuredRuns = parseNumberFlag(value, '--runs') },
96
+ '--json-out': (value) => { options.jsonOut = value },
97
+ }
98
+
99
+ for (let i = 0; i < argv.length; i += 2) {
100
+ const arg = argv[i]
101
+ const next = argv[i + 1]
102
+ const handler = handlers[arg]
103
+
104
+ if (!handler || !next) throw new Error(`Unknown or incomplete argument: ${arg}`)
105
+ handler(next)
106
+ }
107
+
108
+ if (options.measuredRuns < 1) {
109
+ throw new Error('--runs must be at least 1')
110
+ }
111
+
112
+ return {
113
+ ...options,
114
+ scanPath: path.resolve(options.scanPath),
115
+ reviewPath: path.resolve(options.reviewPath),
116
+ trustPath: path.resolve(options.trustPath),
117
+ jsonOut: options.jsonOut ? path.resolve(options.jsonOut) : undefined,
118
+ }
119
+ }
120
+
121
+ function median(values: number[]): number {
122
+ const sorted = [...values].sort((a, b) => a - b)
123
+ const middle = Math.floor(sorted.length / 2)
124
+ if (sorted.length % 2 === 1) return sorted[middle]
125
+ return (sorted[middle - 1] + sorted[middle]) / 2
126
+ }
127
+
128
+ function formatMs(ms: number): string {
129
+ return ms.toFixed(2)
130
+ }
131
+
132
+ async function runTask(
133
+ name: TaskResult['name'],
134
+ warmupRuns: number,
135
+ measuredRuns: number,
136
+ task: () => Promise<void>,
137
+ ): Promise<TaskResult> {
138
+ for (let i = 0; i < warmupRuns; i += 1) {
139
+ await task()
140
+ }
141
+
142
+ const samplesMs: number[] = []
143
+ for (let i = 0; i < measuredRuns; i += 1) {
144
+ const started = performance.now()
145
+ await task()
146
+ samplesMs.push(performance.now() - started)
147
+ }
148
+
149
+ const total = samplesMs.reduce((sum, sample) => sum + sample, 0)
150
+ return {
151
+ name,
152
+ warmupRuns,
153
+ measuredRuns,
154
+ samplesMs,
155
+ medianMs: median(samplesMs),
156
+ meanMs: total / samplesMs.length,
157
+ minMs: Math.min(...samplesMs),
158
+ maxMs: Math.max(...samplesMs),
159
+ }
160
+ }
161
+
162
+ function printTable(results: TaskResult[]): void {
163
+ const headers = TABLE_COLUMNS.map((column) => column.header)
164
+ const widths = TABLE_COLUMNS.map((column) => TABLE_WIDTHS[column.key])
165
+
166
+ const row = (values: string[]): string => values
167
+ .map((value, index) => value.padEnd(widths[index], ' '))
168
+ .join(' ')
169
+
170
+ process.stdout.write('\n')
171
+ process.stdout.write(row(headers) + '\n')
172
+ process.stdout.write(row(widths.map((width) => '-'.repeat(width))) + '\n')
173
+
174
+ for (const result of results) {
175
+ process.stdout.write(row([
176
+ result.name,
177
+ String(result.warmupRuns),
178
+ String(result.measuredRuns),
179
+ formatMs(result.medianMs),
180
+ formatMs(result.meanMs),
181
+ formatMs(result.minMs),
182
+ formatMs(result.maxMs),
183
+ ]) + '\n')
184
+ }
185
+ }
186
+
187
+ async function runScan(scanPath: string): Promise<void> {
188
+ const config = await loadConfig(scanPath)
189
+ const files = analyzeProject(scanPath, config)
190
+ buildReport(scanPath, files)
191
+ }
192
+
193
+ async function runTrust(trustPath: string, baseRef: string): Promise<void> {
194
+ const config = await loadConfig(trustPath)
195
+ const files = analyzeProject(trustPath, config)
196
+ const report = buildReport(trustPath, files)
197
+
198
+ let tempDir: string | undefined
199
+ let diff
200
+
201
+ try {
202
+ tempDir = extractFilesAtRef(trustPath, baseRef)
203
+ const baseFiles = analyzeProject(tempDir, config)
204
+ const baseReport = buildReport(tempDir, baseFiles)
205
+ diff = computeDiff(baseReport, report, baseRef)
206
+ } catch {
207
+ diff = undefined
208
+ } finally {
209
+ if (tempDir) cleanupTempDir(tempDir)
210
+ }
211
+
212
+ buildTrustReport(report, { diff })
213
+ }
214
+
215
+ async function runReview(reviewPath: string, baseRef: string): Promise<void> {
216
+ await generateReview(reviewPath, baseRef)
217
+ }
218
+
219
+ async function main(argv: string[]): Promise<void> {
220
+ const options = parseOptions(argv)
221
+
222
+ const results = [
223
+ await runTask('scan', options.warmupRuns, options.measuredRuns, () => runScan(options.scanPath)),
224
+ await runTask('review', options.warmupRuns, options.measuredRuns, () => runReview(options.reviewPath, options.baseRef)),
225
+ await runTask('trust', options.warmupRuns, options.measuredRuns, () => runTrust(options.trustPath, options.baseRef)),
226
+ ]
227
+
228
+ const output: BenchmarkOutput = {
229
+ meta: {
230
+ scannedAt: new Date().toISOString(),
231
+ node: process.version,
232
+ platform: process.platform,
233
+ cwd: process.cwd(),
234
+ },
235
+ options,
236
+ results,
237
+ }
238
+
239
+ printTable(results)
240
+ process.stdout.write(`\n${JSON.stringify(output, null, 2)}\n`)
241
+
242
+ if (options.jsonOut) {
243
+ mkdirSync(path.dirname(options.jsonOut), { recursive: true })
244
+ writeFileSync(options.jsonOut, JSON.stringify(output, null, 2) + '\n', 'utf8')
245
+ process.stdout.write(`\nSaved benchmark JSON to ${options.jsonOut}\n`)
246
+ }
247
+ }
248
+
249
+ function isExecutedAsEntryPoint(): boolean {
250
+ const entryArg = process.argv[1]
251
+ if (!entryArg) return false
252
+ return import.meta.url === pathToFileURL(path.resolve(entryArg)).href
253
+ }
254
+
255
+ export async function runBenchmarkCli(argv = process.argv.slice(2)): Promise<void> {
256
+ try {
257
+ await main(argv)
258
+ } catch (error) {
259
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
260
+ process.exit(1)
261
+ }
262
+ }
263
+
264
+ if (isExecutedAsEntryPoint()) {
265
+ void runBenchmarkCli()
266
+ }