@fragments-sdk/cli 0.15.10 → 0.17.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 (88) hide show
  1. package/dist/bin.js +901 -789
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-6SQPP47U.js → chunk-ANTWP3UG.js} +532 -31
  4. package/dist/chunk-ANTWP3UG.js.map +1 -0
  5. package/dist/{chunk-ONUP6Z4W.js → chunk-B4A4ZEGS.js} +9 -9
  6. package/dist/{chunk-32LIWN2P.js → chunk-FFCI6OVZ.js} +584 -261
  7. package/dist/chunk-FFCI6OVZ.js.map +1 -0
  8. package/dist/{chunk-HQ6A6DTV.js → chunk-HNHE64CR.js} +315 -1089
  9. package/dist/chunk-HNHE64CR.js.map +1 -0
  10. package/dist/{chunk-BJE3425I.js → chunk-MN3B2EE6.js} +2 -2
  11. package/dist/{chunk-QCN35LJU.js → chunk-SAQW37L5.js} +3 -2
  12. package/dist/chunk-SAQW37L5.js.map +1 -0
  13. package/dist/{chunk-2WXKALIG.js → chunk-SNZXGHL2.js} +2 -2
  14. package/dist/{chunk-5JF26E55.js → chunk-VT2J62ND.js} +11 -11
  15. package/dist/{codebase-scanner-MQHUZC2G.js → codebase-scanner-2T5QIDBA.js} +2 -2
  16. package/dist/core/index.js +53 -1
  17. package/dist/{create-EXURTBKK.js → create-D44QD7MV.js} +2 -2
  18. package/dist/{doctor-BDPMYYE6.js → doctor-7B5N4JYU.js} +2 -2
  19. package/dist/{generate-PVOLUAAC.js → generate-T47JZRVU.js} +4 -4
  20. package/dist/govern-scan-X6UEIOSV.js +632 -0
  21. package/dist/govern-scan-X6UEIOSV.js.map +1 -0
  22. package/dist/index.js +7 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/{init-SSGUSP7Z.js → init-2RGAY4W6.js} +5 -5
  25. package/dist/mcp-bin.js +2 -2
  26. package/dist/scan-A2WJM54L.js +14 -0
  27. package/dist/{scan-generate-VY27PIOX.js → scan-generate-LUSOHT36.js} +4 -4
  28. package/dist/{service-QJGWUIVL.js → service-ROCP7TKG.js} +13 -15
  29. package/dist/{snapshot-WIJMEIFT.js → snapshot-B3SAW74Y.js} +2 -2
  30. package/dist/{static-viewer-7QIBQZRC.js → static-viewer-7L6UEYTJ.js} +3 -3
  31. package/dist/{test-64Z5BKBA.js → test-PQDVDURE.js} +3 -3
  32. package/dist/{token-normalizer-TEPOVBPV.js → token-normalizer-7TFCVDZL.js} +2 -2
  33. package/dist/{tokens-NZWFQIAB.js → tokens-64FG5FDP.js} +8 -9
  34. package/dist/{tokens-NZWFQIAB.js.map → tokens-64FG5FDP.js.map} +1 -1
  35. package/dist/{tokens-generate-5JQSJ27E.js → tokens-generate-CL4LBBQA.js} +2 -2
  36. package/package.json +9 -8
  37. package/src/bin.ts +55 -88
  38. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +1 -1
  39. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +1 -1
  40. package/src/commands/__tests__/context-cloud.test.ts +291 -0
  41. package/src/commands/__tests__/govern-scan.test.ts +185 -0
  42. package/src/commands/__tests__/govern.test.ts +1 -0
  43. package/src/commands/context-cloud.ts +355 -0
  44. package/src/commands/govern-scan-report.ts +170 -0
  45. package/src/commands/govern-scan.ts +282 -135
  46. package/src/commands/govern.ts +0 -157
  47. package/src/mcp/__tests__/server.integration.test.ts +9 -20
  48. package/src/service/enhance/codebase-scanner.ts +3 -2
  49. package/src/service/enhance/types.ts +3 -0
  50. package/dist/chunk-32LIWN2P.js.map +0 -1
  51. package/dist/chunk-6SQPP47U.js.map +0 -1
  52. package/dist/chunk-HQ6A6DTV.js.map +0 -1
  53. package/dist/chunk-MHIBEEW4.js +0 -511
  54. package/dist/chunk-MHIBEEW4.js.map +0 -1
  55. package/dist/chunk-QCN35LJU.js.map +0 -1
  56. package/dist/govern-scan-DW4QUAYD.js +0 -414
  57. package/dist/govern-scan-DW4QUAYD.js.map +0 -1
  58. package/dist/init-cloud-3DNKPWFB.js +0 -304
  59. package/dist/init-cloud-3DNKPWFB.js.map +0 -1
  60. package/dist/node-37AUE74M.js +0 -65
  61. package/dist/push-contracts-WY32TFP6.js +0 -84
  62. package/dist/push-contracts-WY32TFP6.js.map +0 -1
  63. package/dist/scan-PKSYSTRR.js +0 -15
  64. package/dist/static-viewer-7QIBQZRC.js.map +0 -1
  65. package/dist/token-parser-32KOIOFN.js +0 -22
  66. package/dist/token-parser-32KOIOFN.js.map +0 -1
  67. package/dist/tokens-push-HY3KO36V.js +0 -148
  68. package/dist/tokens-push-HY3KO36V.js.map +0 -1
  69. package/src/commands/init-cloud.ts +0 -382
  70. package/src/commands/push-contracts.ts +0 -112
  71. package/src/commands/tokens-push.ts +0 -199
  72. /package/dist/{chunk-ONUP6Z4W.js.map → chunk-B4A4ZEGS.js.map} +0 -0
  73. /package/dist/{chunk-BJE3425I.js.map → chunk-MN3B2EE6.js.map} +0 -0
  74. /package/dist/{chunk-2WXKALIG.js.map → chunk-SNZXGHL2.js.map} +0 -0
  75. /package/dist/{chunk-5JF26E55.js.map → chunk-VT2J62ND.js.map} +0 -0
  76. /package/dist/{codebase-scanner-MQHUZC2G.js.map → codebase-scanner-2T5QIDBA.js.map} +0 -0
  77. /package/dist/{create-EXURTBKK.js.map → create-D44QD7MV.js.map} +0 -0
  78. /package/dist/{doctor-BDPMYYE6.js.map → doctor-7B5N4JYU.js.map} +0 -0
  79. /package/dist/{generate-PVOLUAAC.js.map → generate-T47JZRVU.js.map} +0 -0
  80. /package/dist/{init-SSGUSP7Z.js.map → init-2RGAY4W6.js.map} +0 -0
  81. /package/dist/{node-37AUE74M.js.map → scan-A2WJM54L.js.map} +0 -0
  82. /package/dist/{scan-generate-VY27PIOX.js.map → scan-generate-LUSOHT36.js.map} +0 -0
  83. /package/dist/{scan-PKSYSTRR.js.map → service-ROCP7TKG.js.map} +0 -0
  84. /package/dist/{snapshot-WIJMEIFT.js.map → snapshot-B3SAW74Y.js.map} +0 -0
  85. /package/dist/{service-QJGWUIVL.js.map → static-viewer-7L6UEYTJ.js.map} +0 -0
  86. /package/dist/{test-64Z5BKBA.js.map → test-PQDVDURE.js.map} +0 -0
  87. /package/dist/{token-normalizer-TEPOVBPV.js.map → token-normalizer-7TFCVDZL.js.map} +0 -0
  88. /package/dist/{tokens-generate-5JQSJ27E.js.map → tokens-generate-CL4LBBQA.js.map} +0 -0
