@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,340 +0,0 @@
1
- /**
2
- * Tests for initiative:plan command (WU-1105, renamed in WU-1193)
3
- *
4
- * The initiative:plan command links plan files to initiatives by setting
5
- * the `related_plan` field in the initiative YAML.
6
- *
7
- * TDD: These tests are written BEFORE the implementation.
8
- */
9
- import { describe, it, expect, vi, beforeEach, afterEach, beforeAll } 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
- // Pre-import the module to ensure coverage tracking includes the module itself
15
- let initPlanModule;
16
- beforeAll(async () => {
17
- initPlanModule = await import('../initiative-plan.js');
18
- });
19
- // Mock modules before importing the module under test
20
- const mockGit = {
21
- branch: vi.fn().mockResolvedValue({ current: 'main' }),
22
- status: vi.fn().mockResolvedValue({ isClean: () => true }),
23
- };
24
- vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
25
- getGitForCwd: vi.fn(() => mockGit),
26
- }));
27
- vi.mock('@lumenflow/core/dist/wu-helpers.js', () => ({
28
- ensureOnMain: vi.fn().mockResolvedValue(undefined),
29
- }));
30
- vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
31
- withMicroWorktree: vi.fn(async ({ execute }) => {
32
- // Simulate micro-worktree by executing in temp dir
33
- const tempDir = join(tmpdir(), `init-plan-test-${Date.now()}`);
34
- mkdirSync(tempDir, { recursive: true });
35
- try {
36
- await execute({ worktreePath: tempDir });
37
- }
38
- finally {
39
- // Cleanup handled by test
40
- }
41
- }),
42
- }));
43
- describe('init:plan command', () => {
44
- let tempDir;
45
- let originalCwd;
46
- beforeEach(() => {
47
- tempDir = join(tmpdir(), `init-plan-test-${Date.now()}`);
48
- mkdirSync(tempDir, { recursive: true });
49
- originalCwd = process.cwd();
50
- });
51
- afterEach(() => {
52
- process.chdir(originalCwd);
53
- if (existsSync(tempDir)) {
54
- rmSync(tempDir, { recursive: true, force: true });
55
- }
56
- vi.clearAllMocks();
57
- });
58
- describe('validateInitIdFormat', () => {
59
- it('should accept valid INIT-NNN format', async () => {
60
- const { validateInitIdFormat } = await import('../initiative-plan.js');
61
- // Should not throw
62
- expect(() => validateInitIdFormat('INIT-001')).not.toThrow();
63
- expect(() => validateInitIdFormat('INIT-123')).not.toThrow();
64
- });
65
- it('should accept valid INIT-NAME format', async () => {
66
- const { validateInitIdFormat } = await import('../initiative-plan.js');
67
- expect(() => validateInitIdFormat('INIT-TOOLING')).not.toThrow();
68
- expect(() => validateInitIdFormat('INIT-A1')).not.toThrow();
69
- });
70
- it('should reject invalid formats', async () => {
71
- const { validateInitIdFormat } = await import('../initiative-plan.js');
72
- expect(() => validateInitIdFormat('init-001')).toThrow();
73
- expect(() => validateInitIdFormat('INIT001')).toThrow();
74
- expect(() => validateInitIdFormat('WU-001')).toThrow();
75
- expect(() => validateInitIdFormat('')).toThrow();
76
- });
77
- });
78
- describe('validatePlanPath', () => {
79
- it('should accept existing markdown files', async () => {
80
- const { validatePlanPath } = await import('../initiative-plan.js');
81
- const planPath = join(tempDir, 'test-plan.md');
82
- writeFileSync(planPath, '# Test Plan');
83
- // Should not throw
84
- expect(() => validatePlanPath(planPath)).not.toThrow();
85
- });
86
- it('should reject non-existent files when not creating', async () => {
87
- const { validatePlanPath } = await import('../initiative-plan.js');
88
- const planPath = join(tempDir, 'nonexistent.md');
89
- expect(() => validatePlanPath(planPath)).toThrow();
90
- });
91
- it('should reject non-markdown files', async () => {
92
- const { validatePlanPath } = await import('../initiative-plan.js');
93
- const planPath = join(tempDir, 'test-plan.txt');
94
- writeFileSync(planPath, 'Test Plan');
95
- expect(() => validatePlanPath(planPath)).toThrow();
96
- });
97
- });
98
- describe('formatPlanUri', () => {
99
- it('should format plan path as lumenflow:// URI', async () => {
100
- const { formatPlanUri } = await import('../initiative-plan.js');
101
- expect(formatPlanUri('docs/04-operations/plans/my-plan.md')).toBe('lumenflow://plans/my-plan.md');
102
- });
103
- it('should handle nested paths', async () => {
104
- const { formatPlanUri } = await import('../initiative-plan.js');
105
- expect(formatPlanUri('docs/04-operations/plans/subdir/nested-plan.md')).toBe('lumenflow://plans/subdir/nested-plan.md');
106
- });
107
- it('should handle paths not in standard location', async () => {
108
- const { formatPlanUri } = await import('../initiative-plan.js');
109
- // Should still create a URI even for non-standard paths
110
- expect(formatPlanUri('/absolute/path/custom-plan.md')).toBe('lumenflow://plans/custom-plan.md');
111
- });
112
- });
113
- describe('checkInitiativeExists', () => {
114
- it('should return initiative doc if found', async () => {
115
- const { checkInitiativeExists } = await import('../initiative-plan.js');
116
- // Create a mock initiative file
117
- const initDir = join(tempDir, 'docs', '04-operations', 'tasks', 'initiatives');
118
- mkdirSync(initDir, { recursive: true });
119
- const initPath = join(initDir, 'INIT-001.yaml');
120
- const initDoc = {
121
- id: 'INIT-001',
122
- slug: 'test-initiative',
123
- title: 'Test Initiative',
124
- status: 'open',
125
- created: '2026-01-25',
126
- };
127
- writeFileSync(initPath, stringifyYAML(initDoc));
128
- process.chdir(tempDir);
129
- const result = checkInitiativeExists('INIT-001');
130
- expect(result.id).toBe('INIT-001');
131
- });
132
- it('should throw if initiative not found', async () => {
133
- const { checkInitiativeExists } = await import('../initiative-plan.js');
134
- process.chdir(tempDir);
135
- expect(() => checkInitiativeExists('INIT-999')).toThrow();
136
- });
137
- });
138
- describe('updateInitiativeWithPlan', () => {
139
- it('should add related_plan field to initiative', async () => {
140
- const { updateInitiativeWithPlan } = await import('../initiative-plan.js');
141
- // Setup mock initiative
142
- const initDir = join(tempDir, 'docs', '04-operations', 'tasks', 'initiatives');
143
- mkdirSync(initDir, { recursive: true });
144
- const initPath = join(initDir, 'INIT-001.yaml');
145
- const initDoc = {
146
- id: 'INIT-001',
147
- slug: 'test-initiative',
148
- title: 'Test Initiative',
149
- status: 'open',
150
- created: '2026-01-25',
151
- };
152
- writeFileSync(initPath, stringifyYAML(initDoc));
153
- // Update initiative
154
- const changed = updateInitiativeWithPlan(tempDir, 'INIT-001', 'lumenflow://plans/test-plan.md');
155
- expect(changed).toBe(true);
156
- // Verify the file was updated
157
- const updated = parseYAML(readFileSync(initPath, 'utf-8'));
158
- expect(updated.related_plan).toBe('lumenflow://plans/test-plan.md');
159
- });
160
- it('should return false if plan already linked (idempotent)', async () => {
161
- const { updateInitiativeWithPlan } = await import('../initiative-plan.js');
162
- // Setup mock initiative with existing plan
163
- const initDir = join(tempDir, 'docs', '04-operations', 'tasks', 'initiatives');
164
- mkdirSync(initDir, { recursive: true });
165
- const initPath = join(initDir, 'INIT-001.yaml');
166
- const initDoc = {
167
- id: 'INIT-001',
168
- slug: 'test-initiative',
169
- title: 'Test Initiative',
170
- status: 'open',
171
- created: '2026-01-25',
172
- related_plan: 'lumenflow://plans/test-plan.md',
173
- };
174
- writeFileSync(initPath, stringifyYAML(initDoc));
175
- // Update initiative with same plan
176
- const changed = updateInitiativeWithPlan(tempDir, 'INIT-001', 'lumenflow://plans/test-plan.md');
177
- expect(changed).toBe(false);
178
- });
179
- it('should warn but proceed if different plan already linked', async () => {
180
- const { updateInitiativeWithPlan } = await import('../initiative-plan.js');
181
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
182
- // Setup mock initiative with different plan
183
- const initDir = join(tempDir, 'docs', '04-operations', 'tasks', 'initiatives');
184
- mkdirSync(initDir, { recursive: true });
185
- const initPath = join(initDir, 'INIT-001.yaml');
186
- const initDoc = {
187
- id: 'INIT-001',
188
- slug: 'test-initiative',
189
- title: 'Test Initiative',
190
- status: 'open',
191
- created: '2026-01-25',
192
- related_plan: 'lumenflow://plans/old-plan.md',
193
- };
194
- writeFileSync(initPath, stringifyYAML(initDoc));
195
- // Update initiative with new plan
196
- const changed = updateInitiativeWithPlan(tempDir, 'INIT-001', 'lumenflow://plans/new-plan.md');
197
- expect(changed).toBe(true);
198
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Replacing existing related_plan'));
199
- consoleSpy.mockRestore();
200
- });
201
- });
202
- describe('createPlanTemplate', () => {
203
- it('should create a plan template file', async () => {
204
- const { createPlanTemplate } = await import('../initiative-plan.js');
205
- const plansDir = join(tempDir, 'docs', '04-operations', 'plans');
206
- mkdirSync(plansDir, { recursive: true });
207
- const planPath = createPlanTemplate(tempDir, 'INIT-001', 'Test Initiative');
208
- expect(existsSync(planPath)).toBe(true);
209
- const content = readFileSync(planPath, 'utf-8');
210
- expect(content).toContain('# INIT-001');
211
- expect(content).toContain('Test Initiative');
212
- expect(content).toContain('## Goal');
213
- expect(content).toContain('## Scope');
214
- });
215
- it('should not overwrite existing plan file', async () => {
216
- const { createPlanTemplate } = await import('../initiative-plan.js');
217
- const plansDir = join(tempDir, 'docs', '04-operations', 'plans');
218
- mkdirSync(plansDir, { recursive: true });
219
- // Create existing file
220
- const existingPath = join(plansDir, 'INIT-001-test-initiative.md');
221
- writeFileSync(existingPath, '# Existing Content');
222
- expect(() => createPlanTemplate(tempDir, 'INIT-001', 'Test Initiative')).toThrow();
223
- });
224
- });
225
- describe('LOG_PREFIX', () => {
226
- it('should use correct log prefix', async () => {
227
- const { LOG_PREFIX } = await import('../initiative-plan.js');
228
- expect(LOG_PREFIX).toBe('[initiative:plan]');
229
- });
230
- });
231
- describe('getCommitMessage', () => {
232
- it('should generate correct commit message', async () => {
233
- const { getCommitMessage } = await import('../initiative-plan.js');
234
- expect(getCommitMessage('INIT-001', 'lumenflow://plans/my-plan.md')).toBe('docs: link plan my-plan.md to init-001');
235
- });
236
- it('should handle nested plan paths', async () => {
237
- const { getCommitMessage } = await import('../initiative-plan.js');
238
- expect(getCommitMessage('INIT-TOOLING', 'lumenflow://plans/subdir/nested-plan.md')).toBe('docs: link plan subdir/nested-plan.md to init-tooling');
239
- });
240
- });
241
- describe('updateInitiativeWithPlan ID mismatch', () => {
242
- it('should throw if initiative ID does not match', async () => {
243
- const { updateInitiativeWithPlan } = await import('../initiative-plan.js');
244
- // Setup mock initiative with different ID
245
- const initDir = join(tempDir, 'docs', '04-operations', 'tasks', 'initiatives');
246
- mkdirSync(initDir, { recursive: true });
247
- const initPath = join(initDir, 'INIT-001.yaml');
248
- const initDoc = {
249
- id: 'INIT-002', // Wrong ID
250
- slug: 'test-initiative',
251
- title: 'Test Initiative',
252
- status: 'open',
253
- created: '2026-01-25',
254
- };
255
- writeFileSync(initPath, stringifyYAML(initDoc));
256
- expect(() => updateInitiativeWithPlan(tempDir, 'INIT-001', 'lumenflow://plans/test-plan.md')).toThrow();
257
- });
258
- });
259
- });
260
- describe('init:plan CLI integration', () => {
261
- it('should require --initiative flag', async () => {
262
- // This test verifies that the CLI requires the initiative flag
263
- // The actual CLI integration is tested via subprocess
264
- const { WU_OPTIONS } = await import('@lumenflow/core/dist/arg-parser.js');
265
- expect(WU_OPTIONS.initiative).toBeDefined();
266
- expect(WU_OPTIONS.initiative.flags).toContain('--initiative');
267
- });
268
- it('should export main function for CLI entry', async () => {
269
- const initPlan = await import('../initiative-plan.js');
270
- expect(typeof initPlan.main).toBe('function');
271
- });
272
- it('should export all required functions', async () => {
273
- const initPlan = await import('../initiative-plan.js');
274
- expect(typeof initPlan.validateInitIdFormat).toBe('function');
275
- expect(typeof initPlan.validatePlanPath).toBe('function');
276
- expect(typeof initPlan.formatPlanUri).toBe('function');
277
- expect(typeof initPlan.checkInitiativeExists).toBe('function');
278
- expect(typeof initPlan.updateInitiativeWithPlan).toBe('function');
279
- expect(typeof initPlan.createPlanTemplate).toBe('function');
280
- expect(typeof initPlan.getCommitMessage).toBe('function');
281
- expect(typeof initPlan.LOG_PREFIX).toBe('string');
282
- });
283
- });
284
- describe('createPlanTemplate edge cases', () => {
285
- let tempDir;
286
- let originalCwd;
287
- beforeEach(() => {
288
- tempDir = join(tmpdir(), `init-plan-test-${Date.now()}`);
289
- mkdirSync(tempDir, { recursive: true });
290
- originalCwd = process.cwd();
291
- });
292
- afterEach(() => {
293
- process.chdir(originalCwd);
294
- if (existsSync(tempDir)) {
295
- rmSync(tempDir, { recursive: true, force: true });
296
- }
297
- vi.clearAllMocks();
298
- });
299
- it('should create plans directory if it does not exist', async () => {
300
- const { createPlanTemplate } = await import('../initiative-plan.js');
301
- // Do NOT pre-create the plans directory
302
- const planPath = createPlanTemplate(tempDir, 'INIT-001', 'Test Initiative');
303
- expect(existsSync(planPath)).toBe(true);
304
- expect(planPath).toContain('docs/04-operations/plans');
305
- });
306
- it('should truncate long titles in filename', async () => {
307
- const { createPlanTemplate } = await import('../initiative-plan.js');
308
- const longTitle = 'This is an extremely long initiative title that should be truncated in the filename';
309
- const planPath = createPlanTemplate(tempDir, 'INIT-001', longTitle);
310
- expect(existsSync(planPath)).toBe(true);
311
- // Filename should be truncated
312
- const filename = planPath.split('/').pop() || '';
313
- // INIT-001- is 9 chars, .md is 3 chars, slug should be max 30 chars
314
- expect(filename.length).toBeLessThanOrEqual(9 + 30 + 3);
315
- });
316
- it('should handle special characters in title', async () => {
317
- const { createPlanTemplate } = await import('../initiative-plan.js');
318
- const specialTitle = "Test's Initiative: (Special) Chars! @#$%";
319
- const planPath = createPlanTemplate(tempDir, 'INIT-001', specialTitle);
320
- expect(existsSync(planPath)).toBe(true);
321
- // Filename should only have kebab-case characters
322
- expect(planPath).toMatch(/INIT-001-[a-z0-9-]+\.md$/);
323
- });
324
- });
325
- /**
326
- * Note on main() function testing:
327
- *
328
- * The main() function is intentionally not unit-tested because:
329
- * 1. It calls die() which invokes process.exit() - difficult to mock without complex test infrastructure
330
- * 2. It involves micro-worktree operations with git
331
- * 3. All business logic functions it calls ARE thoroughly tested above
332
- *
333
- * The main() function is integration/orchestration code that composes the tested helper functions.
334
- * Integration testing via subprocess (pnpm init:plan) is the appropriate testing strategy for main().
335
- *
336
- * Coverage statistics:
337
- * - All exported helper functions: ~100% coverage
338
- * - main() function: Not unit tested (orchestration code)
339
- * - Overall file coverage: ~50% (acceptable for CLI commands)
340
- */