@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,279 +0,0 @@
1
- /**
2
- * @file enforcement.test.ts
3
- * Tests for Claude Code enforcement hooks (WU-1367)
4
- *
5
- * TDD: Write failing tests first, then implement.
6
- */
7
- // Test file lint exceptions
8
- import { describe, it, expect, beforeEach, vi } from 'vitest';
9
- import * as fs from 'node:fs';
10
- // Mock fs before importing module under test
11
- vi.mock('node:fs');
12
- vi.mock('node:child_process');
13
- const TEST_PROJECT_DIR = '/test/project';
14
- const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
15
- describe('WU-1367: Enforcement Hooks Config Schema', () => {
16
- describe('ClientConfigSchema enforcement block', () => {
17
- it('should accept enforcement block under agents.clients.claude-code', async () => {
18
- // Import dynamically to allow mocking
19
- const { ClientConfigSchema } = await import('@lumenflow/core/dist/lumenflow-config-schema.js');
20
- const config = {
21
- preamble: 'CLAUDE.md',
22
- skillsDir: '.claude/skills',
23
- enforcement: {
24
- hooks: true,
25
- block_outside_worktree: true,
26
- require_wu_for_edits: true,
27
- warn_on_stop_without_wu_done: true,
28
- },
29
- };
30
- const result = ClientConfigSchema.safeParse(config);
31
- expect(result.success).toBe(true);
32
- if (result.success) {
33
- expect(result.data.enforcement).toEqual({
34
- hooks: true,
35
- block_outside_worktree: true,
36
- require_wu_for_edits: true,
37
- warn_on_stop_without_wu_done: true,
38
- });
39
- }
40
- });
41
- it('should default enforcement values to false when not specified', async () => {
42
- const { ClientConfigSchema } = await import('@lumenflow/core/dist/lumenflow-config-schema.js');
43
- const config = {
44
- preamble: 'CLAUDE.md',
45
- enforcement: {},
46
- };
47
- const result = ClientConfigSchema.safeParse(config);
48
- expect(result.success).toBe(true);
49
- if (result.success) {
50
- expect(result.data.enforcement?.hooks).toBe(false);
51
- expect(result.data.enforcement?.block_outside_worktree).toBe(false);
52
- expect(result.data.enforcement?.require_wu_for_edits).toBe(false);
53
- expect(result.data.enforcement?.warn_on_stop_without_wu_done).toBe(false);
54
- }
55
- });
56
- it('should allow enforcement to be undefined', async () => {
57
- const { ClientConfigSchema } = await import('@lumenflow/core/dist/lumenflow-config-schema.js');
58
- const config = {
59
- preamble: 'CLAUDE.md',
60
- };
61
- const result = ClientConfigSchema.safeParse(config);
62
- expect(result.success).toBe(true);
63
- if (result.success) {
64
- expect(result.data.enforcement).toBeUndefined();
65
- }
66
- });
67
- });
68
- });
69
- describe('WU-1367: Hook Generation', () => {
70
- beforeEach(() => {
71
- vi.resetAllMocks();
72
- });
73
- describe('generateEnforcementHooks', () => {
74
- it('should generate PreToolUse hook for Write/Edit blocking when block_outside_worktree=true', async () => {
75
- const { generateEnforcementHooks } = await import('../../hooks/enforcement-generator.js');
76
- const config = {
77
- block_outside_worktree: true,
78
- require_wu_for_edits: false,
79
- warn_on_stop_without_wu_done: false,
80
- };
81
- const hooks = generateEnforcementHooks(config);
82
- expect(hooks.preToolUse).toBeDefined();
83
- expect(hooks.preToolUse?.length).toBeGreaterThan(0);
84
- expect(hooks.preToolUse?.[0].matcher).toBe('Write|Edit');
85
- });
86
- it('should generate PreToolUse hook for WU requirement when require_wu_for_edits=true', async () => {
87
- const { generateEnforcementHooks } = await import('../../hooks/enforcement-generator.js');
88
- const config = {
89
- block_outside_worktree: false,
90
- require_wu_for_edits: true,
91
- warn_on_stop_without_wu_done: false,
92
- };
93
- const hooks = generateEnforcementHooks(config);
94
- expect(hooks.preToolUse).toBeDefined();
95
- expect(hooks.preToolUse?.some((h) => h.matcher === 'Write|Edit')).toBe(true);
96
- });
97
- it('should generate Stop hook when warn_on_stop_without_wu_done=true', async () => {
98
- const { generateEnforcementHooks } = await import('../../hooks/enforcement-generator.js');
99
- const config = {
100
- block_outside_worktree: false,
101
- require_wu_for_edits: false,
102
- warn_on_stop_without_wu_done: true,
103
- };
104
- const hooks = generateEnforcementHooks(config);
105
- expect(hooks.stop).toBeDefined();
106
- expect(hooks.stop?.length).toBeGreaterThan(0);
107
- });
108
- it('should return empty hooks when all enforcement options are false', async () => {
109
- const { generateEnforcementHooks } = await import('../../hooks/enforcement-generator.js');
110
- const config = {
111
- block_outside_worktree: false,
112
- require_wu_for_edits: false,
113
- warn_on_stop_without_wu_done: false,
114
- };
115
- const hooks = generateEnforcementHooks(config);
116
- expect(hooks.preToolUse).toBeUndefined();
117
- expect(hooks.stop).toBeUndefined();
118
- });
119
- });
120
- });
121
- describe('WU-1367: Integrate Command', () => {
122
- beforeEach(() => {
123
- vi.resetAllMocks();
124
- });
125
- describe('integrateClaudeCode', () => {
126
- it('should create .claude/hooks directory when enforcement.hooks=true', async () => {
127
- const mockMkdirSync = vi.mocked(fs.mkdirSync);
128
- vi.mocked(fs.writeFileSync);
129
- vi.mocked(fs.existsSync).mockReturnValue(false);
130
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
131
- const config = {
132
- enforcement: {
133
- hooks: true,
134
- block_outside_worktree: true,
135
- require_wu_for_edits: false,
136
- warn_on_stop_without_wu_done: false,
137
- },
138
- };
139
- await integrateClaudeCode(TEST_PROJECT_DIR, config);
140
- expect(mockMkdirSync).toHaveBeenCalledWith(expect.stringContaining('.claude/hooks'), expect.any(Object));
141
- });
142
- it('should generate enforce-worktree.sh hook when block_outside_worktree=true', async () => {
143
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
144
- vi.mocked(fs.existsSync).mockReturnValue(false);
145
- vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
146
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
147
- const config = {
148
- enforcement: {
149
- hooks: true,
150
- block_outside_worktree: true,
151
- require_wu_for_edits: false,
152
- warn_on_stop_without_wu_done: false,
153
- },
154
- };
155
- await integrateClaudeCode(TEST_PROJECT_DIR, config);
156
- expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining('enforce-worktree.sh'), expect.any(String), expect.any(Object));
157
- });
158
- it('should update settings.json with hook configuration', async () => {
159
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
160
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
161
- permissions: { allow: ['Bash'] },
162
- }));
163
- vi.mocked(fs.existsSync).mockReturnValue(true);
164
- vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
165
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
166
- const config = {
167
- enforcement: {
168
- hooks: true,
169
- block_outside_worktree: true,
170
- require_wu_for_edits: false,
171
- warn_on_stop_without_wu_done: false,
172
- },
173
- };
174
- await integrateClaudeCode(TEST_PROJECT_DIR, config);
175
- // Should write updated settings.json with hooks config
176
- const settingsCall = mockWriteFileSync.mock.calls.find((call) => String(call[0]).includes('settings.json'));
177
- expect(settingsCall).toBeDefined();
178
- const settingsContent = JSON.parse(settingsCall[1]);
179
- expect(settingsContent.hooks).toBeDefined();
180
- expect(settingsContent.hooks.PreToolUse).toBeDefined();
181
- });
182
- });
183
- });
184
- describe('WU-1367: Hook Graceful Degradation', () => {
185
- it('should allow operation when LumenFlow state cannot be determined', async () => {
186
- // The hook should fail-open if it cannot determine LumenFlow state
187
- // This prevents blocking legitimate work due to infrastructure issues
188
- const { checkWorktreeEnforcement } = await import('../../hooks/enforcement-checks.js');
189
- // Simulate missing .lumenflow directory
190
- vi.mocked(fs.existsSync).mockReturnValue(false);
191
- const result = await checkWorktreeEnforcement({
192
- file_path: '/some/path/file.ts',
193
- tool_name: 'Write',
194
- });
195
- // Should not block - graceful degradation
196
- expect(result.allowed).toBe(true);
197
- expect(result.reason).toContain('graceful');
198
- });
199
- it('should allow operation when worktree detection fails', async () => {
200
- const { checkWorktreeEnforcement } = await import('../../hooks/enforcement-checks.js');
201
- // Simulate git command failure
202
- vi.mocked(fs.existsSync).mockReturnValue(true);
203
- const mockExecFileSync = vi.fn().mockImplementation(() => {
204
- throw new Error('git command failed');
205
- });
206
- vi.doMock('node:child_process', () => ({
207
- execFileSync: mockExecFileSync,
208
- }));
209
- const result = await checkWorktreeEnforcement({
210
- file_path: '/some/path/file.ts',
211
- tool_name: 'Write',
212
- });
213
- // Should not block - graceful degradation
214
- expect(result.allowed).toBe(true);
215
- });
216
- });
217
- describe('WU-1367: Setup Hook Sync', () => {
218
- it('should sync hooks when enforcement.hooks=true in config', async () => {
219
- // This tests that pnpm setup syncs hooks appropriately
220
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
221
- vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
222
- // Mock existsSync to return false for most paths (so dirs get created)
223
- // but return true for the config file
224
- vi.mocked(fs.existsSync).mockImplementation((p) => {
225
- const pathStr = String(p);
226
- return pathStr.endsWith(CONFIG_FILE_NAME);
227
- });
228
- // Config file is YAML, not JSON
229
- vi.mocked(fs.readFileSync).mockImplementation((p) => {
230
- const pathStr = String(p);
231
- if (pathStr.endsWith(CONFIG_FILE_NAME)) {
232
- return `
233
- agents:
234
- clients:
235
- claude-code:
236
- enforcement:
237
- hooks: true
238
- block_outside_worktree: true
239
- `;
240
- }
241
- // Return empty JSON for settings.json
242
- return '{}';
243
- });
244
- // Clear any previous calls
245
- mockWriteFileSync.mockClear();
246
- const { syncEnforcementHooks } = await import('../../hooks/enforcement-sync.js');
247
- const result = await syncEnforcementHooks(TEST_PROJECT_DIR);
248
- // Should have written hook files
249
- expect(result).toBe(true);
250
- expect(mockWriteFileSync).toHaveBeenCalled();
251
- });
252
- it('should skip hook sync when enforcement.hooks=false', async () => {
253
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
254
- vi.mocked(fs.existsSync).mockImplementation((p) => {
255
- const pathStr = String(p);
256
- return pathStr.endsWith(CONFIG_FILE_NAME);
257
- });
258
- vi.mocked(fs.readFileSync).mockImplementation((p) => {
259
- const pathStr = String(p);
260
- if (pathStr.endsWith(CONFIG_FILE_NAME)) {
261
- return `
262
- agents:
263
- clients:
264
- claude-code:
265
- enforcement:
266
- hooks: false
267
- `;
268
- }
269
- return '{}';
270
- });
271
- // Clear any previous calls
272
- mockWriteFileSync.mockClear();
273
- const { syncEnforcementHooks } = await import('../../hooks/enforcement-sync.js');
274
- const result = await syncEnforcementHooks(TEST_PROJECT_DIR);
275
- // Should NOT have written hook files
276
- expect(result).toBe(false);
277
- expect(mockWriteFileSync).not.toHaveBeenCalled();
278
- });
279
- });
@@ -1,131 +0,0 @@
1
- /**
2
- * @file init-config-lanes.test.ts
3
- * Test: .lumenflow.config.yaml includes default lane definitions for parent lanes
4
- *
5
- * WU-1307: Fix lumenflow-init scaffolding
6
- *
7
- * The generated config must include lane definitions that match the parent
8
- * lanes used in the documentation examples (Framework, Experience, Content, Operations).
9
- * These lanes should have sensible defaults for code_paths and wip_limit.
10
- */
11
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
- import * as fs from 'node:fs';
13
- import * as path from 'node:path';
14
- import * as os from 'node:os';
15
- import YAML from 'yaml';
16
- import { scaffoldProject } from '../init.js';
17
- /** Config file name - extracted to avoid duplicate string lint errors */
18
- const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
19
- describe('init config default lanes (WU-1307)', () => {
20
- let tempDir;
21
- beforeEach(() => {
22
- // Create a temporary directory for each test
23
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-init-config-lanes-'));
24
- });
25
- afterEach(() => {
26
- // Clean up temporary directory
27
- if (tempDir && fs.existsSync(tempDir)) {
28
- fs.rmSync(tempDir, { recursive: true, force: true });
29
- }
30
- });
31
- /** Helper to read and parse config from temp directory */
32
- function readConfig() {
33
- const configPath = path.join(tempDir, CONFIG_FILE_NAME);
34
- const configContent = fs.readFileSync(configPath, 'utf-8');
35
- return YAML.parse(configContent);
36
- }
37
- it('should generate .lumenflow.config.yaml with lanes.definitions', async () => {
38
- // Arrange
39
- const configPath = path.join(tempDir, CONFIG_FILE_NAME);
40
- // Act
41
- await scaffoldProject(tempDir, { force: true, full: true });
42
- // Assert
43
- expect(fs.existsSync(configPath)).toBe(true);
44
- const config = readConfig();
45
- // Should have lanes.definitions
46
- expect(config.lanes).toBeDefined();
47
- expect(config.lanes.definitions).toBeDefined();
48
- expect(Array.isArray(config.lanes.definitions)).toBe(true);
49
- });
50
- it('should include Framework parent lane with sublanes', async () => {
51
- // Act
52
- await scaffoldProject(tempDir, { force: true, full: true });
53
- // Assert
54
- const config = readConfig();
55
- const lanes = (config.lanes?.definitions || []);
56
- const frameworkLanes = lanes.filter((l) => l.name.startsWith('Framework:'));
57
- expect(frameworkLanes.length).toBeGreaterThan(0);
58
- // Should have at least Framework: Core and Framework: CLI
59
- const laneNames = frameworkLanes.map((l) => l.name);
60
- expect(laneNames).toContain('Framework: Core');
61
- expect(laneNames).toContain('Framework: CLI');
62
- });
63
- it('should include Experience parent lane for frontend work', async () => {
64
- // Act
65
- await scaffoldProject(tempDir, { force: true, full: true });
66
- // Assert
67
- const config = readConfig();
68
- const lanes = (config.lanes?.definitions || []);
69
- const experienceLanes = lanes.filter((l) => l.name.startsWith('Experience:'));
70
- // Should have at least one Experience lane
71
- expect(experienceLanes.length).toBeGreaterThan(0);
72
- });
73
- it('should include Content: Documentation lane', async () => {
74
- // Act
75
- await scaffoldProject(tempDir, { force: true, full: true });
76
- // Assert
77
- const config = readConfig();
78
- const lanes = (config.lanes?.definitions || []);
79
- const contentLane = lanes.find((l) => l.name === 'Content: Documentation');
80
- expect(contentLane).toBeDefined();
81
- expect(contentLane?.code_paths).toBeDefined();
82
- expect(contentLane?.code_paths).toContain('docs/**');
83
- });
84
- it('should include Operations parent lanes', async () => {
85
- // Act
86
- await scaffoldProject(tempDir, { force: true, full: true });
87
- // Assert
88
- const config = readConfig();
89
- const lanes = (config.lanes?.definitions || []);
90
- const operationsLanes = lanes.filter((l) => l.name.startsWith('Operations:'));
91
- expect(operationsLanes.length).toBeGreaterThan(0);
92
- // Should have Infrastructure and CI/CD
93
- const laneNames = operationsLanes.map((l) => l.name);
94
- expect(laneNames).toContain('Operations: Infrastructure');
95
- expect(laneNames).toContain('Operations: CI/CD');
96
- });
97
- it('should have wip_limit: 1 for code lanes by default', async () => {
98
- // Act
99
- await scaffoldProject(tempDir, { force: true, full: true });
100
- // Assert
101
- const config = readConfig();
102
- const lanes = (config.lanes?.definitions || []);
103
- const frameworkCore = lanes.find((l) => l.name === 'Framework: Core');
104
- expect(frameworkCore).toBeDefined();
105
- expect(frameworkCore?.wip_limit).toBe(1);
106
- });
107
- it('should have code_paths for each lane', async () => {
108
- // Act
109
- await scaffoldProject(tempDir, { force: true, full: true });
110
- // Assert
111
- const config = readConfig();
112
- const lanes = (config.lanes?.definitions || []);
113
- // Every lane should have code_paths
114
- for (const lane of lanes) {
115
- expect(lane.code_paths).toBeDefined();
116
- expect(Array.isArray(lane.code_paths)).toBe(true);
117
- expect(lane.code_paths?.length).toBeGreaterThan(0);
118
- }
119
- });
120
- it('should use "Parent: Sublane" format for lane names', async () => {
121
- // Act
122
- await scaffoldProject(tempDir, { force: true, full: true });
123
- // Assert
124
- const config = readConfig();
125
- const lanes = (config.lanes?.definitions || []);
126
- // All lanes should follow "Parent: Sublane" format (colon + space)
127
- for (const lane of lanes) {
128
- expect(lane.name).toMatch(/^[A-Z][a-z]+: [A-Z]/);
129
- }
130
- });
131
- });
@@ -1,152 +0,0 @@
1
- /**
2
- * @file init-docs-structure.test.ts
3
- * Tests for --docs-structure flag and auto-detection (WU-1309)
4
- */
5
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
- import * as fs from 'node:fs';
7
- import * as path from 'node:path';
8
- import * as os from 'node:os';
9
- import { scaffoldProject, detectDocsStructure, getDocsPath, } from '../init.js';
10
- // Constants to avoid duplicate strings (sonarjs/no-duplicate-string)
11
- const ARC42_DOCS_STRUCTURE = 'arc42';
12
- const SIMPLE_DOCS_STRUCTURE = 'simple';
13
- const DOCS_04_OPERATIONS = '04-operations';
14
- describe('docs-structure', () => {
15
- let tempDir;
16
- beforeEach(() => {
17
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-docs-structure-test-'));
18
- });
19
- afterEach(() => {
20
- fs.rmSync(tempDir, { recursive: true, force: true });
21
- });
22
- describe('detectDocsStructure', () => {
23
- it('should return "arc42" when docs/04-operations exists', () => {
24
- fs.mkdirSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS), { recursive: true });
25
- const result = detectDocsStructure(tempDir);
26
- expect(result).toBe(ARC42_DOCS_STRUCTURE);
27
- });
28
- it('should return "simple" when docs exists without 04-operations', () => {
29
- fs.mkdirSync(path.join(tempDir, 'docs'), { recursive: true });
30
- fs.writeFileSync(path.join(tempDir, 'docs', 'README.md'), '# Docs\n');
31
- const result = detectDocsStructure(tempDir);
32
- expect(result).toBe(SIMPLE_DOCS_STRUCTURE);
33
- });
34
- it('should return "simple" when no docs directory exists', () => {
35
- const result = detectDocsStructure(tempDir);
36
- expect(result).toBe(SIMPLE_DOCS_STRUCTURE);
37
- });
38
- it('should detect arc42 with any numbered directory (01-*, 02-*, etc.)', () => {
39
- fs.mkdirSync(path.join(tempDir, 'docs', '01-introduction'), { recursive: true });
40
- const result = detectDocsStructure(tempDir);
41
- expect(result).toBe(ARC42_DOCS_STRUCTURE);
42
- });
43
- });
44
- describe('getDocsPath', () => {
45
- it('should return simple paths for simple structure', () => {
46
- const paths = getDocsPath(SIMPLE_DOCS_STRUCTURE);
47
- expect(paths.operations).toBe('docs');
48
- expect(paths.tasks).toBe('docs/tasks');
49
- expect(paths.onboarding).toBe('docs/_frameworks/lumenflow/agent/onboarding');
50
- });
51
- it('should return arc42 paths for arc42 structure', () => {
52
- const paths = getDocsPath(ARC42_DOCS_STRUCTURE);
53
- expect(paths.operations).toBe('docs/04-operations');
54
- expect(paths.tasks).toBe('docs/04-operations/tasks');
55
- expect(paths.onboarding).toBe('docs/04-operations/_frameworks/lumenflow/agent/onboarding');
56
- });
57
- });
58
- describe('scaffoldProject with --docs-structure', () => {
59
- it('should scaffold simple structure with --docs-structure simple', async () => {
60
- const options = {
61
- force: false,
62
- full: true,
63
- docsStructure: SIMPLE_DOCS_STRUCTURE,
64
- };
65
- await scaffoldProject(tempDir, options);
66
- // Simple structure: docs/tasks, not docs/04-operations/tasks
67
- expect(fs.existsSync(path.join(tempDir, 'docs', 'tasks'))).toBe(true);
68
- expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS))).toBe(false);
69
- });
70
- it('should scaffold WU template with resilient defaults (simple)', async () => {
71
- const options = {
72
- force: false,
73
- full: true,
74
- docsStructure: SIMPLE_DOCS_STRUCTURE,
75
- };
76
- await scaffoldProject(tempDir, options);
77
- const templatePath = path.join(tempDir, 'docs', 'tasks', 'templates', 'wu-template.yaml');
78
- expect(fs.existsSync(templatePath)).toBe(true);
79
- const content = fs.readFileSync(templatePath, 'utf-8');
80
- // Feature WUs should reference plan protocol by default (plan-less friendly).
81
- expect(content).toContain('lumenflow://plans/WU-XXX-plan.md');
82
- // Ensure non-empty notes to avoid strict spec-linter failures out of the box.
83
- expect(content).not.toContain("notes: ''");
84
- expect(content).toContain('notes:');
85
- // Ensure manual test stub exists to prevent empty tests failures.
86
- expect(content).toContain('Manual check:');
87
- });
88
- it('should scaffold arc42 structure with --docs-structure arc42', async () => {
89
- const options = {
90
- force: false,
91
- full: true,
92
- docsStructure: ARC42_DOCS_STRUCTURE,
93
- };
94
- await scaffoldProject(tempDir, options);
95
- // Arc42 structure: docs/04-operations/tasks
96
- expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS, 'tasks'))).toBe(true);
97
- });
98
- it('should scaffold WU template with resilient defaults (arc42)', async () => {
99
- const options = {
100
- force: false,
101
- full: true,
102
- docsStructure: ARC42_DOCS_STRUCTURE,
103
- };
104
- await scaffoldProject(tempDir, options);
105
- const templatePath = path.join(tempDir, 'docs', DOCS_04_OPERATIONS, 'tasks', 'templates', 'wu-template.yaml');
106
- expect(fs.existsSync(templatePath)).toBe(true);
107
- const content = fs.readFileSync(templatePath, 'utf-8');
108
- expect(content).toContain('lumenflow://plans/WU-XXX-plan.md');
109
- expect(content).not.toContain("notes: ''");
110
- expect(content).toContain('notes:');
111
- expect(content).toContain('Manual check:');
112
- });
113
- it('should auto-detect arc42 when docs/04-operations exists', async () => {
114
- // Create existing arc42 structure
115
- fs.mkdirSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS), { recursive: true });
116
- const options = {
117
- force: false,
118
- full: true,
119
- // No docsStructure specified - should auto-detect arc42
120
- };
121
- await scaffoldProject(tempDir, options);
122
- // Should use arc42 structure
123
- expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS, 'tasks'))).toBe(true);
124
- });
125
- it('should auto-detect simple when only docs exists', async () => {
126
- // Create existing simple structure
127
- fs.mkdirSync(path.join(tempDir, 'docs'), { recursive: true });
128
- fs.writeFileSync(path.join(tempDir, 'docs', 'README.md'), '# Docs\n');
129
- const options = {
130
- force: false,
131
- full: true,
132
- // No docsStructure specified - should auto-detect simple
133
- };
134
- await scaffoldProject(tempDir, options);
135
- // Should use simple structure
136
- expect(fs.existsSync(path.join(tempDir, 'docs', 'tasks'))).toBe(true);
137
- expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS))).toBe(false);
138
- });
139
- it('should respect explicit --docs-structure over auto-detection', async () => {
140
- // Create existing arc42 structure
141
- fs.mkdirSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS), { recursive: true });
142
- const options = {
143
- force: true, // Force overwrite
144
- full: true,
145
- docsStructure: SIMPLE_DOCS_STRUCTURE, // Explicitly request simple
146
- };
147
- await scaffoldProject(tempDir, options);
148
- // Should use simple structure despite arc42 existing
149
- expect(fs.existsSync(path.join(tempDir, 'docs', 'tasks'))).toBe(true);
150
- });
151
- });
152
- });