@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
package/README.md CHANGED
@@ -16,19 +16,22 @@ npm install @lumenflow/cli
16
16
  ## Quick Start
17
17
 
18
18
  ```bash
19
+ # Install the CLI
20
+ pnpm add -D @lumenflow/cli # or: npm install -D @lumenflow/cli
21
+
19
22
  # Initialize LumenFlow (works with any AI)
20
- npx lumenflow-init
23
+ pnpm exec lumenflow
21
24
 
22
25
  # Or specify your AI tool for enhanced integration
23
- npx lumenflow-init --client claude # Claude Code
24
- npx lumenflow-init --client cursor # Cursor
25
- npx lumenflow-init --client windsurf # Windsurf
26
- npx lumenflow-init --client cline # Cline
27
- npx lumenflow-init --client aider # Aider
28
- npx lumenflow-init --client all # All integrations
26
+ pnpm exec lumenflow --client claude # Claude Code
27
+ pnpm exec lumenflow --client cursor # Cursor
28
+ pnpm exec lumenflow --client windsurf # Windsurf
29
+ pnpm exec lumenflow --client cline # Cline
30
+ pnpm exec lumenflow --client aider # Aider
31
+ pnpm exec lumenflow --client all # All integrations
29
32
  ```
30
33
 
31
- The default `lumenflow-init` creates `AGENTS.md` and `LUMENFLOW.md` which work with **any AI coding assistant**. The `--client` flag adds vendor-specific configuration files for deeper integration.
34
+ The default `lumenflow` command creates `AGENTS.md` and `LUMENFLOW.md` which work with **any AI coding assistant**. The `--client` flag adds vendor-specific configuration files for deeper integration.
32
35
 
