@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
package/dist/saas.js CHANGED
@@ -1,321 +1,8 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
3
- const STORE_VERSION = 1;
4
- const ACTIVE_WINDOW_DAYS = 30;
5
- export const DEFAULT_SAAS_POLICY = {
6
- freeUserThreshold: 7500,
7
- maxRunsPerWorkspacePerMonth: 500,
8
- maxReposPerWorkspace: 20,
9
- retentionDays: 90,
10
- };
11
- export function resolveSaasPolicy(policy) {
12
- return {
13
- ...DEFAULT_SAAS_POLICY,
14
- ...(policy ?? {}),
15
- };
16
- }
17
- export function defaultSaasStorePath(root = '.') {
18
- return resolve(root, '.drift-cloud', 'store.json');
19
- }
20
- function ensureStoreFile(storeFile, policy) {
21
- const dir = dirname(storeFile);
22
- if (!existsSync(dir))
23
- mkdirSync(dir, { recursive: true });
24
- if (!existsSync(storeFile)) {
25
- const initial = createEmptyStore(policy);
26
- writeFileSync(storeFile, JSON.stringify(initial, null, 2), 'utf8');
27
- }
28
- }
29
- function createEmptyStore(policy) {
30
- return {
31
- version: STORE_VERSION,
32
- policy: resolveSaasPolicy(policy),
33
- users: {},
34
- workspaces: {},
35
- repos: {},
36
- snapshots: [],
37
- };
38
- }
39
- function monthKey(isoDate) {
40
- const date = new Date(isoDate);
41
- const month = String(date.getUTCMonth() + 1).padStart(2, '0');
42
- return `${date.getUTCFullYear()}-${month}`;
43
- }
44
- function daysAgo(days) {
45
- const now = Date.now();
46
- return now - days * 24 * 60 * 60 * 1000;
47
- }
48
- function applyRetention(store) {
49
- const cutoff = daysAgo(store.policy.retentionDays);
50
- store.snapshots = store.snapshots.filter((snapshot) => {
51
- return new Date(snapshot.createdAt).getTime() >= cutoff;
52
- });
53
- }
54
- function saveStore(storeFile, store) {
55
- writeFileSync(storeFile, JSON.stringify(store, null, 2), 'utf8');
56
- }
57
- function loadStoreInternal(storeFile, policy) {
58
- ensureStoreFile(storeFile, policy);
59
- const raw = readFileSync(storeFile, 'utf8');
60
- const parsed = JSON.parse(raw);
61
- const merged = createEmptyStore(parsed.policy);
62
- merged.version = parsed.version ?? STORE_VERSION;
63
- merged.users = parsed.users ?? {};
64
- merged.workspaces = parsed.workspaces ?? {};
65
- merged.repos = parsed.repos ?? {};
66
- merged.snapshots = parsed.snapshots ?? [];
67
- merged.policy = resolveSaasPolicy({ ...merged.policy, ...policy });
68
- applyRetention(merged);
69
- return merged;
70
- }
71
- function isWorkspaceActive(workspace) {
72
- return new Date(workspace.lastSeenAt).getTime() >= daysAgo(ACTIVE_WINDOW_DAYS);
73
- }
74
- function isRepoActive(repo) {
75
- return new Date(repo.lastSeenAt).getTime() >= daysAgo(ACTIVE_WINDOW_DAYS);
76
- }
77
- function assertGuardrails(store, options, nowIso) {
78
- const usersRegistered = Object.keys(store.users).length;
79
- const isFreePhase = usersRegistered < store.policy.freeUserThreshold;
80
- if (!isFreePhase)
81
- return;
82
- if (!store.users[options.userId] && usersRegistered + 1 > store.policy.freeUserThreshold) {
83
- throw new Error(`Free threshold reached (${store.policy.freeUserThreshold} users).`);
84
- }
85
- const workspace = store.workspaces[options.workspaceId];
86
- const repoName = options.repoName ?? 'default';
87
- const repoId = `${options.workspaceId}:${repoName}`;
88
- const repoExists = Boolean(store.repos[repoId]);
89
- const repoCount = workspace?.repoIds.length ?? 0;
90
- if (!repoExists && repoCount >= store.policy.maxReposPerWorkspace) {
91
- throw new Error(`Workspace '${options.workspaceId}' reached max repos (${store.policy.maxReposPerWorkspace}).`);
92
- }
93
- const currentMonth = monthKey(nowIso);
94
- const runsThisMonth = store.snapshots.filter((snapshot) => {
95
- return snapshot.workspaceId === options.workspaceId && monthKey(snapshot.createdAt) === currentMonth;
96
- }).length;
97
- if (runsThisMonth >= store.policy.maxRunsPerWorkspacePerMonth) {
98
- throw new Error(`Workspace '${options.workspaceId}' reached max monthly runs (${store.policy.maxRunsPerWorkspacePerMonth}).`);
99
- }
100
- }
101
- export function ingestSnapshotFromReport(report, options) {
102
- const storeFile = resolve(options.storeFile ?? defaultSaasStorePath());
103
- const store = loadStoreInternal(storeFile, options.policy);
104
- const nowIso = new Date().toISOString();
105
- assertGuardrails(store, options, nowIso);
106
- const user = store.users[options.userId];
107
- if (user) {
108
- user.lastSeenAt = nowIso;
109
- }
110
- else {
111
- store.users[options.userId] = {
112
- id: options.userId,
113
- createdAt: nowIso,
114
- lastSeenAt: nowIso,
115
- };
116
- }
117
- const workspace = store.workspaces[options.workspaceId];
118
- if (workspace) {
119
- workspace.lastSeenAt = nowIso;
120
- if (!workspace.userIds.includes(options.userId))
121
- workspace.userIds.push(options.userId);
122
- }
123
- else {
124
- store.workspaces[options.workspaceId] = {
125
- id: options.workspaceId,
126
- createdAt: nowIso,
127
- lastSeenAt: nowIso,
128
- userIds: [options.userId],
129
- repoIds: [],
130
- };
131
- }
132
- const repoName = options.repoName ?? 'default';
133
- const repoId = `${options.workspaceId}:${repoName}`;
134
- const repo = store.repos[repoId];
135
- if (repo) {
136
- repo.lastSeenAt = nowIso;
137
- }
138
- else {
139
- store.repos[repoId] = {
140
- id: repoId,
141
- workspaceId: options.workspaceId,
142
- name: repoName,
143
- createdAt: nowIso,
144
- lastSeenAt: nowIso,
145
- };
146
- const ws = store.workspaces[options.workspaceId];
147
- if (!ws.repoIds.includes(repoId))
148
- ws.repoIds.push(repoId);
149
- }
150
- const snapshot = {
151
- id: `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`,
152
- createdAt: nowIso,
153
- scannedAt: report.scannedAt,
154
- workspaceId: options.workspaceId,
155
- userId: options.userId,
156
- repoId,
157
- repoName,
158
- targetPath: report.targetPath,
159
- totalScore: report.totalScore,
160
- totalIssues: report.totalIssues,
161
- totalFiles: report.totalFiles,
162
- summary: {
163
- errors: report.summary.errors,
164
- warnings: report.summary.warnings,
165
- infos: report.summary.infos,
166
- },
167
- };
168
- store.snapshots.push(snapshot);
169
- applyRetention(store);
170
- saveStore(storeFile, store);
171
- return snapshot;
172
- }
173
- export function getSaasSummary(options) {
174
- const storeFile = resolve(options?.storeFile ?? defaultSaasStorePath());
175
- const store = loadStoreInternal(storeFile, options?.policy);
176
- saveStore(storeFile, store);
177
- const usersRegistered = Object.keys(store.users).length;
178
- const workspacesActive = Object.values(store.workspaces).filter(isWorkspaceActive).length;
179
- const reposActive = Object.values(store.repos).filter(isRepoActive).length;
180
- const runsPerMonth = {};
181
- for (const snapshot of store.snapshots) {
182
- const key = monthKey(snapshot.createdAt);
183
- runsPerMonth[key] = (runsPerMonth[key] ?? 0) + 1;
184
- }
185
- const thresholdReached = usersRegistered >= store.policy.freeUserThreshold;
186
- return {
187
- policy: store.policy,
188
- usersRegistered,
189
- workspacesActive,
190
- reposActive,
191
- runsPerMonth,
192
- totalSnapshots: store.snapshots.length,
193
- phase: thresholdReached ? 'paid' : 'free',
194
- thresholdReached,
195
- freeUsersRemaining: Math.max(0, store.policy.freeUserThreshold - usersRegistered),
196
- };
197
- }
198
- function escapeHtml(value) {
199
- return value
200
- .replaceAll('&', '&amp;')
201
- .replaceAll('<', '&lt;')
202
- .replaceAll('>', '&gt;')
203
- .replaceAll('"', '&quot;')
204
- .replaceAll("'", '&#39;');
205
- }
206
- export function generateSaasDashboardHtml(options) {
207
- const storeFile = resolve(options?.storeFile ?? defaultSaasStorePath());
208
- const store = loadStoreInternal(storeFile, options?.policy);
209
- const summary = getSaasSummary(options);
210
- const workspaceStats = Object.values(store.workspaces)
211
- .map((workspace) => {
212
- const snapshots = store.snapshots.filter((snapshot) => snapshot.workspaceId === workspace.id);
213
- const runs = snapshots.length;
214
- const avgScore = runs === 0
215
- ? 0
216
- : Math.round(snapshots.reduce((sum, snapshot) => sum + snapshot.totalScore, 0) / runs);
217
- const lastRun = snapshots.sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0]?.createdAt ?? 'n/a';
218
- return {
219
- id: workspace.id,
220
- runs,
221
- avgScore,
222
- lastRun,
223
- };
224
- })
225
- .sort((a, b) => b.avgScore - a.avgScore);
226
- const repoStats = Object.values(store.repos)
227
- .map((repo) => {
228
- const snapshots = store.snapshots.filter((snapshot) => snapshot.repoId === repo.id);
229
- const runs = snapshots.length;
230
- const avgScore = runs === 0
231
- ? 0
232
- : Math.round(snapshots.reduce((sum, snapshot) => sum + snapshot.totalScore, 0) / runs);
233
- return {
234
- workspaceId: repo.workspaceId,
235
- name: repo.name,
236
- runs,
237
- avgScore,
238
- };
239
- })
240
- .sort((a, b) => b.avgScore - a.avgScore)
241
- .slice(0, 15);
242
- const runsRows = Object.entries(summary.runsPerMonth)
243
- .sort(([a], [b]) => a.localeCompare(b))
244
- .map(([month, count]) => {
245
- const width = Math.max(8, count * 8);
246
- return `<tr><td>${escapeHtml(month)}</td><td>${count}</td><td><div class="bar" style="width:${width}px"></div></td></tr>`;
247
- })
248
- .join('');
249
- const workspaceRows = workspaceStats
250
- .map((workspace) => `<tr><td>${escapeHtml(workspace.id)}</td><td>${workspace.runs}</td><td>${workspace.avgScore}</td><td>${escapeHtml(workspace.lastRun)}</td></tr>`)
251
- .join('');
252
- const repoRows = repoStats
253
- .map((repo) => `<tr><td>${escapeHtml(repo.workspaceId)}</td><td>${escapeHtml(repo.name)}</td><td>${repo.runs}</td><td>${repo.avgScore}</td></tr>`)
254
- .join('');
255
- return `<!doctype html>
256
- <html lang="en">
257
- <head>
258
- <meta charset="utf-8" />
259
- <meta name="viewport" content="width=device-width, initial-scale=1" />
260
- <title>drift cloud dashboard</title>
261
- <style>
262
- :root { color-scheme: light; }
263
- body { margin: 0; font-family: "Segoe UI", Arial, sans-serif; background: #f4f7fb; color: #0f172a; }
264
- main { max-width: 980px; margin: 0 auto; padding: 24px; }
265
- h1 { margin: 0 0 6px; }
266
- p.meta { margin: 0 0 20px; color: #475569; }
267
- .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 18px; }
268
- .card { background: #ffffff; border-radius: 10px; padding: 14px; border: 1px solid #dbe3ef; }
269
- .card .label { font-size: 12px; color: #64748b; text-transform: uppercase; letter-spacing: 0.08em; }
270
- .card .value { font-size: 26px; font-weight: 700; margin-top: 4px; }
271
- table { width: 100%; border-collapse: collapse; margin-top: 10px; background: #ffffff; border: 1px solid #dbe3ef; border-radius: 10px; overflow: hidden; }
272
- th, td { padding: 10px; border-bottom: 1px solid #e2e8f0; text-align: left; font-size: 14px; }
273
- th { background: #eef2f9; }
274
- .section { margin-top: 18px; }
275
- .bar { height: 10px; background: linear-gradient(90deg, #0ea5e9, #22c55e); border-radius: 999px; }
276
- .pill { display: inline-block; border-radius: 999px; padding: 4px 10px; font-size: 12px; font-weight: 600; }
277
- .pill.free { background: #dcfce7; color: #166534; }
278
- .pill.paid { background: #fee2e2; color: #991b1b; }
279
- </style>
280
- </head>
281
- <body>
282
- <main>
283
- <h1>drift cloud dashboard</h1>
284
- <p class="meta">Store: ${escapeHtml(storeFile)}</p>
285
- <div class="cards">
286
- <div class="card"><div class="label">Plan Phase</div><div class="value"><span class="pill ${summary.phase}">${summary.phase.toUpperCase()}</span></div></div>
287
- <div class="card"><div class="label">Users</div><div class="value">${summary.usersRegistered}</div></div>
288
- <div class="card"><div class="label">Active Workspaces</div><div class="value">${summary.workspacesActive}</div></div>
289
- <div class="card"><div class="label">Active Repos</div><div class="value">${summary.reposActive}</div></div>
290
- <div class="card"><div class="label">Snapshots</div><div class="value">${summary.totalSnapshots}</div></div>
291
- <div class="card"><div class="label">Free Seats Left</div><div class="value">${summary.freeUsersRemaining}</div></div>
292
- </div>
293
-
294
- <section class="section">
295
- <h2>Runs Per Month</h2>
296
- <table>
297
- <thead><tr><th>Month</th><th>Runs</th><th>Trend</th></tr></thead>
298
- <tbody>${runsRows || '<tr><td colspan="3">No runs yet</td></tr>'}</tbody>
299
- </table>
300
- </section>
301
-
302
- <section class="section">
303
- <h2>Workspace Hotspots</h2>
304
- <table>
305
- <thead><tr><th>Workspace</th><th>Runs</th><th>Avg Score</th><th>Last Run</th></tr></thead>
306
- <tbody>${workspaceRows || '<tr><td colspan="4">No workspace data</td></tr>'}</tbody>
307
- </table>
308
- </section>
309
-
310
- <section class="section">
311
- <h2>Repo Hotspots</h2>
312
- <table>
313
- <thead><tr><th>Workspace</th><th>Repo</th><th>Runs</th><th>Avg Score</th></tr></thead>
314
- <tbody>${repoRows || '<tr><td colspan="4">No repo data</td></tr>'}</tbody>
315
- </table>
316
- </section>
317
- </main>
318
- </body>
319
- </html>`;
320
- }
1
+ export { DEFAULT_SAAS_POLICY } from './saas/constants.js';
2
+ export { SaasActorRequiredError, SaasPermissionError } from './saas/errors.js';
3
+ export { resolveSaasPolicy, } from './saas/helpers.js';
4
+ export { defaultSaasStorePath, getRequiredRoleForOperation, assertSaasPermission, getSaasEffectiveLimits, getOrganizationEffectiveLimits, } from './saas/store.js';
5
+ export { ingestSnapshotFromReport } from './saas/ingest.js';
6
+ export { changeOrganizationPlan, listOrganizationPlanChanges, getOrganizationUsageSnapshot, } from './saas/organization.js';
7
+ export { listSaasSnapshots, getSaasSummary, generateSaasDashboardHtml, } from './saas/dashboard.js';
321
8
  //# sourceMappingURL=saas.js.map
