@lumenflow/cli 2.20.1 → 2.21.1

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 (111) hide show
  1. package/README.md +8 -4
  2. package/dist/hooks/enforcement-checks.js +120 -0
  3. package/dist/hooks/enforcement-checks.js.map +1 -1
  4. package/dist/init-lane-validation.js +141 -0
  5. package/dist/init-lane-validation.js.map +1 -0
  6. package/dist/init-templates.js +36 -8
  7. package/dist/init-templates.js.map +1 -1
  8. package/dist/init.js +27 -58
  9. package/dist/init.js.map +1 -1
  10. package/dist/initiative-create.js +35 -4
  11. package/dist/initiative-create.js.map +1 -1
  12. package/dist/lane-lifecycle-process.js +364 -0
  13. package/dist/lane-lifecycle-process.js.map +1 -0
  14. package/dist/lane-lock.js +41 -0
  15. package/dist/lane-lock.js.map +1 -0
  16. package/dist/lane-setup.js +55 -0
  17. package/dist/lane-setup.js.map +1 -0
  18. package/dist/lane-status.js +38 -0
  19. package/dist/lane-status.js.map +1 -0
  20. package/dist/lane-validate.js +43 -0
  21. package/dist/lane-validate.js.map +1 -0
  22. package/dist/onboarding-smoke-test.js +17 -0
  23. package/dist/onboarding-smoke-test.js.map +1 -1
  24. package/dist/public-manifest.js +28 -0
  25. package/dist/public-manifest.js.map +1 -1
  26. package/dist/wu-claim-cloud.js +16 -0
  27. package/dist/wu-claim-cloud.js.map +1 -1
  28. package/dist/wu-claim.js +12 -2
  29. package/dist/wu-claim.js.map +1 -1
  30. package/dist/wu-create-content.js +8 -2
  31. package/dist/wu-create-content.js.map +1 -1
  32. package/dist/wu-create-validation.js +5 -3
  33. package/dist/wu-create-validation.js.map +1 -1
  34. package/dist/wu-create.js +21 -1
  35. package/dist/wu-create.js.map +1 -1
  36. package/dist/wu-done.js +57 -8
  37. package/dist/wu-done.js.map +1 -1
  38. package/dist/wu-prep.js +22 -0
  39. package/dist/wu-prep.js.map +1 -1
  40. package/package.json +15 -11
  41. package/dist/__tests__/agent-log-issue.test.js +0 -56
  42. package/dist/__tests__/agent-spawn-coordination.test.js +0 -451
  43. package/dist/__tests__/backlog-prune.test.js +0 -478
  44. package/dist/__tests__/cli-entry-point.test.js +0 -160
  45. package/dist/__tests__/cli-subprocess.test.js +0 -89
  46. package/dist/__tests__/commands/integrate.test.js +0 -165
  47. package/dist/__tests__/commands.test.js +0 -271
  48. package/dist/__tests__/deps-operations.test.js +0 -206
  49. package/dist/__tests__/doctor.test.js +0 -510
  50. package/dist/__tests__/file-operations.test.js +0 -906
  51. package/dist/__tests__/flow-report.test.js +0 -24
  52. package/dist/__tests__/gates-config.test.js +0 -303
  53. package/dist/__tests__/gates-integration-tests.test.js +0 -112
  54. package/dist/__tests__/git-operations.test.js +0 -668
  55. package/dist/__tests__/guard-main-branch.test.js +0 -79
  56. package/dist/__tests__/guards-validation.test.js +0 -416
  57. package/dist/__tests__/hooks/enforcement.test.js +0 -279
  58. package/dist/__tests__/init-config-lanes.test.js +0 -131
  59. package/dist/__tests__/init-docs-structure.test.js +0 -152
  60. package/dist/__tests__/init-greenfield.test.js +0 -247
  61. package/dist/__tests__/init-lane-inference.test.js +0 -125
  62. package/dist/__tests__/init-onboarding-docs.test.js +0 -132
  63. package/dist/__tests__/init-quick-ref.test.js +0 -144
  64. package/dist/__tests__/init-scripts.test.js +0 -207
  65. package/dist/__tests__/init-template-portability.test.js +0 -96
  66. package/dist/__tests__/init.test.js +0 -968
  67. package/dist/__tests__/initiative-add-wu.test.js +0 -490
  68. package/dist/__tests__/initiative-e2e.test.js +0 -442
  69. package/dist/__tests__/initiative-plan-replacement.test.js +0 -161
  70. package/dist/__tests__/initiative-plan.test.js +0 -340
  71. package/dist/__tests__/initiative-remove-wu.test.js +0 -458
  72. package/dist/__tests__/lumenflow-upgrade.test.js +0 -260
  73. package/dist/__tests__/mem-cleanup-execution.test.js +0 -19
  74. package/dist/__tests__/memory-integration.test.js +0 -333
  75. package/dist/__tests__/merge-block.test.js +0 -220
  76. package/dist/__tests__/metrics-cli.test.js +0 -619
  77. package/dist/__tests__/metrics-snapshot.test.js +0 -24
  78. package/dist/__tests__/no-beacon-references-docs.test.js +0 -30
  79. package/dist/__tests__/no-beacon-references.test.js +0 -39
  80. package/dist/__tests__/onboarding-smoke-test.test.js +0 -211
  81. package/dist/__tests__/path-centralization-cli.test.js +0 -234
  82. package/dist/__tests__/plan-create.test.js +0 -126
  83. package/dist/__tests__/plan-edit.test.js +0 -157
  84. package/dist/__tests__/plan-link.test.js +0 -239
  85. package/dist/__tests__/plan-promote.test.js +0 -181
  86. package/dist/__tests__/release.test.js +0 -372
  87. package/dist/__tests__/rotate-progress.test.js +0 -127
  88. package/dist/__tests__/safe-git.test.js +0 -190
  89. package/dist/__tests__/session-coordinator.test.js +0 -109
  90. package/dist/__tests__/state-bootstrap.test.js +0 -432
  91. package/dist/__tests__/state-doctor.test.js +0 -328
  92. package/dist/__tests__/sync-templates.test.js +0 -255
  93. package/dist/__tests__/templates-sync.test.js +0 -219
  94. package/dist/__tests__/trace-gen.test.js +0 -115
  95. package/dist/__tests__/wu-create-required-fields.test.js +0 -143
  96. package/dist/__tests__/wu-create-strict.test.js +0 -118
  97. package/dist/__tests__/wu-create.test.js +0 -121
  98. package/dist/__tests__/wu-done-auto-cleanup.test.js +0 -135
  99. package/dist/__tests__/wu-done-docs-only-policy.test.js +0 -20
  100. package/dist/__tests__/wu-done-staging-whitelist.test.js +0 -35
  101. package/dist/__tests__/wu-done.test.js +0 -36
  102. package/dist/__tests__/wu-edit-strict.test.js +0 -109
  103. package/dist/__tests__/wu-edit.test.js +0 -119
  104. package/dist/__tests__/wu-lifecycle-integration.test.js +0 -388
  105. package/dist/__tests__/wu-prep-default-exec.test.js +0 -35
  106. package/dist/__tests__/wu-prep.test.js +0 -140
  107. package/dist/__tests__/wu-proto.test.js +0 -97
  108. package/dist/__tests__/wu-validate-strict.test.js +0 -113
  109. package/dist/__tests__/wu-validate.test.js +0 -36
  110. package/dist/spawn-list.js +0 -143
  111. package/dist/spawn-list.js.map +0 -1
