@fragments-sdk/cli 0.15.10 → 0.16.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 (78) hide show
  1. package/dist/bin.js +896 -787
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-6SQPP47U.js → chunk-77AAP6R6.js} +532 -31
  4. package/dist/chunk-77AAP6R6.js.map +1 -0
  5. package/dist/{chunk-5JF26E55.js → chunk-ACFVKMVZ.js} +11 -11
  6. package/dist/{chunk-BJE3425I.js → chunk-ACX7YWZW.js} +2 -2
  7. package/dist/{chunk-ONUP6Z4W.js → chunk-G6UVWMFU.js} +8 -8
  8. package/dist/{chunk-2WXKALIG.js → chunk-OZZ4SVZX.js} +2 -2
  9. package/dist/{chunk-32LIWN2P.js → chunk-SJFSG7QF.js} +582 -261
  10. package/dist/chunk-SJFSG7QF.js.map +1 -0
  11. package/dist/{chunk-HQ6A6DTV.js → chunk-XRADMHMV.js} +315 -1089
  12. package/dist/chunk-XRADMHMV.js.map +1 -0
  13. package/dist/core/index.js +53 -1
  14. package/dist/{create-EXURTBKK.js → create-3ZFYQB3T.js} +2 -2
  15. package/dist/{doctor-BDPMYYE6.js → doctor-4IDUM7HI.js} +2 -2
  16. package/dist/{generate-PVOLUAAC.js → generate-VNUUWVWQ.js} +4 -4
  17. package/dist/{govern-scan-DW4QUAYD.js → govern-scan-HTACKYPF.js} +158 -120
  18. package/dist/govern-scan-HTACKYPF.js.map +1 -0
  19. package/dist/index.js +6 -7
  20. package/dist/index.js.map +1 -1
  21. package/dist/{init-SSGUSP7Z.js → init-PXFRAQ64.js} +5 -5
  22. package/dist/mcp-bin.js +2 -2
  23. package/dist/{scan-PKSYSTRR.js → scan-L4GWGEZX.js} +5 -6
  24. package/dist/{scan-generate-VY27PIOX.js → scan-generate-74EYSAGH.js} +4 -4
  25. package/dist/{service-QJGWUIVL.js → service-VELQHEWV.js} +12 -14
  26. package/dist/{snapshot-WIJMEIFT.js → snapshot-DT4B6DPR.js} +2 -2
  27. package/dist/{static-viewer-7QIBQZRC.js → static-viewer-E4OJWFDJ.js} +3 -3
  28. package/dist/{test-64Z5BKBA.js → test-QJY2QO4X.js} +3 -3
  29. package/dist/{token-normalizer-TEPOVBPV.js → token-normalizer-56H4242J.js} +2 -2
  30. package/dist/{tokens-NZWFQIAB.js → tokens-K6URXFPK.js} +7 -8
  31. package/dist/{tokens-NZWFQIAB.js.map → tokens-K6URXFPK.js.map} +1 -1
  32. package/dist/{tokens-generate-5JQSJ27E.js → tokens-generate-EL6IN536.js} +2 -2
  33. package/package.json +7 -6
  34. package/src/bin.ts +49 -88
  35. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +1 -1
  36. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +1 -1
  37. package/src/commands/__tests__/context-cloud.test.ts +291 -0
  38. package/src/commands/__tests__/govern-scan.test.ts +185 -0
  39. package/src/commands/__tests__/govern.test.ts +1 -0
  40. package/src/commands/context-cloud.ts +355 -0
  41. package/src/commands/govern-scan-report.ts +170 -0
  42. package/src/commands/govern-scan.ts +42 -147
  43. package/src/commands/govern.ts +0 -157
  44. package/dist/chunk-32LIWN2P.js.map +0 -1
  45. package/dist/chunk-6SQPP47U.js.map +0 -1
  46. package/dist/chunk-HQ6A6DTV.js.map +0 -1
  47. package/dist/chunk-MHIBEEW4.js +0 -511
  48. package/dist/chunk-MHIBEEW4.js.map +0 -1
  49. package/dist/govern-scan-DW4QUAYD.js.map +0 -1
  50. package/dist/init-cloud-3DNKPWFB.js +0 -304
  51. package/dist/init-cloud-3DNKPWFB.js.map +0 -1
  52. package/dist/node-37AUE74M.js +0 -65
  53. package/dist/push-contracts-WY32TFP6.js +0 -84
  54. package/dist/push-contracts-WY32TFP6.js.map +0 -1
  55. package/dist/static-viewer-7QIBQZRC.js.map +0 -1
  56. package/dist/token-parser-32KOIOFN.js +0 -22
  57. package/dist/token-parser-32KOIOFN.js.map +0 -1
  58. package/dist/tokens-push-HY3KO36V.js +0 -148
  59. package/dist/tokens-push-HY3KO36V.js.map +0 -1
  60. package/src/commands/init-cloud.ts +0 -382
  61. package/src/commands/push-contracts.ts +0 -112
  62. package/src/commands/tokens-push.ts +0 -199
  63. /package/dist/{chunk-5JF26E55.js.map → chunk-ACFVKMVZ.js.map} +0 -0
  64. /package/dist/{chunk-BJE3425I.js.map → chunk-ACX7YWZW.js.map} +0 -0
  65. /package/dist/{chunk-ONUP6Z4W.js.map → chunk-G6UVWMFU.js.map} +0 -0
  66. /package/dist/{chunk-2WXKALIG.js.map → chunk-OZZ4SVZX.js.map} +0 -0
  67. /package/dist/{create-EXURTBKK.js.map → create-3ZFYQB3T.js.map} +0 -0
  68. /package/dist/{doctor-BDPMYYE6.js.map → doctor-4IDUM7HI.js.map} +0 -0
  69. /package/dist/{generate-PVOLUAAC.js.map → generate-VNUUWVWQ.js.map} +0 -0
  70. /package/dist/{init-SSGUSP7Z.js.map → init-PXFRAQ64.js.map} +0 -0
  71. /package/dist/{node-37AUE74M.js.map → scan-L4GWGEZX.js.map} +0 -0
  72. /package/dist/{scan-generate-VY27PIOX.js.map → scan-generate-74EYSAGH.js.map} +0 -0
  73. /package/dist/{scan-PKSYSTRR.js.map → service-VELQHEWV.js.map} +0 -0
  74. /package/dist/{snapshot-WIJMEIFT.js.map → snapshot-DT4B6DPR.js.map} +0 -0
  75. /package/dist/{service-QJGWUIVL.js.map → static-viewer-E4OJWFDJ.js.map} +0 -0
  76. /package/dist/{test-64Z5BKBA.js.map → test-QJY2QO4X.js.map} +0 -0
  77. /package/dist/{token-normalizer-TEPOVBPV.js.map → token-normalizer-56H4242J.js.map} +0 -0
  78. /package/dist/{tokens-generate-5JQSJ27E.js.map → tokens-generate-EL6IN536.js.map} +0 -0
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Parses real JSX/TSX files via the existing codebase scanner, converts
5
5
  * component usages to UISpec, and runs governance checks per file.
