@lumenflow/cli 2.20.1 → 2.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/hooks/enforcement-checks.js +120 -0
- package/dist/hooks/enforcement-checks.js.map +1 -1
- package/dist/init-lane-validation.js +141 -0
- package/dist/init-lane-validation.js.map +1 -0
- package/dist/init-templates.js +36 -8
- package/dist/init-templates.js.map +1 -1
- package/dist/init.js +27 -58
- package/dist/init.js.map +1 -1
- package/dist/initiative-create.js +35 -4
- package/dist/initiative-create.js.map +1 -1
- package/dist/lane-lifecycle-process.js +364 -0
- package/dist/lane-lifecycle-process.js.map +1 -0
- package/dist/lane-lock.js +41 -0
- package/dist/lane-lock.js.map +1 -0
- package/dist/lane-setup.js +55 -0
- package/dist/lane-setup.js.map +1 -0
- package/dist/lane-status.js +38 -0
- package/dist/lane-status.js.map +1 -0
- package/dist/lane-validate.js +43 -0
- package/dist/lane-validate.js.map +1 -0
- package/dist/onboarding-smoke-test.js +17 -0
- package/dist/onboarding-smoke-test.js.map +1 -1
- package/dist/public-manifest.js +28 -0
- package/dist/public-manifest.js.map +1 -1
- package/dist/wu-claim-cloud.js +16 -0
- package/dist/wu-claim-cloud.js.map +1 -1
- package/dist/wu-claim.js +12 -2
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-create-content.js +8 -2
- package/dist/wu-create-content.js.map +1 -1
- package/dist/wu-create-validation.js +5 -3
- package/dist/wu-create-validation.js.map +1 -1
- package/dist/wu-create.js +21 -1
- package/dist/wu-create.js.map +1 -1
- package/dist/wu-done.js +57 -8
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-prep.js +22 -0
- package/dist/wu-prep.js.map +1 -1
- package/package.json +15 -11
- package/dist/__tests__/agent-log-issue.test.js +0 -56
- package/dist/__tests__/agent-spawn-coordination.test.js +0 -451
- package/dist/__tests__/backlog-prune.test.js +0 -478
- package/dist/__tests__/cli-entry-point.test.js +0 -160
- package/dist/__tests__/cli-subprocess.test.js +0 -89
- package/dist/__tests__/commands/integrate.test.js +0 -165
- package/dist/__tests__/commands.test.js +0 -271
- package/dist/__tests__/deps-operations.test.js +0 -206
- package/dist/__tests__/doctor.test.js +0 -510
- package/dist/__tests__/file-operations.test.js +0 -906
- package/dist/__tests__/flow-report.test.js +0 -24
- package/dist/__tests__/gates-config.test.js +0 -303
- package/dist/__tests__/gates-integration-tests.test.js +0 -112
- package/dist/__tests__/git-operations.test.js +0 -668
- package/dist/__tests__/guard-main-branch.test.js +0 -79
- package/dist/__tests__/guards-validation.test.js +0 -416
- package/dist/__tests__/hooks/enforcement.test.js +0 -279
- package/dist/__tests__/init-config-lanes.test.js +0 -131
- package/dist/__tests__/init-docs-structure.test.js +0 -152
- package/dist/__tests__/init-greenfield.test.js +0 -247
- package/dist/__tests__/init-lane-inference.test.js +0 -125
- package/dist/__tests__/init-onboarding-docs.test.js +0 -132
- package/dist/__tests__/init-quick-ref.test.js +0 -144
- package/dist/__tests__/init-scripts.test.js +0 -207
- package/dist/__tests__/init-template-portability.test.js +0 -96
- package/dist/__tests__/init.test.js +0 -968
- package/dist/__tests__/initiative-add-wu.test.js +0 -490
- package/dist/__tests__/initiative-e2e.test.js +0 -442
- package/dist/__tests__/initiative-plan-replacement.test.js +0 -161
- package/dist/__tests__/initiative-plan.test.js +0 -340
- package/dist/__tests__/initiative-remove-wu.test.js +0 -458
- package/dist/__tests__/lumenflow-upgrade.test.js +0 -260
- package/dist/__tests__/mem-cleanup-execution.test.js +0 -19
- package/dist/__tests__/memory-integration.test.js +0 -333
- package/dist/__tests__/merge-block.test.js +0 -220
- package/dist/__tests__/metrics-cli.test.js +0 -619
- package/dist/__tests__/metrics-snapshot.test.js +0 -24
- package/dist/__tests__/no-beacon-references-docs.test.js +0 -30
- package/dist/__tests__/no-beacon-references.test.js +0 -39
- package/dist/__tests__/onboarding-smoke-test.test.js +0 -211
- package/dist/__tests__/path-centralization-cli.test.js +0 -234
- package/dist/__tests__/plan-create.test.js +0 -126
- package/dist/__tests__/plan-edit.test.js +0 -157
- package/dist/__tests__/plan-link.test.js +0 -239
- package/dist/__tests__/plan-promote.test.js +0 -181
- package/dist/__tests__/release.test.js +0 -372
- package/dist/__tests__/rotate-progress.test.js +0 -127
- package/dist/__tests__/safe-git.test.js +0 -190
- package/dist/__tests__/session-coordinator.test.js +0 -109
- package/dist/__tests__/state-bootstrap.test.js +0 -432
- package/dist/__tests__/state-doctor.test.js +0 -328
- package/dist/__tests__/sync-templates.test.js +0 -255
- package/dist/__tests__/templates-sync.test.js +0 -219
- package/dist/__tests__/trace-gen.test.js +0 -115
- package/dist/__tests__/wu-create-required-fields.test.js +0 -143
- package/dist/__tests__/wu-create-strict.test.js +0 -118
- package/dist/__tests__/wu-create.test.js +0 -121
- package/dist/__tests__/wu-done-auto-cleanup.test.js +0 -135
- package/dist/__tests__/wu-done-docs-only-policy.test.js +0 -20
- package/dist/__tests__/wu-done-staging-whitelist.test.js +0 -35
- package/dist/__tests__/wu-done.test.js +0 -36
- package/dist/__tests__/wu-edit-strict.test.js +0 -109
- package/dist/__tests__/wu-edit.test.js +0 -119
- package/dist/__tests__/wu-lifecycle-integration.test.js +0 -388
- package/dist/__tests__/wu-prep-default-exec.test.js +0 -35
- package/dist/__tests__/wu-prep.test.js +0 -140
- package/dist/__tests__/wu-proto.test.js +0 -97
- package/dist/__tests__/wu-validate-strict.test.js +0 -113
- package/dist/__tests__/wu-validate.test.js +0 -36
- package/dist/spawn-list.js +0 -143
- package/dist/spawn-list.js.map +0 -1
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file init-greenfield.test.ts
|
|
3
|
-
* Tests for greenfield onboarding with initiative-first workflow (WU-1364)
|
|
4
|
-
*
|
|
5
|
-
* Verifies:
|
|
6
|
-
* - Init output includes initiative-first workflow guidance
|
|
7
|
-
* - starting-prompt.md has 'When Starting From Product Vision' section
|
|
8
|
-
* - Init auto-creates initial commit when git repo has no commits
|
|
9
|
-
* - Init auto-sets git.requireRemote=false when no remote configured
|
|
10
|
-
* - Default lane-inference template includes Core and Feature as parent lanes
|
|
11
|
-
* - LUMENFLOW.md mentions initiatives and when to use them
|
|
12
|
-
*/
|
|
13
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
14
|
-
import * as fs from 'node:fs';
|
|
15
|
-
import * as path from 'node:path';
|
|
16
|
-
import * as os from 'node:os';
|
|
17
|
-
import { execFileSync } from 'node:child_process';
|
|
18
|
-
import { scaffoldProject } from '../init.js';
|
|
19
|
-
// Constants to avoid duplicate strings
|
|
20
|
-
const ARC42_DOCS_STRUCTURE = 'arc42';
|
|
21
|
-
const STARTING_PROMPT_FILE = 'starting-prompt.md';
|
|
22
|
-
const LUMENFLOW_CONFIG_FILE = '.lumenflow.config.yaml';
|
|
23
|
-
const LANE_INFERENCE_FILE = '.lumenflow.lane-inference.yaml';
|
|
24
|
-
describe('greenfield onboarding (WU-1364)', () => {
|
|
25
|
-
let tempDir;
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lumenflow-greenfield-test-'));
|
|
28
|
-
});
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
31
|
-
});
|
|
32
|
-
function getOnboardingDir() {
|
|
33
|
-
return path.join(tempDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
|
|
34
|
-
}
|
|
35
|
-
function getArc42Options() {
|
|
36
|
-
return {
|
|
37
|
-
force: false,
|
|
38
|
-
full: true,
|
|
39
|
-
docsStructure: ARC42_DOCS_STRUCTURE,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Initialize a git repo without commits (empty repo state)
|
|
44
|
-
* Uses execFileSync for safety (no shell injection)
|
|
45
|
-
*/
|
|
46
|
-
function initEmptyGitRepo() {
|
|
47
|
-
execFileSync('git', ['init'], { cwd: tempDir, stdio: 'pipe' });
|
|
48
|
-
// Configure git user for commit (required in some environments)
|
|
49
|
-
execFileSync('git', ['config', 'user.email', 'test@example.com'], {
|
|
50
|
-
cwd: tempDir,
|
|
51
|
-
stdio: 'pipe',
|
|
52
|
-
});
|
|
53
|
-
execFileSync('git', ['config', 'user.name', 'Test User'], { cwd: tempDir, stdio: 'pipe' });
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Initialize a git repo with an initial commit
|
|
57
|
-
*/
|
|
58
|
-
function initGitRepoWithCommit() {
|
|
59
|
-
initEmptyGitRepo();
|
|
60
|
-
fs.writeFileSync(path.join(tempDir, '.gitkeep'), '');
|
|
61
|
-
execFileSync('git', ['add', '.gitkeep'], { cwd: tempDir, stdio: 'pipe' });
|
|
62
|
-
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: tempDir, stdio: 'pipe' });
|
|
63
|
-
}
|
|
64
|
-
describe('AC: starting-prompt.md has initiative-first workflow section', () => {
|
|
65
|
-
it('should include "When Starting From Product Vision" section in starting-prompt.md', async () => {
|
|
66
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
67
|
-
const startingPromptPath = path.join(getOnboardingDir(), STARTING_PROMPT_FILE);
|
|
68
|
-
expect(fs.existsSync(startingPromptPath)).toBe(true);
|
|
69
|
-
const content = fs.readFileSync(startingPromptPath, 'utf-8');
|
|
70
|
-
expect(content).toContain('When Starting From Product Vision');
|
|
71
|
-
});
|
|
72
|
-
it('should describe 4-step initiative workflow in product vision section', async () => {
|
|
73
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
74
|
-
const startingPromptPath = path.join(getOnboardingDir(), STARTING_PROMPT_FILE);
|
|
75
|
-
const content = fs.readFileSync(startingPromptPath, 'utf-8');
|
|
76
|
-
// Should mention initiative creation
|
|
77
|
-
expect(content).toContain('initiative:create');
|
|
78
|
-
// Should mention phased work
|
|
79
|
-
expect(content).toMatch(/phase|INIT-/i);
|
|
80
|
-
// Should mention WU organization under initiatives
|
|
81
|
-
expect(content).toMatch(/initiative.*WU|WU.*initiative/i);
|
|
82
|
-
});
|
|
83
|
-
it('should warn against creating orphan WUs without initiative structure', async () => {
|
|
84
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
85
|
-
const startingPromptPath = path.join(getOnboardingDir(), STARTING_PROMPT_FILE);
|
|
86
|
-
const content = fs.readFileSync(startingPromptPath, 'utf-8');
|
|
87
|
-
// Should have guidance about when NOT to create standalone WUs
|
|
88
|
-
expect(content).toMatch(/don't|avoid|instead.*initiative/i);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
describe('AC: Init auto-creates initial commit when git repo has no commits', () => {
|
|
92
|
-
it('should create initial commit in empty git repo', async () => {
|
|
93
|
-
initEmptyGitRepo();
|
|
94
|
-
// Verify no commits exist
|
|
95
|
-
try {
|
|
96
|
-
execFileSync('git', ['rev-parse', 'HEAD'], { cwd: tempDir, stdio: 'pipe' });
|
|
97
|
-
throw new Error('Expected HEAD to not exist');
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
// Expected: fatal: ambiguous argument 'HEAD'
|
|
101
|
-
}
|
|
102
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
103
|
-
// Now HEAD should exist
|
|
104
|
-
const result = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
105
|
-
cwd: tempDir,
|
|
106
|
-
encoding: 'utf-8',
|
|
107
|
-
stdio: 'pipe',
|
|
108
|
-
});
|
|
109
|
-
expect(result.trim()).toMatch(/^[a-f0-9]{40}$/);
|
|
110
|
-
});
|
|
111
|
-
it('should not create extra commit if repo already has commits', async () => {
|
|
112
|
-
initGitRepoWithCommit();
|
|
113
|
-
// Get initial commit count
|
|
114
|
-
const beforeCount = execFileSync('git', ['rev-list', '--count', 'HEAD'], {
|
|
115
|
-
cwd: tempDir,
|
|
116
|
-
encoding: 'utf-8',
|
|
117
|
-
stdio: 'pipe',
|
|
118
|
-
}).trim();
|
|
119
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
120
|
-
// Commit count should be the same (init doesn't auto-commit if commits exist)
|
|
121
|
-
const afterCount = execFileSync('git', ['rev-list', '--count', 'HEAD'], {
|
|
122
|
-
cwd: tempDir,
|
|
123
|
-
encoding: 'utf-8',
|
|
124
|
-
stdio: 'pipe',
|
|
125
|
-
}).trim();
|
|
126
|
-
expect(afterCount).toBe(beforeCount);
|
|
127
|
-
});
|
|
128
|
-
it('should skip auto-commit if not in a git repo', async () => {
|
|
129
|
-
// Not a git repo - just a plain directory
|
|
130
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
131
|
-
// Should not fail, just skip the git operations
|
|
132
|
-
expect(fs.existsSync(path.join(tempDir, LUMENFLOW_CONFIG_FILE))).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
describe('AC: Init auto-sets git.requireRemote=false when no remote configured', () => {
|
|
136
|
-
it('should set requireRemote=false in config when no origin remote', async () => {
|
|
137
|
-
initGitRepoWithCommit();
|
|
138
|
-
// No remote added
|
|
139
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
140
|
-
const configPath = path.join(tempDir, LUMENFLOW_CONFIG_FILE);
|
|
141
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
142
|
-
expect(content).toContain('requireRemote: false');
|
|
143
|
-
});
|
|
144
|
-
it('should not set requireRemote=false if origin remote exists', async () => {
|
|
145
|
-
initGitRepoWithCommit();
|
|
146
|
-
// Add a remote
|
|
147
|
-
execFileSync('git', ['remote', 'add', 'origin', 'https://github.com/test/repo.git'], {
|
|
148
|
-
cwd: tempDir,
|
|
149
|
-
stdio: 'pipe',
|
|
150
|
-
});
|
|
151
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
152
|
-
const configPath = path.join(tempDir, LUMENFLOW_CONFIG_FILE);
|
|
153
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
154
|
-
// Should not have requireRemote: false (remote exists)
|
|
155
|
-
expect(content).not.toContain('requireRemote: false');
|
|
156
|
-
});
|
|
157
|
-
it('should skip remote check if not in a git repo', async () => {
|
|
158
|
-
// Not a git repo
|
|
159
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
160
|
-
const configPath = path.join(tempDir, LUMENFLOW_CONFIG_FILE);
|
|
161
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
162
|
-
// When not in a git repo, should default to requireRemote: false for safety
|
|
163
|
-
expect(content).toContain('requireRemote: false');
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
describe('AC: Default lane-inference template includes Core and Feature parent lanes', () => {
|
|
167
|
-
it('should include Core as a parent lane', async () => {
|
|
168
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
169
|
-
const laneInferencePath = path.join(tempDir, LANE_INFERENCE_FILE);
|
|
170
|
-
expect(fs.existsSync(laneInferencePath)).toBe(true);
|
|
171
|
-
const content = fs.readFileSync(laneInferencePath, 'utf-8');
|
|
172
|
-
// Should have Core as a top-level parent lane (not just Framework: Core)
|
|
173
|
-
expect(content).toMatch(/^Core:/m);
|
|
174
|
-
});
|
|
175
|
-
it('should include Feature as a parent lane', async () => {
|
|
176
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
177
|
-
const laneInferencePath = path.join(tempDir, LANE_INFERENCE_FILE);
|
|
178
|
-
const content = fs.readFileSync(laneInferencePath, 'utf-8');
|
|
179
|
-
// Should have Feature as a top-level parent lane
|
|
180
|
-
expect(content).toMatch(/^Feature:/m);
|
|
181
|
-
});
|
|
182
|
-
it('should support intuitive lane names like "Core: Platform"', async () => {
|
|
183
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
184
|
-
const laneInferencePath = path.join(tempDir, LANE_INFERENCE_FILE);
|
|
185
|
-
const content = fs.readFileSync(laneInferencePath, 'utf-8');
|
|
186
|
-
// Should have sublanes under Core and Feature
|
|
187
|
-
// e.g., Core: followed by sublanes like Platform, Library, etc.
|
|
188
|
-
expect(content).toMatch(/Core:\n\s+\w+:/m);
|
|
189
|
-
expect(content).toMatch(/Feature:\n\s+\w+:/m);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
describe('AC: LUMENFLOW.md mentions initiatives and when to use them', () => {
|
|
193
|
-
it('should mention initiatives in generated LUMENFLOW.md', async () => {
|
|
194
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
195
|
-
const lumenflowPath = path.join(tempDir, 'LUMENFLOW.md');
|
|
196
|
-
const content = fs.readFileSync(lumenflowPath, 'utf-8');
|
|
197
|
-
expect(content).toMatch(/initiative/i);
|
|
198
|
-
});
|
|
199
|
-
it('should explain when to use initiatives vs standalone WUs', async () => {
|
|
200
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
201
|
-
const lumenflowPath = path.join(tempDir, 'LUMENFLOW.md');
|
|
202
|
-
const content = fs.readFileSync(lumenflowPath, 'utf-8');
|
|
203
|
-
// Should mention when to use initiatives
|
|
204
|
-
expect(content).toMatch(/multi-phase|product vision|larger|complex/i);
|
|
205
|
-
});
|
|
206
|
-
it('should reference initiative:create command', async () => {
|
|
207
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
208
|
-
const lumenflowPath = path.join(tempDir, 'LUMENFLOW.md');
|
|
209
|
-
const content = fs.readFileSync(lumenflowPath, 'utf-8');
|
|
210
|
-
expect(content).toContain('initiative:create');
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
describe('AC: Init output includes initiative-first workflow guidance', () => {
|
|
214
|
-
// This test verifies the console output, which requires capturing stdout
|
|
215
|
-
// We'll mock console.log to capture the output
|
|
216
|
-
it('should print initiative-first guidance in init output', async () => {
|
|
217
|
-
const consoleLogs = [];
|
|
218
|
-
const originalLog = console.log;
|
|
219
|
-
console.log = (...args) => {
|
|
220
|
-
consoleLogs.push(args.join(' '));
|
|
221
|
-
};
|
|
222
|
-
try {
|
|
223
|
-
// Import and run main() to capture console output
|
|
224
|
-
const { main } = await import('../init.js');
|
|
225
|
-
// Change to temp directory and run init
|
|
226
|
-
const originalCwd = process.cwd();
|
|
227
|
-
process.chdir(tempDir);
|
|
228
|
-
// Mock process.argv for parseInitOptions
|
|
229
|
-
const originalArgv = process.argv;
|
|
230
|
-
process.argv = ['node', 'init', '--full'];
|
|
231
|
-
try {
|
|
232
|
-
await main();
|
|
233
|
-
}
|
|
234
|
-
finally {
|
|
235
|
-
process.argv = originalArgv;
|
|
236
|
-
process.chdir(originalCwd);
|
|
237
|
-
}
|
|
238
|
-
const output = consoleLogs.join('\n');
|
|
239
|
-
// Should mention initiatives in the "Next steps" or guidance section
|
|
240
|
-
expect(output).toMatch(/initiative|product vision|INIT-/i);
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
console.log = originalLog;
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,132 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,144 +0,0 @@
|
|
|
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
|
-
expect(content).toMatch(/##.*?Setup|##.*?Project/i);
|
|
109
|
-
expect(content).toContain('lumenflow init');
|
|
110
|
-
});
|
|
111
|
-
it('should have WU management commands', async () => {
|
|
112
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
113
|
-
const quickRefPath = getQuickRefPath();
|
|
114
|
-
const content = fs.readFileSync(quickRefPath, 'utf-8');
|
|
115
|
-
// Should have WU commands
|
|
116
|
-
expect(content).toContain('wu:create');
|
|
117
|
-
expect(content).toContain('wu:claim');
|
|
118
|
-
expect(content).toContain('wu:done');
|
|
119
|
-
expect(content).toContain('wu:block');
|
|
120
|
-
});
|
|
121
|
-
it('should have gates commands', async () => {
|
|
122
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
123
|
-
const quickRefPath = getQuickRefPath();
|
|
124
|
-
const content = fs.readFileSync(quickRefPath, 'utf-8');
|
|
125
|
-
// Should have gates commands
|
|
126
|
-
expect(content).toContain('pnpm gates');
|
|
127
|
-
expect(content).toContain('--docs-only');
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
describe('workflow sequence', () => {
|
|
131
|
-
it('should have a complete workflow sequence example', async () => {
|
|
132
|
-
await scaffoldProject(tempDir, getArc42Options());
|
|
133
|
-
const quickRefPath = getQuickRefPath();
|
|
134
|
-
const content = fs.readFileSync(quickRefPath, 'utf-8');
|
|
135
|
-
// Should have workflow sequence
|
|
136
|
-
expect(content).toMatch(/workflow|sequence/i);
|
|
137
|
-
// Should show the full flow: create -> claim -> work -> commit -> gates -> done
|
|
138
|
-
expect(content).toContain('wu:create');
|
|
139
|
-
expect(content).toContain('wu:claim');
|
|
140
|
-
expect(content).toContain('pnpm gates');
|
|
141
|
-
expect(content).toContain('wu:done');
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
});
|