@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,388 +0,0 @@
1
- /**
2
- * WU Lifecycle Integration Tests (WU-1363)
3
- *
4
- * Integration tests covering the full WU lifecycle:
5
- * - AC1: wu:create, wu:claim, wu:status
6
- * - AC2: wu:prep, wu:done workflow
7
- *
8
- * These tests validate the end-to-end behavior of WU lifecycle commands
9
- * by running them in isolated temporary directories with proper git setup.
10
- *
11
- * TDD: Tests written BEFORE implementation verification.
12
- */
13
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
14
- import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
15
- import { join } from 'node:path';
16
- import { tmpdir } from 'node:os';
17
- import { execFileSync } from 'node:child_process';
18
- import { parseYAML, stringifyYAML } from '@lumenflow/core/dist/wu-yaml.js';
19
- import { WU_STATUS } from '@lumenflow/core/dist/wu-constants.js';
20
- // Test constants
21
- const TEST_WU_ID = 'WU-9901';
22
- const TEST_LANE = 'Framework: CLI';
23
- const TEST_TITLE = 'Integration test WU';
24
- const TEST_DESCRIPTION = 'Context: Integration test. Problem: Need to test lifecycle. Solution: Run integration tests.';
25
- /**
26
- * Helper to create a minimal LumenFlow project structure
27
- */
28
- function createTestProject(baseDir) {
29
- // Create directory structure
30
- const dirs = [
31
- 'docs/04-operations/tasks/wu',
32
- 'docs/04-operations/tasks/initiatives',
33
- '.lumenflow/state',
34
- '.lumenflow/stamps',
35
- 'packages/@lumenflow/cli/src/__tests__',
36
- ];
37
- for (const dir of dirs) {
38
- mkdirSync(join(baseDir, dir), { recursive: true });
39
- }
40
- // Create minimal .lumenflow.config.yaml
41
- const configContent = `
42
- version: 1
43
- lanes:
44
- definitions:
45
- - name: 'Framework: CLI'
46
- wip_limit: 1
47
- code_paths:
48
- - 'packages/@lumenflow/cli/**'
49
- git:
50
- requireRemote: false
51
- experimental:
52
- context_validation: false
53
- `;
54
- writeFileSync(join(baseDir, '.lumenflow.config.yaml'), configContent);
55
- // Create minimal package.json
56
- const packageJson = {
57
- name: 'test-project',
58
- version: '1.0.0',
59
- type: 'module',
60
- };
61
- writeFileSync(join(baseDir, 'package.json'), JSON.stringify(packageJson, null, 2));
62
- // Initialize git repo using execFileSync (safer than execSync)
63
- execFileSync('git', ['init'], { cwd: baseDir, stdio: 'pipe' });
64
- execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: baseDir, stdio: 'pipe' });
65
- execFileSync('git', ['config', 'user.name', 'Test'], { cwd: baseDir, stdio: 'pipe' });
66
- writeFileSync(join(baseDir, 'README.md'), '# Test Project\n');
67
- execFileSync('git', ['add', '.'], { cwd: baseDir, stdio: 'pipe' });
68
- execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: baseDir, stdio: 'pipe' });
69
- }
70
- /**
71
- * Helper to create a WU YAML file directly
72
- */
73
- function createWUFile(baseDir, id, options = {}) {
74
- const wuDir = join(baseDir, 'docs/04-operations/tasks/wu');
75
- const wuPath = join(wuDir, `${id}.yaml`);
76
- const doc = {
77
- id,
78
- title: options.title || TEST_TITLE,
79
- lane: options.lane || TEST_LANE,
80
- status: options.status || WU_STATUS.READY,
81
- type: 'feature',
82
- priority: 'P2',
83
- created: '2026-02-03',
84
- description: options.description || TEST_DESCRIPTION,
85
- acceptance: options.acceptance || ['Test criterion 1', 'Test criterion 2'],
86
- code_paths: options.codePaths || ['packages/@lumenflow/cli/src/__tests__'],
87
- tests: {
88
- unit: ['packages/@lumenflow/cli/src/__tests__/wu-lifecycle-integration.test.ts'],
89
- },
90
- exposure: 'backend-only',
91
- };
92
- writeFileSync(wuPath, stringifyYAML(doc));
93
- return wuPath;
94
- }
95
- describe('WU Lifecycle Integration Tests (WU-1363)', () => {
96
- let tempDir;
97
- let originalCwd;
98
- beforeEach(() => {
99
- tempDir = join(tmpdir(), `wu-lifecycle-integration-${Date.now()}-${Math.random().toString(36).slice(2)}`);
100
- mkdirSync(tempDir, { recursive: true });
101
- originalCwd = process.cwd();
102
- createTestProject(tempDir);
103
- vi.resetModules(); // Reset module cache for fresh imports
104
- });
105
- afterEach(() => {
106
- process.chdir(originalCwd);
107
- if (existsSync(tempDir)) {
108
- try {
109
- rmSync(tempDir, { recursive: true, force: true });
110
- }
111
- catch {
112
- // Ignore cleanup errors
113
- }
114
- }
115
- vi.clearAllMocks();
116
- });
117
- describe('AC1: Integration tests for wu:create, wu:claim, wu:status', () => {
118
- describe('wu:create core functionality', () => {
119
- it('should create a WU YAML file with correct structure', async () => {
120
- // Arrange
121
- process.chdir(tempDir);
122
- // Act - Create WU file directly (simulating wu:create core behavior)
123
- const wuPath = createWUFile(tempDir, TEST_WU_ID, {
124
- status: WU_STATUS.READY,
125
- lane: TEST_LANE,
126
- title: TEST_TITLE,
127
- description: TEST_DESCRIPTION,
128
- acceptance: ['Criterion 1', 'Criterion 2'],
129
- codePaths: ['packages/@lumenflow/cli/src'],
130
- });
131
- // Assert
132
- expect(existsSync(wuPath)).toBe(true);
133
- const content = readFileSync(wuPath, 'utf-8');
134
- const doc = parseYAML(content);
135
- expect(doc.id).toBe(TEST_WU_ID);
136
- expect(doc.lane).toBe(TEST_LANE);
137
- expect(doc.status).toBe(WU_STATUS.READY);
138
- expect(doc.title).toBe(TEST_TITLE);
139
- expect(doc.acceptance).toHaveLength(2);
140
- });
141
- it('should validate required fields are present', () => {
142
- // Arrange
143
- process.chdir(tempDir);
144
- // Create a minimal WU and verify required fields
145
- const wuPath = createWUFile(tempDir, TEST_WU_ID);
146
- const content = readFileSync(wuPath, 'utf-8');
147
- const doc = parseYAML(content);
148
- // Assert required fields exist
149
- expect(doc.id).toBeDefined();
150
- expect(doc.title).toBeDefined();
151
- expect(doc.lane).toBeDefined();
152
- expect(doc.status).toBeDefined();
153
- expect(doc.description).toBeDefined();
154
- expect(doc.acceptance).toBeDefined();
155
- expect(doc.code_paths).toBeDefined();
156
- });
157
- });
158
- describe('wu:claim core functionality', () => {
159
- it('should update WU status to in_progress when claimed', async () => {
160
- // Arrange
161
- process.chdir(tempDir);
162
- const wuPath = createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.READY });
163
- // Act - Simulate claim by updating status
164
- const content = readFileSync(wuPath, 'utf-8');
165
- const doc = parseYAML(content);
166
- doc.status = WU_STATUS.IN_PROGRESS;
167
- doc.claimed_at = new Date().toISOString();
168
- doc.worktree_path = `worktrees/framework-cli-${TEST_WU_ID.toLowerCase()}`;
169
- writeFileSync(wuPath, stringifyYAML(doc));
170
- // Assert
171
- const updatedContent = readFileSync(wuPath, 'utf-8');
172
- const updatedDoc = parseYAML(updatedContent);
173
- expect(updatedDoc.status).toBe(WU_STATUS.IN_PROGRESS);
174
- expect(updatedDoc.claimed_at).toBeDefined();
175
- expect(updatedDoc.worktree_path).toContain('worktrees');
176
- });
177
- it('should reject claim when WU does not exist', () => {
178
- // Arrange
179
- process.chdir(tempDir);
180
- const nonExistentPath = join(tempDir, 'docs/04-operations/tasks/wu', 'WU-9999.yaml');
181
- // Assert
182
- expect(existsSync(nonExistentPath)).toBe(false);
183
- });
184
- it('should reject claim when WU is not in ready status', () => {
185
- // Arrange
186
- process.chdir(tempDir);
187
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.DONE });
188
- // Read and verify status prevents claiming
189
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID}.yaml`);
190
- const content = readFileSync(wuPath, 'utf-8');
191
- const doc = parseYAML(content);
192
- // Assert
193
- expect(doc.status).toBe(WU_STATUS.DONE);
194
- expect(doc.status).not.toBe(WU_STATUS.READY);
195
- });
196
- });
197
- describe('wu:status core functionality', () => {
198
- it('should return WU details correctly', () => {
199
- // Arrange
200
- process.chdir(tempDir);
201
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.READY });
202
- // Act - Read WU status
203
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID}.yaml`);
204
- const content = readFileSync(wuPath, 'utf-8');
205
- const doc = parseYAML(content);
206
- // Assert
207
- expect(doc.id).toBe(TEST_WU_ID);
208
- expect(doc.status).toBe(WU_STATUS.READY);
209
- expect(doc.lane).toBe(TEST_LANE);
210
- });
211
- it('should return valid commands for ready status', () => {
212
- // Arrange
213
- process.chdir(tempDir);
214
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.READY });
215
- // Act
216
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID}.yaml`);
217
- const content = readFileSync(wuPath, 'utf-8');
218
- const doc = parseYAML(content);
219
- // Compute valid commands based on status
220
- const validCommands = doc.status === WU_STATUS.READY
221
- ? ['wu:claim', 'wu:edit', 'wu:delete']
222
- : ['wu:prep', 'wu:block'];
223
- // Assert
224
- expect(validCommands).toContain('wu:claim');
225
- });
226
- it('should return valid commands for in_progress status', () => {
227
- // Arrange
228
- process.chdir(tempDir);
229
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.IN_PROGRESS });
230
- // Act
231
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID}.yaml`);
232
- const content = readFileSync(wuPath, 'utf-8');
233
- const doc = parseYAML(content);
234
- // Compute valid commands based on status
235
- const validCommands = doc.status === WU_STATUS.IN_PROGRESS ? ['wu:prep', 'wu:block'] : ['wu:claim'];
236
- // Assert
237
- expect(validCommands).toContain('wu:prep');
238
- });
239
- });
240
- });
241
- describe('AC2: Integration tests for wu:prep, wu:done workflow', () => {
242
- describe('wu:prep core functionality', () => {
243
- it('should validate WU is in in_progress status', () => {
244
- // Arrange
245
- process.chdir(tempDir);
246
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.IN_PROGRESS });
247
- // Act
248
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID}.yaml`);
249
- const content = readFileSync(wuPath, 'utf-8');
250
- const doc = parseYAML(content);
251
- // Assert
252
- expect(doc.status).toBe(WU_STATUS.IN_PROGRESS);
253
- });
254
- it('should generate next command pointing to wu:done', () => {
255
- // Arrange
256
- process.chdir(tempDir);
257
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.IN_PROGRESS });
258
- // Act - Generate next command
259
- const mainCheckoutPath = tempDir;
260
- const nextCommand = `cd ${mainCheckoutPath} && pnpm wu:done --id ${TEST_WU_ID}`;
261
- // Assert
262
- expect(nextCommand).toContain('wu:done');
263
- expect(nextCommand).toContain(TEST_WU_ID);
264
- expect(nextCommand).toContain(mainCheckoutPath);
265
- });
266
- it('should reject prep when WU is not in_progress', () => {
267
- // Arrange
268
- process.chdir(tempDir);
269
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.READY });
270
- // Act
271
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID}.yaml`);
272
- const content = readFileSync(wuPath, 'utf-8');
273
- const doc = parseYAML(content);
274
- // Assert
275
- expect(doc.status).not.toBe(WU_STATUS.IN_PROGRESS);
276
- });
277
- });
278
- describe('wu:done core functionality', () => {
279
- it('should create stamp file on completion', () => {
280
- // Arrange
281
- process.chdir(tempDir);
282
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.IN_PROGRESS });
283
- // Act - Create stamp file
284
- const stampDir = join(tempDir, '.lumenflow/stamps');
285
- mkdirSync(stampDir, { recursive: true });
286
- const stampPath = join(stampDir, `${TEST_WU_ID}.done`);
287
- const stampContent = {
288
- completed_at: new Date().toISOString(),
289
- wu_id: TEST_WU_ID,
290
- };
291
- writeFileSync(stampPath, JSON.stringify(stampContent, null, 2));
292
- // Assert
293
- expect(existsSync(stampPath)).toBe(true);
294
- const savedStamp = JSON.parse(readFileSync(stampPath, 'utf-8'));
295
- expect(savedStamp.wu_id).toBe(TEST_WU_ID);
296
- });
297
- it('should update WU status to done', () => {
298
- // Arrange
299
- process.chdir(tempDir);
300
- const wuPath = createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.IN_PROGRESS });
301
- // Act - Update status to done
302
- const content = readFileSync(wuPath, 'utf-8');
303
- const doc = parseYAML(content);
304
- doc.status = WU_STATUS.DONE;
305
- doc.completed_at = new Date().toISOString();
306
- writeFileSync(wuPath, stringifyYAML(doc));
307
- // Assert
308
- const updatedContent = readFileSync(wuPath, 'utf-8');
309
- const updatedDoc = parseYAML(updatedContent);
310
- expect(updatedDoc.status).toBe(WU_STATUS.DONE);
311
- expect(updatedDoc.completed_at).toBeDefined();
312
- });
313
- it('should reject done when WU is not in_progress', () => {
314
- // Arrange
315
- process.chdir(tempDir);
316
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.READY });
317
- // Act
318
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID}.yaml`);
319
- const content = readFileSync(wuPath, 'utf-8');
320
- const doc = parseYAML(content);
321
- // Assert - Cannot complete a WU that isn't in_progress
322
- expect(doc.status).not.toBe(WU_STATUS.IN_PROGRESS);
323
- });
324
- it('should support skip-gates flag with reason and fix-wu', () => {
325
- // Arrange
326
- process.chdir(tempDir);
327
- createWUFile(tempDir, TEST_WU_ID, { status: WU_STATUS.IN_PROGRESS });
328
- // Act - Simulate skip-gates audit log
329
- const skipGatesLog = {
330
- wu_id: TEST_WU_ID,
331
- skipped_at: new Date().toISOString(),
332
- reason: 'pre-existing on main',
333
- fix_wu: 'WU-1234',
334
- };
335
- const auditPath = join(tempDir, '.lumenflow/skip-gates-audit.log');
336
- writeFileSync(auditPath, JSON.stringify(skipGatesLog) + '\n');
337
- // Assert
338
- expect(existsSync(auditPath)).toBe(true);
339
- const logContent = readFileSync(auditPath, 'utf-8');
340
- expect(logContent).toContain('pre-existing on main');
341
- expect(logContent).toContain('WU-1234');
342
- });
343
- });
344
- describe('wu:prep + wu:done complete workflow', () => {
345
- it('should complete full lifecycle from create to done', () => {
346
- // This test validates the complete workflow state transitions:
347
- // 1. Create WU (status: ready)
348
- // 2. Claim WU (status: in_progress, worktree created)
349
- // 3. Prep WU (gates run, provides next command)
350
- // 4. Done WU (status: done, stamp created)
351
- // Arrange
352
- process.chdir(tempDir);
353
- // Step 1: Create WU
354
- const wuPath = createWUFile(tempDir, TEST_WU_ID, {
355
- status: WU_STATUS.READY,
356
- lane: TEST_LANE,
357
- title: TEST_TITLE,
358
- description: TEST_DESCRIPTION,
359
- acceptance: ['Full lifecycle test'],
360
- });
361
- expect(existsSync(wuPath)).toBe(true);
362
- // Step 2: Simulate Claim WU
363
- let doc = parseYAML(readFileSync(wuPath, 'utf-8'));
364
- expect(doc.status).toBe(WU_STATUS.READY);
365
- doc.status = WU_STATUS.IN_PROGRESS;
366
- doc.claimed_at = new Date().toISOString();
367
- doc.worktree_path = `worktrees/framework-cli-${TEST_WU_ID.toLowerCase()}`;
368
- writeFileSync(wuPath, stringifyYAML(doc));
369
- // Step 3: Verify Prep is valid (status check)
370
- doc = parseYAML(readFileSync(wuPath, 'utf-8'));
371
- expect(doc.status).toBe(WU_STATUS.IN_PROGRESS);
372
- const nextCommand = `cd ${tempDir} && pnpm wu:done --id ${TEST_WU_ID}`;
373
- expect(nextCommand).toContain('wu:done');
374
- // Step 4: Complete WU
375
- doc.status = WU_STATUS.DONE;
376
- doc.completed_at = new Date().toISOString();
377
- writeFileSync(wuPath, stringifyYAML(doc));
378
- // Create stamp
379
- const stampPath = join(tempDir, '.lumenflow/stamps', `${TEST_WU_ID}.done`);
380
- writeFileSync(stampPath, JSON.stringify({ completed_at: new Date().toISOString() }));
381
- // Verify final state
382
- expect(existsSync(stampPath)).toBe(true);
383
- doc = parseYAML(readFileSync(wuPath, 'utf-8'));
384
- expect(doc.status).toBe(WU_STATUS.DONE);
385
- });
386
- });
387
- });
388
- });
@@ -1,35 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- vi.mock('node:child_process', async (importOriginal) => {
3
- const actual = await importOriginal();
4
- return { ...actual, spawnSync: vi.fn() };
5
- });
6
- vi.mock('node:fs', async (importOriginal) => {
7
- const actual = await importOriginal();
8
- return { ...actual, existsSync: vi.fn() };
9
- });
10
- describe('wu-prep default exec helpers (WU-1441)', () => {
11
- beforeEach(() => {
12
- vi.resetModules();
13
- vi.resetAllMocks();
14
- });
15
- it('uses node + dist wu-validate for JSON comparison when available', async () => {
16
- const { spawnSync } = await import('node:child_process');
17
- const { existsSync } = await import('node:fs');
18
- // Pretend dist sibling exists so defaultExec picks node+dist path (not pnpm on main).
19
- vi.mocked(existsSync).mockReturnValue(true);
20
- // Both worktree and main should report WU-1 invalid.
21
- vi.mocked(spawnSync).mockReturnValue({
22
- status: 1,
23
- stdout: JSON.stringify({ invalid: [{ wuId: 'WU-1' }] }),
24
- stderr: '',
25
- });
26
- const { checkPreExistingFailures } = await import('../wu-prep.js');
27
- const result = await checkPreExistingFailures({ mainCheckout: '/repo' });
28
- expect(result.error).toBeUndefined();
29
- expect(result.hasPreExisting).toBe(true);
30
- expect(result.hasNewFailures).toBe(false);
31
- // Default exec should run node directly, not "pnpm wu:validate" from the main checkout.
32
- expect(vi.mocked(spawnSync).mock.calls.length).toBeGreaterThan(0);
33
- expect(vi.mocked(spawnSync).mock.calls[0]?.[0]).toBe('node');
34
- });
35
- });
@@ -1,140 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import * as locationResolver from '@lumenflow/core/dist/context/location-resolver.js';
3
- import * as wuYaml from '@lumenflow/core/dist/wu-yaml.js';
4
- import { CONTEXT_VALIDATION, WU_STATUS } from '@lumenflow/core/dist/wu-constants.js';
5
- const { LOCATION_TYPES } = CONTEXT_VALIDATION;
6
- // Mock dependencies
7
- vi.mock('@lumenflow/core/dist/context/location-resolver.js');
8
- vi.mock('@lumenflow/core/dist/error-handler.js');
9
- vi.mock('@lumenflow/core/dist/wu-yaml.js');
10
- vi.mock('../gates.js', () => ({
11
- runGates: vi.fn().mockResolvedValue(true),
12
- }));
13
- describe('wu-prep (WU-1223)', () => {
14
- beforeEach(() => {
15
- vi.resetAllMocks();
16
- });
17
- describe('location validation', () => {
18
- it('should error when run from main checkout', async () => {
19
- // Mock location as main checkout
20
- vi.mocked(locationResolver.resolveLocation).mockResolvedValue({
21
- type: LOCATION_TYPES.MAIN,
22
- cwd: '/repo',
23
- gitRoot: '/repo',
24
- mainCheckout: '/repo',
25
- worktreeName: null,
26
- worktreeWuId: null,
27
- });
28
- // Import after mocks are set up
29
- const { resolveLocation } = await import('@lumenflow/core/dist/context/location-resolver.js');
30
- const location = await resolveLocation();
31
- // Verify the mock returns main
32
- expect(location.type).toBe(LOCATION_TYPES.MAIN);
33
- });
34
- it('should proceed when run from worktree', async () => {
35
- // Mock location as worktree
36
- vi.mocked(locationResolver.resolveLocation).mockResolvedValue({
37
- type: LOCATION_TYPES.WORKTREE,
38
- cwd: '/repo/worktrees/framework-cli-wu-1223',
39
- gitRoot: '/repo/worktrees/framework-cli-wu-1223',
40
- mainCheckout: '/repo',
41
- worktreeName: 'framework-cli-wu-1223',
42
- worktreeWuId: 'WU-1223',
43
- });
44
- const { resolveLocation } = await import('@lumenflow/core/dist/context/location-resolver.js');
45
- const location = await resolveLocation();
46
- // Verify the mock returns worktree
47
- expect(location.type).toBe(LOCATION_TYPES.WORKTREE);
48
- expect(location.mainCheckout).toBe('/repo');
49
- });
50
- });
51
- describe('WU status validation', () => {
52
- it('should only allow in_progress WUs', async () => {
53
- // Mock WU YAML with wrong status
54
- const mockDoc = {
55
- id: 'WU-1223',
56
- status: WU_STATUS.DONE,
57
- title: 'Test WU',
58
- };
59
- vi.mocked(wuYaml.readWU).mockReturnValue(mockDoc);
60
- const { readWU } = await import('@lumenflow/core/dist/wu-yaml.js');
61
- const doc = readWU('path/to/wu.yaml', 'WU-1223');
62
- expect(doc.status).toBe(WU_STATUS.DONE);
63
- expect(doc.status).not.toBe(WU_STATUS.IN_PROGRESS);
64
- });
65
- });
66
- describe('success message', () => {
67
- it('should include copy-paste instruction with main path', async () => {
68
- // The success message should include:
69
- // 1. Main checkout path
70
- // 2. WU ID
71
- // 3. Copy-paste command: cd <main> && pnpm wu:done --id <WU-ID>
72
- const mainCheckout = '/repo';
73
- const wuId = 'WU-1223';
74
- // Build expected command that would be in the success message
75
- const expectedCommand = `cd ${mainCheckout} && pnpm wu:done --id ${wuId}`;
76
- expect(expectedCommand).toBe('cd /repo && pnpm wu:done --id WU-1223');
77
- });
78
- });
79
- });
80
- describe('wu:done worktree check (WU-1223)', () => {
81
- beforeEach(() => {
82
- vi.resetAllMocks();
83
- });
84
- it('should error when run from worktree with guidance to use wu:prep', async () => {
85
- // Mock location as worktree
86
- vi.mocked(locationResolver.resolveLocation).mockResolvedValue({
87
- type: LOCATION_TYPES.WORKTREE,
88
- cwd: '/repo/worktrees/framework-cli-wu-1223',
89
- gitRoot: '/repo/worktrees/framework-cli-wu-1223',
90
- mainCheckout: '/repo',
91
- worktreeName: 'framework-cli-wu-1223',
92
- worktreeWuId: 'WU-1223',
93
- });
94
- const { resolveLocation } = await import('@lumenflow/core/dist/context/location-resolver.js');
95
- const location = await resolveLocation();
96
- // The error message should guide user to wu:prep workflow
97
- expect(location.type).toBe(LOCATION_TYPES.WORKTREE);
98
- // Error message should contain:
99
- const errorShouldContain = [
100
- 'wu:prep', // Mention the new command
101
- 'main checkout', // Explain where wu:done should run
102
- '/repo', // Main checkout path
103
- ];
104
- // Build the expected error content
105
- const expectedGuidance = `pnpm wu:prep --id WU-1223`;
106
- expect(expectedGuidance).toContain('wu:prep');
107
- });
108
- });
109
- describe('wu-prep spec-linter classification (WU-1441)', () => {
110
- it('should detect pre-existing failures only', async () => {
111
- const { classifySpecLinterFailures } = await import('../wu-prep.js');
112
- const result = classifySpecLinterFailures({
113
- mainInvalid: ['WU-1'],
114
- worktreeInvalid: ['WU-1'],
115
- });
116
- expect(result.hasPreExisting).toBe(true);
117
- expect(result.hasNewFailures).toBe(false);
118
- expect(result.newFailures).toEqual([]);
119
- });
120
- it('should detect newly introduced failures', async () => {
121
- const { classifySpecLinterFailures } = await import('../wu-prep.js');
122
- const result = classifySpecLinterFailures({
123
- mainInvalid: ['WU-1'],
124
- worktreeInvalid: ['WU-1', 'WU-2'],
125
- });
126
- expect(result.hasPreExisting).toBe(true);
127
- expect(result.hasNewFailures).toBe(true);
128
- expect(result.newFailures).toEqual(['WU-2']);
129
- });
130
- it('should detect failures when main is clean', async () => {
131
- const { classifySpecLinterFailures } = await import('../wu-prep.js');
132
- const result = classifySpecLinterFailures({
133
- mainInvalid: [],
134
- worktreeInvalid: ['WU-3'],
135
- });
136
- expect(result.hasPreExisting).toBe(false);
137
- expect(result.hasNewFailures).toBe(true);
138
- expect(result.newFailures).toEqual(['WU-3']);
139
- });
140
- });
@@ -1,97 +0,0 @@
1
- /**
2
- * @file wu-proto.test.ts
3
- * Test suite for wu:proto command (WU-1359)
4
- *
5
- * WU-1359: Add wu:proto convenience command for rapid prototyping
6
- *
7
- * Tests:
8
- * - wu:proto creates WU with type: prototype
9
- * - wu:proto has relaxed validation (no --acceptance required)
10
- * - wu:proto immediately claims the WU
11
- * - wu:proto prints cd command to worktree
12
- */
13
- import { describe, it, expect } from 'vitest';
14
- // WU-1359: Import wu:proto validation and helpers
15
- import { validateProtoSpec } from '../wu-proto.js';
16
- /** Test constants to avoid duplicate string literals (sonarjs/no-duplicate-string) */
17
- const TEST_WU_ID = 'WU-9999';
18
- const TEST_LANE = 'Framework: CLI';
19
- const TEST_TITLE = 'Quick prototype';
20
- const TEST_DESCRIPTION = 'Context: testing.\nProblem: need to test.\nSolution: add tests.';
21
- describe('wu:proto command (WU-1359)', () => {
22
- describe('validateProtoSpec relaxed validation', () => {
23
- it('should pass validation without --acceptance', () => {
24
- const result = validateProtoSpec({
25
- id: TEST_WU_ID,
26
- lane: TEST_LANE,
27
- title: TEST_TITLE,
28
- opts: {
29
- description: TEST_DESCRIPTION,
30
- codePaths: ['packages/@lumenflow/cli/src/wu-proto.ts'],
31
- },
32
- });
33
- expect(result.valid).toBe(true);
34
- expect(result.errors).toHaveLength(0);
35
- });
36
- it('should pass validation without --exposure', () => {
37
- const result = validateProtoSpec({
38
- id: TEST_WU_ID,
39
- lane: TEST_LANE,
40
- title: TEST_TITLE,
41
- opts: {
42
- description: TEST_DESCRIPTION,
43
- },
44
- });
45
- expect(result.valid).toBe(true);
46
- expect(result.errors).toHaveLength(0);
47
- });
48
- it('should pass validation without --code-paths', () => {
49
- const result = validateProtoSpec({
50
- id: TEST_WU_ID,
51
- lane: TEST_LANE,
52
- title: TEST_TITLE,
53
- opts: {
54
- description: TEST_DESCRIPTION,
55
- },
56
- });
57
- expect(result.valid).toBe(true);
58
- expect(result.errors).toHaveLength(0);
59
- });
60
- it('should require lane', () => {
61
- const result = validateProtoSpec({
62
- id: TEST_WU_ID,
63
- lane: '',
64
- title: TEST_TITLE,
65
- opts: {},
66
- });
67
- expect(result.valid).toBe(false);
68
- expect(result.errors.some((e) => e.toLowerCase().includes('lane'))).toBe(true);
69
- });
70
- it('should require title', () => {
71
- const result = validateProtoSpec({
72
- id: TEST_WU_ID,
73
- lane: TEST_LANE,
74
- title: '',
75
- opts: {},
76
- });
77
- expect(result.valid).toBe(false);
78
- expect(result.errors.some((e) => e.toLowerCase().includes('title'))).toBe(true);
79
- });
80
- });
81
- describe('prototype WU type', () => {
82
- it('should set type to prototype', () => {
83
- // The buildProtoWUContent function should return type: prototype
84
- // We test this indirectly through the validateProtoSpec which checks content
85
- const result = validateProtoSpec({
86
- id: TEST_WU_ID,
87
- lane: TEST_LANE,
88
- title: TEST_TITLE,
89
- opts: {
90
- description: TEST_DESCRIPTION,
91
- },
92
- });
93
- // Prototype WUs should always be valid with minimal input
94
- expect(result.valid).toBe(true);
95
- });
96
- });
97
- });