33
36
  See [AI Integrations](https://lumenflow.dev/guides/ai-integrations) for details on each tool.
34
37
 
@@ -0,0 +1,131 @@
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
+ });
@@ -0,0 +1,119 @@
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 arc42 structure with --docs-structure arc42', async () => {
71
+ const options = {
72
+ force: false,
73
+ full: true,
74
+ docsStructure: ARC42_DOCS_STRUCTURE,
75
+ };
76
+ await scaffoldProject(tempDir, options);
77
+ // Arc42 structure: docs/04-operations/tasks
78
+ expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS, 'tasks'))).toBe(true);
79
+ });
80
+ it('should auto-detect arc42 when docs/04-operations exists', async () => {
81
+ // Create existing arc42 structure
82
+ fs.mkdirSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS), { recursive: true });
83
+ const options = {
84
+ force: false,
85
+ full: true,
86
+ // No docsStructure specified - should auto-detect arc42
87
+ };
88
+ await scaffoldProject(tempDir, options);
89
+ // Should use arc42 structure
90
+ expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS, 'tasks'))).toBe(true);
91
+ });
92
+ it('should auto-detect simple when only docs exists', async () => {
93
+ // Create existing simple structure
94
+ fs.mkdirSync(path.join(tempDir, 'docs'), { recursive: true });
95
+ fs.writeFileSync(path.join(tempDir, 'docs', 'README.md'), '# Docs\n');
96
+ const options = {
97
+ force: false,
98
+ full: true,
99
+ // No docsStructure specified - should auto-detect simple
100
+ };
101
+ await scaffoldProject(tempDir, options);
102
+ // Should use simple structure
103
+ expect(fs.existsSync(path.join(tempDir, 'docs', 'tasks'))).toBe(true);
104
+ expect(fs.existsSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS))).toBe(false);
105
+ });
106
+ it('should respect explicit --docs-structure over auto-detection', async () => {
107
+ // Create existing arc42 structure
108
+ fs.mkdirSync(path.join(tempDir, 'docs', DOCS_04_OPERATIONS), { recursive: true });
109
+ const options = {
110
+ force: true, // Force overwrite
111
+ full: true,
112
+ docsStructure: SIMPLE_DOCS_STRUCTURE, // Explicitly request simple
113
+ };
114
+ await scaffoldProject(tempDir, options);
115
+ // Should use simple structure despite arc42 existing
116
+ expect(fs.existsSync(path.join(tempDir, 'docs', 'tasks'))).toBe(true);
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @file init-lane-inference.test.ts
3
+ * Test: .lumenflow.lane-inference.yaml is generated in hierarchical Parent→Sublane format
4
+ *
5
+ * WU-1307: Fix lumenflow-init scaffolding
6
+ *
7
+ * The generated lane inference config must use the hierarchical format that
8
+ * lane-inference.ts/lane-checker.ts expect:
9
+ *
10
+ * Parent:
11
+ * Sublane:
12
+ * code_paths:
13
+ * - pattern
14
+ * keywords:
15
+ * - keyword
16
+ *
17
+ * NOT the flat format:
18
+ * lanes:
19
+ * - name: "Parent: Sublane"
20
+ * patterns:
21
+ * - pattern
22
+ */
23
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
24
+ import * as fs from 'node:fs';
25
+ import * as path from 'node:path';
26
+ import * as os from 'node:os';
27
+ import YAML from 'yaml';
28
+ import { scaffoldProject } from '../init.js';
29
+ /** Lane inference file name - extracted to avoid duplicate string lint errors */
30
+ const LANE_INFERENCE_FILE_NAME = '.lumenflow.lane-inference.yaml';
31
+ describe('init lane inference generation (WU-1307)', () => {
32
+ let tempDir;
33
+ beforeEach(() => {
34
+ // Create a temporary directory for each test
35
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-init-lane-inference-'));
36
+ });
37
+ afterEach(() => {
38
+ // Clean up temporary directory
39
+ if (tempDir && fs.existsSync(tempDir)) {
40
+ fs.rmSync(tempDir, { recursive: true, force: true });
41
+ }
42
+ });
43
+ /** Helper to read and parse lane inference config from temp directory */
44
+ function readLaneInference() {
45
+ const laneInferencePath = path.join(tempDir, LANE_INFERENCE_FILE_NAME);
46
+ const laneInferenceContent = fs.readFileSync(laneInferencePath, 'utf-8');
47
+ return YAML.parse(laneInferenceContent);
48
+ }
49
+ it('should generate .lumenflow.lane-inference.yaml in hierarchical format', async () => {
50
+ // Arrange
51
+ const laneInferencePath = path.join(tempDir, LANE_INFERENCE_FILE_NAME);
52
+ // Act
53
+ await scaffoldProject(tempDir, { force: true, full: true });
54
+ // Assert
55
+ expect(fs.existsSync(laneInferencePath)).toBe(true);
56
+ const laneInference = readLaneInference();
57
+ // Should NOT have a flat 'lanes' array
58
+ expect(laneInference.lanes).toBeUndefined();
59
+ // Should have hierarchical Parent -> Sublane structure
60
+ // At minimum, should have Framework, Operations, Content parents
61
+ expect(laneInference.Framework).toBeDefined();
62
+ expect(laneInference.Operations).toBeDefined();
63
+ expect(laneInference.Content).toBeDefined();
64
+ });
65
+ it('should include sublanes under parent lanes', async () => {
66
+ // Act
67
+ await scaffoldProject(tempDir, { force: true, full: true });
68
+ // Assert
69
+ const laneInference = readLaneInference();
70
+ // Framework parent should have sublanes like Core, CLI
71
+ expect(laneInference.Framework?.Core).toBeDefined();
72
+ expect(laneInference.Framework?.CLI).toBeDefined();
73
+ // Operations parent should have sublanes like Infrastructure, CI/CD
74
+ expect(laneInference.Operations?.Infrastructure).toBeDefined();
75
+ expect(laneInference.Operations?.['CI/CD']).toBeDefined();
76
+ // Content parent should have Documentation sublane
77
+ expect(laneInference.Content?.Documentation).toBeDefined();
78
+ });
79
+ it('should have code_paths in sublane config (not patterns)', async () => {
80
+ // Act
81
+ await scaffoldProject(tempDir, { force: true, full: true });
82
+ // Assert
83
+ const laneInference = readLaneInference();
84
+ // Sublanes should have code_paths (not patterns)
85
+ const frameworkCore = laneInference.Framework?.Core;
86
+ expect(frameworkCore).toBeDefined();
87
+ expect(frameworkCore?.code_paths).toBeDefined();
88
+ expect(Array.isArray(frameworkCore?.code_paths)).toBe(true);
89
+ expect(frameworkCore?.patterns).toBeUndefined();
90
+ });
91
+ it('should include keywords in sublane config', async () => {
92
+ // Act
93
+ await scaffoldProject(tempDir, { force: true, full: true });
94
+ // Assert
95
+ const laneInference = readLaneInference();
96
+ // Sublanes should have keywords array
97
+ const contentDocs = laneInference.Content?.Documentation;
98
+ expect(contentDocs).toBeDefined();
99
+ expect(contentDocs?.keywords).toBeDefined();
100
+ expect(Array.isArray(contentDocs?.keywords)).toBe(true);
101
+ expect(contentDocs?.keywords?.length).toBeGreaterThan(0);
102
+ });
103
+ it('should generate Experience parent lane for frontend projects', async () => {
104
+ // Act
105
+ await scaffoldProject(tempDir, { force: true, full: true });
106
+ // Assert
107
+ const laneInference = readLaneInference();
108
+ // Should have Experience parent for frontend work
109
+ expect(laneInference.Experience).toBeDefined();
110
+ expect(laneInference.Experience?.UI || laneInference.Experience?.Web).toBeDefined();
111
+ });
112
+ it('should add framework-specific lanes when --framework is provided', async () => {
113
+ // Act
114
+ await scaffoldProject(tempDir, {
115
+ force: true,
116
+ full: true,
117
+ framework: 'nextjs',
118
+ });
119
+ // Assert
120
+ const laneInference = readLaneInference();
121
+ // Should still have base lanes
122
+ expect(laneInference.Framework).toBeDefined();
123
+ expect(laneInference.Content).toBeDefined();
124
+ });
125
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @file init-onboarding-docs.test.ts
3
+ * Tests for onboarding docs scaffold (WU-1309)
4
+ * Verifies: starting-prompt, first-15-mins, local-only, lane-inference
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
+ describe('onboarding docs scaffold', () => {
12
+ let tempDir;
13
+ beforeEach(() => {
14
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-onboarding-test-'));
15
+ });
16
+ afterEach(() => {
17
+ fs.rmSync(tempDir, { recursive: true, force: true });
18
+ });
19
+ // Constants to avoid duplicate strings (sonarjs/no-duplicate-string)
20
+ const ARC42_DOCS_STRUCTURE = 'arc42';
21
+ const STARTING_PROMPT_FILE = 'starting-prompt.md';
22
+ const FIRST_15_MINS_FILE = 'first-15-mins.md';
23
+ const LOCAL_ONLY_FILE = 'local-only.md';
24
+ const LANE_INFERENCE_FILE = 'lane-inference.md';
25
+ function getOnboardingDir(docsStructure = ARC42_DOCS_STRUCTURE) {
26
+ if (docsStructure === 'simple') {
27
+ return path.join(tempDir, 'docs', '_frameworks', 'lumenflow', 'agent', 'onboarding');
28
+ }
29
+ return path.join(tempDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
30
+ }
31
+ function getArc42Options() {
32
+ return {
33
+ force: false,
34
+ full: true,
35
+ docsStructure: ARC42_DOCS_STRUCTURE,
36
+ };
37
+ }
38
+ describe('required onboarding docs', () => {
39
+ it('should scaffold starting-prompt.md', async () => {
40
+ await scaffoldProject(tempDir, getArc42Options());
41
+ const docPath = path.join(getOnboardingDir(), STARTING_PROMPT_FILE);
42
+ expect(fs.existsSync(docPath)).toBe(true);
43
+ const content = fs.readFileSync(docPath, 'utf-8');
44
+ expect(content).toContain('Starting Prompt');
45
+ expect(content).toContain('LUMENFLOW.md');
46
+ });
47
+ it('should scaffold first-15-mins.md', async () => {
48
+ await scaffoldProject(tempDir, getArc42Options());
49
+ const docPath = path.join(getOnboardingDir(), FIRST_15_MINS_FILE);
50
+ expect(fs.existsSync(docPath)).toBe(true);
51
+ const content = fs.readFileSync(docPath, 'utf-8');
52
+ expect(content).toContain('First 15 Minutes');
53
+ });
54
+ it('should scaffold local-only.md', async () => {
55
+ await scaffoldProject(tempDir, getArc42Options());
56
+ const docPath = path.join(getOnboardingDir(), LOCAL_ONLY_FILE);
57
+ expect(fs.existsSync(docPath)).toBe(true);
58
+ const content = fs.readFileSync(docPath, 'utf-8');
59
+ expect(content).toContain('requireRemote');
60
+ expect(content).toContain('local');
61
+ });
62
+ it('should scaffold lane-inference.md', async () => {
63
+ await scaffoldProject(tempDir, getArc42Options());
64
+ const docPath = path.join(getOnboardingDir(), LANE_INFERENCE_FILE);
65
+ expect(fs.existsSync(docPath)).toBe(true);
66
+ const content = fs.readFileSync(docPath, 'utf-8');
67
+ expect(content).toContain('lane');
68
+ expect(content).toContain('.lumenflow.lane-inference.yaml');
69
+ });
70
+ });
71
+ describe('onboarding docs with simple structure', () => {
72
+ it('should scaffold onboarding docs in simple structure', async () => {
73
+ const options = {
74
+ force: false,
75
+ full: true,
76
+ docsStructure: 'simple',
77
+ };
78
+ await scaffoldProject(tempDir, options);
79
+ const onboardingDir = getOnboardingDir('simple');
80
+ expect(fs.existsSync(path.join(onboardingDir, STARTING_PROMPT_FILE))).toBe(true);
81
+ expect(fs.existsSync(path.join(onboardingDir, FIRST_15_MINS_FILE))).toBe(true);
82
+ expect(fs.existsSync(path.join(onboardingDir, LOCAL_ONLY_FILE))).toBe(true);
83
+ expect(fs.existsSync(path.join(onboardingDir, LANE_INFERENCE_FILE))).toBe(true);
84
+ });
85
+ });
86
+ describe('complete onboarding docs set', () => {
87
+ it('should scaffold all required onboarding docs with --full', async () => {
88
+ await scaffoldProject(tempDir, getArc42Options());
89
+ const onboardingDir = getOnboardingDir();
90
+ // Required docs from WU-1309
91
+ const requiredDocs = [
92
+ STARTING_PROMPT_FILE,
93
+ FIRST_15_MINS_FILE,
94
+ LOCAL_ONLY_FILE,
95
+ LANE_INFERENCE_FILE,
96
+ // Previously existing docs
97
+ 'quick-ref-commands.md',
98
+ 'first-wu-mistakes.md',
99
+ 'troubleshooting-wu-done.md',
100
+ 'agent-safety-card.md',
101
+ 'wu-create-checklist.md',
102
+ ];
103
+ for (const doc of requiredDocs) {
104
+ const docPath = path.join(onboardingDir, doc);
105
+ expect(fs.existsSync(docPath), `Expected ${doc} to exist`).toBe(true);
106
+ }
107
+ });
108
+ it('should not scaffold onboarding docs without --full', async () => {
109
+ const options = {
110
+ force: false,
111
+ full: false,
112
+ };
113
+ await scaffoldProject(tempDir, options);
114
+ const onboardingDir = getOnboardingDir();
115
+ // Onboarding docs should not exist without --full (unless --client claude)
116
+ expect(fs.existsSync(path.join(onboardingDir, STARTING_PROMPT_FILE))).toBe(false);
117
+ });
118
+ });
119
+ describe('onboarding docs content quality', () => {
120
+ it('should have consistent date placeholder in all docs', async () => {
121
+ await scaffoldProject(tempDir, getArc42Options());
122
+ const onboardingDir = getOnboardingDir();
123
+ const docs = fs.readdirSync(onboardingDir).filter((f) => f.endsWith('.md'));
124
+ for (const doc of docs) {
125
+ const content = fs.readFileSync(path.join(onboardingDir, doc), 'utf-8');
126
+ // Should have a date in YYYY-MM-DD format (not {{DATE}} placeholder)
127
+ expect(content).toMatch(/\d{4}-\d{2}-\d{2}/);
128
+ expect(content).not.toContain('{{DATE}}');
129
+ }
130
+ });
131
+ });
132
+ });