@@ -0,0 +1,74 @@
1
+ import type { DriftIssue, DriftReport } from './types.js';
2
+ import type { DriftDiff } from './types.js';
3
+ export type SarifLevel = 'error' | 'warning' | 'note';
4
+ export interface DriftSarifRule {
5
+ id: string;
6
+ name?: string;
7
+ shortDescription?: {
8
+ text: string;
9
+ };
10
+ defaultConfiguration?: {
11
+ level: SarifLevel;
12
+ };
13
+ properties?: {
14
+ weight?: number;
15
+ };
16
+ }
17
+ export interface DriftSarifResult {
18
+ ruleId: string;
19
+ level: SarifLevel;
20
+ message: {
21
+ text: string;
22
+ };
23
+ locations: Array<{
24
+ physicalLocation: {
25
+ artifactLocation: {
26
+ uri: string;
27
+ };
28
+ region: {
29
+ startLine: number;
30
+ startColumn: number;
31
+ };
32
+ };
33
+ }>;
34
+ properties?: {
35
+ weight?: number;
36
+ fileScore?: number;
37
+ driftSeverity: DriftIssue['severity'];
38
+ baseRef?: string;
39
+ scoreBefore?: number;
40
+ scoreAfter?: number;
41
+ scoreDelta?: number;
42
+ changeType?: 'new-issue';
43
+ };
44
+ }
45
+ export interface DriftSarifRun {
46
+ tool: {
47
+ driver: {
48
+ name: string;
49
+ version: string;
50
+ informationUri: string;
51
+ rules: DriftSarifRule[];
52
+ };
53
+ };
54
+ results: DriftSarifResult[];
55
+ properties: {
56
+ scannedAt: string;
57
+ targetPath: string;
58
+ totalIssues: number;
59
+ totalScore: number;
60
+ totalFiles: number;
61
+ baseRef?: string;
62
+ totalDelta?: number;
63
+ newIssuesCount?: number;
64
+ resolvedIssuesCount?: number;
65
+ };
66
+ }
67
+ export interface DriftSarifLog {
68
+ $schema: string;
69
+ version: '2.1.0';
70
+ runs: DriftSarifRun[];
71
+ }
72
+ export declare function toSarif(report: DriftReport): DriftSarifLog;
73
+ export declare function diffToSarif(diff: DriftDiff): DriftSarifLog;
74
+ //# sourceMappingURL=sarif.d.ts.map
package/dist/sarif.js ADDED
@@ -0,0 +1,122 @@
1
+ import { createRequire } from 'node:module';
2
+ import { RULE_WEIGHTS } from './analyzer.js';
3
+ const require = createRequire(import.meta.url);
4
+ const { version: VERSION } = require('../package.json');
5
+ const HTTPS_SCHEME = 'https:';
6
+ const URL_SEPARATOR = '//';
7
+ const SARIF_SCHEMA_HOST_AND_PATH = 'json.schemastore.org/sarif-2.1.0.json';
8
+ const DRIFT_INFORMATION_HOST_AND_PATH = 'github.com/eduardbar/drift';
9
+ const SARIF_SCHEMA_URL = `${HTTPS_SCHEME}${URL_SEPARATOR}${SARIF_SCHEMA_HOST_AND_PATH}`;
10
+ const DRIFT_INFORMATION_URI = `${HTTPS_SCHEME}${URL_SEPARATOR}${DRIFT_INFORMATION_HOST_AND_PATH}`;
11
+ function mapSeverityToSarifLevel(severity) {
12
+ switch (severity) {
13
+ case 'error':
14
+ return 'error';
15
+ case 'warning':
16
+ return 'warning';
17
+ default:
18
+ return 'note';
19
+ }
20
+ }
21
+ function normalizeArtifactUri(filePath) {
22
+ return filePath.replaceAll('\\', '/');
23
+ }
24
+ function toSarifResult(filePath, fileScore, issue, extraProperties) {
25
+ const line = Math.max(issue.line, 1);
26
+ const column = Math.max(issue.column, 1);
27
+ const weight = RULE_WEIGHTS[issue.rule]?.weight;
28
+ return {
29
+ ruleId: issue.rule,
30
+ level: mapSeverityToSarifLevel(issue.severity),
31
+ message: {
32
+ text: issue.message,
33
+ },
34
+ locations: [{
35
+ physicalLocation: {
36
+ artifactLocation: {
37
+ uri: normalizeArtifactUri(filePath),
38
+ },
39
+ region: {
40
+ startLine: line,
41
+ startColumn: column,
42
+ },
43
+ },
44
+ }],
45
+ properties: {
46
+ weight,
47
+ fileScore,
48
+ driftSeverity: issue.severity,
49
+ ...extraProperties,
50
+ },
51
+ };
52
+ }
53
+ function buildRules(results) {
54
+ const byRule = new Map();
55
+ for (const result of results) {
56
+ if (byRule.has(result.ruleId))
57
+ continue;
58
+ byRule.set(result.ruleId, {
59
+ id: result.ruleId,
60
+ name: result.ruleId,
61
+ shortDescription: {
62
+ text: `drift rule: ${result.ruleId}`,
63
+ },
64
+ defaultConfiguration: {
65
+ level: result.level,
66
+ },
67
+ properties: {
68
+ weight: result.properties?.weight,
69
+ },
70
+ });
71
+ }
72
+ return [...byRule.values()];
73
+ }
74
+ function buildSarifLog(results, metrics) {
75
+ return {
76
+ $schema: SARIF_SCHEMA_URL,
77
+ version: '2.1.0',
78
+ runs: [{
79
+ tool: {
80
+ driver: {
81
+ name: 'drift',
82
+ version: VERSION,
83
+ informationUri: DRIFT_INFORMATION_URI,
84
+ rules: buildRules(results),
85
+ },
86
+ },
87
+ results,
88
+ properties: metrics,
89
+ }],
90
+ };
91
+ }
92
+ export function toSarif(report) {
93
+ const results = report.files.flatMap((file) => file.issues.map((issue) => toSarifResult(file.path, file.score, issue)));
94
+ return buildSarifLog(results, {
95
+ scannedAt: report.scannedAt,
96
+ targetPath: report.targetPath,
97
+ totalIssues: report.totalIssues,
98
+ totalScore: report.totalScore,
99
+ totalFiles: report.totalFiles,
100
+ });
101
+ }
102
+ export function diffToSarif(diff) {
103
+ const results = diff.files.flatMap((file) => file.newIssues.map((issue) => toSarifResult(file.path, file.scoreAfter, issue, {
104
+ baseRef: diff.baseRef,
105
+ scoreBefore: file.scoreBefore,
106
+ scoreAfter: file.scoreAfter,
107
+ scoreDelta: file.scoreDelta,
108
+ changeType: 'new-issue',
109
+ })));
110
+ return buildSarifLog(results, {
111
+ scannedAt: diff.scannedAt,
112
+ targetPath: diff.projectPath,
113
+ totalIssues: diff.newIssuesCount,
114
+ totalScore: diff.totalScoreAfter,
115
+ totalFiles: diff.files.length,
116
+ baseRef: diff.baseRef,
117
+ totalDelta: diff.totalDelta,
118
+ newIssuesCount: diff.newIssuesCount,
119
+ resolvedIssuesCount: diff.resolvedIssuesCount,
120
+ });
121
+ }
122
+ //# sourceMappingURL=sarif.js.map
@@ -0,0 +1,14 @@
1
+ import type { DriftReport, DriftTrustReport, TrustDiffContext, TrustFixPriority } from './types.js';
2
+ import type { SnapshotEntry } from './snapshot.js';
3
+ export declare function buildAdvancedContext(input: {
4
+ report: DriftReport;
5
+ advancedOptions: {
6
+ enabled?: boolean;
7
+ previousTrust?: Partial<DriftTrustReport>;
8
+ snapshots?: SnapshotEntry[];
9
+ } | undefined;
10
+ trustScore: number;
11
+ fixPriorities: TrustFixPriority[];
12
+ diffContext: TrustDiffContext | undefined;
13
+ }): DriftTrustReport['advanced_context'];
14
+ //# sourceMappingURL=trust-advanced.d.ts.map
@@ -0,0 +1,65 @@
1
+ const SYSTEMIC_GUIDANCE_LIMIT = 2;
2
+ const TEAM_GUIDANCE_LIMIT = 3;
3
+ function buildComparisonFromPreviousTrust(trustScore, previousTrust) {
4
+ if (!previousTrust || typeof previousTrust.trust_score !== 'number')
5
+ return undefined;
6
+ const trustDelta = trustScore - previousTrust.trust_score;
7
+ const trend = trustDelta > 0 ? 'improving' : trustDelta < 0 ? 'regressing' : 'stable';
8
+ return {
9
+ source: 'previous-trust-json',
10
+ trend,
11
+ summary: `Trust moved ${trustDelta >= 0 ? '+' : ''}${trustDelta} vs provided previous trust JSON.`,
12
+ trust_delta: trustDelta,
13
+ previous_trust_score: previousTrust.trust_score,
14
+ previous_merge_risk: previousTrust.merge_risk,
15
+ };
16
+ }
17
+ function buildComparisonFromSnapshotHistory(report, snapshots) {
18
+ const lastSnapshot = snapshots && snapshots.length > 0 ? snapshots[snapshots.length - 1] : undefined;
19
+ if (!lastSnapshot)
20
+ return undefined;
21
+ const snapshotScoreDelta = report.totalScore - lastSnapshot.score;
22
+ const trend = snapshotScoreDelta < 0 ? 'improving' : snapshotScoreDelta > 0 ? 'regressing' : 'stable';
23
+ const snapshotContext = lastSnapshot.label
24
+ ? `${lastSnapshot.timestamp} (${lastSnapshot.label})`
25
+ : lastSnapshot.timestamp;
26
+ return {
27
+ source: 'snapshot-history',
28
+ trend,
29
+ summary: `Drift score moved ${snapshotScoreDelta >= 0 ? '+' : ''}${snapshotScoreDelta} vs snapshot ${snapshotContext}.`,
30
+ snapshot_score_delta: snapshotScoreDelta,
31
+ snapshot_label: lastSnapshot.label || undefined,
32
+ snapshot_timestamp: lastSnapshot.timestamp,
33
+ };
34
+ }
35
+ function buildTeamGuidance(priorities, comparison, diffContext) {
36
+ const systemicTargets = priorities
37
+ .filter((priority) => priority.systemic)
38
+ .slice(0, SYSTEMIC_GUIDANCE_LIMIT)
39
+ .map((priority) => `${priority.rule} (x${priority.occurrences})`);
40
+ const guidance = [];
41
+ if (systemicTargets.length > 0) {
42
+ guidance.push(`Start with systemic rules: ${systemicTargets.join(', ')}.`);
43
+ }
44
+ if (comparison?.trend === 'regressing') {
45
+ guidance.push('Trend regressed; freeze net-new debt in CI and assign owners per systemic rule.');
46
+ }
47
+ if (diffContext && diffContext.newIssues > 0) {
48
+ guidance.push(`Block net-new issue growth first (+${diffContext.newIssues} new issue(s) in diff context).`);
49
+ }
50
+ if (guidance.length === 0) {
51
+ guidance.push('Maintain current baseline and schedule periodic systemic debt cleanup by rule ownership.');
52
+ }
53
+ return guidance.slice(0, TEAM_GUIDANCE_LIMIT);
54
+ }
55
+ export function buildAdvancedContext(input) {
56
+ if (input.advancedOptions?.enabled !== true)
57
+ return undefined;
58
+ const comparison = buildComparisonFromPreviousTrust(input.trustScore, input.advancedOptions.previousTrust)
59
+ ?? buildComparisonFromSnapshotHistory(input.report, input.advancedOptions.snapshots);
60
+ return {
61
+ comparison,
62
+ team_guidance: buildTeamGuidance(input.fixPriorities, comparison, input.diffContext),
63
+ };
64
+ }
65
+ //# sourceMappingURL=trust-advanced.js.map
@@ -0,0 +1,3 @@
1
+ import type { DiscoverResult } from './trust-kpi-types.js';
2
+ export declare function discoverTrustJsonFiles(input: string, cwd: string): DiscoverResult;
3
+ //# sourceMappingURL=trust-kpi-fs.d.ts.map