@lumenflow/cli 2.4.0 → 2.5.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 (147) hide show
  1. package/README.md +11 -8
  2. package/dist/__tests__/init-config-lanes.test.js +131 -0
  3. package/dist/__tests__/init-docs-structure.test.js +119 -0
  4. package/dist/__tests__/init-lane-inference.test.js +125 -0
  5. package/dist/__tests__/init-onboarding-docs.test.js +132 -0
  6. package/dist/__tests__/init-quick-ref.test.js +145 -0
  7. package/dist/__tests__/init-scripts.test.js +207 -0
  8. package/dist/__tests__/init-template-portability.test.js +97 -0
  9. package/dist/__tests__/init.test.js +7 -2
  10. package/dist/__tests__/initiative-add-wu.test.js +420 -0
  11. package/dist/__tests__/initiative-plan-replacement.test.js +162 -0
  12. package/dist/__tests__/initiative-remove-wu.test.js +458 -0
  13. package/dist/__tests__/onboarding-smoke-test.test.js +211 -0
  14. package/dist/__tests__/path-centralization-cli.test.js +234 -0
  15. package/dist/__tests__/plan-create.test.js +126 -0
  16. package/dist/__tests__/plan-edit.test.js +157 -0
  17. package/dist/__tests__/plan-link.test.js +239 -0
  18. package/dist/__tests__/plan-promote.test.js +181 -0
  19. package/dist/__tests__/templates-sync.test.js +219 -0
  20. package/dist/__tests__/wu-create-strict.test.js +118 -0
  21. package/dist/__tests__/wu-edit-strict.test.js +109 -0
  22. package/dist/__tests__/wu-validate-strict.test.js +113 -0
  23. package/dist/flow-bottlenecks.js +4 -2
  24. package/dist/gates.js +22 -0
  25. package/dist/init.js +670 -87
  26. package/dist/initiative-add-wu.js +112 -16
  27. package/dist/initiative-remove-wu.js +248 -0
  28. package/dist/onboarding-smoke-test.js +400 -0
  29. package/dist/orchestrate-init-status.js +37 -9
  30. package/dist/orchestrate-initiative.js +10 -4
  31. package/dist/plan-create.js +199 -0
  32. package/dist/plan-edit.js +235 -0
  33. package/dist/plan-link.js +233 -0
  34. package/dist/plan-promote.js +231 -0
  35. package/dist/sync-templates.js +137 -5
  36. package/dist/wu-block.js +16 -5
  37. package/dist/wu-claim.js +15 -9
  38. package/dist/wu-create.js +50 -2
  39. package/dist/wu-deps.js +3 -1
  40. package/dist/wu-done.js +14 -5
  41. package/dist/wu-edit.js +35 -0
  42. package/dist/wu-prep.js +131 -8
  43. package/dist/wu-spawn.js +14 -1
  44. package/dist/wu-unblock.js +34 -2
  45. package/dist/wu-validate.js +25 -17
  46. package/package.json +11 -7
  47. package/templates/core/.lumenflow/constraints.md.template +61 -3
  48. package/templates/core/AGENTS.md.template +2 -2
  49. package/templates/core/LUMENFLOW.md.template +85 -23
  50. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +157 -0
  51. package/templates/core/ai/onboarding/agent-safety-card.md.template +227 -0
  52. package/templates/core/ai/onboarding/docs-generation.md.template +277 -0
  53. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +49 -7
  54. package/templates/core/ai/onboarding/quick-ref-commands.md.template +343 -110
  55. package/templates/core/ai/onboarding/release-process.md.template +8 -2
  56. package/templates/core/ai/onboarding/starting-prompt.md.template +407 -0
  57. package/templates/core/ai/onboarding/test-ratchet.md.template +131 -0
  58. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +91 -38
  59. package/templates/core/ai/onboarding/vendor-support.md.template +219 -0
  60. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +13 -1
  61. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +14 -16
  62. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +48 -4
  63. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -1
  64. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +19 -8
  65. package/dist/__tests__/init-plan.test.js +0 -340
  66. package/dist/agent-issues-query.d.ts +0 -16
  67. package/dist/agent-log-issue.d.ts +0 -10
  68. package/dist/agent-session-end.d.ts +0 -10
  69. package/dist/agent-session.d.ts +0 -10
  70. package/dist/backlog-prune.d.ts +0 -84
  71. package/dist/cli-entry-point.d.ts +0 -8
  72. package/dist/deps-add.d.ts +0 -91
  73. package/dist/deps-remove.d.ts +0 -17
  74. package/dist/docs-sync.d.ts +0 -50
  75. package/dist/file-delete.d.ts +0 -84
  76. package/dist/file-edit.d.ts +0 -82
  77. package/dist/file-read.d.ts +0 -92
  78. package/dist/file-write.d.ts +0 -90
  79. package/dist/flow-bottlenecks.d.ts +0 -16
  80. package/dist/flow-report.d.ts +0 -16
  81. package/dist/gates.d.ts +0 -94
  82. package/dist/git-branch.d.ts +0 -65
  83. package/dist/git-diff.d.ts +0 -58
  84. package/dist/git-log.d.ts +0 -69
  85. package/dist/git-status.d.ts +0 -58
  86. package/dist/guard-locked.d.ts +0 -62
  87. package/dist/guard-main-branch.d.ts +0 -50
  88. package/dist/guard-worktree-commit.d.ts +0 -59
  89. package/dist/index.d.ts +0 -10
  90. package/dist/init-plan.d.ts +0 -80
  91. package/dist/init-plan.js +0 -337
  92. package/dist/init.d.ts +0 -46
  93. package/dist/initiative-add-wu.d.ts +0 -22
  94. package/dist/initiative-bulk-assign-wus.d.ts +0 -16
  95. package/dist/initiative-create.d.ts +0 -28
  96. package/dist/initiative-edit.d.ts +0 -34
  97. package/dist/initiative-list.d.ts +0 -12
  98. package/dist/initiative-status.d.ts +0 -11
  99. package/dist/lumenflow-upgrade.d.ts +0 -103
  100. package/dist/mem-checkpoint.d.ts +0 -16
  101. package/dist/mem-cleanup.d.ts +0 -29
  102. package/dist/mem-create.d.ts +0 -17
  103. package/dist/mem-export.d.ts +0 -10
  104. package/dist/mem-inbox.d.ts +0 -35
  105. package/dist/mem-init.d.ts +0 -15
  106. package/dist/mem-ready.d.ts +0 -16
  107. package/dist/mem-signal.d.ts +0 -16
  108. package/dist/mem-start.d.ts +0 -16
  109. package/dist/mem-summarize.d.ts +0 -22
  110. package/dist/mem-triage.d.ts +0 -22
  111. package/dist/metrics-cli.d.ts +0 -90
  112. package/dist/metrics-snapshot.d.ts +0 -18
  113. package/dist/orchestrate-init-status.d.ts +0 -11
  114. package/dist/orchestrate-initiative.d.ts +0 -12
  115. package/dist/orchestrate-monitor.d.ts +0 -11
  116. package/dist/release.d.ts +0 -117
  117. package/dist/rotate-progress.d.ts +0 -48
  118. package/dist/session-coordinator.d.ts +0 -74
  119. package/dist/spawn-list.d.ts +0 -16
  120. package/dist/state-bootstrap.d.ts +0 -92
  121. package/dist/sync-templates.d.ts +0 -52
  122. package/dist/trace-gen.d.ts +0 -84
  123. package/dist/validate-agent-skills.d.ts +0 -50
  124. package/dist/validate-agent-sync.d.ts +0 -36
  125. package/dist/validate-backlog-sync.d.ts +0 -37
  126. package/dist/validate-skills-spec.d.ts +0 -40
  127. package/dist/validate.d.ts +0 -60
  128. package/dist/wu-block.d.ts +0 -16
  129. package/dist/wu-claim.d.ts +0 -74
  130. package/dist/wu-cleanup.d.ts +0 -35
  131. package/dist/wu-create.d.ts +0 -69
  132. package/dist/wu-delete.d.ts +0 -21
  133. package/dist/wu-deps.d.ts +0 -13
  134. package/dist/wu-done.d.ts +0 -225
  135. package/dist/wu-edit.d.ts +0 -63
  136. package/dist/wu-infer-lane.d.ts +0 -17
  137. package/dist/wu-preflight.d.ts +0 -47
  138. package/dist/wu-prune.d.ts +0 -16
  139. package/dist/wu-recover.d.ts +0 -37
  140. package/dist/wu-release.d.ts +0 -19
  141. package/dist/wu-repair.d.ts +0 -60
  142. package/dist/wu-spawn-completion.d.ts +0 -10
  143. package/dist/wu-spawn.d.ts +0 -192
  144. package/dist/wu-status.d.ts +0 -25
  145. package/dist/wu-unblock.d.ts +0 -16
  146. package/dist/wu-unlock-lane.d.ts +0 -19
  147. package/dist/wu-validate.d.ts +0 -16
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @file init-quick-ref.test.ts
3
+ * Tests for quick-ref commands content (WU-1309)
4
+ * Verifies: correct init command, complete wu:create example
5
+ */
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
10
+ import { scaffoldProject } from '../init.js';
11
+ // Constants to avoid duplicate strings (sonarjs/no-duplicate-string)
12
+ const ARC42_DOCS_STRUCTURE = 'arc42';
13
+ describe('quick-ref commands', () => {
14
+ let tempDir;
15
+ beforeEach(() => {
16
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-quickref-test-'));
17
+ });
18
+ afterEach(() => {
19
+ fs.rmSync(tempDir, { recursive: true, force: true });
20
+ });
21
+ function getQuickRefPath(docsStructure = ARC42_DOCS_STRUCTURE) {
22
+ if (docsStructure === 'simple') {
23
+ return path.join(tempDir, 'docs', '_frameworks', 'lumenflow', 'agent', 'onboarding', 'quick-ref-commands.md');
24
+ }
25
+ return path.join(tempDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding', 'quick-ref-commands.md');
26
+ }
27
+ function getArc42Options() {
28
+ return {
29
+ force: false,
30
+ full: true,
31
+ docsStructure: ARC42_DOCS_STRUCTURE,
32
+ };
33
+ }
34
+ describe('init command documentation', () => {
35
+ it('should show correct lumenflow init command', async () => {
36
+ await scaffoldProject(tempDir, getArc42Options());
37
+ const quickRefPath = getQuickRefPath();
38
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
39
+ // Should document the init command correctly
40
+ expect(content).toContain('lumenflow init');
41
+ // Should show the various init flags
42
+ expect(content).toContain('--full');
43
+ });
44
+ it('should document --docs-structure flag in quick-ref', async () => {
45
+ await scaffoldProject(tempDir, getArc42Options());
46
+ const quickRefPath = getQuickRefPath();
47
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
48
+ // Should document the docs-structure option
49
+ expect(content).toContain('--docs-structure');
50
+ expect(content).toMatch(/simple|arc42/);
51
+ });
52
+ });
53
+ describe('wu:create example', () => {
54
+ it('should include a complete wu:create example with all required fields', async () => {
55
+ await scaffoldProject(tempDir, getArc42Options());
56
+ const quickRefPath = getQuickRefPath();
57
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
58
+ // Should have a complete wu:create example
59
+ expect(content).toContain('wu:create');
60
+ expect(content).toContain('--lane');
61
+ expect(content).toContain('--title');
62
+ expect(content).toContain('--description');
63
+ expect(content).toContain('--acceptance');
64
+ expect(content).toContain('--code-paths');
65
+ expect(content).toContain('--exposure');
66
+ });
67
+ it('should show test-paths in wu:create example', async () => {
68
+ await scaffoldProject(tempDir, getArc42Options());
69
+ const quickRefPath = getQuickRefPath();
70
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
71
+ // Should include test paths
72
+ expect(content).toMatch(/--test-paths-(unit|e2e)/);
73
+ });
74
+ it('should show spec-refs in wu:create example for feature WUs', async () => {
75
+ await scaffoldProject(tempDir, getArc42Options());
76
+ const quickRefPath = getQuickRefPath();
77
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
78
+ // Should include spec-refs for feature WUs
79
+ expect(content).toContain('--spec-refs');
80
+ });
81
+ });
82
+ describe('AGENTS.md quick-ref link', () => {
83
+ it('should have correct quick-ref link for arc42 structure', async () => {
84
+ await scaffoldProject(tempDir, getArc42Options());
85
+ const agentsContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
86
+ // Should point to arc42 path
87
+ expect(agentsContent).toContain('docs/04-operations/_frameworks/lumenflow/agent/onboarding/quick-ref-commands.md');
88
+ });
89
+ it('should have correct quick-ref link for simple structure', async () => {
90
+ const options = {
91
+ force: false,
92
+ full: true,
93
+ docsStructure: 'simple',
94
+ };
95
+ await scaffoldProject(tempDir, options);
96
+ const agentsContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
97
+ // Should point to simple path (without 04-operations)
98
+ expect(agentsContent).toContain('docs/_frameworks/lumenflow/agent/onboarding/quick-ref-commands.md');
99
+ expect(agentsContent).not.toContain('04-operations');
100
+ });
101
+ });
102
+ describe('quick-ref command tables', () => {
103
+ it('should have project setup commands including init', async () => {
104
+ await scaffoldProject(tempDir, getArc42Options());
105
+ const quickRefPath = getQuickRefPath();
106
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
107
+ // Should have a project setup section
108
+ // eslint-disable-next-line sonarjs/slow-regex -- Simple alternation, no backtracking risk
109
+ expect(content).toMatch(/##.*?Setup|##.*?Project/i);
110
+ expect(content).toContain('lumenflow init');
111
+ });
112
+ it('should have WU management commands', async () => {
113
+ await scaffoldProject(tempDir, getArc42Options());
114
+ const quickRefPath = getQuickRefPath();
115
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
116
+ // Should have WU commands
117
+ expect(content).toContain('wu:create');
118
+ expect(content).toContain('wu:claim');
119
+ expect(content).toContain('wu:done');
120
+ expect(content).toContain('wu:block');
121
+ });
122
+ it('should have gates commands', async () => {
123
+ await scaffoldProject(tempDir, getArc42Options());
124
+ const quickRefPath = getQuickRefPath();
125
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
126
+ // Should have gates commands
127
+ expect(content).toContain('pnpm gates');
128
+ expect(content).toContain('--docs-only');
129
+ });
130
+ });
131
+ describe('workflow sequence', () => {
132
+ it('should have a complete workflow sequence example', async () => {
133
+ await scaffoldProject(tempDir, getArc42Options());
134
+ const quickRefPath = getQuickRefPath();
135
+ const content = fs.readFileSync(quickRefPath, 'utf-8');
136
+ // Should have workflow sequence
137
+ expect(content).toMatch(/workflow|sequence/i);
138
+ // Should show the full flow: create -> claim -> work -> commit -> gates -> done
139
+ expect(content).toContain('wu:create');
140
+ expect(content).toContain('wu:claim');
141
+ expect(content).toContain('pnpm gates');
142
+ expect(content).toContain('wu:done');
143
+ });
144
+ });
145
+ });
@@ -0,0 +1,207 @@
1
+ /**
2
+ * @file init-scripts.test.ts
3
+ * Test: Generated package.json scripts use correct format (wu-create, wu-claim, wu-done, gates)
4
+ *
5
+ * WU-1307: Fix lumenflow-init scaffolding
6
+ *
7
+ * The init command should inject standalone binary scripts that work
8
+ * in consumer projects without requiring the full @lumenflow/cli path.
9
+ */
10
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import * as os from 'node:os';
14
+ import { scaffoldProject } from '../init.js';
15
+ /** Package.json file name - extracted to avoid duplicate string lint errors */
16
+ const PACKAGE_JSON_FILE_NAME = 'package.json';
17
+ describe('init scripts generation (WU-1307)', () => {
18
+ let tempDir;
19
+ beforeEach(() => {
20
+ // Create a temporary directory for each test
21
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-init-scripts-'));
22
+ });
23
+ afterEach(() => {
24
+ // Clean up temporary directory
25
+ if (tempDir && fs.existsSync(tempDir)) {
26
+ fs.rmSync(tempDir, { recursive: true, force: true });
27
+ }
28
+ });
29
+ /** Helper to read and parse package.json from temp directory */
30
+ function readPackageJson() {
31
+ const packageJsonPath = path.join(tempDir, PACKAGE_JSON_FILE_NAME);
32
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
33
+ }
34
+ it('should generate package.json scripts using standalone binaries', async () => {
35
+ // Arrange
36
+ const packageJsonPath = path.join(tempDir, PACKAGE_JSON_FILE_NAME);
37
+ // Act
38
+ await scaffoldProject(tempDir, { force: true, full: true });
39
+ // Assert
40
+ expect(fs.existsSync(packageJsonPath)).toBe(true);
41
+ const packageJson = readPackageJson();
42
+ expect(packageJson.scripts).toBeDefined();
43
+ // Scripts should use standalone binary format (wu-create, wu-claim, etc.)
44
+ // NOT 'pnpm exec lumenflow' format
45
+ expect(packageJson.scripts?.['wu:claim']).toBe('wu-claim');
46
+ expect(packageJson.scripts?.['wu:done']).toBe('wu-done');
47
+ expect(packageJson.scripts?.['wu:create']).toBe('wu-create');
48
+ expect(packageJson.scripts?.gates).toBe('gates');
49
+ });
50
+ it('should NOT use pnpm exec lumenflow format', async () => {
51
+ // Act
52
+ await scaffoldProject(tempDir, { force: true, full: true });
53
+ // Assert
54
+ const packageJson = readPackageJson();
55
+ // Ensure scripts do NOT use the old 'pnpm exec lumenflow' format
56
+ const scriptValues = Object.values(packageJson.scripts ?? {});
57
+ const hasOldFormat = scriptValues.some((script) => script.includes('pnpm exec lumenflow'));
58
+ expect(hasOldFormat).toBe(false);
59
+ });
60
+ it('should include all essential WU lifecycle scripts', async () => {
61
+ // Act
62
+ await scaffoldProject(tempDir, { force: true, full: true });
63
+ // Assert
64
+ const packageJson = readPackageJson();
65
+ // Essential scripts that must be present
66
+ const essentialScripts = ['wu:claim', 'wu:done', 'wu:create', 'wu:status', 'gates'];
67
+ for (const scriptName of essentialScripts) {
68
+ expect(packageJson.scripts?.[scriptName]).toBeDefined();
69
+ }
70
+ });
71
+ it('should preserve existing scripts when updating package.json', async () => {
72
+ // Arrange
73
+ const packageJsonPath = path.join(tempDir, PACKAGE_JSON_FILE_NAME);
74
+ const existingPackageJson = {
75
+ name: 'test-project',
76
+ version: '1.0.0',
77
+ scripts: {
78
+ test: 'vitest',
79
+ build: 'tsc',
80
+ custom: 'echo hello',
81
+ },
82
+ };
83
+ fs.writeFileSync(packageJsonPath, JSON.stringify(existingPackageJson, null, 2));
84
+ // Act
85
+ await scaffoldProject(tempDir, { force: false, full: true });
86
+ // Assert
87
+ const packageJson = readPackageJson();
88
+ // Existing scripts should be preserved
89
+ expect(packageJson.scripts?.test).toBe('vitest');
90
+ expect(packageJson.scripts?.build).toBe('tsc');
91
+ expect(packageJson.scripts?.custom).toBe('echo hello');
92
+ // LumenFlow scripts should be added
93
+ expect(packageJson.scripts?.['wu:claim']).toBeDefined();
94
+ expect(packageJson.scripts?.gates).toBeDefined();
95
+ });
96
+ // WU-1342: Test for all 17 essential commands
97
+ it('should include all 17 essential commands (WU-1342)', async () => {
98
+ // Act
99
+ await scaffoldProject(tempDir, { force: true, full: true });
100
+ // Assert
101
+ const packageJson = readPackageJson();
102
+ // All 17 essential commands that must be present per WU-1342 acceptance criteria
103
+ const essentialScripts = [
104
+ // Core WU lifecycle
105
+ 'wu:claim',
106
+ 'wu:done',
107
+ 'wu:create',
108
+ 'wu:status',
109
+ 'wu:block',
110
+ 'wu:unblock',
111
+ // Additional critical commands (WU-1342)
112
+ 'wu:prep',
113
+ 'wu:recover',
114
+ 'wu:spawn',
115
+ 'wu:validate',
116
+ 'wu:infer-lane',
117
+ // Memory commands
118
+ 'mem:init',
119
+ 'mem:checkpoint',
120
+ 'mem:inbox',
121
+ // Lane commands
122
+ 'lane:suggest',
123
+ // Gates
124
+ 'gates',
125
+ 'gates:docs',
126
+ ];
127
+ for (const scriptName of essentialScripts) {
128
+ expect(packageJson.scripts?.[scriptName], `Missing essential script: ${scriptName}`).toBeDefined();
129
+ }
130
+ // Verify count
131
+ const lumenflowScripts = Object.keys(packageJson.scripts ?? {}).filter((key) => key.startsWith('wu:') ||
132
+ key.startsWith('mem:') ||
133
+ key.startsWith('lane:') ||
134
+ key === 'gates' ||
135
+ key === 'gates:docs');
136
+ expect(lumenflowScripts.length).toBeGreaterThanOrEqual(17);
137
+ });
138
+ });
139
+ describe('init .gitignore generation (WU-1342)', () => {
140
+ let tempDir;
141
+ beforeEach(() => {
142
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-init-gitignore-'));
143
+ });
144
+ afterEach(() => {
145
+ if (tempDir && fs.existsSync(tempDir)) {
146
+ fs.rmSync(tempDir, { recursive: true, force: true });
147
+ }
148
+ });
149
+ it('should create .gitignore with required exclusions (WU-1342)', async () => {
150
+ // Act
151
+ await scaffoldProject(tempDir, { force: true, full: true });
152
+ // Assert
153
+ const gitignorePath = path.join(tempDir, '.gitignore');
154
+ expect(fs.existsSync(gitignorePath)).toBe(true);
155
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
156
+ // Must include node_modules
157
+ expect(content).toContain('node_modules');
158
+ // Must include .lumenflow/state
159
+ expect(content).toContain('.lumenflow/state');
160
+ // Must include worktrees
161
+ expect(content).toContain('worktrees');
162
+ });
163
+ it('should preserve existing .gitignore content in merge mode (WU-1342)', async () => {
164
+ // Arrange
165
+ const gitignorePath = path.join(tempDir, '.gitignore');
166
+ const existingContent = '# Custom ignores\n.env\n*.log\n';
167
+ fs.writeFileSync(gitignorePath, existingContent);
168
+ // Act
169
+ await scaffoldProject(tempDir, { force: false, full: true, merge: true });
170
+ // Assert
171
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
172
+ // Should preserve existing content
173
+ expect(content).toContain('.env');
174
+ expect(content).toContain('*.log');
175
+ // Should add LumenFlow exclusions
176
+ expect(content).toContain('node_modules');
177
+ expect(content).toContain('.lumenflow/state');
178
+ expect(content).toContain('worktrees');
179
+ });
180
+ });
181
+ describe('init .claude directory creation (WU-1342)', () => {
182
+ let tempDir;
183
+ beforeEach(() => {
184
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-init-claude-'));
185
+ });
186
+ afterEach(() => {
187
+ if (tempDir && fs.existsSync(tempDir)) {
188
+ fs.rmSync(tempDir, { recursive: true, force: true });
189
+ }
190
+ });
191
+ it('should create .claude directory when --client claude specified (WU-1342)', async () => {
192
+ // Act
193
+ await scaffoldProject(tempDir, { force: true, full: false, client: 'claude' });
194
+ // Assert
195
+ const claudeDir = path.join(tempDir, '.claude');
196
+ expect(fs.existsSync(claudeDir)).toBe(true);
197
+ // Should have agents directory
198
+ const agentsDir = path.join(claudeDir, 'agents');
199
+ expect(fs.existsSync(agentsDir)).toBe(true);
200
+ // Should have settings.json
201
+ const settingsPath = path.join(claudeDir, 'settings.json');
202
+ expect(fs.existsSync(settingsPath)).toBe(true);
203
+ // Should have skills directory
204
+ const skillsDir = path.join(claudeDir, 'skills');
205
+ expect(fs.existsSync(skillsDir)).toBe(true);
206
+ });
207
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @file init-template-portability.test.ts
3
+ * Tests for template portability - no absolute paths (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 } from '../init.js';
10
+ // Constants to avoid duplicate strings (sonarjs/no-duplicate-string)
11
+ const ARC42_DOCS_STRUCTURE = 'arc42';
12
+ const PROJECT_ROOT_PLACEHOLDER = '<project-root>';
13
+ describe('template portability', () => {
14
+ let tempDir;
15
+ beforeEach(() => {
16
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-portability-test-'));
17
+ });
18
+ afterEach(() => {
19
+ fs.rmSync(tempDir, { recursive: true, force: true });
20
+ });
21
+ describe('no absolute paths in templates', () => {
22
+ it('should use <project-root> placeholder instead of absolute paths', async () => {
23
+ const options = {
24
+ force: false,
25
+ full: true,
26
+ client: 'claude',
27
+ };
28
+ await scaffoldProject(tempDir, options);
29
+ // Check LUMENFLOW.md for absolute paths
30
+ const lumenflowContent = fs.readFileSync(path.join(tempDir, 'LUMENFLOW.md'), 'utf-8');
31
+ expect(lumenflowContent).not.toMatch(/\/home\//);
32
+ expect(lumenflowContent).not.toMatch(/\/Users\//);
33
+ expect(lumenflowContent).not.toMatch(/C:\\/);
34
+ // Should contain <project-root> placeholder for portable references
35
+ expect(lumenflowContent).toContain(PROJECT_ROOT_PLACEHOLDER);
36
+ });
37
+ it('should not contain hardcoded user paths in AGENTS.md', async () => {
38
+ const options = {
39
+ force: false,
40
+ full: true,
41
+ };
42
+ await scaffoldProject(tempDir, options);
43
+ const agentsContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
44
+ expect(agentsContent).not.toMatch(/\/home\/[a-zA-Z0-9_-]+\//);
45
+ expect(agentsContent).not.toMatch(/\/Users\/[a-zA-Z0-9_-]+\//);
46
+ expect(agentsContent).not.toMatch(/C:\\Users\\[a-zA-Z0-9_-]+\\/);
47
+ });
48
+ it('should not contain hardcoded paths in quick-ref-commands.md', async () => {
49
+ const options = {
50
+ force: false,
51
+ full: true,
52
+ docsStructure: ARC42_DOCS_STRUCTURE,
53
+ };
54
+ await scaffoldProject(tempDir, options);
55
+ // Find the quick-ref-commands.md based on docs structure (arc42)
56
+ const quickRefPath = path.join(tempDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding', 'quick-ref-commands.md');
57
+ if (fs.existsSync(quickRefPath)) {
58
+ const quickRefContent = fs.readFileSync(quickRefPath, 'utf-8');
59
+ expect(quickRefContent).not.toMatch(/\/home\/[a-zA-Z0-9_-]+\//);
60
+ expect(quickRefContent).not.toMatch(/\/Users\/[a-zA-Z0-9_-]+\//);
61
+ expect(quickRefContent).toContain(PROJECT_ROOT_PLACEHOLDER);
62
+ }
63
+ });
64
+ it('should use relative paths for docs cross-references', async () => {
65
+ const options = {
66
+ force: false,
67
+ full: true,
68
+ client: 'claude',
69
+ docsStructure: ARC42_DOCS_STRUCTURE,
70
+ };
71
+ await scaffoldProject(tempDir, options);
72
+ // Starting prompt should have relative paths to other docs
73
+ const onboardingDir = path.join(tempDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
74
+ const startingPromptPath = path.join(onboardingDir, 'starting-prompt.md');
75
+ if (fs.existsSync(startingPromptPath)) {
76
+ const content = fs.readFileSync(startingPromptPath, 'utf-8');
77
+ // Should use relative paths like ../../../../../../LUMENFLOW.md
78
+ // eslint-disable-next-line sonarjs/slow-regex -- Simple path pattern, no backtracking risk
79
+ expect(content).toMatch(/\[.*?\]\([./]+.*?\.md\)/);
80
+ }
81
+ });
82
+ });
83
+ describe('PROJECT_ROOT token replacement', () => {
84
+ it('should replace {{PROJECT_ROOT}} with <project-root> placeholder', async () => {
85
+ const options = {
86
+ force: false,
87
+ full: true,
88
+ };
89
+ await scaffoldProject(tempDir, options);
90
+ const lumenflowContent = fs.readFileSync(path.join(tempDir, 'LUMENFLOW.md'), 'utf-8');
91
+ // Should not have unreplaced {{PROJECT_ROOT}} tokens
92
+ expect(lumenflowContent).not.toContain('{{PROJECT_ROOT}}');
93
+ // Should have the portable placeholder
94
+ expect(lumenflowContent).toContain('<project-root>');
95
+ });
96
+ });
97
+ });
@@ -268,6 +268,7 @@ describe('lumenflow init', () => {
268
268
  const options = {
269
269
  force: false,
270
270
  full: true, // This is now the default when parsed
271
+ docsStructure: 'arc42', // WU-1309: Explicitly request arc42 for legacy test
271
272
  };
272
273
  await scaffoldProject(tempDir, options);
273
274
  // Should create agent onboarding docs
@@ -311,8 +312,10 @@ describe('lumenflow init', () => {
311
312
  const laneInferencePath = path.join(tempDir, '.lumenflow.lane-inference.yaml');
312
313
  expect(fs.existsSync(laneInferencePath)).toBe(true);
313
314
  const content = fs.readFileSync(laneInferencePath, 'utf-8');
314
- // Should have lane definitions
315
- expect(content).toContain('lanes:');
315
+ // WU-1307: Should have hierarchical lane definitions (not flat lanes: array)
316
+ expect(content).toContain('Framework:');
317
+ expect(content).toContain('Content:');
318
+ expect(content).toContain('Operations:');
316
319
  });
317
320
  it('should scaffold lane-inference with framework-specific lanes when --framework is provided', async () => {
318
321
  const options = {
@@ -330,6 +333,7 @@ describe('lumenflow init', () => {
330
333
  const options = {
331
334
  force: false,
332
335
  full: true,
336
+ docsStructure: 'arc42', // WU-1309: Explicitly request arc42 for legacy test
333
337
  };
334
338
  await scaffoldProject(tempDir, options);
335
339
  const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
@@ -381,6 +385,7 @@ describe('lumenflow init', () => {
381
385
  const options = {
382
386
  force: false,
383
387
  full: true,
388
+ docsStructure: 'arc42', // WU-1309: Explicitly request arc42 for legacy test
384
389
  };
385
390
  await scaffoldProject(tempDir, options);
386
391
  const agentsContent = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');