6
- * Optionally submits results to Fragments Cloud.
7
6
  */
8
7
 
9
8
  import pc from 'picocolors';
@@ -11,6 +10,13 @@ import { resolve, relative } from 'node:path';
11
10
  import { existsSync } from 'node:fs';
12
11
  import { BRAND } from '../core/index.js';
13
12
  import type { ComponentUsage } from '../service/enhance/types.js';
13
+ import {
14
+ aggregateVerdicts,
15
+ flattenComponentUsage,
16
+ buildComplianceSummary,
17
+ writeGovernScanReport,
18
+ type GovernScanReport,
19
+ } from './govern-scan-report.js';
14
20
 
15
21
  // ---------------------------------------------------------------------------
16
22
  // Options
@@ -23,6 +29,8 @@ export interface GovernScanOptions {
23
29
  config?: string;
24
30
  /** Output format */
25
31
  format?: 'summary' | 'json' | 'sarif';
32
+ /** Write an aggregated machine-readable JSON report */
33
+ report?: string;
26
34
  /** Suppress non-error output */
27
35
  quiet?: boolean;
28
36
  }
@@ -78,6 +86,7 @@ function groupByFile(usages: ComponentUsage[]): Map<string, ComponentUsage[]> {
78
86
  return grouped;
79
87
  }
80
88
 
89
+
81
90
  // ---------------------------------------------------------------------------
82
91
  // governScan
83
92
  // ---------------------------------------------------------------------------
@@ -89,9 +98,9 @@ export async function governScan(
89
98
  loadPolicy,
90
99
  createEngine,
91
100
  buildAdaptersFromConfig,
92
- createCloudAdapter,
93
101
  formatVerdict,
94
102
  computeComponentHealth,
103
+ computeScore,
95
104
  } = await import('@fragments-sdk/govern');
