@lumenflow/cli 2.4.0 → 2.5.0

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 (124) hide show
  1. package/dist/__tests__/init-config-lanes.test.js +131 -0
  2. package/dist/__tests__/init-docs-structure.test.js +119 -0
  3. package/dist/__tests__/init-lane-inference.test.js +125 -0
  4. package/dist/__tests__/init-onboarding-docs.test.js +132 -0
  5. package/dist/__tests__/init-quick-ref.test.js +145 -0
  6. package/dist/__tests__/init-scripts.test.js +96 -0
  7. package/dist/__tests__/init-template-portability.test.js +97 -0
  8. package/dist/__tests__/init.test.js +7 -2
  9. package/dist/__tests__/initiative-add-wu.test.js +420 -0
  10. package/dist/__tests__/initiative-plan-replacement.test.js +162 -0
  11. package/dist/__tests__/initiative-remove-wu.test.js +458 -0
  12. package/dist/__tests__/onboarding-smoke-test.test.js +211 -0
  13. package/dist/__tests__/path-centralization-cli.test.js +234 -0
  14. package/dist/__tests__/plan-create.test.js +126 -0
  15. package/dist/__tests__/plan-edit.test.js +157 -0
  16. package/dist/__tests__/plan-link.test.js +239 -0
  17. package/dist/__tests__/plan-promote.test.js +181 -0
  18. package/dist/__tests__/wu-create-strict.test.js +118 -0
  19. package/dist/__tests__/wu-edit-strict.test.js +109 -0
  20. package/dist/__tests__/wu-validate-strict.test.js +113 -0
  21. package/dist/flow-bottlenecks.js +4 -2
  22. package/dist/gates.js +22 -0
  23. package/dist/init.js +580 -87
  24. package/dist/initiative-add-wu.js +112 -16
  25. package/dist/initiative-remove-wu.js +248 -0
  26. package/dist/onboarding-smoke-test.js +400 -0
  27. package/dist/plan-create.js +199 -0
  28. package/dist/plan-edit.js +235 -0
  29. package/dist/plan-link.js +233 -0
  30. package/dist/plan-promote.js +231 -0
  31. package/dist/wu-block.js +16 -5
  32. package/dist/wu-claim.js +15 -9
  33. package/dist/wu-create.js +50 -2
  34. package/dist/wu-deps.js +3 -1
  35. package/dist/wu-done.js +14 -5
  36. package/dist/wu-edit.js +35 -0
  37. package/dist/wu-spawn.js +8 -0
  38. package/dist/wu-unblock.js +34 -2
  39. package/dist/wu-validate.js +25 -17
  40. package/package.json +10 -6
  41. package/templates/core/AGENTS.md.template +2 -2
  42. package/dist/__tests__/init-plan.test.js +0 -340
  43. package/dist/agent-issues-query.d.ts +0 -16
  44. package/dist/agent-log-issue.d.ts +0 -10
  45. package/dist/agent-session-end.d.ts +0 -10
  46. package/dist/agent-session.d.ts +0 -10
  47. package/dist/backlog-prune.d.ts +0 -84
  48. package/dist/cli-entry-point.d.ts +0 -8
  49. package/dist/deps-add.d.ts +0 -91
  50. package/dist/deps-remove.d.ts +0 -17
  51. package/dist/docs-sync.d.ts +0 -50
  52. package/dist/file-delete.d.ts +0 -84
  53. package/dist/file-edit.d.ts +0 -82
  54. package/dist/file-read.d.ts +0 -92
  55. package/dist/file-write.d.ts +0 -90
  56. package/dist/flow-bottlenecks.d.ts +0 -16
  57. package/dist/flow-report.d.ts +0 -16
  58. package/dist/gates.d.ts +0 -94
  59. package/dist/git-branch.d.ts +0 -65
  60. package/dist/git-diff.d.ts +0 -58
  61. package/dist/git-log.d.ts +0 -69
  62. package/dist/git-status.d.ts +0 -58
  63. package/dist/guard-locked.d.ts +0 -62
  64. package/dist/guard-main-branch.d.ts +0 -50
  65. package/dist/guard-worktree-commit.d.ts +0 -59
  66. package/dist/index.d.ts +0 -10
  67. package/dist/init-plan.d.ts +0 -80
  68. package/dist/init-plan.js +0 -337
  69. package/dist/init.d.ts +0 -46
  70. package/dist/initiative-add-wu.d.ts +0 -22
  71. package/dist/initiative-bulk-assign-wus.d.ts +0 -16
  72. package/dist/initiative-create.d.ts +0 -28
  73. package/dist/initiative-edit.d.ts +0 -34
  74. package/dist/initiative-list.d.ts +0 -12
  75. package/dist/initiative-status.d.ts +0 -11
  76. package/dist/lumenflow-upgrade.d.ts +0 -103
  77. package/dist/mem-checkpoint.d.ts +0 -16
  78. package/dist/mem-cleanup.d.ts +0 -29
  79. package/dist/mem-create.d.ts +0 -17
  80. package/dist/mem-export.d.ts +0 -10
  81. package/dist/mem-inbox.d.ts +0 -35
  82. package/dist/mem-init.d.ts +0 -15
  83. package/dist/mem-ready.d.ts +0 -16
  84. package/dist/mem-signal.d.ts +0 -16
  85. package/dist/mem-start.d.ts +0 -16
  86. package/dist/mem-summarize.d.ts +0 -22
  87. package/dist/mem-triage.d.ts +0 -22
  88. package/dist/metrics-cli.d.ts +0 -90
  89. package/dist/metrics-snapshot.d.ts +0 -18
  90. package/dist/orchestrate-init-status.d.ts +0 -11
  91. package/dist/orchestrate-initiative.d.ts +0 -12
  92. package/dist/orchestrate-monitor.d.ts +0 -11
  93. package/dist/release.d.ts +0 -117
  94. package/dist/rotate-progress.d.ts +0 -48
  95. package/dist/session-coordinator.d.ts +0 -74
  96. package/dist/spawn-list.d.ts +0 -16
  97. package/dist/state-bootstrap.d.ts +0 -92
  98. package/dist/sync-templates.d.ts +0 -52
  99. package/dist/trace-gen.d.ts +0 -84
  100. package/dist/validate-agent-skills.d.ts +0 -50
  101. package/dist/validate-agent-sync.d.ts +0 -36
  102. package/dist/validate-backlog-sync.d.ts +0 -37
  103. package/dist/validate-skills-spec.d.ts +0 -40
  104. package/dist/validate.d.ts +0 -60
  105. package/dist/wu-block.d.ts +0 -16
  106. package/dist/wu-claim.d.ts +0 -74
  107. package/dist/wu-cleanup.d.ts +0 -35
  108. package/dist/wu-create.d.ts +0 -69
  109. package/dist/wu-delete.d.ts +0 -21
  110. package/dist/wu-deps.d.ts +0 -13
  111. package/dist/wu-done.d.ts +0 -225
  112. package/dist/wu-edit.d.ts +0 -63
  113. package/dist/wu-infer-lane.d.ts +0 -17
  114. package/dist/wu-preflight.d.ts +0 -47
  115. package/dist/wu-prune.d.ts +0 -16
  116. package/dist/wu-recover.d.ts +0 -37
  117. package/dist/wu-release.d.ts +0 -19
  118. package/dist/wu-repair.d.ts +0 -60
  119. package/dist/wu-spawn-completion.d.ts +0 -10
  120. package/dist/wu-spawn.d.ts +0 -192
  121. package/dist/wu-status.d.ts +0 -25
  122. package/dist/wu-unblock.d.ts +0 -16
  123. package/dist/wu-unlock-lane.d.ts +0 -19
  124. package/dist/wu-validate.d.ts +0 -16
@@ -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
+ });
@@ -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
+ });