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