@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
@@ -0,0 +1,291 @@
1
+ import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { strToU8, zipSync } from 'fflate';
6
+
7
+ const confirmMock = vi.hoisted(() => vi.fn());
8
+
9
+ vi.mock('@inquirer/prompts', () => ({
10
+ confirm: confirmMock,
11
+ }));
12
+
13
+ function makeManifest(
14
+ overrides: Partial<Record<string, unknown>> = {},
15
+ ) {
16
+ return {
17
+ schemaVersion: 1,
18
+ catalogRevision: 'abc123',
19
+ catalogUpdatedAt: '2026-04-01T12:00:00.000Z',
20
+ org: {
21
+ id: 'org_123',
22
+ name: 'Acme',
23
+ slug: 'acme',
24
+ },
25
+ designSystem: {
26
+ name: 'Acme UI',
27
+ packageName: '@acme/react',
28
+ importPath: '@acme/react',
29
+ },
30
+ sourceBinding: {
31
+ bindingId: 'binding_123',
32
+ projectId: 'project_123',
33
+ projectName: 'Acme UI',
34
+ repoFullName: 'acme/ui',
35
+ resolution: 'explicit',
36
+ },
37
+ totalComponents: 2,
38
+ totalTokens: 1,
39
+ tokenCategories: {
40
+ color: 1,
41
+ },
42
+ components: {
43
+ component_1: {
44
+ componentId: 'component_1',
45
+ file: '.fragments/components/button-a7f3e2b1.json',
46
+ name: 'Button',
47
+ publicSlug: 'button',
48
+ tier: 'core',
49
+ category: 'inputs',
50
+ status: 'stable',
51
+ description: 'Primary action trigger.',
52
+ propCount: 2,
53
+ hasExamples: true,
54
+ hasCompoundChildren: false,
55
+ relations: [],
56
+ compoundChildren: [],
57
+ },
58
+ },
59
+ ...overrides,
60
+ };
61
+ }
62
+
63
+ function makeBundleZip(manifestOverrides: Partial<Record<string, unknown>> = {}) {
64
+ const manifest = makeManifest(manifestOverrides);
65
+ const files = {
66
+ '.fragments/manifest.json': JSON.stringify(manifest, null, 2),
67
+ '.fragments/tokens.json': JSON.stringify(
68
+ {
69
+ schemaVersion: 1,
70
+ catalogRevision: manifest.catalogRevision,
71
+ catalogUpdatedAt: manifest.catalogUpdatedAt,
72
+ categories: { color: { count: 1, tokens: [] } },
73
+ flat: [],
74
+ },
75
+ null,
76
+ 2,
77
+ ),
78
+ '.fragments/design-system.md': '# Design System: Acme UI',
79
+ '.fragments/README.md': '# Acme UI Fragments Bundle',
80
+ '.fragments/instructions/agents.md': 'Agents helper',
81
+ '.fragments/instructions/claude-code.md': 'Claude helper',
82
+ '.fragments/instructions/copilot.md': 'Copilot helper',
83
+ '.cursor/rules/fragments-design-system.mdc': 'Cursor helper',
84
+ };
85
+
86
+ return zipSync(
87
+ Object.fromEntries(
88
+ Object.entries(files).map(([path, content]) => [path, strToU8(content)]),
89
+ ),
90
+ );
91
+ }
92
+
93
+ describe('context-cloud commands', () => {
94
+ let cwd: string;
95
+ let consoleLogSpy: ReturnType<typeof vi.spyOn>;
96
+
97
+ beforeEach(async () => {
98
+ cwd = await mkdtemp(join(tmpdir(), 'fragments-context-cloud-'));
99
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
100
+ confirmMock.mockReset();
101
+ vi.stubGlobal('fetch', vi.fn(async (input: string | URL) => {
102
+ const url = new URL(String(input));
103
+ if (url.pathname === '/api/bundle') {
104
+ return new Response(makeBundleZip(), {
105
+ status: 200,
106
+ headers: { 'Content-Type': 'application/zip' },
107
+ });
108
+ }
109
+ if (url.pathname === '/api/bundle-artifact') {
110
+ return Response.json({
111
+ content: JSON.stringify(makeManifest(), null, 2),
112
+ });
113
+ }
114
+ return Response.json({ error: 'not found' }, { status: 404 });
115
+ }));
116
+ });
117
+
118
+ afterEach(() => {
119
+ consoleLogSpy.mockRestore();
120
+ vi.unstubAllGlobals();
121
+ delete process.env.FRAGMENTS_API_KEY;
122
+ });
123
+
124
+ it('fails fast when no API key is provided', async () => {
125
+ const { contextInstallCloud } = await import('../context-cloud');
126
+
127
+ await expect(
128
+ contextInstallCloud({ cwd, rootFiles: 'never' }),
129
+ ).rejects.toThrow(
130
+ 'Missing Fragments Cloud API key. Set FRAGMENTS_API_KEY or pass --api-key.',
131
+ );
132
+ });
133
+
134
+ it('installs the downloaded bundle into an empty repo', async () => {
135
+ const { contextInstallCloud } = await import('../context-cloud');
136
+
137
+ await contextInstallCloud({
138
+ cwd,
139
+ apiKey: 'fc_test_key',
140
+ rootFiles: 'never',
141
+ });
142
+
143
+ await expect(
144
+ readFile(join(cwd, '.fragments/manifest.json'), 'utf-8'),
145
+ ).resolves.toContain('"schemaVersion": 1');
146
+ await expect(
147
+ readFile(
148
+ join(cwd, '.fragments/instructions/agents.md'),
149
+ 'utf-8',
150
+ ),
151
+ ).resolves.toContain('Agents helper');
152
+ await expect(
153
+ readFile(
154
+ join(cwd, '.cursor/rules/fragments-design-system.mdc'),
155
+ 'utf-8',
156
+ ),
157
+ ).resolves.toContain('Cursor helper');
158
+ });
159
+
160
+ it('patches root files in prompt mode after confirmation', async () => {
161
+ const { contextInstallCloud } = await import('../context-cloud');
162
+ confirmMock.mockResolvedValue(true);
163
+ await writeFile(join(cwd, 'AGENTS.md'), 'Existing guidance', 'utf-8');
164
+
165
+ await contextInstallCloud({
166
+ cwd,
167
+ apiKey: 'fc_test_key',
168
+ rootFiles: 'prompt',
169
+ });
170
+
171
+ await expect(readFile(join(cwd, 'AGENTS.md'), 'utf-8')).resolves.toContain(
172
+ 'BEGIN FRAGMENTS DESIGN SYSTEM',
173
+ );
174
+ });
175
+
176
+ it('does not patch root files in non-interactive mode without explicit patch', async () => {
177
+ const { contextInstallCloud } = await import('../context-cloud');
178
+ await writeFile(join(cwd, 'AGENTS.md'), 'Existing guidance', 'utf-8');
179
+
180
+ await contextInstallCloud({
181
+ cwd,
182
+ apiKey: 'fc_test_key',
183
+ yes: true,
184
+ });
185
+
186
+ await expect(readFile(join(cwd, 'AGENTS.md'), 'utf-8')).resolves.toBe(
187
+ 'Existing guidance',
188
+ );
189
+ });
190
+
191
+ it('can opt .fragments out of git by updating .gitignore', async () => {
192
+ const { contextInstallCloud } = await import('../context-cloud');
193
+ await writeFile(join(cwd, '.gitignore'), 'node_modules/\n', 'utf-8');
194
+
195
+ await contextInstallCloud({
196
+ cwd,
197
+ apiKey: 'fc_test_key',
198
+ rootFiles: 'never',
199
+ gitignoreFragments: true,
200
+ });
201
+
202
+ await expect(readFile(join(cwd, '.gitignore'), 'utf-8')).resolves.toContain(
203
+ '.fragments/',
204
+ );
205
+ });
206
+
207
+ it('surfaces a readable error when the bundle download is corrupted', async () => {
208
+ const { contextInstallCloud } = await import('../context-cloud');
209
+ vi.stubGlobal('fetch', vi.fn(async (input: string | URL) => {
210
+ const url = new URL(String(input));
211
+ if (url.pathname === '/api/bundle') {
212
+ return new Response('not-a-zip', {
213
+ status: 200,
214
+ headers: { 'Content-Type': 'application/zip' },
215
+ });
216
+ }
217
+ return Response.json({ error: 'not found' }, { status: 404 });
218
+ }));
219
+
220
+ await expect(
221
+ contextInstallCloud({
222
+ cwd,
223
+ apiKey: 'fc_test_key',
224
+ rootFiles: 'never',
225
+ }),
226
+ ).rejects.toThrow('Bundle download was corrupted, try again.');
227
+ });
228
+
229
+ it('reports status by comparing local and remote catalog revisions', async () => {
230
+ const { contextStatusCloud } = await import('../context-cloud');
231
+ await mkdir(join(cwd, '.fragments'), { recursive: true });
232
+ await writeFile(
233
+ join(cwd, '.fragments/manifest.json'),
234
+ JSON.stringify(makeManifest({ catalogRevision: 'old-revision' }), null, 2),
235
+ 'utf-8',
236
+ );
237
+
238
+ await contextStatusCloud({
239
+ cwd,
240
+ apiKey: 'fc_test_key',
241
+ });
242
+
243
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Status: outdated'));
244
+ });
245
+
246
+ it('rejects unsupported bundle schema versions with a clear upgrade message', async () => {
247
+ const { contextStatusCloud } = await import('../context-cloud');
248
+ await mkdir(join(cwd, '.fragments'), { recursive: true });
249
+ await writeFile(
250
+ join(cwd, '.fragments/manifest.json'),
251
+ JSON.stringify(makeManifest({ schemaVersion: 2 }), null, 2),
252
+ 'utf-8',
253
+ );
254
+
255
+ await expect(
256
+ contextStatusCloud({
257
+ cwd,
258
+ apiKey: 'fc_test_key',
259
+ }),
260
+ ).rejects.toThrow('Unsupported Fragments bundle schemaVersion 2. Upgrade your CLI.');
261
+ });
262
+
263
+ it('rejects unsupported remote schema versions with the same upgrade guidance', async () => {
264
+ const { contextStatusCloud } = await import('../context-cloud');
265
+ await mkdir(join(cwd, '.fragments'), { recursive: true });
266
+ await writeFile(
267
+ join(cwd, '.fragments/manifest.json'),
268
+ JSON.stringify(makeManifest(), null, 2),
269
+ 'utf-8',
270
+ );
271
+ vi.stubGlobal('fetch', vi.fn(async (input: string | URL) => {
272
+ const url = new URL(String(input));
273
+ if (url.pathname === '/api/bundle-artifact') {
274
+ return Response.json({
275
+ content: JSON.stringify(makeManifest({ schemaVersion: 2 }), null, 2),
276
+ });
277
+ }
278
+ return new Response(makeBundleZip(), {
279
+ status: 200,
280
+ headers: { 'Content-Type': 'application/zip' },
281
+ });
282
+ }));
283
+
284
+ await expect(
285
+ contextStatusCloud({
286
+ cwd,
287
+ apiKey: 'fc_test_key',
288
+ }),
289
+ ).rejects.toThrow('Unsupported Fragments bundle schemaVersion 2. Upgrade your CLI.');
290
+ });
291
+ });
@@ -0,0 +1,185 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { ComponentUsage } from '../../service/enhance/types.js';
3
+ import { computeScore, type GovernanceVerdict } from '@fragments-sdk/govern';
4
+ import {
5
+ aggregateVerdicts,
6
+ flattenComponentUsage,
7
+ buildComplianceSummary,
8
+ } from '../govern-scan-report.js';
9
+
10
+ function makeVerdict(
11
+ overrides: Partial<GovernanceVerdict>,
12
+ ): GovernanceVerdict {
13
+ return {
14
+ passed: true,
15
+ score: 100,
16
+ results: [],
17
+ metadata: {
18
+ runner: 'cli',
19
+ duration: 10,
20
+ nodeCount: 3,
21
+ componentTypes: [],
22
+ },
23
+ ...overrides,
24
+ };
25
+ }
26
+
27
+ describe('aggregateVerdicts', () => {
28
+ it('merges results by validator and computes aggregate metadata', () => {
29
+ const verdictA = makeVerdict({
30
+ passed: false,
31
+ results: [
32
+ {
33
+ validator: 'tokens',
34
+ severity: 'moderate',
35
+ passed: false,
36
+ violations: [
37
+ {
38
+ nodeId: 'btn-a',
39
+ nodeType: 'Button',
40
+ rule: 'tokens/require-design-tokens',
41
+ severity: 'moderate',
42
+ message: 'Use design token',
43
+ },
44
+ ],
45
+ },
46
+ {
47
+ validator: 'safety',
48
+ severity: 'serious',
49
+ passed: true,
50
+ violations: [],
51
+ },
52
+ ],
53
+ metadata: {
54
+ runner: 'cli',
55
+ duration: 25,
56
+ nodeCount: 4,
57
+ componentTypes: ['Button', 'Card'],
58
+ },
59
+ });
60
+
61
+ const verdictB = makeVerdict({
62
+ passed: false,
63
+ results: [
64
+ {
65
+ validator: 'tokens',
66
+ severity: 'serious',
67
+ passed: false,
68
+ violations: [
69
+ {
70
+ nodeId: 'btn-b',
71
+ nodeType: 'Button',
72
+ rule: 'tokens/require-design-tokens',
73
+ severity: 'serious',
74
+ message: 'Token mismatch',
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ validator: 'a11y',
80
+ severity: 'minor',
81
+ passed: false,
82
+ violations: [
83
+ {
84
+ nodeId: 'card-a',
85
+ nodeType: 'Card',
86
+ rule: 'a11y/standard',
87
+ severity: 'minor',
88
+ message: 'Missing aria-label',
89
+ },
90
+ ],
91
+ },
92
+ ],
93
+ metadata: {
94
+ runner: 'cli',
95
+ duration: 35,
96
+ nodeCount: 6,
97
+ componentTypes: ['Button', 'Modal'],
98
+ },
99
+ });
100
+
101
+ const aggregated = aggregateVerdicts([verdictA, verdictB], computeScore, 'ci');
102
+
103
+ expect(aggregated.passed).toBe(false);
104
+ expect(aggregated.metadata.runner).toBe('ci');
105
+ expect(aggregated.metadata.duration).toBe(60);
106
+ expect(aggregated.metadata.nodeCount).toBe(10);
107
+ expect(aggregated.metadata.componentTypes).toEqual(['Button', 'Card', 'Modal']);
108
+
109
+ const tokens = aggregated.results.find((result) => result.validator === 'tokens');
110
+ expect(tokens).toBeDefined();
111
+ expect(tokens?.violations).toHaveLength(2);
112
+ expect(tokens?.severity).toBe('serious');
113
+ expect(tokens?.passed).toBe(false);
114
+
115
+ const allViolations = aggregated.results.flatMap((result) => result.violations);
116
+ expect(aggregated.score).toBe(computeScore(allViolations));
117
+ });
118
+ });
119
+
120
+ describe('flattenComponentUsage', () => {
121
+ it('flattens and counts usage per component per file', () => {
122
+ const rootDir = '/repo';
123
+ const usages: ComponentUsage[] = [
124
+ {
125
+ componentName: 'Button',
126
+ filePath: '/repo/src/app.tsx',
127
+ line: 4,
128
+ column: 0,
129
+ props: { static: {}, dynamic: [], spreads: [] },
130
+ context: '<Button />',
131
+ hasSpreadProps: false,
132
+ isConditional: false,
133
+ },
134
+ {
135
+ componentName: 'Button',
136
+ filePath: '/repo/src/app.tsx',
137
+ line: 10,
138
+ column: 2,
139
+ props: { static: {}, dynamic: [], spreads: [] },
140
+ context: '<Button />',
141
+ hasSpreadProps: false,
142
+ isConditional: false,
143
+ },
144
+ {
145
+ componentName: 'Card',
146
+ filePath: '/repo/src/panel.tsx',
147
+ line: 2,
148
+ column: 1,
149
+ props: { static: {}, dynamic: [], spreads: [] },
150
+ context: '<Card />',
151
+ hasSpreadProps: false,
152
+ isConditional: false,
153
+ },
154
+ ];
155
+
156
+ expect(flattenComponentUsage(usages, rootDir)).toEqual([
157
+ { component: 'Button', file: 'src/app.tsx', occurrences: 2 },
158
+ { component: 'Card', file: 'src/panel.tsx', occurrences: 1 },
159
+ ]);
160
+ });
161
+ });
162
+
163
+ describe('buildComplianceSummary', () => {
164
+ it('derives totals from component usage counts', () => {
165
+ const summary = buildComplianceSummary({
166
+ contractCoverage: 80,
167
+ overallCompliance: 75,
168
+ components: {
169
+ Button: { total: 4, passed: 3, violations: 1, score: 75 },
170
+ Card: { total: 2, passed: 1, violations: 1, score: 50 },
171
+ },
172
+ uncontracted: ['Modal'],
173
+ totalComponents: 3,
174
+ contractedComponents: 2,
175
+ });
176
+
177
+ expect(summary).toEqual({
178
+ complianceRate: 75,
179
+ passingUsages: 4,
180
+ totalUsages: 6,
181
+ contractedCount: 2,
182
+ detectedCount: 3,
183
+ });
184
+ });
185
+ });
@@ -34,6 +34,7 @@ describe('govern init', () => {
34
34
 
35
35
  const content = await readFile(configPath, 'utf-8');
36
36
  expect(content).toContain('rules');
37
+ expect(content).not.toContain('cloud: true');
37
38
  },
38
39
  );
39
40