@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,442 +0,0 @@
1
- /**
2
- * Initiative Orchestration E2E Tests (WU-1363)
3
- *
4
- * End-to-end tests for initiative orchestration:
5
- * - AC5: E2E test for initiative orchestration
6
- *
7
- * These tests validate the complete initiative workflow:
8
- * - Creating initiatives with phases
9
- * - Adding WUs to initiatives
10
- * - Tracking initiative progress
11
- * - Wave-based orchestration
12
- *
13
- * TDD: Tests written BEFORE implementation verification.
14
- */
15
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
16
- import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
17
- import { join } from 'node:path';
18
- import { tmpdir } from 'node:os';
19
- import { execFileSync } from 'node:child_process';
20
- import { parseYAML, stringifyYAML } from '@lumenflow/core/dist/wu-yaml.js';
21
- import { WU_STATUS } from '@lumenflow/core/dist/wu-constants.js';
22
- // Test constants
23
- const TEST_INIT_ID = 'INIT-901';
24
- const TEST_INIT_TITLE = 'Test Initiative';
25
- const TEST_WU_ID_1 = 'WU-9930';
26
- const TEST_WU_ID_2 = 'WU-9931';
27
- const TEST_WU_ID_3 = 'WU-9932';
28
- const TEST_LANE = 'Framework: CLI';
29
- /**
30
- * Helper to create a test project for initiative orchestration
31
- */
32
- function createInitiativeProject(baseDir) {
33
- const dirs = [
34
- 'docs/04-operations/tasks/wu',
35
- 'docs/04-operations/tasks/initiatives',
36
- '.lumenflow/state',
37
- '.lumenflow/memory',
38
- '.lumenflow/stamps',
39
- 'packages/@lumenflow/cli/src',
40
- ];
41
- for (const dir of dirs) {
42
- mkdirSync(join(baseDir, dir), { recursive: true });
43
- }
44
- // Create config
45
- const configContent = `
46
- version: 1
47
- lanes:
48
- definitions:
49
- - name: 'Framework: CLI'
50
- wip_limit: 1
51
- code_paths:
52
- - 'packages/@lumenflow/cli/**'
53
- - name: 'Framework: Core'
54
- wip_limit: 1
55
- code_paths:
56
- - 'packages/@lumenflow/core/**'
57
- git:
58
- requireRemote: false
59
- initiatives:
60
- enabled: true
61
- `;
62
- writeFileSync(join(baseDir, '.lumenflow.config.yaml'), configContent);
63
- // Initialize git
64
- execFileSync('git', ['init'], { cwd: baseDir, stdio: 'pipe' });
65
- execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: baseDir, stdio: 'pipe' });
66
- execFileSync('git', ['config', 'user.name', 'Test'], { cwd: baseDir, stdio: 'pipe' });
67
- writeFileSync(join(baseDir, 'README.md'), '# Test\n');
68
- execFileSync('git', ['add', '.'], { cwd: baseDir, stdio: 'pipe' });
69
- execFileSync('git', ['commit', '-m', 'init'], { cwd: baseDir, stdio: 'pipe' });
70
- }
71
- /**
72
- * Helper to create an initiative YAML file
73
- */
74
- function createInitiative(baseDir, id, options = {}) {
75
- const initDir = join(baseDir, 'docs/04-operations/tasks/initiatives');
76
- const initPath = join(initDir, `${id}.yaml`);
77
- const doc = {
78
- id,
79
- slug: id.toLowerCase().replace('init-', 'initiative-'),
80
- title: options.title || TEST_INIT_TITLE,
81
- status: options.status || 'open',
82
- created: '2026-02-03',
83
- description: 'Test initiative for E2E testing',
84
- phases: options.phases || [
85
- { name: 'Phase 1: Foundation', status: 'in_progress' },
86
- { name: 'Phase 2: Features', status: 'pending' },
87
- ],
88
- wus: options.wus || [],
89
- };
90
- writeFileSync(initPath, stringifyYAML(doc));
91
- return initPath;
92
- }
93
- /**
94
- * Helper to create a WU linked to an initiative
95
- */
96
- function createWUForInitiative(baseDir, id, options = {}) {
97
- const wuDir = join(baseDir, 'docs/04-operations/tasks/wu');
98
- const wuPath = join(wuDir, `${id}.yaml`);
99
- const doc = {
100
- id,
101
- title: `WU for ${options.initiative || 'testing'}`,
102
- lane: options.lane || TEST_LANE,
103
- status: options.status || WU_STATUS.READY,
104
- type: 'feature',
105
- priority: 'P2',
106
- created: '2026-02-03',
107
- description: 'Context: Test. Problem: Testing. Solution: Test it.',
108
- acceptance: ['Test passes'],
109
- code_paths: ['packages/@lumenflow/cli/src'],
110
- tests: { unit: ['test.test.ts'] },
111
- exposure: 'backend-only',
112
- dependencies: options.dependencies || [],
113
- };
114
- if (options.initiative) {
115
- doc.initiative = options.initiative;
116
- }
117
- if (options.phase !== undefined) {
118
- doc.phase = options.phase;
119
- }
120
- writeFileSync(wuPath, stringifyYAML(doc));
121
- return wuPath;
122
- }
123
- describe('Initiative Orchestration E2E Tests (WU-1363)', () => {
124
- let tempDir;
125
- let originalCwd;
126
- beforeEach(() => {
127
- tempDir = join(tmpdir(), `initiative-e2e-${Date.now()}-${Math.random().toString(36).slice(2)}`);
128
- mkdirSync(tempDir, { recursive: true });
129
- originalCwd = process.cwd();
130
- createInitiativeProject(tempDir);
131
- vi.resetModules();
132
- });
133
- afterEach(() => {
134
- process.chdir(originalCwd);
135
- if (existsSync(tempDir)) {
136
- try {
137
- rmSync(tempDir, { recursive: true, force: true });
138
- }
139
- catch {
140
- // Ignore cleanup errors
141
- }
142
- }
143
- vi.clearAllMocks();
144
- });
145
- describe('AC5: E2E test for initiative orchestration', () => {
146
- describe('initiative creation', () => {
147
- it('should create an initiative with phases', () => {
148
- // Arrange
149
- process.chdir(tempDir);
150
- // Act
151
- const initPath = createInitiative(tempDir, TEST_INIT_ID, {
152
- title: TEST_INIT_TITLE,
153
- phases: [
154
- { name: 'Phase 1: MVP', status: 'pending' },
155
- { name: 'Phase 2: Polish', status: 'pending' },
156
- { name: 'Phase 3: Launch', status: 'pending' },
157
- ],
158
- });
159
- // Assert
160
- expect(existsSync(initPath)).toBe(true);
161
- const content = readFileSync(initPath, 'utf-8');
162
- const doc = parseYAML(content);
163
- expect(doc.id).toBe(TEST_INIT_ID);
164
- expect(doc.phases).toHaveLength(3);
165
- });
166
- it('should track initiative status', () => {
167
- // Arrange
168
- process.chdir(tempDir);
169
- createInitiative(tempDir, TEST_INIT_ID, { status: 'open' });
170
- // Act
171
- const initPath = join(tempDir, 'docs/04-operations/tasks/initiatives', `${TEST_INIT_ID}.yaml`);
172
- const doc = parseYAML(readFileSync(initPath, 'utf-8'));
173
- // Assert
174
- expect(doc.status).toBe('open');
175
- });
176
- });
177
- describe('WU linkage', () => {
178
- it('should link WUs to initiatives', () => {
179
- // Arrange
180
- process.chdir(tempDir);
181
- createInitiative(tempDir, TEST_INIT_ID, { wus: [] });
182
- // Act - Create WU linked to initiative
183
- const wuPath = createWUForInitiative(tempDir, TEST_WU_ID_1, {
184
- initiative: TEST_INIT_ID,
185
- phase: 1,
186
- });
187
- // Update initiative with WU reference
188
- const initPath = join(tempDir, 'docs/04-operations/tasks/initiatives', `${TEST_INIT_ID}.yaml`);
189
- const initDoc = parseYAML(readFileSync(initPath, 'utf-8'));
190
- initDoc.wus = [TEST_WU_ID_1];
191
- writeFileSync(initPath, stringifyYAML(initDoc));
192
- // Assert
193
- expect(existsSync(wuPath)).toBe(true);
194
- const wuDoc = parseYAML(readFileSync(wuPath, 'utf-8'));
195
- expect(wuDoc.initiative).toBe(TEST_INIT_ID);
196
- const updatedInit = parseYAML(readFileSync(initPath, 'utf-8'));
197
- expect(updatedInit.wus).toContain(TEST_WU_ID_1);
198
- });
199
- it('should track multiple WUs per phase', () => {
200
- // Arrange
201
- process.chdir(tempDir);
202
- createInitiative(tempDir, TEST_INIT_ID);
203
- // Act - Create multiple WUs for phase 1
204
- createWUForInitiative(tempDir, TEST_WU_ID_1, { initiative: TEST_INIT_ID, phase: 1 });
205
- createWUForInitiative(tempDir, TEST_WU_ID_2, { initiative: TEST_INIT_ID, phase: 1 });
206
- createWUForInitiative(tempDir, TEST_WU_ID_3, { initiative: TEST_INIT_ID, phase: 2 });
207
- // Update initiative
208
- const initPath = join(tempDir, 'docs/04-operations/tasks/initiatives', `${TEST_INIT_ID}.yaml`);
209
- const initDoc = parseYAML(readFileSync(initPath, 'utf-8'));
210
- initDoc.wus = [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3];
211
- writeFileSync(initPath, stringifyYAML(initDoc));
212
- // Assert
213
- const updatedInit = parseYAML(readFileSync(initPath, 'utf-8'));
214
- expect(updatedInit.wus).toHaveLength(3);
215
- });
216
- });
217
- describe('progress tracking', () => {
218
- it('should calculate initiative progress from WU statuses', () => {
219
- // Arrange
220
- process.chdir(tempDir);
221
- createInitiative(tempDir, TEST_INIT_ID, {
222
- wus: [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3],
223
- });
224
- createWUForInitiative(tempDir, TEST_WU_ID_1, {
225
- initiative: TEST_INIT_ID,
226
- status: WU_STATUS.DONE,
227
- });
228
- createWUForInitiative(tempDir, TEST_WU_ID_2, {
229
- initiative: TEST_INIT_ID,
230
- status: WU_STATUS.IN_PROGRESS,
231
- });
232
- createWUForInitiative(tempDir, TEST_WU_ID_3, {
233
- initiative: TEST_INIT_ID,
234
- status: WU_STATUS.READY,
235
- });
236
- // Act - Calculate progress
237
- const wuStatuses = [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3].map((id) => {
238
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${id}.yaml`);
239
- const doc = parseYAML(readFileSync(wuPath, 'utf-8'));
240
- return doc.status;
241
- });
242
- const doneCount = wuStatuses.filter((s) => s === WU_STATUS.DONE).length;
243
- const totalCount = wuStatuses.length;
244
- const progressPercent = Math.round((doneCount / totalCount) * 100);
245
- // Assert
246
- expect(doneCount).toBe(1);
247
- expect(totalCount).toBe(3);
248
- expect(progressPercent).toBe(33);
249
- });
250
- it('should track phase completion', () => {
251
- // Arrange
252
- process.chdir(tempDir);
253
- const initPath = createInitiative(tempDir, TEST_INIT_ID, {
254
- phases: [
255
- { name: 'Phase 1', status: 'in_progress' },
256
- { name: 'Phase 2', status: 'pending' },
257
- ],
258
- });
259
- // Create phase 1 WUs (all done)
260
- createWUForInitiative(tempDir, TEST_WU_ID_1, {
261
- initiative: TEST_INIT_ID,
262
- phase: 1,
263
- status: WU_STATUS.DONE,
264
- });
265
- createWUForInitiative(tempDir, TEST_WU_ID_2, {
266
- initiative: TEST_INIT_ID,
267
- phase: 1,
268
- status: WU_STATUS.DONE,
269
- });
270
- // Act - Mark phase 1 as done
271
- const initDoc = parseYAML(readFileSync(initPath, 'utf-8'));
272
- initDoc.phases[0].status = 'done';
273
- initDoc.phases[1].status = 'in_progress';
274
- writeFileSync(initPath, stringifyYAML(initDoc));
275
- // Assert
276
- const updatedDoc = parseYAML(readFileSync(initPath, 'utf-8'));
277
- expect(updatedDoc.phases[0].status).toBe('done');
278
- expect(updatedDoc.phases[1].status).toBe('in_progress');
279
- });
280
- });
281
- describe('wave-based orchestration', () => {
282
- it('should identify parallelizable WUs (no dependencies)', () => {
283
- // Arrange
284
- process.chdir(tempDir);
285
- createInitiative(tempDir, TEST_INIT_ID, {
286
- wus: [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3],
287
- });
288
- // Create WUs with different lanes (parallelizable)
289
- createWUForInitiative(tempDir, TEST_WU_ID_1, {
290
- initiative: TEST_INIT_ID,
291
- lane: 'Framework: CLI',
292
- dependencies: [],
293
- });
294
- createWUForInitiative(tempDir, TEST_WU_ID_2, {
295
- initiative: TEST_INIT_ID,
296
- lane: 'Framework: Core',
297
- dependencies: [],
298
- });
299
- createWUForInitiative(tempDir, TEST_WU_ID_3, {
300
- initiative: TEST_INIT_ID,
301
- lane: 'Content: Documentation',
302
- dependencies: [],
303
- });
304
- // Act - Identify parallel WUs
305
- const wus = [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3].map((id) => {
306
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${id}.yaml`);
307
- return parseYAML(readFileSync(wuPath, 'utf-8'));
308
- });
309
- const parallelWUs = wus.filter((wu) => !wu.dependencies || wu.dependencies.length === 0);
310
- // Assert - All three can run in parallel (different lanes, no dependencies)
311
- expect(parallelWUs).toHaveLength(3);
312
- });
313
- it('should respect dependencies for wave ordering', () => {
314
- // Arrange
315
- process.chdir(tempDir);
316
- createInitiative(tempDir, TEST_INIT_ID, {
317
- wus: [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3],
318
- });
319
- // WU-1 has no dependencies (wave 1)
320
- createWUForInitiative(tempDir, TEST_WU_ID_1, {
321
- initiative: TEST_INIT_ID,
322
- dependencies: [],
323
- });
324
- // WU-2 depends on WU-1 (wave 2)
325
- createWUForInitiative(tempDir, TEST_WU_ID_2, {
326
- initiative: TEST_INIT_ID,
327
- dependencies: [TEST_WU_ID_1],
328
- });
329
- // WU-3 depends on WU-2 (wave 3)
330
- createWUForInitiative(tempDir, TEST_WU_ID_3, {
331
- initiative: TEST_INIT_ID,
332
- dependencies: [TEST_WU_ID_2],
333
- });
334
- // Act - Compute waves
335
- const wus = [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3].map((id) => {
336
- const wuPath = join(tempDir, 'docs/04-operations/tasks/wu', `${id}.yaml`);
337
- return parseYAML(readFileSync(wuPath, 'utf-8'));
338
- });
339
- const wave1 = wus.filter((wu) => !wu.dependencies || wu.dependencies.length === 0);
340
- const wave2 = wus.filter((wu) => wu.dependencies &&
341
- wu.dependencies.length > 0 &&
342
- wu.dependencies.every((dep) => wave1.some((w) => w.id === dep)));
343
- const wave3 = wus.filter((wu) => wu.dependencies &&
344
- wu.dependencies.length > 0 &&
345
- wu.dependencies.every((dep) => wave2.some((w) => w.id === dep)));
346
- // Assert
347
- expect(wave1).toHaveLength(1);
348
- expect(wave1[0].id).toBe(TEST_WU_ID_1);
349
- expect(wave2).toHaveLength(1);
350
- expect(wave2[0].id).toBe(TEST_WU_ID_2);
351
- expect(wave3).toHaveLength(1);
352
- expect(wave3[0].id).toBe(TEST_WU_ID_3);
353
- });
354
- });
355
- describe('complete initiative workflow', () => {
356
- it('should execute full initiative lifecycle', () => {
357
- // This test validates the complete initiative workflow:
358
- // 1. Create initiative with phases
359
- // 2. Add WUs to initiative
360
- // 3. Execute WUs (simulated status changes)
361
- // 4. Track progress
362
- // 5. Complete initiative
363
- // Arrange
364
- process.chdir(tempDir);
365
- // Step 1: Create initiative
366
- const initPath = createInitiative(tempDir, TEST_INIT_ID, {
367
- title: 'E2E Test Initiative',
368
- status: 'open',
369
- phases: [
370
- { name: 'Phase 1: Core', status: 'pending' },
371
- { name: 'Phase 2: Features', status: 'pending' },
372
- ],
373
- wus: [],
374
- });
375
- expect(existsSync(initPath)).toBe(true);
376
- // Step 2: Add WUs to initiative
377
- createWUForInitiative(tempDir, TEST_WU_ID_1, {
378
- initiative: TEST_INIT_ID,
379
- phase: 1,
380
- status: WU_STATUS.READY,
381
- });
382
- createWUForInitiative(tempDir, TEST_WU_ID_2, {
383
- initiative: TEST_INIT_ID,
384
- phase: 1,
385
- dependencies: [TEST_WU_ID_1],
386
- status: WU_STATUS.READY,
387
- });
388
- createWUForInitiative(tempDir, TEST_WU_ID_3, {
389
- initiative: TEST_INIT_ID,
390
- phase: 2,
391
- status: WU_STATUS.READY,
392
- });
393
- let initDoc = parseYAML(readFileSync(initPath, 'utf-8'));
394
- initDoc.wus = [TEST_WU_ID_1, TEST_WU_ID_2, TEST_WU_ID_3];
395
- initDoc.phases[0].status = 'in_progress';
396
- writeFileSync(initPath, stringifyYAML(initDoc));
397
- // Step 3: Execute Phase 1 WUs
398
- // Complete WU-1
399
- const wu1Path = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID_1}.yaml`);
400
- const wu1 = parseYAML(readFileSync(wu1Path, 'utf-8'));
401
- wu1.status = WU_STATUS.DONE;
402
- writeFileSync(wu1Path, stringifyYAML(wu1));
403
- // Complete WU-2
404
- const wu2Path = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID_2}.yaml`);
405
- const wu2 = parseYAML(readFileSync(wu2Path, 'utf-8'));
406
- wu2.status = WU_STATUS.DONE;
407
- writeFileSync(wu2Path, stringifyYAML(wu2));
408
- // Step 4: Check progress
409
- const wuPaths = [
410
- join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID_1}.yaml`),
411
- join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID_2}.yaml`),
412
- join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID_3}.yaml`),
413
- ];
414
- const statuses = wuPaths.map((p) => parseYAML(readFileSync(p, 'utf-8')).status);
415
- const doneCount = statuses.filter((s) => s === WU_STATUS.DONE).length;
416
- expect(doneCount).toBe(2); // 2 out of 3 done
417
- // Mark Phase 1 as done
418
- initDoc = parseYAML(readFileSync(initPath, 'utf-8'));
419
- initDoc.phases[0].status = 'done';
420
- initDoc.phases[1].status = 'in_progress';
421
- writeFileSync(initPath, stringifyYAML(initDoc));
422
- // Complete WU-3 (Phase 2)
423
- const wu3Path = join(tempDir, 'docs/04-operations/tasks/wu', `${TEST_WU_ID_3}.yaml`);
424
- const wu3 = parseYAML(readFileSync(wu3Path, 'utf-8'));
425
- wu3.status = WU_STATUS.DONE;
426
- writeFileSync(wu3Path, stringifyYAML(wu3));
427
- // Step 5: Complete initiative
428
- initDoc = parseYAML(readFileSync(initPath, 'utf-8'));
429
- initDoc.phases[1].status = 'done';
430
- initDoc.status = 'completed';
431
- initDoc.completed_at = new Date().toISOString();
432
- writeFileSync(initPath, stringifyYAML(initDoc));
433
- // Final assertions
434
- const finalInit = parseYAML(readFileSync(initPath, 'utf-8'));
435
- expect(finalInit.status).toBe('completed');
436
- expect(finalInit.phases.every((p) => p.status === 'done')).toBe(true);
437
- const finalWuStatuses = wuPaths.map((p) => parseYAML(readFileSync(p, 'utf-8')).status);
438
- expect(finalWuStatuses.every((s) => s === WU_STATUS.DONE)).toBe(true);
439
- });
440
- });
441
- });
442
- });
@@ -1,161 +0,0 @@
1
- /**
2
- * Tests for initiative:plan replacement by plan:link (WU-1313)
3
- *
4
- * Validates that the existing initiative:plan functionality is preserved
5
- * when replaced by plan:link --target INIT-XXX.
6
- *
7
- * TDD: These tests are written BEFORE the implementation.
8
- */
9
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
- import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
11
- import { join } from 'node:path';
12
- import { tmpdir } from 'node:os';
13
- import { parseYAML, stringifyYAML } from '@lumenflow/core/dist/wu-yaml.js';
14
- /** Test constants - avoid sonarjs/no-duplicate-string */
15
- const TEST_INIT_DIR = 'docs/04-operations/tasks/initiatives';
16
- const TEST_INIT_ID = 'INIT-001';
17
- const TEST_INIT_PLAN_URI = `lumenflow://plans/${TEST_INIT_ID}-plan.md`;
18
- const TEST_PLANS_DIR = 'docs/04-operations/plans';
19
- const TEST_LUMENFLOW_HOME_BAD = '/tmp/lumenflow-home-should-not-be-used';
20
- const TEST_INIT_SLUG = 'test-initiative';
21
- const TEST_INIT_TITLE = 'Test Initiative';
22
- const TEST_STATUS_OPEN = 'open';
23
- const TEST_DATE = '2026-01-25';
24
- // Mock modules before importing
25
- vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
26
- getGitForCwd: vi.fn(() => ({
27
- branch: vi.fn().mockResolvedValue({ current: 'main' }),
28
- status: vi.fn().mockResolvedValue({ isClean: () => true }),
29
- })),
30
- }));
31
- vi.mock('@lumenflow/core/dist/wu-helpers.js', () => ({
32
- ensureOnMain: vi.fn().mockResolvedValue(undefined),
33
- }));
34
- vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
35
- withMicroWorktree: vi.fn(async ({ execute }) => {
36
- const tempDir = join(tmpdir(), `init-plan-replace-test-${Date.now()}`);
37
- mkdirSync(tempDir, { recursive: true });
38
- return execute({ worktreePath: tempDir });
39
- }),
40
- }));
41
- describe('initiative:plan replaced by plan:link', () => {
42
- let tempDir;
43
- let originalCwd;
44
- beforeEach(() => {
45
- tempDir = join(tmpdir(), `init-plan-replace-test-${Date.now()}`);
46
- mkdirSync(tempDir, { recursive: true });
47
- originalCwd = process.cwd();
48
- });
49
- afterEach(() => {
50
- process.chdir(originalCwd);
51
- if (existsSync(tempDir)) {
52
- rmSync(tempDir, { recursive: true, force: true });
53
- }
54
- vi.clearAllMocks();
55
- });
56
- describe('plan:link for initiatives (backwards compatibility)', () => {
57
- it('should link plan to initiative via related_plan field', async () => {
58
- const { linkPlanToInitiative } = await import('../plan-link.js');
59
- // Setup mock initiative file
60
- const initDir = join(tempDir, ...TEST_INIT_DIR.split('/'));
61
- mkdirSync(initDir, { recursive: true });
62
- const initPath = join(initDir, `${TEST_INIT_ID}.yaml`);
63
- const initDoc = {
64
- id: TEST_INIT_ID,
65
- slug: TEST_INIT_SLUG,
66
- title: TEST_INIT_TITLE,
67
- status: TEST_STATUS_OPEN,
68
- created: TEST_DATE,
69
- };
70
- writeFileSync(initPath, stringifyYAML(initDoc));
71
- // Link plan (same operation as initiative:plan --initiative --plan)
72
- const changed = linkPlanToInitiative(tempDir, TEST_INIT_ID, TEST_INIT_PLAN_URI);
73
- expect(changed).toBe(true);
74
- // Verify the file was updated
75
- const updated = parseYAML(readFileSync(initPath, 'utf-8'));
76
- expect(updated.related_plan).toBe(TEST_INIT_PLAN_URI);
77
- });
78
- it('should create plan and link in single operation', async () => {
79
- const { createPlan } = await import('../plan-create.js');
80
- const { linkPlanToInitiative } = await import('../plan-link.js');
81
- // Setup mock initiative file
82
- const initDir = join(tempDir, ...TEST_INIT_DIR.split('/'));
83
- mkdirSync(initDir, { recursive: true });
84
- const initPath = join(initDir, `${TEST_INIT_ID}.yaml`);
85
- const initDoc = {
86
- id: TEST_INIT_ID,
87
- slug: TEST_INIT_SLUG,
88
- title: TEST_INIT_TITLE,
89
- status: TEST_STATUS_OPEN,
90
- created: TEST_DATE,
91
- };
92
- writeFileSync(initPath, stringifyYAML(initDoc));
93
- // Create plan (like initiative:plan --create)
94
- const planPath = createPlan(tempDir, TEST_INIT_ID, TEST_INIT_TITLE);
95
- expect(existsSync(planPath)).toBe(true);
96
- // Link plan
97
- const changed = linkPlanToInitiative(tempDir, TEST_INIT_ID, TEST_INIT_PLAN_URI);
98
- expect(changed).toBe(true);
99
- // Verify both plan file and initiative were updated
100
- const updated = parseYAML(readFileSync(initPath, 'utf-8'));
101
- expect(updated.related_plan).toBe(TEST_INIT_PLAN_URI);
102
- });
103
- });
104
- describe('initiative:plan deprecation', () => {
105
- it('should warn when using deprecated initiative:plan command', async () => {
106
- // The initiative:plan command should still work but warn about deprecation
107
- // and suggest using plan:link instead
108
- const initPlan = await import('../initiative-plan.js');
109
- expect(typeof initPlan.main).toBe('function');
110
- // The deprecation warning will be in the main function
111
- });
112
- });
113
- describe('plan:link auto-detection', () => {
114
- it('should auto-detect INIT target and call linkPlanToInitiative', async () => {
115
- const { resolveTargetType } = await import('../plan-link.js');
116
- expect(resolveTargetType(TEST_INIT_ID)).toBe('initiative');
117
- expect(resolveTargetType('INIT-TOOLING')).toBe('initiative');
118
- });
119
- it('should auto-detect WU target and call linkPlanToWU', async () => {
120
- const { resolveTargetType } = await import('../plan-link.js');
121
- expect(resolveTargetType('WU-1313')).toBe('wu');
122
- expect(resolveTargetType('WU-001')).toBe('wu');
123
- });
124
- });
125
- });
126
- describe('plan storage defaults to repo plansDir', () => {
127
- let tempDir;
128
- let originalCwd;
129
- beforeEach(() => {
130
- tempDir = join(tmpdir(), `plan-storage-test-${Date.now()}`);
131
- mkdirSync(tempDir, { recursive: true });
132
- originalCwd = process.cwd();
133
- });
134
- afterEach(() => {
135
- process.chdir(originalCwd);
136
- if (existsSync(tempDir)) {
137
- rmSync(tempDir, { recursive: true, force: true });
138
- }
139
- vi.clearAllMocks();
140
- });
141
- it('should create plans in repo directories.plansDir, not LUMENFLOW_HOME', async () => {
142
- const { createPlan } = await import('../plan-create.js');
143
- // Set LUMENFLOW_HOME to a different location (should be ignored)
144
- const oldLfHome = process.env.LUMENFLOW_HOME;
145
- process.env.LUMENFLOW_HOME = TEST_LUMENFLOW_HOME_BAD;
146
- try {
147
- const planPath = createPlan(tempDir, 'WU-1313', 'Test Plan');
148
- // Plan should be in repo plansDir, not LUMENFLOW_HOME
149
- expect(planPath).toContain(TEST_PLANS_DIR);
150
- expect(planPath).not.toContain(TEST_LUMENFLOW_HOME_BAD);
151
- }
152
- finally {
153
- if (oldLfHome === undefined) {
154
- delete process.env.LUMENFLOW_HOME;
155
- }
156
- else {
157
- process.env.LUMENFLOW_HOME = oldLfHome;
158
- }
159
- }
160
- });
161
- });