@@ -1,968 +0,0 @@
1
- /**
2
- * @file init.test.ts
3
- * Tests for lumenflow init command (WU-1171)
4
- *
5
- * Tests the new --merge mode, --client flag, AGENTS.md creation,
6
- * and updated vendor overlay paths.
7
- */
8
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
- import * as fs from 'node:fs';
10
- import * as path from 'node:path';
11
- import * as os from 'node:os';
12
- import { scaffoldProject } from '../init.js';
13
- // Constants to avoid sonarjs/no-duplicate-string
14
- const LUMENFLOW_MD = 'LUMENFLOW.md';
15
- const VENDOR_RULES_FILE = 'lumenflow.md';
16
- // WU-1300: Additional constants for lint compliance
17
- const ONBOARDING_DOCS_PATH = 'docs/04-operations/_frameworks/lumenflow/agent/onboarding';
18
- const DOCS_OPS_DIR = 'docs/04-operations';
19
- const PACKAGE_JSON_FILE = 'package.json';
20
- describe('lumenflow init', () => {
21
- let tempDir;
22
- beforeEach(() => {
23
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-init-test-'));
24
- });
25
- afterEach(() => {
26
- fs.rmSync(tempDir, { recursive: true, force: true });
27
- });
28
- describe('AGENTS.md creation', () => {
29
- it('should create AGENTS.md by default', async () => {
30
- const options = {
31
- force: false,
32
- full: false,
33
- };
34
- await scaffoldProject(tempDir, options);
35
- const agentsPath = path.join(tempDir, 'AGENTS.md');
36
- expect(fs.existsSync(agentsPath)).toBe(true);
37
- const content = fs.readFileSync(agentsPath, 'utf-8');
38
- expect(content).toContain(LUMENFLOW_MD);
39
- expect(content).toContain('universal');
40
- });
41
- it('should link AGENTS.md to LUMENFLOW.md', async () => {
42
- const options = {
43
- force: false,
44
- full: false,
45
- };
46
- await scaffoldProject(tempDir, options);
47
- const agentsContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
48
- expect(agentsContent).toContain(`[${LUMENFLOW_MD}]`);
49
- });
50
- });
51
- describe('--client flag', () => {
52
- it('should accept --client claude', async () => {
53
- const options = {
54
- force: false,
55
- full: false,
56
- client: 'claude',
57
- };
58
- const result = await scaffoldProject(tempDir, options);
59
- expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(true);
60
- expect(result.created).toContain('CLAUDE.md');
61
- });
62
- it('should accept --client cursor', async () => {
63
- const options = {
64
- force: false,
65
- full: false,
66
- client: 'cursor',
67
- };
68
- await scaffoldProject(tempDir, options);
69
- // Cursor uses .cursor/rules/lumenflow.md (not .cursor/rules.md)
70
- expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', VENDOR_RULES_FILE))).toBe(true);
71
- });
72
- it('should accept --client windsurf', async () => {
73
- const options = {
74
- force: false,
75
- full: false,
76
- client: 'windsurf',
77
- };
78
- await scaffoldProject(tempDir, options);
79
- // Windsurf uses .windsurf/rules/lumenflow.md (not .windsurfrules)
80
- expect(fs.existsSync(path.join(tempDir, '.windsurf', 'rules', VENDOR_RULES_FILE))).toBe(true);
81
- });
82
- it('should accept --client codex', async () => {
83
- const options = {
84
- force: false,
85
- full: false,
86
- client: 'codex',
87
- };
88
- await scaffoldProject(tempDir, options);
89
- // Codex reads AGENTS.md directly, minimal extra config
90
- expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
91
- });
92
- it('should accept --client all', async () => {
93
- const options = {
94
- force: false,
95
- full: false,
96
- client: 'all',
97
- };
98
- await scaffoldProject(tempDir, options);
99
- // Should create all vendor files
100
- expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
101
- expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(true);
102
- expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', VENDOR_RULES_FILE))).toBe(true);
103
- expect(fs.existsSync(path.join(tempDir, '.windsurf', 'rules', VENDOR_RULES_FILE))).toBe(true);
104
- });
105
- it('should treat --vendor as alias for --client (backwards compatibility)', async () => {
106
- const options = {
107
- force: false,
108
- full: false,
109
- vendor: 'claude', // Using old --vendor flag
110
- };
111
- await scaffoldProject(tempDir, options);
112
- expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(true);
113
- });
114
- });
115
- describe('--merge mode', () => {
116
- it('should insert LUMENFLOW block into existing file', async () => {
117
- // Create existing AGENTS.md
118
- const existingContent = '# My Project Agents\n\nCustom content here.\n';
119
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), existingContent);
120
- const options = {
121
- force: false,
122
- full: false,
123
- merge: true,
124
- };
125
- await scaffoldProject(tempDir, options);
126
- const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
127
- // Should preserve original content
128
- expect(content).toContain('# My Project Agents');
129
- expect(content).toContain('Custom content here.');
130
- // Should add LumenFlow block
131
- expect(content).toContain('<!-- LUMENFLOW:START -->');
132
- expect(content).toContain('<!-- LUMENFLOW:END -->');
133
- expect(content).toContain(LUMENFLOW_MD);
134
- });
135
- it('should be idempotent (running twice produces no diff)', async () => {
136
- // Create existing file
137
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), '# My Project\n');
138
- const options = {
139
- force: false,
140
- full: false,
141
- merge: true,
142
- };
143
- // First run
144
- await scaffoldProject(tempDir, options);
145
- const firstContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
146
- // Second run
147
- await scaffoldProject(tempDir, options);
148
- const secondContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
149
- expect(firstContent).toBe(secondContent);
150
- });
151
- it('should preserve CRLF line endings', async () => {
152
- // Create existing file with CRLF
153
- const existingContent = '# My Project\r\n\r\nWindows style.\r\n';
154
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), existingContent);
155
- const options = {
156
- force: false,
157
- full: false,
158
- merge: true,
159
- };
160
- await scaffoldProject(tempDir, options);
161
- const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
162
- // Should preserve CRLF
163
- expect(content).toContain('\r\n');
164
- // Should not have mixed line endings (standalone LF without preceding CR)
165
- const lfCount = (content.match(/(?<!\r)\n/g) || []).length;
166
- expect(lfCount).toBe(0);
167
- });
168
- it('should warn on malformed markers and append fresh block', async () => {
169
- // Create file with only START marker (malformed)
170
- const malformedContent = '# My Project\n\n<!-- LUMENFLOW:START -->\nOrphan block\n';
171
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), malformedContent);
172
- const options = {
173
- force: false,
174
- full: false,
175
- merge: true,
176
- };
177
- const result = await scaffoldProject(tempDir, options);
178
- // Should have warnings about malformed markers
179
- expect(result.warnings).toBeDefined();
180
- expect(result.warnings?.some((w) => w.includes('malformed'))).toBe(true);
181
- const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
182
- // Should have complete block
183
- expect(content).toContain('<!-- LUMENFLOW:END -->');
184
- });
185
- });
186
- describe('createFile mode option', () => {
187
- it('should skip existing files in skip mode (default)', async () => {
188
- const existingContent = 'Original content';
189
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), existingContent);
190
- const options = {
191
- force: false,
192
- full: false,
193
- // Default mode is 'skip'
194
- };
195
- const result = await scaffoldProject(tempDir, options);
196
- expect(result.skipped).toContain('AGENTS.md');
197
- const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
198
- expect(content).toBe(existingContent);
199
- });
200
- it('should overwrite in force mode', async () => {
201
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), 'Original');
202
- const options = {
203
- force: true,
204
- full: false,
205
- };
206
- const result = await scaffoldProject(tempDir, options);
207
- expect(result.created).toContain('AGENTS.md');
208
- const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
209
- expect(content).not.toBe('Original');
210
- });
211
- it('should merge in merge mode', async () => {
212
- fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), '# Custom Header\n');
213
- const options = {
214
- force: false,
215
- full: false,
216
- merge: true,
217
- };
218
- const result = await scaffoldProject(tempDir, options);
219
- expect(result.merged).toContain('AGENTS.md');
220
- const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
221
- expect(content).toContain('# Custom Header');
222
- expect(content).toContain('<!-- LUMENFLOW:START -->');
223
- });
224
- });
225
- describe('vendor overlay paths', () => {
226
- it('should use .cursor/rules/lumenflow.md for Cursor', async () => {
227
- const options = {
228
- force: false,
229
- full: false,
230
- client: 'cursor',
231
- };
232
- await scaffoldProject(tempDir, options);
233
- // Should NOT create old path
234
- expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules.md'))).toBe(false);
235
- // Should create new path
236
- expect(fs.existsSync(path.join(tempDir, '.cursor', 'rules', VENDOR_RULES_FILE))).toBe(true);
237
- });
238
- it('should use .windsurf/rules/lumenflow.md for Windsurf', async () => {
239
- const options = {
240
- force: false,
241
- full: false,
242
- client: 'windsurf',
243
- };
244
- await scaffoldProject(tempDir, options);
245
- // Should NOT create old path
246
- expect(fs.existsSync(path.join(tempDir, '.windsurfrules'))).toBe(false);
247
- // Should create new path
248
- expect(fs.existsSync(path.join(tempDir, '.windsurf', 'rules', VENDOR_RULES_FILE))).toBe(true);
249
- });
250
- });
251
- describe('CLAUDE.md location', () => {
252
- it('should create single CLAUDE.md at root only', async () => {
253
- const options = {
254
- force: false,
255
- full: false,
256
- client: 'claude',
257
- };
258
- await scaffoldProject(tempDir, options);
259
- // Should create root CLAUDE.md
260
- expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(true);
261
- // Should NOT create .claude/CLAUDE.md (no duplication)
262
- expect(fs.existsSync(path.join(tempDir, '.claude', 'CLAUDE.md'))).toBe(false);
263
- });
264
- });
265
- // WU-1286: --full is now the default
266
- describe('--full default and --minimal flag', () => {
267
- it('should scaffold agent onboarding docs by default (full=true)', async () => {
268
- const options = {
269
- force: false,
270
- full: true, // This is now the default when parsed
271
- docsStructure: 'arc42', // WU-1309: Explicitly request arc42 for legacy test
272
- };
273
- await scaffoldProject(tempDir, options);
274
- // Should create agent onboarding docs
275
- const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
276
- expect(fs.existsSync(path.join(onboardingDir, 'quick-ref-commands.md'))).toBe(true);
277
- expect(fs.existsSync(path.join(onboardingDir, 'first-wu-mistakes.md'))).toBe(true);
278
- expect(fs.existsSync(path.join(onboardingDir, 'troubleshooting-wu-done.md'))).toBe(true);
279
- });
280
- it('should skip agent onboarding docs when full=false (minimal mode)', async () => {
281
- const options = {
282
- force: false,
283
- full: false, // Explicitly minimal
284
- };
285
- await scaffoldProject(tempDir, options);
286
- // Should NOT create agent onboarding docs
287
- const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
288
- expect(fs.existsSync(path.join(onboardingDir, 'quick-ref-commands.md'))).toBe(false);
289
- });
290
- it('should still create core files in minimal mode', async () => {
291
- const options = {
292
- force: false,
293
- full: false,
294
- };
295
- await scaffoldProject(tempDir, options);
296
- // Core files should always be created
297
- expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
298
- expect(fs.existsSync(path.join(tempDir, LUMENFLOW_MD))).toBe(true);
299
- expect(fs.existsSync(path.join(tempDir, '.lumenflow.config.yaml'))).toBe(true);
300
- expect(fs.existsSync(path.join(tempDir, '.lumenflow', 'constraints.md'))).toBe(true);
301
- });
302
- });
303
- // WU-1300: Scaffolding fixes and template portability
304
- describe('WU-1300: scaffolding fixes', () => {
305
- describe('lane-inference.yaml generation', () => {
306
- it('should scaffold .lumenflow.lane-inference.yaml with --full', async () => {
307
- const options = {
308
- force: false,
309
- full: true,
310
- };
311
- await scaffoldProject(tempDir, options);
312
- const laneInferencePath = path.join(tempDir, '.lumenflow.lane-inference.yaml');
313
- expect(fs.existsSync(laneInferencePath)).toBe(true);
314
- const content = fs.readFileSync(laneInferencePath, 'utf-8');
315
- // WU-1307: Should have hierarchical lane definitions (not flat lanes: array)
316
- expect(content).toContain('Framework:');
317
- expect(content).toContain('Content:');
318
- expect(content).toContain('Operations:');
319
- });
320
- it('should scaffold lane-inference with framework-specific lanes when --framework is provided', async () => {
321
- const options = {
322
- force: false,
323
- full: true,
324
- framework: 'Next.js',
325
- };
326
- await scaffoldProject(tempDir, options);
327
- const laneInferencePath = path.join(tempDir, '.lumenflow.lane-inference.yaml');
328
- expect(fs.existsSync(laneInferencePath)).toBe(true);
329
- });
330
- });
331
- describe('starting-prompt.md scaffolding', () => {
332
- it('should scaffold starting-prompt.md in onboarding docs with --full', async () => {
333
- const options = {
334
- force: false,
335
- full: true,
336
- docsStructure: 'arc42', // WU-1309: Explicitly request arc42 for legacy test
337
- };
338
- await scaffoldProject(tempDir, options);
339
- const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
340
- const startingPromptPath = path.join(onboardingDir, 'starting-prompt.md');
341
- expect(fs.existsSync(startingPromptPath)).toBe(true);
342
- const content = fs.readFileSync(startingPromptPath, 'utf-8');
343
- expect(content).toContain(LUMENFLOW_MD);
344
- expect(content).toContain('constraints');
345
- });
346
- });
347
- describe('template path portability', () => {
348
- it('should not have absolute paths in generated templates', async () => {
349
- const options = {
350
- force: false,
351
- full: true,
352
- };
353
- await scaffoldProject(tempDir, options);
354
- // Check common files for absolute paths
355
- const filesToCheck = ['AGENTS.md', LUMENFLOW_MD, '.lumenflow/constraints.md'];
356
- for (const file of filesToCheck) {
357
- const filePath = path.join(tempDir, file);
358
- if (fs.existsSync(filePath)) {
359
- const content = fs.readFileSync(filePath, 'utf-8');
360
- // Should not contain absolute paths (unix home dirs or macOS user dirs)
361
- // Build patterns dynamically to avoid triggering pre-commit hook
362
- const homePattern = new RegExp('/' + 'home' + '/' + '\\w+');
363
- const usersPattern = new RegExp('/' + 'Users' + '/' + '\\w+');
364
- expect(content).not.toMatch(homePattern);
365
- expect(content).not.toMatch(usersPattern);
366
- // Should use <project-root> placeholder for project root references
367
- // or relative paths like ./docs/
368
- }
369
- }
370
- });
371
- it('should use <project-root> placeholder in templates where project root is needed', async () => {
372
- const options = {
373
- force: false,
374
- full: true,
375
- };
376
- await scaffoldProject(tempDir, options);
377
- const agentsContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
378
- // AGENTS.md should have placeholder for cd command back to project root
379
- // Using {{PROJECT_ROOT}} token which gets replaced with actual path
380
- expect(agentsContent).toMatch(/cd\s+[\w./\\${}]+/); // Should have cd command with path
381
- });
382
- });
383
- describe('AGENTS.md quick-ref link', () => {
384
- it('should have correct quick-ref-commands.md link in AGENTS.md when --full', async () => {
385
- const options = {
386
- force: false,
387
- full: true,
388
- docsStructure: 'arc42', // WU-1309: Explicitly request arc42 for legacy test
389
- };
390
- await scaffoldProject(tempDir, options);
391
- const agentsContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
392
- // If quick-ref is mentioned, link should point to correct location
393
- // docs/04-operations/_frameworks/lumenflow/agent/onboarding/quick-ref-commands.md
394
- if (agentsContent.includes('quick-ref')) {
395
- expect(agentsContent).toContain(`${ONBOARDING_DOCS_PATH}/quick-ref-commands.md`);
396
- }
397
- });
398
- });
399
- describe('--docs-structure flag', () => {
400
- it('should accept --docs-structure simple', async () => {
401
- const options = {
402
- force: false,
403
- full: true,
404
- docsStructure: 'simple',
405
- };
406
- await scaffoldProject(tempDir, options);
407
- // Simple structure uses docs/ directly, not arc42 structure
408
- expect(fs.existsSync(path.join(tempDir, 'docs'))).toBe(true);
409
- });
410
- it('should accept --docs-structure arc42', async () => {
411
- const options = {
412
- force: false,
413
- full: true,
414
- docsStructure: 'arc42',
415
- };
416
- await scaffoldProject(tempDir, options);
417
- // Arc42 uses numbered directories: 01-*, 02-*, etc.
418
- // The current default is arc42-style with 04-operations
419
- const operationsDir = path.join(tempDir, DOCS_OPS_DIR);
420
- expect(fs.existsSync(operationsDir)).toBe(true);
421
- });
422
- it('should auto-detect existing docs structure', async () => {
423
- // Create existing simple structure
424
- fs.mkdirSync(path.join(tempDir, 'docs'), { recursive: true });
425
- fs.writeFileSync(path.join(tempDir, 'docs/README.md'), '# Docs\n');
426
- const options = {
427
- force: false,
428
- full: true,
429
- // No docsStructure specified - should auto-detect
430
- };
431
- await scaffoldProject(tempDir, options);
432
- // Should preserve existing structure
433
- expect(fs.existsSync(path.join(tempDir, 'docs/README.md'))).toBe(true);
434
- });
435
- });
436
- describe('package.json scripts injection', () => {
437
- it('should inject LumenFlow scripts into existing package.json', async () => {
438
- // Create existing package.json
439
- const existingPackageJson = {
440
- name: 'test-project',
441
- version: '1.0.0',
442
- scripts: {
443
- test: 'vitest',
444
- build: 'tsc',
445
- },
446
- };
447
- fs.writeFileSync(path.join(tempDir, PACKAGE_JSON_FILE), JSON.stringify(existingPackageJson, null, 2));
448
- const options = {
449
- force: false,
450
- full: true,
451
- };
452
- await scaffoldProject(tempDir, options);
453
- const packageJson = JSON.parse(fs.readFileSync(path.join(tempDir, PACKAGE_JSON_FILE), 'utf-8'));
454
- // Should preserve existing scripts
455
- expect(packageJson.scripts.test).toBe('vitest');
456
- expect(packageJson.scripts.build).toBe('tsc');
457
- // Should add LumenFlow scripts
458
- expect(packageJson.scripts['wu:claim']).toBeDefined();
459
- expect(packageJson.scripts['wu:done']).toBeDefined();
460
- expect(packageJson.scripts.gates).toBeDefined();
461
- });
462
- it('should not overwrite existing LumenFlow scripts unless --force', async () => {
463
- // Create existing package.json with custom wu:claim
464
- const existingPackageJson = {
465
- name: 'test-project',
466
- scripts: {
467
- 'wu:claim': 'custom-claim-command',
468
- },
469
- };
470
- fs.writeFileSync(path.join(tempDir, PACKAGE_JSON_FILE), JSON.stringify(existingPackageJson, null, 2));
471
- const options = {
472
- force: false,
473
- full: true,
474
- };
475
- await scaffoldProject(tempDir, options);
476
- const packageJson = JSON.parse(fs.readFileSync(path.join(tempDir, PACKAGE_JSON_FILE), 'utf-8'));
477
- // Should preserve custom script
478
- expect(packageJson.scripts['wu:claim']).toBe('custom-claim-command');
479
- });
480
- it('should create package.json with LumenFlow scripts if none exists', async () => {
481
- const options = {
482
- force: false,
483
- full: true,
484
- };
485
- await scaffoldProject(tempDir, options);
486
- const packageJsonPath = path.join(tempDir, PACKAGE_JSON_FILE);
487
- if (fs.existsSync(packageJsonPath)) {
488
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
489
- expect(packageJson.scripts).toBeDefined();
490
- }
491
- });
492
- });
493
- });
494
- // WU-1382: Improved templates for agent clarity
495
- describe('WU-1382: improved templates for agent clarity', () => {
496
- describe('CLAUDE.md template enhancements', () => {
497
- it('should include CLI commands table inline in CLAUDE.md', async () => {
498
- const options = {
499
- force: false,
500
- full: false,
501
- client: 'claude',
502
- };
503
- await scaffoldProject(tempDir, options);
504
- const claudeMdPath = path.join(tempDir, 'CLAUDE.md');
505
- expect(fs.existsSync(claudeMdPath)).toBe(true);
506
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
507
- // Should have CLI commands table with common commands
508
- expect(content).toContain('| Command');
509
- expect(content).toContain('wu:claim');
510
- expect(content).toContain('wu:done');
511
- expect(content).toContain('wu:status');
512
- expect(content).toContain('gates');
513
- });
514
- it('should include warning about manual YAML editing in CLAUDE.md', async () => {
515
- const options = {
516
- force: false,
517
- full: false,
518
- client: 'claude',
519
- };
520
- await scaffoldProject(tempDir, options);
521
- const claudeMdPath = path.join(tempDir, 'CLAUDE.md');
522
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
523
- // Should warn against manual WU YAML edits
524
- expect(content).toMatch(/do\s+not\s+(manually\s+)?edit|never\s+(manually\s+)?edit/i);
525
- expect(content).toMatch(/wu.*yaml|yaml.*wu/i);
526
- });
527
- });
528
- describe('config.yaml managed file header', () => {
529
- it('should include managed file header in .lumenflow.config.yaml', async () => {
530
- const options = {
531
- force: false,
532
- full: false,
533
- };
534
- await scaffoldProject(tempDir, options);
535
- const configPath = path.join(tempDir, '.lumenflow.config.yaml');
536
- expect(fs.existsSync(configPath)).toBe(true);
537
- const content = fs.readFileSync(configPath, 'utf-8');
538
- // Should have managed file header
539
- expect(content).toMatch(/LUMENFLOW\s+MANAGED\s+FILE/i);
540
- expect(content).toMatch(/do\s+not\s+(manually\s+)?edit/i);
541
- });
542
- });
543
- describe('lane-inference.yaml managed file header', () => {
544
- it('should include managed file header in .lumenflow.lane-inference.yaml', async () => {
545
- const options = {
546
- force: false,
547
- full: true,
548
- };
549
- await scaffoldProject(tempDir, options);
550
- const laneInferencePath = path.join(tempDir, '.lumenflow.lane-inference.yaml');
551
- expect(fs.existsSync(laneInferencePath)).toBe(true);
552
- const content = fs.readFileSync(laneInferencePath, 'utf-8');
553
- // Should have managed file header
554
- expect(content).toMatch(/LUMENFLOW\s+MANAGED\s+FILE/i);
555
- expect(content).toMatch(/do\s+not\s+(manually\s+)?edit/i);
556
- });
557
- });
558
- });
559
- // WU-1383: CLI safeguards against manual file editing
560
- describe('WU-1383: CLI safeguards for Claude client', () => {
561
- const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
562
- describe('enforcement hooks enabled by default for --client claude', () => {
563
- it('should add enforcement hooks config when --client claude is used', async () => {
564
- const options = {
565
- force: false,
566
- full: false,
567
- client: 'claude',
568
- };
569
- await scaffoldProject(tempDir, options);
570
- const configPath = path.join(tempDir, CONFIG_FILE_NAME);
571
- expect(fs.existsSync(configPath)).toBe(true);
572
- const content = fs.readFileSync(configPath, 'utf-8');
573
- // Should have enforcement hooks enabled for claude-code
574
- expect(content).toContain('claude-code');
575
- expect(content).toContain('enforcement');
576
- expect(content).toContain('hooks: true');
577
- });
578
- it('should set block_outside_worktree to true by default for claude client', async () => {
579
- const options = {
580
- force: false,
581
- full: false,
582
- client: 'claude',
583
- };
584
- await scaffoldProject(tempDir, options);
585
- const configPath = path.join(tempDir, CONFIG_FILE_NAME);
586
- const content = fs.readFileSync(configPath, 'utf-8');
587
- expect(content).toContain('block_outside_worktree: true');
588
- });
589
- it('should NOT add enforcement hooks for other clients like cursor', async () => {
590
- const options = {
591
- force: false,
592
- full: false,
593
- client: 'cursor',
594
- };
595
- await scaffoldProject(tempDir, options);
596
- const configPath = path.join(tempDir, CONFIG_FILE_NAME);
597
- const content = fs.readFileSync(configPath, 'utf-8');
598
- // Should NOT have claude-code enforcement section (check for the nested enforcement block)
599
- // Note: The default config has agents.defaultClient: claude-code, but no enforcement section
600
- expect(content).not.toContain('block_outside_worktree');
601
- expect(content).not.toMatch(/claude-code:\s*\n\s*enforcement/);
602
- });
603
- });
604
- describe('warning when config already exists', () => {
605
- it('should add warning to result when config yaml already exists', async () => {
606
- // Create existing config file
607
- fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), '# Existing config\ndirectories:\n tasksDir: docs/tasks\n');
608
- const options = {
609
- force: false,
610
- full: false,
611
- };
612
- const result = await scaffoldProject(tempDir, options);
613
- // Should have warning about existing config
614
- expect(result.warnings).toBeDefined();
615
- expect(result.warnings?.some((w) => w.includes('already exists'))).toBe(true);
616
- // Warning should suggest CLI commands
617
- expect(result.warnings?.some((w) => w.includes('CLI') || w.includes('lumenflow'))).toBe(true);
618
- });
619
- it('should skip config file when it already exists (not force)', async () => {
620
- const existingContent = '# My custom config\n';
621
- fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), existingContent);
622
- const options = {
623
- force: false,
624
- full: false,
625
- };
626
- const result = await scaffoldProject(tempDir, options);
627
- expect(result.skipped).toContain(CONFIG_FILE_NAME);
628
- // Content should not be changed
629
- const content = fs.readFileSync(path.join(tempDir, CONFIG_FILE_NAME), 'utf-8');
630
- expect(content).toBe(existingContent);
631
- });
632
- });
633
- describe('post-init output shows CLI commands prominently', () => {
634
- // Note: These test the ScaffoldResult which contains info for the CLI output
635
- // The main() function uses these to print output
636
- it('should include CLI usage guidance in warnings when config exists', async () => {
637
- fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), '# Existing\n');
638
- const options = {
639
- force: false,
640
- full: false,
641
- };
642
- const result = await scaffoldProject(tempDir, options);
643
- // Warning should mention CLI commands for editing config
644
- expect(result.warnings?.some((w) => /pnpm|lumenflow|CLI/i.test(w))).toBe(true);
645
- });
646
- });
647
- describe('warning message suggests CLI commands not manual editing', () => {
648
- it('should warn users to use CLI commands instead of manual editing', async () => {
649
- fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), '# Existing config\n');
650
- const options = {
651
- force: false,
652
- full: false,
653
- };
654
- const result = await scaffoldProject(tempDir, options);
655
- // Should have a warning that mentions not to manually edit
656
- expect(result.warnings?.some((w) => w.includes('manual') || w.includes('CLI') || w.includes('lumenflow'))).toBe(true);
657
- });
658
- });
659
- });
660
- // WU-1362: Branch guard tests for init.ts
661
- describe('WU-1362: branch guard for tracked file writes', () => {
662
- it('should block scaffold when on main branch and targeting main checkout', async () => {
663
- // This test verifies that scaffoldProject checks branch before writing
664
- // Note: This test uses a temp directory (not on main), so it should pass
665
- // The actual blocking only applies when targeting main checkout on main branch
666
- const options = {
667
- force: false,
668
- full: false,
669
- };
670
- // Since we're in a temp dir, not on main branch, this should work
671
- const result = await scaffoldProject(tempDir, options);
672
- expect(result.created.length).toBeGreaterThan(0);
673
- });
674
- it('should allow scaffold in worktree directory', async () => {
675
- // Simulate worktree-like path by creating directory structure
676
- const worktreePath = path.join(tempDir, 'worktrees', 'operations-wu-999');
677
- fs.mkdirSync(worktreePath, { recursive: true });
678
- const options = {
679
- force: false,
680
- full: false,
681
- };
682
- // Should succeed when in worktree-like path
683
- const result = await scaffoldProject(worktreePath, options);
684
- expect(result.created.length).toBeGreaterThan(0);
685
- });
686
- });
687
- // WU-1385: Include wu-sizing-guide.md in lumenflow init onboarding docs
688
- describe('WU-1385: wu-sizing-guide.md scaffolding', () => {
689
- describe('wu-sizing-guide.md creation with --full', () => {
690
- it('should scaffold wu-sizing-guide.md in onboarding docs with --full', async () => {
691
- const options = {
692
- force: false,
693
- full: true,
694
- docsStructure: 'arc42',
695
- };
696
- await scaffoldProject(tempDir, options);
697
- const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
698
- const sizingGuidePath = path.join(onboardingDir, 'wu-sizing-guide.md');
699
- expect(fs.existsSync(sizingGuidePath)).toBe(true);
700
- });
701
- it('should include key sizing guide content', async () => {
702
- const options = {
703
- force: false,
704
- full: true,
705
- docsStructure: 'arc42',
706
- };
707
- await scaffoldProject(tempDir, options);
708
- const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
709
- const sizingGuidePath = path.join(onboardingDir, 'wu-sizing-guide.md');
710
- const content = fs.readFileSync(sizingGuidePath, 'utf-8');
711
- // Should have key content from the sizing guide
712
- expect(content).toContain('Complexity');
713
- expect(content).toContain('Tool Calls');
714
- expect(content).toContain('Context');
715
- });
716
- it('should not scaffold wu-sizing-guide.md with --minimal (full=false)', async () => {
717
- const options = {
718
- force: false,
719
- full: false,
720
- };
721
- await scaffoldProject(tempDir, options);
722
- const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
723
- const sizingGuidePath = path.join(onboardingDir, 'wu-sizing-guide.md');
724
- expect(fs.existsSync(sizingGuidePath)).toBe(false);
725
- });
726
- });
727
- describe('starting-prompt.md references sizing guide', () => {
728
- it('should reference wu-sizing-guide.md in starting-prompt.md', async () => {
729
- const options = {
730
- force: false,
731
- full: true,
732
- docsStructure: 'arc42',
733
- };
734
- await scaffoldProject(tempDir, options);
735
- const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
736
- const startingPromptPath = path.join(onboardingDir, 'starting-prompt.md');
737
- const content = fs.readFileSync(startingPromptPath, 'utf-8');
738
- // Should reference the sizing guide
739
- expect(content).toContain('wu-sizing-guide.md');
740
- });
741
- });
742
- });
743
- // WU-1408: safe-git and pre-commit hook scaffolding
744
- describe('WU-1408: safe-git and pre-commit scaffolding', () => {
745
- describe('safe-git wrapper', () => {
746
- it('should scaffold scripts/safe-git', async () => {
747
- const options = {
748
- force: false,
749
- full: true,
750
- };
751
- await scaffoldProject(tempDir, options);
752
- const safeGitPath = path.join(tempDir, 'scripts', 'safe-git');
753
- expect(fs.existsSync(safeGitPath)).toBe(true);
754
- });
755
- it('should make safe-git executable', async () => {
756
- const options = {
757
- force: false,
758
- full: true,
759
- };
760
- await scaffoldProject(tempDir, options);
761
- const safeGitPath = path.join(tempDir, 'scripts', 'safe-git');
762
- const stats = fs.statSync(safeGitPath);
763
- // Check for executable bit (owner, group, or other)
764
- // eslint-disable-next-line no-bitwise
765
- const isExecutable = (stats.mode & 0o111) !== 0;
766
- expect(isExecutable).toBe(true);
767
- });
768
- it('should include worktree remove block in safe-git', async () => {
769
- const options = {
770
- force: false,
771
- full: true,
772
- };
773
- await scaffoldProject(tempDir, options);
774
- const safeGitPath = path.join(tempDir, 'scripts', 'safe-git');
775
- const content = fs.readFileSync(safeGitPath, 'utf-8');
776
- expect(content).toContain('worktree');
777
- expect(content).toContain('remove');
778
- expect(content).toContain('BLOCKED');
779
- });
780
- it('should scaffold safe-git even in minimal mode', async () => {
781
- const options = {
782
- force: false,
783
- full: false, // minimal mode
784
- };
785
- await scaffoldProject(tempDir, options);
786
- const safeGitPath = path.join(tempDir, 'scripts', 'safe-git');
787
- expect(fs.existsSync(safeGitPath)).toBe(true);
788
- });
789
- });
790
- describe('pre-commit hook', () => {
791
- it('should scaffold .husky/pre-commit', async () => {
792
- const options = {
793
- force: false,
794
- full: true,
795
- };
796
- await scaffoldProject(tempDir, options);
797
- const preCommitPath = path.join(tempDir, '.husky', 'pre-commit');
798
- expect(fs.existsSync(preCommitPath)).toBe(true);
799
- });
800
- it('should make pre-commit executable', async () => {
801
- const options = {
802
- force: false,
803
- full: true,
804
- };
805
- await scaffoldProject(tempDir, options);
806
- const preCommitPath = path.join(tempDir, '.husky', 'pre-commit');
807
- const stats = fs.statSync(preCommitPath);
808
- // eslint-disable-next-line no-bitwise
809
- const isExecutable = (stats.mode & 0o111) !== 0;
810
- expect(isExecutable).toBe(true);
811
- });
812
- it('should NOT run pnpm test in pre-commit hook', async () => {
813
- const options = {
814
- force: false,
815
- full: true,
816
- };
817
- await scaffoldProject(tempDir, options);
818
- const preCommitPath = path.join(tempDir, '.husky', 'pre-commit');
819
- const content = fs.readFileSync(preCommitPath, 'utf-8');
820
- // The pre-commit hook should NOT assume pnpm test exists
821
- expect(content).not.toContain('pnpm test');
822
- expect(content).not.toContain('npm test');
823
- });
824
- it('should block commits to main/master in pre-commit', async () => {
825
- const options = {
826
- force: false,
827
- full: true,
828
- };
829
- await scaffoldProject(tempDir, options);
830
- const preCommitPath = path.join(tempDir, '.husky', 'pre-commit');
831
- const content = fs.readFileSync(preCommitPath, 'utf-8');
832
- // Should protect main branch
833
- expect(content).toContain('main');
834
- expect(content).toContain('BLOCK');
835
- });
836
- it('should scaffold pre-commit even in minimal mode', async () => {
837
- const options = {
838
- force: false,
839
- full: false, // minimal mode
840
- };
841
- await scaffoldProject(tempDir, options);
842
- const preCommitPath = path.join(tempDir, '.husky', 'pre-commit');
843
- expect(fs.existsSync(preCommitPath)).toBe(true);
844
- });
845
- });
846
- });
847
- // WU-1413: MCP server configuration scaffolding
848
- describe('WU-1413: .mcp.json scaffolding', () => {
849
- const MCP_JSON_FILE = '.mcp.json';
850
- describe('.mcp.json creation with --client claude', () => {
851
- it('should scaffold .mcp.json when --client claude is used', async () => {
852
- const options = {
853
- force: false,
854
- full: false,
855
- client: 'claude',
856
- };
857
- await scaffoldProject(tempDir, options);
858
- const mcpJsonPath = path.join(tempDir, MCP_JSON_FILE);
859
- expect(fs.existsSync(mcpJsonPath)).toBe(true);
860
- });
861
- it('should include lumenflow MCP server configuration', async () => {
862
- const options = {
863
- force: false,
864
- full: false,
865
- client: 'claude',
866
- };
867
- await scaffoldProject(tempDir, options);
868
- const mcpJsonPath = path.join(tempDir, MCP_JSON_FILE);
869
- const content = fs.readFileSync(mcpJsonPath, 'utf-8');
870
- const mcpConfig = JSON.parse(content);
871
- // Should have mcpServers key
872
- expect(mcpConfig.mcpServers).toBeDefined();
873
- // Should have lumenflow server entry
874
- expect(mcpConfig.mcpServers.lumenflow).toBeDefined();
875
- // Should use npx command
876
- expect(mcpConfig.mcpServers.lumenflow.command).toBe('npx');
877
- // Should reference @lumenflow/mcp package
878
- expect(mcpConfig.mcpServers.lumenflow.args).toContain('@lumenflow/mcp');
879
- });
880
- it('should be valid JSON', async () => {
881
- const options = {
882
- force: false,
883
- full: false,
884
- client: 'claude',
885
- };
886
- await scaffoldProject(tempDir, options);
887
- const mcpJsonPath = path.join(tempDir, MCP_JSON_FILE);
888
- const content = fs.readFileSync(mcpJsonPath, 'utf-8');
889
- // Should parse without error
890
- expect(() => JSON.parse(content)).not.toThrow();
891
- });
892
- });
893
- describe('.mcp.json creation with --client all', () => {
894
- it('should scaffold .mcp.json when --client all is used', async () => {
895
- const options = {
896
- force: false,
897
- full: false,
898
- client: 'all',
899
- };
900
- await scaffoldProject(tempDir, options);
901
- const mcpJsonPath = path.join(tempDir, MCP_JSON_FILE);
902
- expect(fs.existsSync(mcpJsonPath)).toBe(true);
903
- });
904
- });
905
- describe('.mcp.json NOT created with other clients', () => {
906
- it('should NOT scaffold .mcp.json when --client none is used', async () => {
907
- const options = {
908
- force: false,
909
- full: false,
910
- client: 'none',
911
- };
912
- await scaffoldProject(tempDir, options);
913
- const mcpJsonPath = path.join(tempDir, MCP_JSON_FILE);
914
- expect(fs.existsSync(mcpJsonPath)).toBe(false);
915
- });
916
- it('should NOT scaffold .mcp.json when --client cursor is used', async () => {
917
- const options = {
918
- force: false,
919
- full: false,
920
- client: 'cursor',
921
- };
922
- await scaffoldProject(tempDir, options);
923
- const mcpJsonPath = path.join(tempDir, MCP_JSON_FILE);
924
- expect(fs.existsSync(mcpJsonPath)).toBe(false);
925
- });
926
- it('should NOT scaffold .mcp.json when no client is specified', async () => {
927
- const options = {
928
- force: false,
929
- full: false,
930
- };
931
- await scaffoldProject(tempDir, options);
932
- const mcpJsonPath = path.join(tempDir, MCP_JSON_FILE);
933
- expect(fs.existsSync(mcpJsonPath)).toBe(false);
934
- });
935
- });
936
- describe('.mcp.json file modes', () => {
937
- it('should skip .mcp.json if it already exists (skip mode)', async () => {
938
- // Create existing .mcp.json
939
- const existingContent = '{"mcpServers":{"custom":{}}}';
940
- fs.writeFileSync(path.join(tempDir, MCP_JSON_FILE), existingContent);
941
- const options = {
942
- force: false,
943
- full: false,
944
- client: 'claude',
945
- };
946
- const result = await scaffoldProject(tempDir, options);
947
- expect(result.skipped).toContain(MCP_JSON_FILE);
948
- // Content should not be changed
949
- const content = fs.readFileSync(path.join(tempDir, MCP_JSON_FILE), 'utf-8');
950
- expect(content).toBe(existingContent);
951
- });
952
- it('should overwrite .mcp.json in force mode', async () => {
953
- // Create existing .mcp.json
954
- fs.writeFileSync(path.join(tempDir, MCP_JSON_FILE), '{"custom":true}');
955
- const options = {
956
- force: true,
957
- full: false,
958
- client: 'claude',
959
- };
960
- const result = await scaffoldProject(tempDir, options);
961
- expect(result.created).toContain(MCP_JSON_FILE);
962
- const content = fs.readFileSync(path.join(tempDir, MCP_JSON_FILE), 'utf-8');
963
- const mcpConfig = JSON.parse(content);
964
- expect(mcpConfig.mcpServers.lumenflow).toBeDefined();
965
- });
966
- });
967
- });
968
- });