96
105
 
97
106
  const { scanCodebase } = await import(
@@ -125,15 +134,9 @@ export async function governScan(
125
134
  }
126
135
  }
127
136
 
128
- // 3. Extract code tokens for cloud ingest
129
- let codeTokens: string | undefined;
130
- if (process.env.FRAGMENTS_API_KEY) {
131
- codeTokens = await extractCodeTokens(rootDir, options.config, quiet);
132
- }
133
-
134
- // 3b. Load contract registry for governance + cloud ingest (if fragments.json exists)
135
- let contractRegistry: string | undefined;
137
+ // 3. Load contract registry for contract-aware scoring (if fragments.json exists)
136
138
  let registryMap: Record<string, unknown> | undefined;
139
+ let hasRegistry = false;
137
140
  {
138
141
  const { readFileSync, existsSync } = await import('node:fs');
139
142
  const fragmentsJsonPath = resolve(rootDir, 'fragments.json');
@@ -142,37 +145,28 @@ export async function governScan(
142
145
  const raw = readFileSync(fragmentsJsonPath, 'utf-8');
143
146
  const parsed = JSON.parse(raw);
144
147
  if (parsed.fragments && Array.isArray(parsed.fragments)) {
145
- // Build name-keyed map for engine registry injection
146
148
  const map: Record<string, unknown> = {};
147
- for (const f of parsed.fragments) {
148
- if (f.meta?.name) {
149
- map[f.meta.name] = f;
149
+ for (const fragment of parsed.fragments) {
150
+ if (fragment.meta?.name) {
151
+ map[fragment.meta.name] = fragment;
150
152
  }
151
153
  }
152
154
  registryMap = map;
153
-
154
- if (process.env.FRAGMENTS_API_KEY) {
155
- contractRegistry = JSON.stringify({ fragments: parsed.fragments });
156
- }
155
+ hasRegistry = true;
157
156
  if (!quiet) {
158
- console.log(pc.dim(` Contract registry loaded (${parsed.fragments.length} components)\n`));
157
+ console.log(
158
+ pc.dim(` Contract registry loaded (${parsed.fragments.length} components)\n`),
159
+ );
159
160
  }
160
161
  }
161
162
  } catch {
162
- // Invalid fragments.json — skip
163
+ // Invalid fragments.json — skip registry-aware summary
163
164
  }
164
165
  }
165
166
  }
166
167
 
167
- // 4. Build adapters. Auto-add cloud if FRAGMENTS_API_KEY is set
168
+ // 4. Build adapters from policy config
168
169
  const adapters = buildAdaptersFromConfig(policy.audit);
169
- const hasCloudAdapter = adapters.length > 0 && policy.audit?.cloud;
170
- if (!hasCloudAdapter && process.env.FRAGMENTS_API_KEY) {
171
- adapters.push(createCloudAdapter({ codeTokens, contractRegistry }));
172
- if (!quiet) {
173
- console.log(pc.dim(' Cloud audit enabled (FRAGMENTS_API_KEY detected)\n'));
174
- }
175
- }
176
170
 
177
171
  // 5. Create engine (with registry for contract-aware validators)
178
172
  const engine = createEngine(
@@ -220,6 +214,16 @@ export async function governScan(
220
214
  if (!quiet) {
221
215
  console.log(pc.yellow(' No component usages found.\n'));
222
216
  }
217
+ if (options.report) {
218
+ const report: GovernScanReport = {
219
+ verdict: aggregateVerdicts([], computeScore, 'ci'),
220
+ componentUsage: [],
221
+ };
222
+ await writeGovernScanReport(options.report, report);
223
+ if (!quiet) {
224
+ console.log(pc.dim(` Wrote governance report: ${resolve(options.report)}\n`));
225
+ }
226
+ }
223
227
  return { exitCode: 0 };
224
228
  }
225
229
 
@@ -231,16 +235,6 @@ export async function governScan(
231
235
  const violationCounts = new Map<string, number>();
232
236
  const allVerdicts: Awaited<ReturnType<typeof engine.check>>[] = [];
233
237
 
234
- // Build per-file usage snapshot for cloud
235
- const usageSnapshot: Array<{
236
- file: string;
237
- components: Array<{
238
- name: string;
239
- line: number;
240
- props: { static: Record<string, unknown>; dynamic: string[] };
241
- }>;
242
- }> = [];
243
-
244
238
  for (const [filePath, usages] of grouped) {
245
239
  const spec = usagesToSpec(usages, filePath, rootDir);
246
240
  const relPath = relative(rootDir, filePath);
@@ -251,19 +245,6 @@ export async function governScan(
251
245
  });
252
246
  allVerdicts.push(verdict);
253
247
 
254
- // Collect per-file usage snapshot
255
- usageSnapshot.push({
256
- file: relPath,
257
- components: usages.map((u) => ({
258
- name: u.componentName,
259
- line: u.line,
260
- props: {
261
- static: u.props.static,
262
- dynamic: u.props.dynamic,
263
- },
264
- })),
265
- });
266
-
267
248
  totalFiles++;
268
249
 
269
250
  if (verdict.passed) {
@@ -340,103 +321,21 @@ export async function governScan(
340
321
  }
341
322
  }
342
323
 
343
- // 10. Push component usage snapshot + health to Cloud (if API key set)
344
- if (process.env.FRAGMENTS_API_KEY && usageSnapshot.length > 0) {
345
- try {
346
- const apiKey = process.env.FRAGMENTS_API_KEY;
347
- const url = process.env.FRAGMENTS_URL ?? 'https://app.usefragments.com';
348
- await fetch(`${url}/api/ingest`, {
349
- method: 'POST',
350
- headers: {
351
- 'Content-Type': 'application/json',
352
- Authorization: `Bearer ${apiKey}`,
353
- },
354
- body: JSON.stringify({
355
- componentUsage: JSON.stringify(usageSnapshot),
356
- componentHealth: JSON.stringify(health),
357
- }),
358
- });
359
- } catch {
360
- // Non-critical — don't fail the scan
324
+ if (options.report) {
325
+ const report: GovernScanReport = {
326
+ verdict: aggregateVerdicts(allVerdicts, computeScore, 'ci'),
327
+ componentUsage: flattenComponentUsage(allUsages, rootDir),
328
+ };
329
+ if (hasRegistry) {
330
+ report.complianceSummary = buildComplianceSummary(health);
361
331
  }
362
- }
363
-
364
- return { exitCode: passedFiles === totalFiles ? 0 : 1 };
365
- }
366
-
367
- // ---------------------------------------------------------------------------
368
- // Token extraction for cloud ingest
369
- // ---------------------------------------------------------------------------
370
-
371
- /**
372
- * Auto-detect and extract code tokens to send alongside governance verdicts.
373
- * Tries: 1) tokens config from fragments.config.ts, 2) Tailwind config.
374
- * Returns a flat JSON string of token name → value, or undefined if nothing found.
375
- */
376
- async function extractCodeTokens(
377
- rootDir: string,
378
- configPath?: string,
379
- quiet?: boolean,
380
- ): Promise<string | undefined> {
381
- try {
382
- // 1. Try fragments.config.ts tokens config
383
- try {
384
- const { loadConfig } = await import('../core/node.js');
385
- const { parseTokenFiles } = await import('../service/index.js');
386
-
387
- const { config, configDir } = await loadConfig(configPath);
388
- if (config.tokens?.include?.length) {
389
- const result = await parseTokenFiles(config.tokens, configDir);
390
- if (result.tokens.length > 0) {
391
- const flat: Record<string, string> = {};
392
- for (const token of result.tokens) {
393
- flat[token.name] = token.resolvedValue;
394
- }
395
- if (!quiet) {
396
- console.log(
397
- pc.dim(` Extracted ${result.tokens.length} code tokens from config\n`),
398
- );
399
- }
400
- return JSON.stringify(flat);
401
- }
402
- }
403
- } catch {
404
- // No config or no tokens section — fall through
405
- }
406
-
407
- // 2. Try Tailwind config
408
- const {
409
- findTailwindConfig,
410
- loadTailwindConfig,
411
- } = await import('../service/token-normalizer.js');
412
-
413
- const tailwindPath = findTailwindConfig(rootDir);
414
- if (tailwindPath) {
415
- const tokens = await loadTailwindConfig(tailwindPath);
416
- if (tokens.length > 0) {
417
- const flat: Record<string, string> = {};
418
- for (const token of tokens) {
419
- flat[token.name] = token.value;
420
- }
421
- if (!quiet) {
422
- console.log(
423
- pc.dim(` Extracted ${tokens.length} tokens from Tailwind config\n`),
424
- );
425
- }
426
- return JSON.stringify(flat);
427
- }
428
- }
429
- } catch (error) {
332
+ await writeGovernScanReport(options.report, report);
430
333
  if (!quiet) {
431
- console.log(
432
- pc.dim(
433
- ` Token extraction skipped: ${error instanceof Error ? error.message : 'unknown error'}\n`,
434
- ),
435
- );
334
+ console.log(pc.dim(` Wrote governance report: ${resolve(options.report)}\n`));
436
335
  }
437
336
  }
438
337
 
439
- return undefined;
338
+ return { exitCode: passedFiles === totalFiles ? 0 : 1 };
440
339
  }
441
340
 
442
341
  // ---------------------------------------------------------------------------
@@ -450,7 +349,6 @@ export async function governWatch(
450
349
  loadPolicy,
451
350
  createEngine,
452
351
  buildAdaptersFromConfig,
453
- createCloudAdapter,
454
352
  formatVerdict,
455
353
  } = await import('@fragments-sdk/govern');
456
354
 
@@ -480,9 +378,6 @@ export async function governWatch(
480
378
  policy = { ...policy, rules: SCAN_DEFAULT_RULES };
481
379
  }
482
380
  const adapters = buildAdaptersFromConfig(policy.audit);
483
- if (!adapters.some(() => policy.audit?.cloud) && process.env.FRAGMENTS_API_KEY) {
484
- adapters.push(createCloudAdapter());
485
- }
486
381
  const engine = createEngine(policy, adapters);
487
382
 
488
383
  // 3. Watch for changes
@@ -135,163 +135,6 @@ export async function governInit(options: GovernInitOptions = {}): Promise<void>
135
135
  );
136
136
  }
137
137
 
138
- // ---------------------------------------------------------------------------
139
- // connect
140
- // ---------------------------------------------------------------------------
141
-
142
- export async function governConnect(): Promise<void> {
143
- const { readFile, writeFile, appendFile } = await import('node:fs/promises');
144
- const { existsSync } = await import('node:fs');
145
- const { resolve } = await import('node:path');
146
- const { platform } = await import('node:os');
147
- const { exec } = await import('node:child_process');
148
- const { password, confirm } = await import('@inquirer/prompts');
149
-
150
- const cloudUrl = process.env.FRAGMENTS_URL ?? 'https://app.usefragments.com';
151
-
152
- console.log(pc.cyan(`\n ${BRAND.name} — Connect to Cloud\n`));
153
- console.log(
154
- pc.dim(' This will connect your project to the Fragments dashboard\n') +
155
- pc.dim(' for centralized audit tracking and team visibility.\n'),
156
- );
157
-
158
- // ── Step 1: Get API key ──────────────────────────────────────────────────
159
- console.log(pc.bold(' Step 1 of 3: Get your API key\n'));
160
-
161
- const dashboardUrl = `${cloudUrl}/api-keys`;
162
- console.log(pc.dim(` → Opening the dashboard in your browser...`));
163
- console.log(pc.dim(` Copy your API key from Settings → API Keys\n`));
164
-
165
- // Open browser (best-effort)
166
- const os = platform();
167
- const openCmd = os === 'darwin'
168
- ? `open "${dashboardUrl}"`
169
- : os === 'win32'
170
- ? `start "" "${dashboardUrl}"`
171
- : `xdg-open "${dashboardUrl}"`;
172
- exec(openCmd);
173
-
174
- let apiKey: string;
175
- let orgName: string;
176
-
177
- // eslint-disable-next-line no-constant-condition
178
- while (true) {
179
- apiKey = await password({
180
- message: 'Paste your API key:',
181
- mask: '*',
182
- });
183
-
184
- if (!apiKey.trim()) {
185
- console.log(pc.yellow('\n API key cannot be empty. Please try again.\n'));
186
- continue;
187
- }
188
-
189
- // Verify key against cloud
190
- console.log(pc.dim('\n Verifying...'));
191
- try {
192
- const response = await fetch(`${cloudUrl}/api/verify`, {
193
- headers: { Authorization: `Bearer ${apiKey.trim()}` },
194
- });
195
-
196
- if (!response.ok) {
197
- console.log(pc.red(`\n ✗ Invalid API key (HTTP ${response.status}). Please try again.\n`));
198
- continue;
199
- }
200
-
201
- const data = (await response.json()) as { valid: boolean; orgName?: string };
202
- if (!data.valid) {
203
- console.log(pc.red('\n ✗ API key not recognized. Please try again.\n'));
204
- continue;
205
- }
206
-
207
- orgName = data.orgName ?? 'your organization';
208
- console.log(pc.green(`\n ✓ Connected to "${orgName}" (verified)\n`));
209
- break;
210
- } catch (error) {
211
- console.log(
212
- pc.red('\n ✗ Could not reach the dashboard.'),
213
- );
214
- console.log(
215
- pc.dim(` ${error instanceof Error ? error.message : 'Network error'}\n`),
216
- );
217
- continue;
218
- }
219
- }
220
-
221
- // ── Step 2: Save configuration ──────────────────────────────────────────
222
- console.log(pc.bold(' Step 2 of 3: Save configuration\n'));
223
-
224
- const saveToEnv = await confirm({
225
- message: 'Save API key to .env file?',
226
- default: true,
227
- });
228
-
229
- if (saveToEnv) {
230
- const envPath = resolve('.env');
231
- const envEntry = `FRAGMENTS_API_KEY=${apiKey.trim()}`;
232
-
233
- if (existsSync(envPath)) {
234
- const envContent = await readFile(envPath, 'utf-8');
235
- if (envContent.includes('FRAGMENTS_API_KEY=')) {
236
- // Replace existing entry
237
- const updated = envContent.replace(
238
- /^FRAGMENTS_API_KEY=.*$/m,
239
- envEntry,
240
- );
241
- await writeFile(envPath, updated, 'utf-8');
242
- console.log(pc.green(' ✓ Updated FRAGMENTS_API_KEY in .env'));
243
- } else {
244
- await appendFile(envPath, `\n${envEntry}\n`, 'utf-8');
245
- console.log(pc.green(' ✓ Added FRAGMENTS_API_KEY to .env'));
246
- }
247
- } else {
248
- await writeFile(envPath, `${envEntry}\n`, 'utf-8');
249
- console.log(pc.green(' ✓ Created .env with FRAGMENTS_API_KEY'));
250
- }
251
-
252
- // Write FRAGMENTS_URL only if non-default
253
- if (cloudUrl !== 'https://app.usefragments.com') {
254
- const envContent = await readFile(envPath, 'utf-8');
255
- if (!envContent.includes('FRAGMENTS_URL=')) {
256
- await appendFile(envPath, `FRAGMENTS_URL=${cloudUrl}\n`, 'utf-8');
257
- console.log(pc.green(` ✓ Added FRAGMENTS_URL to .env`));
258
- }
259
- }
260
-
261
- // Ensure .env is in .gitignore
262
- const gitignorePath = resolve('.gitignore');
263
- if (existsSync(gitignorePath)) {
264
- const gitignore = await readFile(gitignorePath, 'utf-8');
265
- if (!gitignore.split('\n').some((line) => line.trim() === '.env')) {
266
- await appendFile(gitignorePath, '\n.env\n', 'utf-8');
267
- console.log(pc.green(' ✓ Added .env to .gitignore'));
268
- }
269
- } else {
270
- await writeFile(gitignorePath, '.env\n', 'utf-8');
271
- console.log(pc.green(' ✓ Created .gitignore with .env entry'));
272
- }
273
- }
274
-
275
- // ── Step 3: Config check ────────────────────────────────────────────────
276
- console.log(pc.bold('\n Step 3 of 3: Config check\n'));
277
-
278
- const { findGovernConfig } = await import('@fragments-sdk/govern');
279
- const configPath = findGovernConfig();
280
-
281
- if (configPath) {
282
- console.log(pc.green(` ✓ Found govern config: ${configPath}`));
283
- } else {
284
- console.log(
285
- pc.yellow(' No govern config found — run `fragments govern init` to create one'),
286
- );
287
- }
288
-
289
- // ── Done ────────────────────────────────────────────────────────────────
290
- console.log(pc.dim('\n ─────────────────────────────────────\n'));
291
- console.log(pc.green(' ✓ All set!') + ' Run `fragments govern check` to send your first audit.\n');
292
- console.log(pc.dim(` Dashboard: ${cloudUrl}/overview\n`));
293
- }
294
-
295
138
  // ---------------------------------------------------------------------------
296
139
  // report
297
140
  // ---------------------------------------------------------------------------