@lumenflow/cli 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +120 -105
  2. package/dist/__tests__/agent-spawn-coordination.test.js +451 -0
  3. package/dist/__tests__/commands/integrate.test.js +165 -0
  4. package/dist/__tests__/gates-config.test.js +0 -1
  5. package/dist/__tests__/hooks/enforcement.test.js +279 -0
  6. package/dist/__tests__/init-greenfield.test.js +247 -0
  7. package/dist/__tests__/init-quick-ref.test.js +0 -1
  8. package/dist/__tests__/init-template-portability.test.js +0 -1
  9. package/dist/__tests__/init.test.js +27 -0
  10. package/dist/__tests__/initiative-e2e.test.js +442 -0
  11. package/dist/__tests__/initiative-plan-replacement.test.js +0 -1
  12. package/dist/__tests__/memory-integration.test.js +333 -0
  13. package/dist/__tests__/release.test.js +1 -1
  14. package/dist/__tests__/safe-git.test.js +0 -1
  15. package/dist/__tests__/state-doctor.test.js +54 -0
  16. package/dist/__tests__/sync-templates.test.js +255 -0
  17. package/dist/__tests__/wu-create-required-fields.test.js +121 -0
  18. package/dist/__tests__/wu-done-auto-cleanup.test.js +135 -0
  19. package/dist/__tests__/wu-lifecycle-integration.test.js +388 -0
  20. package/dist/__tests__/wu-proto.test.js +97 -0
  21. package/dist/backlog-prune.js +0 -1
  22. package/dist/cli-entry-point.js +0 -1
  23. package/dist/commands/integrate.js +229 -0
  24. package/dist/docs-sync.js +46 -0
  25. package/dist/doctor.js +0 -2
  26. package/dist/gates.js +0 -7
  27. package/dist/hooks/enforcement-checks.js +209 -0
  28. package/dist/hooks/enforcement-generator.js +365 -0
  29. package/dist/hooks/enforcement-sync.js +243 -0
  30. package/dist/hooks/index.js +7 -0
  31. package/dist/init.js +266 -11
  32. package/dist/initiative-add-wu.js +0 -2
  33. package/dist/initiative-create.js +0 -3
  34. package/dist/initiative-edit.js +0 -5
  35. package/dist/initiative-plan.js +0 -1
  36. package/dist/initiative-remove-wu.js +0 -2
  37. package/dist/lane-health.js +0 -2
  38. package/dist/lane-suggest.js +0 -1
  39. package/dist/mem-checkpoint.js +0 -2
  40. package/dist/mem-cleanup.js +0 -2
  41. package/dist/mem-context.js +0 -3
  42. package/dist/mem-create.js +0 -2
  43. package/dist/mem-delete.js +0 -3
  44. package/dist/mem-inbox.js +0 -2
  45. package/dist/mem-index.js +0 -1
  46. package/dist/mem-init.js +0 -2
  47. package/dist/mem-profile.js +0 -1
  48. package/dist/mem-promote.js +0 -1
  49. package/dist/mem-ready.js +0 -2
  50. package/dist/mem-signal.js +0 -2
  51. package/dist/mem-start.js +0 -2
  52. package/dist/mem-summarize.js +0 -2
  53. package/dist/metrics-cli.js +1 -1
  54. package/dist/metrics-snapshot.js +1 -1
  55. package/dist/onboarding-smoke-test.js +0 -5
  56. package/dist/orchestrate-init-status.js +0 -1
  57. package/dist/orchestrate-initiative.js +0 -1
  58. package/dist/orchestrate-monitor.js +0 -1
  59. package/dist/plan-create.js +0 -2
  60. package/dist/plan-edit.js +0 -2
  61. package/dist/plan-link.js +0 -2
  62. package/dist/plan-promote.js +0 -2
  63. package/dist/signal-cleanup.js +0 -4
  64. package/dist/state-bootstrap.js +0 -1
  65. package/dist/state-cleanup.js +0 -4
  66. package/dist/state-doctor-fix.js +5 -8
  67. package/dist/state-doctor.js +0 -11
  68. package/dist/sync-templates.js +188 -34
  69. package/dist/wu-block.js +100 -48
  70. package/dist/wu-claim.js +1 -22
  71. package/dist/wu-cleanup.js +0 -1
  72. package/dist/wu-create.js +0 -2
  73. package/dist/wu-done-auto-cleanup.js +139 -0
  74. package/dist/wu-done.js +11 -4
  75. package/dist/wu-edit.js +0 -12
  76. package/dist/wu-preflight.js +0 -1
  77. package/dist/wu-prep.js +0 -1
  78. package/dist/wu-proto.js +329 -0
  79. package/dist/wu-spawn.js +0 -3
  80. package/dist/wu-unblock.js +0 -2
  81. package/dist/wu-validate.js +0 -1
  82. package/package.json +9 -7
  83. package/templates/core/.husky/pre-commit.template +93 -0
  84. package/templates/core/ai/onboarding/quick-ref-commands.md.template +27 -0
  85. package/templates/core/ai/onboarding/rapid-prototyping.md +143 -0
  86. package/templates/core/ai/onboarding/starting-prompt.md.template +3 -3
  87. package/templates/vendors/claude/.claude/CLAUDE.md.template +25 -0
  88. package/templates/vendors/claude/.claude/hooks/enforce-worktree.sh +135 -0
@@ -0,0 +1,442 @@
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
+ });
@@ -16,7 +16,6 @@ const TEST_INIT_DIR = 'docs/04-operations/tasks/initiatives';
16
16
  const TEST_INIT_ID = 'INIT-001';
17
17
  const TEST_INIT_PLAN_URI = `lumenflow://plans/${TEST_INIT_ID}-plan.md`;
18
18
  const TEST_PLANS_DIR = 'docs/04-operations/plans';
19
- // eslint-disable-next-line sonarjs/publicly-writable-directories -- test constant for bad path detection
20
19
  const TEST_LUMENFLOW_HOME_BAD = '/tmp/lumenflow-home-should-not-be-used';
21
20
  const TEST_INIT_SLUG = 'test-initiative';
22
21
  const TEST_INIT_TITLE = 'Test Initiative';