@@ -0,0 +1,632 @@
1
+ import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
+ import "./chunk-D2CDBRNU.js";
3
+ import {
4
+ BRAND
5
+ } from "./chunk-FFCI6OVZ.js";
6
+
7
+ // src/commands/govern-scan.ts
8
+ import pc from "picocolors";
9
+ import { resolve as resolve2, relative as relative2 } from "path";
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { execSync } from "child_process";
12
+
13
+ // src/commands/govern-scan-report.ts
14
+ import { resolve, dirname, relative } from "path";
15
+ var SEVERITY_RANK = {
16
+ critical: 4,
17
+ serious: 3,
18
+ moderate: 2,
19
+ minor: 1
20
+ };
21
+ function mergeSeverity(a, b) {
22
+ return SEVERITY_RANK[a] >= SEVERITY_RANK[b] ? a : b;
23
+ }
24
+ function aggregateVerdicts(verdicts, computeScore, runner = "cli") {
25
+ if (verdicts.length === 0) {
26
+ return {
27
+ passed: true,
28
+ score: 100,
29
+ results: [],
30
+ metadata: {
31
+ runner,
32
+ duration: 0,
33
+ nodeCount: 0,
34
+ componentTypes: []
35
+ }
36
+ };
37
+ }
38
+ const byValidator = /* @__PURE__ */ new Map();
39
+ let duration = 0;
40
+ let nodeCount = 0;
41
+ const componentTypes = /* @__PURE__ */ new Set();
42
+ for (const verdict of verdicts) {
43
+ duration += verdict.metadata.duration;
44
+ nodeCount += verdict.metadata.nodeCount;
45
+ for (const type of verdict.metadata.componentTypes) {
46
+ componentTypes.add(type);
47
+ }
48
+ for (const result of verdict.results) {
49
+ const existing = byValidator.get(result.validator);
50
+ if (!existing) {
51
+ byValidator.set(result.validator, {
52
+ validator: result.validator,
53
+ severity: result.severity,
54
+ passed: result.passed,
55
+ violations: [...result.violations],
56
+ suggestions: result.suggestions ? [...result.suggestions] : void 0
57
+ });
58
+ continue;
59
+ }
60
+ existing.passed = existing.passed && result.passed;
61
+ existing.severity = mergeSeverity(existing.severity, result.severity);
62
+ existing.violations.push(...result.violations);
63
+ if (result.suggestions?.length) {
64
+ const merged = existing.suggestions ?? [];
65
+ merged.push(...result.suggestions);
66
+ existing.suggestions = merged;
67
+ }
68
+ }
69
+ }
70
+ const results = [...byValidator.values()].sort(
71
+ (a, b) => a.validator.localeCompare(b.validator)
72
+ );
73
+ const allViolations = results.flatMap((result) => result.violations);
74
+ return {
75
+ passed: verdicts.every((verdict) => verdict.passed),
76
+ score: computeScore(allViolations),
77
+ results,
78
+ metadata: {
79
+ runner,
80
+ duration,
81
+ nodeCount,
82
+ componentTypes: [...componentTypes].sort()
83
+ }
84
+ };
85
+ }
86
+ function flattenComponentUsage(usages, rootDir) {
87
+ const counts = /* @__PURE__ */ new Map();
88
+ for (const usage of usages) {
89
+ const relPath = relative(rootDir, usage.filePath);
90
+ const key = `${relPath}\0${usage.componentName}`;
91
+ counts.set(key, (counts.get(key) ?? 0) + 1);
92
+ }
93
+ return [...counts.entries()].map(([key, occurrences]) => {
94
+ const separatorIndex = key.indexOf("\0");
95
+ const file = key.slice(0, separatorIndex);
96
+ const component = key.slice(separatorIndex + 1);
97
+ return { component, file, occurrences };
98
+ }).sort(
99
+ (a, b) => a.file === b.file ? a.component.localeCompare(b.component) : a.file.localeCompare(b.file)
100
+ );
101
+ }
102
+ function buildComplianceSummary(health) {
103
+ const usageTotals = Object.values(health.components).reduce(
104
+ (acc, component) => {
105
+ acc.passingUsages += component.passed;
106
+ acc.totalUsages += component.total;
107
+ return acc;
108
+ },
109
+ { passingUsages: 0, totalUsages: 0 }
110
+ );
111
+ return {
112
+ complianceRate: health.overallCompliance,
113
+ passingUsages: usageTotals.passingUsages,
114
+ totalUsages: usageTotals.totalUsages,
115
+ contractedCount: health.contractedComponents,
116
+ detectedCount: health.totalComponents
117
+ };
118
+ }
119
+ async function writeGovernScanReport(path, report) {
120
+ const { mkdir, writeFile } = await import("fs/promises");
121
+ const absPath = resolve(path);
122
+ await mkdir(dirname(absPath), { recursive: true });
123
+ await writeFile(absPath, JSON.stringify(report, null, 2), "utf-8");
124
+ }
125
+
126
+ // src/commands/govern-scan.ts
127
+ var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([
128
+ ".tsx",
129
+ ".ts",
130
+ ".jsx",
131
+ ".js"
132
+ ]);
133
+ function getChangedFiles(rootDir, base) {
134
+ try {
135
+ const baseRef = base || detectMergeBase(rootDir);
136
+ if (!baseRef) return null;
137
+ const output = execSync(
138
+ `git diff --name-only --diff-filter=ACMR ${baseRef}...HEAD`,
139
+ { cwd: rootDir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
140
+ );
141
+ return output.split("\n").map((f) => f.trim()).filter((f) => f && SCANNABLE_EXTENSIONS.has(f.slice(f.lastIndexOf(".")))).map((f) => resolve2(rootDir, f));
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+ function detectMergeBase(rootDir) {
147
+ try {
148
+ const remote = execSync("git rev-parse --abbrev-ref origin/HEAD", {
149
+ cwd: rootDir,
150
+ encoding: "utf-8",
151
+ stdio: ["pipe", "pipe", "pipe"]
152
+ }).trim();
153
+ if (remote) return remote;
154
+ } catch {
155
+ }
156
+ for (const candidate of ["origin/main", "origin/master"]) {
157
+ try {
158
+ execSync(`git rev-parse --verify ${candidate}`, {
159
+ cwd: rootDir,
160
+ encoding: "utf-8",
161
+ stdio: ["pipe", "pipe", "pipe"]
162
+ });
163
+ return candidate;
164
+ } catch {
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+ var SCAN_DEFAULT_RULES = {
170
+ "safety/block-event-handlers": true,
171
+ "safety/block-dangerous-props": true,
172
+ "safety/block-controlled-props": true,
173
+ "safety/block-function-props": true,
174
+ "safety/sanitize-hrefs": true,
175
+ "tokens/require-design-tokens": true
176
+ };
177
+ function detectRootDir(cwd) {
178
+ const candidates = ["src", "app", "pages", "components"];
179
+ for (const dir of candidates) {
180
+ if (existsSync(resolve2(cwd, dir))) {
181
+ return cwd;
182
+ }
183
+ }
184
+ return cwd;
185
+ }
186
+ function groupByFile(usages) {
187
+ const grouped = /* @__PURE__ */ new Map();
188
+ for (const usage of usages) {
189
+ const existing = grouped.get(usage.filePath);
190
+ if (existing) {
191
+ existing.push(usage);
192
+ } else {
193
+ grouped.set(usage.filePath, [usage]);
194
+ }
195
+ }
196
+ return grouped;
197
+ }
198
+ async function governScan(options = {}) {
199
+ const {
200
+ loadPolicy,
201
+ createEngine,
202
+ buildAdaptersFromConfig,
203
+ formatVerdict,
204
+ computeComponentHealth,
205
+ computeScore
206
+ } = await import("@fragments-sdk/govern");
207
+ const { scanCodebase } = await import("./codebase-scanner-2T5QIDBA.js");
208
+ const { usagesToSpec } = await import("./converter-7XM3Y6NJ.js");
209
+ const format = options.format ?? "summary";
210
+ const quiet = options.quiet ?? false;
211
+ if (!quiet) {
212
+ console.log(pc.cyan(`
213
+ ${BRAND.name} Governance Scan
214
+ `));
215
+ }
216
+ const rootDir = resolve2(options.dir ?? detectRootDir(process.cwd()));
217
+ if (!quiet) {
218
+ console.log(pc.dim(` Root: ${rootDir}
219
+ `));
220
+ }
221
+ let policy = await loadPolicy(options.config);
222
+ const hasRules = Object.keys(policy.rules).length > 0;
223
+ if (!hasRules) {
224
+ policy = { ...policy, rules: SCAN_DEFAULT_RULES };
225
+ if (!quiet) {
226
+ console.log(pc.dim(" No config found \u2014 using scan defaults (safety + tokens)\n"));
227
+ }
228
+ }
229
+ let registryMap;
230
+ let hasRegistry = false;
231
+ {
232
+ const fragmentsJsonPath = resolve2(rootDir, "fragments.json");
233
+ if (existsSync(fragmentsJsonPath)) {
234
+ try {
235
+ const raw = readFileSync(fragmentsJsonPath, "utf-8");
236
+ const parsed = JSON.parse(raw);
237
+ if (parsed.fragments && Array.isArray(parsed.fragments)) {
238
+ const map = {};
239
+ for (const fragment of parsed.fragments) {
240
+ if (fragment.meta?.name) {
241
+ map[fragment.meta.name] = fragment;
242
+ }
243
+ }
244
+ registryMap = map;
245
+ hasRegistry = true;
246
+ if (!quiet) {
247
+ console.log(
248
+ pc.dim(` Contract registry loaded (${parsed.fragments.length} components)
249
+ `)
250
+ );
251
+ }
252
+ }
253
+ } catch {
254
+ }
255
+ }
256
+ }
257
+ const adapters = buildAdaptersFromConfig(policy.audit);
258
+ const engine = createEngine(
259
+ policy,
260
+ adapters,
261
+ registryMap ? { registry: { fragments: registryMap } } : void 0
262
+ );
263
+ let diffFiles;
264
+ if (options.diff) {
265
+ const base = typeof options.diff === "string" ? options.diff : void 0;
266
+ const changed = getChangedFiles(rootDir, base);
267
+ if (changed && changed.length > 0) {
268
+ diffFiles = changed;
269
+ if (!quiet) {
270
+ console.log(pc.dim(` Diff mode: scanning ${changed.length} changed file(s)...
271
+ `));
272
+ }
273
+ } else if (changed && changed.length === 0) {
274
+ if (!quiet) {
275
+ console.log(pc.green(" No scannable files changed \u2014 all clear.\n"));
276
+ }
277
+ return { exitCode: 0 };
278
+ } else {
279
+ if (!quiet) {
280
+ console.log(pc.yellow(" Could not detect git diff \u2014 falling back to full scan.\n"));
281
+ }
282
+ }
283
+ }
284
+ if (!quiet && !diffFiles) {
285
+ console.log(pc.dim(" Scanning files...\n"));
286
+ }
287
+ const analysis = await scanCodebase({
288
+ rootDir,
289
+ useCache: true,
290
+ files: diffFiles,
291
+ onProgress: quiet ? void 0 : (progress) => {
292
+ if (progress.phase === "scanning") {
293
+ process.stdout.write(
294
+ `\r ${pc.dim(`[${progress.current}/${progress.total}]`)} ${pc.dim(relative2(rootDir, progress.currentFile))}`
295
+ );
296
+ }
297
+ }
298
+ });
299
+ if (!quiet) {
300
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
301
+ console.log(
302
+ pc.dim(` Scanned ${analysis.totalFiles} files, found ${analysis.totalComponents} component types
303
+ `)
304
+ );
305
+ }
306
+ const allUsages = [];
307
+ for (const comp of Object.values(analysis.components)) {
308
+ allUsages.push(...comp.usages);
309
+ }
310
+ if (allUsages.length === 0) {
311
+ if (!quiet) {
312
+ console.log(pc.yellow(" No component usages found.\n"));
313
+ }
314
+ if (options.report) {
315
+ const report = {
316
+ verdict: aggregateVerdicts([], computeScore, "ci"),
317
+ componentUsage: []
318
+ };
319
+ await writeGovernScanReport(options.report, report);
320
+ if (!quiet) {
321
+ console.log(pc.dim(` Wrote governance report: ${resolve2(options.report)}
322
+ `));
323
+ }
324
+ }
325
+ return { exitCode: 0 };
326
+ }
327
+ const grouped = groupByFile(allUsages);
328
+ let totalFiles = 0;
329
+ let passedFiles = 0;
330
+ let totalViolations = 0;
331
+ const violationCounts = /* @__PURE__ */ new Map();
332
+ const allVerdicts = [];
333
+ for (const [filePath, usages] of grouped) {
334
+ const spec = usagesToSpec(usages, filePath, rootDir);
335
+ const relPath = relative2(rootDir, filePath);
336
+ const verdict = await engine.check(spec, {
337
+ runner: "cli",
338
+ input: relPath
339
+ });
340
+ allVerdicts.push(verdict);
341
+ totalFiles++;
342
+ if (verdict.passed) {
343
+ passedFiles++;
344
+ } else {
345
+ if (!quiet) {
346
+ console.log(pc.red(` \u2717 ${relPath}`));
347
+ if (format === "summary") {
348
+ for (const result of verdict.results) {
349
+ for (const v of result.violations) {
350
+ const count = violationCounts.get(v.rule) ?? 0;
351
+ violationCounts.set(v.rule, count + 1);
352
+ totalViolations++;
353
+ console.log(
354
+ pc.dim(` ${v.severity} `) + pc.yellow(v.rule) + pc.dim(` \u2014 ${v.message}`)
355
+ );
356
+ if (v.nodeId) {
357
+ console.log(pc.dim(` at ${v.nodeId}`));
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+ }
364
+ if (verdict.passed && !quiet && format === "summary") {
365
+ console.log(pc.green(` \u2713 ${relPath}`) + pc.dim(` (${usages.length} components, score: ${verdict.score}/100)`));
366
+ }
367
+ if (format === "json" || format === "sarif") {
368
+ const output = formatVerdict(verdict, format);
369
+ console.log(output);
370
+ }
371
+ }
372
+ const health = computeComponentHealth(allVerdicts, registryMap ?? {});
373
+ if (!quiet && format === "summary") {
374
+ console.log(pc.dim("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
375
+ console.log(` Files checked: ${totalFiles}`);
376
+ console.log(` Passed: ${passedFiles}/${totalFiles}`);
377
+ console.log(` Violations: ${totalViolations}`);
378
+ if (violationCounts.size > 0) {
379
+ console.log(pc.dim("\n Top violations:"));
380
+ const sorted = [...violationCounts.entries()].sort((a, b) => b[1] - a[1]);
381
+ for (const [rule, count] of sorted.slice(0, 5)) {
382
+ console.log(pc.dim(` ${count}\xD7 `) + pc.yellow(rule));
383
+ }
384
+ }
385
+ console.log(pc.dim("\n Component Health:"));
386
+ console.log(` Contract coverage: ${health.contractCoverage}% (${health.contractedComponents}/${health.totalComponents})`);
387
+ console.log(` Compliance rate: ${health.overallCompliance}%`);
388
+ if (health.uncontracted.length > 0) {
389
+ console.log(pc.dim(` Uncontracted: ${health.uncontracted.slice(0, 5).join(", ")}${health.uncontracted.length > 5 ? ` (+${health.uncontracted.length - 5} more)` : ""}`));
390
+ }
391
+ console.log();
392
+ if (passedFiles === totalFiles) {
393
+ console.log(pc.green(` \u2713 All files passed governance checks
394
+ `));
395
+ } else {
396
+ console.log(
397
+ pc.red(` \u2717 ${totalFiles - passedFiles} file(s) failed governance checks
398
+ `)
399
+ );
400
+ }
401
+ }
402
+ if (options.report) {
403
+ const report = {
404
+ verdict: aggregateVerdicts(allVerdicts, computeScore, "ci"),
405
+ componentUsage: flattenComponentUsage(allUsages, rootDir)
406
+ };
407
+ if (hasRegistry) {
408
+ report.complianceSummary = buildComplianceSummary(health);
409
+ }
410
+ await writeGovernScanReport(options.report, report);
411
+ if (!quiet) {
412
+ console.log(pc.dim(` Wrote governance report: ${resolve2(options.report)}
413
+ `));
414
+ }
415
+ }
416
+ if (options.apiKey) {
417
+ await reportToCloud({
418
+ apiKey: options.apiKey,
419
+ cloudUrl: options.cloudUrl,
420
+ verdicts: allVerdicts,
421
+ rootDir,
422
+ quiet,
423
+ diffOnly: !!diffFiles
424
+ });
425
+ }
426
+ return { exitCode: passedFiles === totalFiles ? 0 : 1 };
427
+ }
428
+ var DEFAULT_CLOUD_URL = "https://app.usefragments.com";
429
+ function detectGitMetadata() {
430
+ const env = process.env;
431
+ const meta = {};
432
+ if (env.GITHUB_SHA) meta.commitSha = env.GITHUB_SHA;
433
+ if (env.GITHUB_REPOSITORY) meta.repoFullName = env.GITHUB_REPOSITORY;
434
+ if (env.GITHUB_REF_NAME) meta.branch = env.GITHUB_REF_NAME;
435
+ if (env.GITHUB_EVENT_NAME === "pull_request" && env.GITHUB_EVENT_PATH) {
436
+ try {
437
+ const event = JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, "utf-8"));
438
+ if (event?.pull_request?.number) {
439
+ meta.pr = event.pull_request.number;
440
+ }
441
+ if (event?.pull_request?.head?.sha) {
442
+ meta.commitSha = event.pull_request.head?.sha;
443
+ }
444
+ } catch {
445
+ }
446
+ }
447
+ return meta;
448
+ }
449
+ async function reportToCloud(options) {
450
+ const { apiKey, verdicts, rootDir, quiet, diffOnly } = options;
451
+ const baseUrl = (options.cloudUrl ?? DEFAULT_CLOUD_URL).replace(/\/+$/, "");
452
+ const findings = [];
453
+ const { createHash } = await import("crypto");
454
+ for (const verdict of verdicts) {
455
+ for (const result of verdict.results) {
456
+ for (const v of result.violations) {
457
+ const severity = v.severity === "critical" || v.severity === "serious" ? "error" : v.severity === "moderate" ? "warning" : "info";
458
+ const fingerprint = createHash("sha256").update(`${v.rule}:${v.nodeType}:${v.nodeId}:${v.message}`).digest("hex").slice(0, 16);
459
+ let filePath;
460
+ let line;
461
+ let column;
462
+ if (v.filePath) {
463
+ filePath = v.filePath;
464
+ line = v.line;
465
+ column = v.column;
466
+ } else if (v.nodeId) {
467
+ const parts = v.nodeId.split(":");
468
+ if (parts.length >= 3) {
469
+ const col = parseInt(parts.pop(), 10);
470
+ const ln = parseInt(parts.pop(), 10);
471
+ const path = parts.join(":");
472
+ if (!isNaN(ln) && !isNaN(col) && path) {
473
+ filePath = path;
474
+ line = ln;
475
+ column = col;
476
+ }
477
+ }
478
+ if (!filePath) {
479
+ filePath = relative2(rootDir, v.nodeId);
480
+ }
481
+ }
482
+ findings.push({
483
+ ruleId: v.rule,
484
+ severity,
485
+ filePath,
486
+ line,
487
+ column,
488
+ rawValue: v.rawValue,
489
+ message: `[${result.validator}] ${v.message}`,
490
+ category: result.validator,
491
+ fingerprint,
492
+ suggestedToken: v.suggestion
493
+ });
494
+ }
495
+ }
496
+ }
497
+ if (!quiet) {
498
+ console.log(pc.dim(` Reporting ${findings.length} finding(s) to Fragments Cloud...`));
499
+ }
500
+ const gitMeta = detectGitMetadata();
501
+ try {
502
+ const response = await fetch(`${baseUrl}/api/govern/ingest`, {
503
+ method: "POST",
504
+ headers: {
505
+ "Content-Type": "application/json",
506
+ "Authorization": `Bearer ${apiKey}`
507
+ },
508
+ body: JSON.stringify({
509
+ findings,
510
+ source: "ci",
511
+ diffOnly: diffOnly ?? false,
512
+ ...gitMeta
513
+ })
514
+ });
515
+ if (!response.ok) {
516
+ const body2 = await response.json().catch(() => ({}));
517
+ const msg = body2.error ?? `HTTP ${response.status}`;
518
+ console.error(pc.red(` \u2717 Cloud report failed: ${msg}
519
+ `));
520
+ return;
521
+ }
522
+ const body = await response.json();
523
+ if (!quiet) {
524
+ console.log(
525
+ pc.green(` \u2713 Reported ${body.ingested ?? findings.length} finding(s) to Cloud`) + (body.orgSlug ? pc.dim(` (${body.orgSlug})`) : "") + "\n"
526
+ );
527
+ }
528
+ } catch (err) {
529
+ console.error(
530
+ pc.red(` \u2717 Cloud report failed: `) + pc.dim(err instanceof Error ? err.message : "Network error") + "\n"
531
+ );
532
+ }
533
+ }
534
+ async function governWatch(options = {}) {
535
+ const {
536
+ loadPolicy,
537
+ createEngine,
538
+ buildAdaptersFromConfig,
539
+ formatVerdict
540
+ } = await import("@fragments-sdk/govern");
541
+ const { scanFile } = await import("./scanner-4KZNOXAK.js");
542
+ const { usagesToSpec } = await import("./converter-7XM3Y6NJ.js");
543
+ const quiet = options.quiet ?? false;
544
+ const debounceMs = options.debounce ?? 300;
545
+ const format = options.format ?? "summary";
546
+ console.log(pc.cyan(`
547
+ ${BRAND.name} Governance Watch
548
+ `));
549
+ const { exitCode } = await governScan(options);
550
+ if (!quiet) {
551
+ console.log(
552
+ pc.dim(` Initial scan ${exitCode === 0 ? "passed" : "completed with violations"}
553
+ `)
554
+ );
555
+ }
556
+ const rootDir = resolve2(options.dir ?? detectRootDir(process.cwd()));
557
+ let policy = await loadPolicy(options.config);
558
+ if (Object.keys(policy.rules).length === 0) {
559
+ policy = { ...policy, rules: SCAN_DEFAULT_RULES };
560
+ }
561
+ const adapters = buildAdaptersFromConfig(policy.audit);
562
+ const engine = createEngine(policy, adapters);
563
+ console.log(pc.dim(" Watching for changes... (Ctrl+C to stop)\n"));
564
+ const chokidar = await import("chokidar");
565
+ const watcher = chokidar.watch(
566
+ ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
567
+ {
568
+ cwd: rootDir,
569
+ ignoreInitial: true,
570
+ ignored: [
571
+ "**/node_modules/**",
572
+ "**/dist/**",
573
+ "**/build/**",
574
+ "**/.next/**",
575
+ "**/*.test.*",
576
+ "**/*.spec.*",
577
+ "**/*.stories.*"
578
+ ],
579
+ awaitWriteFinish: { stabilityThreshold: debounceMs }
580
+ }
581
+ );
582
+ const handleChange = async (changedRelPath) => {
583
+ const absolutePath = resolve2(rootDir, changedRelPath);
584
+ try {
585
+ const { usages } = await scanFile(absolutePath);
586
+ if (usages.length === 0) {
587
+ if (!quiet) {
588
+ console.log(pc.dim(` \u25CB ${changedRelPath} \u2014 no component usages`));
589
+ }
590
+ return;
591
+ }
592
+ const spec = usagesToSpec(usages, absolutePath, rootDir);
593
+ const verdict = await engine.check(spec, {
594
+ runner: "cli",
595
+ input: changedRelPath
596
+ });
597
+ if (verdict.passed) {
598
+ console.log(
599
+ pc.green(` \u2713 ${changedRelPath}`) + pc.dim(` (${usages.length} components, score: ${verdict.score}/100)`)
600
+ );
601
+ } else {
602
+ console.log(pc.red(` \u2717 ${changedRelPath}`));
603
+ if (format === "summary") {
604
+ for (const result of verdict.results) {
605
+ for (const v of result.violations) {
606
+ console.log(
607
+ pc.dim(` ${v.severity} `) + pc.yellow(v.rule) + pc.dim(` \u2014 ${v.message}`)
608
+ );
609
+ }
610
+ }
611
+ } else {
612
+ console.log(formatVerdict(verdict, format));
613
+ }
614
+ }
615
+ } catch (error) {
616
+ if (!quiet) {
617
+ console.log(
618
+ pc.dim(` \u26A0 ${changedRelPath} \u2014 `) + pc.yellow(error instanceof Error ? error.message : "parse error")
619
+ );
620
+ }
621
+ }
622
+ };
623
+ watcher.on("change", handleChange);
624
+ watcher.on("add", handleChange);
625
+ await new Promise(() => {
626
+ });
627
+ }
628
+ export {
629
+ governScan,
630
+ governWatch
631
+ };
632
+ //# sourceMappingURL=govern-scan-X6UEIOSV.js.map