@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,157 +0,0 @@
1
- /**
2
- * Tests for plan:edit command (WU-1313)
3
- *
4
- * The plan:edit command edits existing plan files in the repo-native plansDir.
5
- * Uses micro-worktree isolation for atomic commits.
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
- /** Test constants - avoid sonarjs/no-duplicate-string */
14
- const TEST_PLANS_DIR = 'docs/04-operations/plans';
15
- const TEST_WU_ID = 'WU-1313';
16
- // Mock modules before importing
17
- vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
18
- getGitForCwd: vi.fn(() => ({
19
- branch: vi.fn().mockResolvedValue({ current: 'main' }),
20
- status: vi.fn().mockResolvedValue({ isClean: () => true }),
21
- })),
22
- }));
23
- vi.mock('@lumenflow/core/dist/wu-helpers.js', () => ({
24
- ensureOnMain: vi.fn().mockResolvedValue(undefined),
25
- }));
26
- vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
27
- withMicroWorktree: vi.fn(async ({ execute }) => {
28
- const tempDir = join(tmpdir(), `plan-edit-test-${Date.now()}`);
29
- mkdirSync(tempDir, { recursive: true });
30
- return execute({ worktreePath: tempDir });
31
- }),
32
- }));
33
- describe('plan:edit command', () => {
34
- let tempDir;
35
- let originalCwd;
36
- beforeEach(() => {
37
- tempDir = join(tmpdir(), `plan-edit-test-${Date.now()}`);
38
- mkdirSync(tempDir, { recursive: true });
39
- originalCwd = process.cwd();
40
- });
41
- afterEach(() => {
42
- process.chdir(originalCwd);
43
- if (existsSync(tempDir)) {
44
- rmSync(tempDir, { recursive: true, force: true });
45
- }
46
- vi.clearAllMocks();
47
- });
48
- describe('updatePlanSection', () => {
49
- it('should update a section in the plan', async () => {
50
- const { updatePlanSection } = await import('../plan-edit.js');
51
- // Setup plan file
52
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
53
- mkdirSync(plansDir, { recursive: true });
54
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
55
- writeFileSync(planPath, `# WU-1313 Plan
56
-
57
- ## Goal
58
-
59
- Original goal content.
60
-
61
- ## Scope
62
-
63
- In scope items.
64
- `);
65
- // Update goal section
66
- const changed = updatePlanSection(planPath, 'Goal', 'New goal content from edit.');
67
- expect(changed).toBe(true);
68
- const content = readFileSync(planPath, 'utf-8');
69
- expect(content).toContain('New goal content from edit.');
70
- expect(content).not.toContain('Original goal content.');
71
- expect(content).toContain('In scope items.'); // Other sections unchanged
72
- });
73
- it('should return false if section not found', async () => {
74
- const { updatePlanSection } = await import('../plan-edit.js');
75
- // Setup plan file without the target section
76
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
77
- mkdirSync(plansDir, { recursive: true });
78
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
79
- writeFileSync(planPath, `# WU-1313 Plan
80
-
81
- ## Goal
82
-
83
- Goal content.
84
- `);
85
- // Try to update non-existent section
86
- const changed = updatePlanSection(planPath, 'NonExistent', 'New content');
87
- expect(changed).toBe(false);
88
- });
89
- it('should throw if plan file not found', async () => {
90
- const { updatePlanSection } = await import('../plan-edit.js');
91
- const planPath = join(tempDir, 'nonexistent.md');
92
- expect(() => updatePlanSection(planPath, 'Goal', 'content')).toThrow();
93
- });
94
- });
95
- describe('appendToSection', () => {
96
- it('should append content to an existing section', async () => {
97
- const { appendToSection } = await import('../plan-edit.js');
98
- // Setup plan file
99
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
100
- mkdirSync(plansDir, { recursive: true });
101
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
102
- writeFileSync(planPath, `# WU-1313 Plan
103
-
104
- ## Risks
105
-
106
- - Risk 1
107
-
108
- ## References
109
- `);
110
- // Append to risks section
111
- const changed = appendToSection(planPath, 'Risks', '- Risk 2 from append');
112
- expect(changed).toBe(true);
113
- const content = readFileSync(planPath, 'utf-8');
114
- expect(content).toContain('- Risk 1');
115
- expect(content).toContain('- Risk 2 from append');
116
- });
117
- });
118
- describe('getPlanPath', () => {
119
- it('should resolve plan path from ID', async () => {
120
- const { getPlanPath } = await import('../plan-edit.js');
121
- // Setup plan file
122
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
123
- mkdirSync(plansDir, { recursive: true });
124
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
125
- writeFileSync(planPath, '# Plan');
126
- process.chdir(tempDir);
127
- const resolved = getPlanPath('WU-1313');
128
- expect(resolved).toContain(`${TEST_WU_ID}-plan.md`);
129
- });
130
- it('should throw if plan not found', async () => {
131
- const { getPlanPath } = await import('../plan-edit.js');
132
- process.chdir(tempDir);
133
- expect(() => getPlanPath('WU-9999')).toThrow();
134
- });
135
- });
136
- describe('getCommitMessage', () => {
137
- it('should generate correct commit message', async () => {
138
- const { getCommitMessage } = await import('../plan-edit.js');
139
- expect(getCommitMessage('WU-1313', 'Goal')).toBe('docs: update Goal section in wu-1313 plan');
140
- expect(getCommitMessage('INIT-001', 'Scope')).toBe('docs: update Scope section in init-001 plan');
141
- });
142
- });
143
- });
144
- describe('plan:edit CLI exports', () => {
145
- it('should export main function for CLI entry', async () => {
146
- const planEdit = await import('../plan-edit.js');
147
- expect(typeof planEdit.main).toBe('function');
148
- });
149
- it('should export all required functions', async () => {
150
- const planEdit = await import('../plan-edit.js');
151
- expect(typeof planEdit.updatePlanSection).toBe('function');
152
- expect(typeof planEdit.appendToSection).toBe('function');
153
- expect(typeof planEdit.getPlanPath).toBe('function');
154
- expect(typeof planEdit.getCommitMessage).toBe('function');
155
- expect(typeof planEdit.LOG_PREFIX).toBe('string');
156
- });
157
- });
@@ -1,239 +0,0 @@
1
- /**
2
- * Tests for plan:link command (WU-1313)
3
- *
4
- * The plan:link command links existing plan files to WUs (via spec_refs)
5
- * or initiatives (via related_plan). This replaces the initiative:plan command.
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_WU_DIR = 'docs/04-operations/tasks/wu';
16
- const TEST_INIT_DIR = 'docs/04-operations/tasks/initiatives';
17
- const TEST_PLANS_DIR = 'docs/04-operations/plans';
18
- const TEST_WU_ID = 'WU-1313';
19
- const TEST_INIT_ID = 'INIT-001';
20
- const TEST_WU_PLAN_URI = `lumenflow://plans/${TEST_WU_ID}-plan.md`;
21
- const TEST_INIT_PLAN_URI = `lumenflow://plans/${TEST_INIT_ID}-plan.md`;
22
- const TEST_LANE = 'Framework: CLI';
23
- const TEST_INIT_SLUG = 'test-initiative';
24
- const TEST_INIT_TITLE = 'Test Initiative';
25
- const TEST_WU_TITLE = 'Test WU';
26
- const TEST_STATUS_OPEN = 'open';
27
- const TEST_DATE = '2026-01-25';
28
- // Mock modules before importing
29
- vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
30
- getGitForCwd: vi.fn(() => ({
31
- branch: vi.fn().mockResolvedValue({ current: 'main' }),
32
- status: vi.fn().mockResolvedValue({ isClean: () => true }),
33
- })),
34
- }));
35
- vi.mock('@lumenflow/core/dist/wu-helpers.js', () => ({
36
- ensureOnMain: vi.fn().mockResolvedValue(undefined),
37
- }));
38
- vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
39
- withMicroWorktree: vi.fn(async ({ execute }) => {
40
- const tempDir = join(tmpdir(), `plan-link-test-${Date.now()}`);
41
- mkdirSync(tempDir, { recursive: true });
42
- return execute({ worktreePath: tempDir });
43
- }),
44
- }));
45
- describe('plan:link command', () => {
46
- let tempDir;
47
- let originalCwd;
48
- beforeEach(() => {
49
- tempDir = join(tmpdir(), `plan-link-test-${Date.now()}`);
50
- mkdirSync(tempDir, { recursive: true });
51
- originalCwd = process.cwd();
52
- });
53
- afterEach(() => {
54
- process.chdir(originalCwd);
55
- if (existsSync(tempDir)) {
56
- rmSync(tempDir, { recursive: true, force: true });
57
- }
58
- vi.clearAllMocks();
59
- });
60
- describe('linkPlanToWU', () => {
61
- it('should add spec_refs field to WU YAML', async () => {
62
- const { linkPlanToWU } = await import('../plan-link.js');
63
- // Setup mock WU file
64
- const wuDir = join(tempDir, ...TEST_WU_DIR.split('/'));
65
- mkdirSync(wuDir, { recursive: true });
66
- const wuPath = join(wuDir, `${TEST_WU_ID}.yaml`);
67
- const wuDoc = {
68
- id: TEST_WU_ID,
69
- title: TEST_WU_TITLE,
70
- lane: TEST_LANE,
71
- status: 'ready',
72
- type: 'feature',
73
- };
74
- writeFileSync(wuPath, stringifyYAML(wuDoc));
75
- // Link plan
76
- const changed = linkPlanToWU(tempDir, TEST_WU_ID, TEST_WU_PLAN_URI);
77
- expect(changed).toBe(true);
78
- // Verify the file was updated
79
- const updated = parseYAML(readFileSync(wuPath, 'utf-8'));
80
- expect(updated.spec_refs).toContain(TEST_WU_PLAN_URI);
81
- });
82
- it('should append to existing spec_refs', async () => {
83
- const { linkPlanToWU } = await import('../plan-link.js');
84
- // Setup mock WU file with existing spec_refs
85
- const wuDir = join(tempDir, ...TEST_WU_DIR.split('/'));
86
- mkdirSync(wuDir, { recursive: true });
87
- const wuPath = join(wuDir, `${TEST_WU_ID}.yaml`);
88
- const wuDoc = {
89
- id: TEST_WU_ID,
90
- title: TEST_WU_TITLE,
91
- lane: TEST_LANE,
92
- status: 'ready',
93
- type: 'feature',
94
- spec_refs: ['lumenflow://plans/existing-plan.md'],
95
- };
96
- writeFileSync(wuPath, stringifyYAML(wuDoc));
97
- // Link additional plan
98
- const changed = linkPlanToWU(tempDir, TEST_WU_ID, TEST_WU_PLAN_URI);
99
- expect(changed).toBe(true);
100
- // Verify both refs are present
101
- const updated = parseYAML(readFileSync(wuPath, 'utf-8'));
102
- expect(updated.spec_refs).toContain('lumenflow://plans/existing-plan.md');
103
- expect(updated.spec_refs).toContain(TEST_WU_PLAN_URI);
104
- });
105
- it('should be idempotent if plan already linked', async () => {
106
- const { linkPlanToWU } = await import('../plan-link.js');
107
- // Setup mock WU file with plan already linked
108
- const wuDir = join(tempDir, ...TEST_WU_DIR.split('/'));
109
- mkdirSync(wuDir, { recursive: true });
110
- const wuPath = join(wuDir, `${TEST_WU_ID}.yaml`);
111
- const wuDoc = {
112
- id: TEST_WU_ID,
113
- title: TEST_WU_TITLE,
114
- lane: TEST_LANE,
115
- status: 'ready',
116
- type: 'feature',
117
- spec_refs: [TEST_WU_PLAN_URI],
118
- };
119
- writeFileSync(wuPath, stringifyYAML(wuDoc));
120
- // Link same plan again
121
- const changed = linkPlanToWU(tempDir, TEST_WU_ID, TEST_WU_PLAN_URI);
122
- expect(changed).toBe(false);
123
- });
124
- it('should throw if WU not found', async () => {
125
- const { linkPlanToWU } = await import('../plan-link.js');
126
- expect(() => linkPlanToWU(tempDir, 'WU-9999', 'lumenflow://plans/plan.md')).toThrow();
127
- });
128
- });
129
- describe('linkPlanToInitiative', () => {
130
- it('should add related_plan field to initiative YAML', async () => {
131
- const { linkPlanToInitiative } = await import('../plan-link.js');
132
- // Setup mock initiative file
133
- const initDir = join(tempDir, ...TEST_INIT_DIR.split('/'));
134
- mkdirSync(initDir, { recursive: true });
135
- const initPath = join(initDir, `${TEST_INIT_ID}.yaml`);
136
- const initDoc = {
137
- id: TEST_INIT_ID,
138
- slug: TEST_INIT_SLUG,
139
- title: TEST_INIT_TITLE,
140
- status: TEST_STATUS_OPEN,
141
- created: TEST_DATE,
142
- };
143
- writeFileSync(initPath, stringifyYAML(initDoc));
144
- // Link plan
145
- const changed = linkPlanToInitiative(tempDir, TEST_INIT_ID, TEST_INIT_PLAN_URI);
146
- expect(changed).toBe(true);
147
- // Verify the file was updated
148
- const updated = parseYAML(readFileSync(initPath, 'utf-8'));
149
- expect(updated.related_plan).toBe(TEST_INIT_PLAN_URI);
150
- });
151
- it('should be idempotent if plan already linked', async () => {
152
- const { linkPlanToInitiative } = await import('../plan-link.js');
153
- // Setup mock initiative file with plan already linked
154
- const initDir = join(tempDir, ...TEST_INIT_DIR.split('/'));
155
- mkdirSync(initDir, { recursive: true });
156
- const initPath = join(initDir, `${TEST_INIT_ID}.yaml`);
157
- const initDoc = {
158
- id: TEST_INIT_ID,
159
- slug: TEST_INIT_SLUG,
160
- title: TEST_INIT_TITLE,
161
- status: TEST_STATUS_OPEN,
162
- created: TEST_DATE,
163
- related_plan: TEST_INIT_PLAN_URI,
164
- };
165
- writeFileSync(initPath, stringifyYAML(initDoc));
166
- // Link same plan again
167
- const changed = linkPlanToInitiative(tempDir, TEST_INIT_ID, TEST_INIT_PLAN_URI);
168
- expect(changed).toBe(false);
169
- });
170
- it('should warn but proceed if replacing existing plan', async () => {
171
- const { linkPlanToInitiative } = await import('../plan-link.js');
172
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
173
- // Setup mock initiative with different plan
174
- const initDir = join(tempDir, ...TEST_INIT_DIR.split('/'));
175
- mkdirSync(initDir, { recursive: true });
176
- const initPath = join(initDir, `${TEST_INIT_ID}.yaml`);
177
- const initDoc = {
178
- id: TEST_INIT_ID,
179
- slug: TEST_INIT_SLUG,
180
- title: TEST_INIT_TITLE,
181
- status: TEST_STATUS_OPEN,
182
- created: TEST_DATE,
183
- related_plan: 'lumenflow://plans/old-plan.md',
184
- };
185
- writeFileSync(initPath, stringifyYAML(initDoc));
186
- // Link new plan
187
- const changed = linkPlanToInitiative(tempDir, TEST_INIT_ID, 'lumenflow://plans/new-plan.md');
188
- expect(changed).toBe(true);
189
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Replacing existing'));
190
- consoleSpy.mockRestore();
191
- });
192
- });
193
- describe('validatePlanExists', () => {
194
- it('should pass for existing plan file', async () => {
195
- const { validatePlanExists } = await import('../plan-link.js');
196
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
197
- mkdirSync(plansDir, { recursive: true });
198
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
199
- writeFileSync(planPath, '# Plan');
200
- expect(() => validatePlanExists(tempDir, TEST_WU_PLAN_URI)).not.toThrow();
201
- });
202
- it('should throw for non-existent plan file', async () => {
203
- const { validatePlanExists } = await import('../plan-link.js');
204
- expect(() => validatePlanExists(tempDir, 'lumenflow://plans/nonexistent.md')).toThrow();
205
- });
206
- });
207
- describe('resolveTargetType', () => {
208
- it('should detect WU IDs', async () => {
209
- const { resolveTargetType } = await import('../plan-link.js');
210
- expect(resolveTargetType(TEST_WU_ID)).toBe('wu');
211
- expect(resolveTargetType('WU-001')).toBe('wu');
212
- expect(resolveTargetType('WU-99999')).toBe('wu');
213
- });
214
- it('should detect initiative IDs', async () => {
215
- const { resolveTargetType } = await import('../plan-link.js');
216
- expect(resolveTargetType(TEST_INIT_ID)).toBe('initiative');
217
- expect(resolveTargetType('INIT-TOOLING')).toBe('initiative');
218
- });
219
- it('should throw for invalid IDs', async () => {
220
- const { resolveTargetType } = await import('../plan-link.js');
221
- expect(() => resolveTargetType('invalid')).toThrow();
222
- expect(() => resolveTargetType('')).toThrow();
223
- });
224
- });
225
- });
226
- describe('plan:link CLI exports', () => {
227
- it('should export main function for CLI entry', async () => {
228
- const planLink = await import('../plan-link.js');
229
- expect(typeof planLink.main).toBe('function');
230
- });
231
- it('should export all required functions', async () => {
232
- const planLink = await import('../plan-link.js');
233
- expect(typeof planLink.linkPlanToWU).toBe('function');
234
- expect(typeof planLink.linkPlanToInitiative).toBe('function');
235
- expect(typeof planLink.validatePlanExists).toBe('function');
236
- expect(typeof planLink.resolveTargetType).toBe('function');
237
- expect(typeof planLink.LOG_PREFIX).toBe('string');
238
- });
239
- });
@@ -1,181 +0,0 @@
1
- /**
2
- * Tests for plan:promote command (WU-1313)
3
- *
4
- * The plan:promote command promotes a plan from draft to approved status,
5
- * or creates WUs from plan sections.
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
- /** Test constants - avoid sonarjs/no-duplicate-string */
14
- const TEST_PLANS_DIR = 'docs/04-operations/plans';
15
- const TEST_WU_ID = 'WU-1313';
16
- // Mock modules before importing
17
- vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
18
- getGitForCwd: vi.fn(() => ({
19
- branch: vi.fn().mockResolvedValue({ current: 'main' }),
20
- status: vi.fn().mockResolvedValue({ isClean: () => true }),
21
- })),
22
- }));
23
- vi.mock('@lumenflow/core/dist/wu-helpers.js', () => ({
24
- ensureOnMain: vi.fn().mockResolvedValue(undefined),
25
- }));
26
- vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
27
- withMicroWorktree: vi.fn(async ({ execute }) => {
28
- const tempDir = join(tmpdir(), `plan-promote-test-${Date.now()}`);
29
- mkdirSync(tempDir, { recursive: true });
30
- return execute({ worktreePath: tempDir });
31
- }),
32
- }));
33
- describe('plan:promote command', () => {
34
- let tempDir;
35
- let originalCwd;
36
- beforeEach(() => {
37
- tempDir = join(tmpdir(), `plan-promote-test-${Date.now()}`);
38
- mkdirSync(tempDir, { recursive: true });
39
- originalCwd = process.cwd();
40
- });
41
- afterEach(() => {
42
- process.chdir(originalCwd);
43
- if (existsSync(tempDir)) {
44
- rmSync(tempDir, { recursive: true, force: true });
45
- }
46
- vi.clearAllMocks();
47
- });
48
- describe('promotePlan', () => {
49
- it('should add approved status marker to plan', async () => {
50
- const { promotePlan } = await import('../plan-promote.js');
51
- // Setup plan file
52
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
53
- mkdirSync(plansDir, { recursive: true });
54
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
55
- writeFileSync(planPath, `# WU-1313 Plan
56
-
57
- Created: 2026-02-01
58
-
59
- ## Goal
60
-
61
- Implement plan tooling.
62
- `);
63
- // Promote plan
64
- const changed = promotePlan(planPath);
65
- expect(changed).toBe(true);
66
- const content = readFileSync(planPath, 'utf-8');
67
- expect(content).toContain('Status: approved');
68
- expect(content).toContain('Approved:');
69
- });
70
- it('should return false if plan already approved', async () => {
71
- const { promotePlan } = await import('../plan-promote.js');
72
- // Setup plan file with approved status
73
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
74
- mkdirSync(plansDir, { recursive: true });
75
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
76
- writeFileSync(planPath, `# WU-1313 Plan
77
-
78
- Created: 2026-02-01
79
- Status: approved
80
- Approved: 2026-02-01
81
-
82
- ## Goal
83
-
84
- Implement plan tooling.
85
- `);
86
- // Try to promote again
87
- const changed = promotePlan(planPath);
88
- expect(changed).toBe(false);
89
- });
90
- it('should throw if plan file not found', async () => {
91
- const { promotePlan } = await import('../plan-promote.js');
92
- const planPath = join(tempDir, 'nonexistent.md');
93
- expect(() => promotePlan(planPath)).toThrow();
94
- });
95
- });
96
- describe('validatePlanComplete', () => {
97
- it('should pass for complete plan', async () => {
98
- const { validatePlanComplete } = await import('../plan-promote.js');
99
- // Setup complete plan file
100
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
101
- mkdirSync(plansDir, { recursive: true });
102
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
103
- writeFileSync(planPath, `# WU-1313 Plan
104
-
105
- Created: 2026-02-01
106
-
107
- ## Goal
108
-
109
- Clear goal statement here.
110
-
111
- ## Scope
112
-
113
- - In scope: A
114
- - Out of scope: B
115
-
116
- ## Approach
117
-
118
- Step 1: Do X
119
- Step 2: Do Y
120
- `);
121
- const result = validatePlanComplete(planPath);
122
- expect(result.valid).toBe(true);
123
- expect(result.errors).toHaveLength(0);
124
- });
125
- it('should fail for plan with empty sections', async () => {
126
- const { validatePlanComplete } = await import('../plan-promote.js');
127
- // Setup incomplete plan file
128
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
129
- mkdirSync(plansDir, { recursive: true });
130
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
131
- writeFileSync(planPath, `# WU-1313 Plan
132
-
133
- Created: 2026-02-01
134
-
135
- ## Goal
136
-
137
- ## Scope
138
-
139
- ## Approach
140
- `);
141
- const result = validatePlanComplete(planPath);
142
- expect(result.valid).toBe(false);
143
- expect(result.errors.length).toBeGreaterThan(0);
144
- expect(result.errors.some((e) => e.includes('Goal'))).toBe(true);
145
- });
146
- });
147
- describe('getPlanPath', () => {
148
- it('should resolve plan path from ID', async () => {
149
- const { getPlanPath } = await import('../plan-promote.js');
150
- // Setup plan file
151
- const plansDir = join(tempDir, ...TEST_PLANS_DIR.split('/'));
152
- mkdirSync(plansDir, { recursive: true });
153
- const planPath = join(plansDir, `${TEST_WU_ID}-plan.md`);
154
- writeFileSync(planPath, '# Plan');
155
- process.chdir(tempDir);
156
- const resolved = getPlanPath('WU-1313');
157
- expect(resolved).toContain(`${TEST_WU_ID}-plan.md`);
158
- });
159
- });
160
- describe('getCommitMessage', () => {
161
- it('should generate correct commit message', async () => {
162
- const { getCommitMessage } = await import('../plan-promote.js');
163
- expect(getCommitMessage('WU-1313')).toBe('docs: promote wu-1313 plan to approved');
164
- expect(getCommitMessage('INIT-001')).toBe('docs: promote init-001 plan to approved');
165
- });
166
- });
167
- });
168
- describe('plan:promote CLI exports', () => {
169
- it('should export main function for CLI entry', async () => {
170
- const planPromote = await import('../plan-promote.js');
171
- expect(typeof planPromote.main).toBe('function');
172
- });
173
- it('should export all required functions', async () => {
174
- const planPromote = await import('../plan-promote.js');
175
- expect(typeof planPromote.promotePlan).toBe('function');
176
- expect(typeof planPromote.validatePlanComplete).toBe('function');
177
- expect(typeof planPromote.getPlanPath).toBe('function');
178
- expect(typeof planPromote.getCommitMessage).toBe('function');
179
- expect(typeof planPromote.LOG_PREFIX).toBe('string');
180
- });
181
- });