@lumenflow/cli 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +11 -8
  2. package/dist/__tests__/init-scripts.test.js +111 -0
  3. package/dist/__tests__/templates-sync.test.js +219 -0
  4. package/dist/init.js +90 -0
  5. package/dist/orchestrate-init-status.js +37 -9
  6. package/dist/orchestrate-initiative.js +10 -4
  7. package/dist/sync-templates.js +137 -5
  8. package/dist/wu-prep.js +131 -8
  9. package/dist/wu-spawn.js +7 -2
  10. package/package.json +7 -7
  11. package/templates/core/.lumenflow/constraints.md.template +61 -3
  12. package/templates/core/LUMENFLOW.md.template +85 -23
  13. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +157 -0
  14. package/templates/core/ai/onboarding/agent-safety-card.md.template +227 -0
  15. package/templates/core/ai/onboarding/docs-generation.md.template +277 -0
  16. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +49 -7
  17. package/templates/core/ai/onboarding/quick-ref-commands.md.template +343 -110
  18. package/templates/core/ai/onboarding/release-process.md.template +8 -2
  19. package/templates/core/ai/onboarding/starting-prompt.md.template +407 -0
  20. package/templates/core/ai/onboarding/test-ratchet.md.template +131 -0
  21. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +91 -38
  22. package/templates/core/ai/onboarding/vendor-support.md.template +219 -0
  23. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +13 -1
  24. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +14 -16
  25. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +48 -4
  26. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +5 -1
  27. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +19 -8
package/README.md CHANGED
@@ -16,19 +16,22 @@ npm install @lumenflow/cli
16
16
  ## Quick Start
17
17
 
18
18
  ```bash
19
+ # Install the CLI
20
+ pnpm add -D @lumenflow/cli # or: npm install -D @lumenflow/cli
21
+
19
22
  # Initialize LumenFlow (works with any AI)
20
- npx lumenflow-init
23
+ pnpm exec lumenflow
21
24
 
22
25
  # Or specify your AI tool for enhanced integration
23
- npx lumenflow-init --client claude # Claude Code
24
- npx lumenflow-init --client cursor # Cursor
25
- npx lumenflow-init --client windsurf # Windsurf
26
- npx lumenflow-init --client cline # Cline
27
- npx lumenflow-init --client aider # Aider
28
- npx lumenflow-init --client all # All integrations
26
+ pnpm exec lumenflow --client claude # Claude Code
27
+ pnpm exec lumenflow --client cursor # Cursor
28
+ pnpm exec lumenflow --client windsurf # Windsurf
29
+ pnpm exec lumenflow --client cline # Cline
30
+ pnpm exec lumenflow --client aider # Aider
31
+ pnpm exec lumenflow --client all # All integrations
29
32
  ```
30
33
 
31
- The default `lumenflow-init` creates `AGENTS.md` and `LUMENFLOW.md` which work with **any AI coding assistant**. The `--client` flag adds vendor-specific configuration files for deeper integration.
34
+ The default `lumenflow` command creates `AGENTS.md` and `LUMENFLOW.md` which work with **any AI coding assistant**. The `--client` flag adds vendor-specific configuration files for deeper integration.
32
35
 
33
36
  See [AI Integrations](https://lumenflow.dev/guides/ai-integrations) for details on each tool.
34
37
 
@@ -93,4 +93,115 @@ describe('init scripts generation (WU-1307)', () => {
93
93
  expect(packageJson.scripts?.['wu:claim']).toBeDefined();
94
94
  expect(packageJson.scripts?.gates).toBeDefined();
95
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
+ });
96
207
  });
@@ -0,0 +1,219 @@
1
+ /**
2
+ * @file templates-sync.test.ts
3
+ * Tests for templates synchronization and drift detection (WU-1353)
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 { syncTemplates, syncOnboardingDocs, syncCoreDocs, convertToTemplate, checkTemplateDrift, } from '../sync-templates.js';
10
+ // Constants for frequently used path segments (sonarjs/no-duplicate-string)
11
+ const PACKAGES_DIR = 'packages';
12
+ const LUMENFLOW_SCOPE = '@lumenflow';
13
+ const CLI_DIR = 'cli';
14
+ const TEMPLATES_DIR = 'templates';
15
+ const CORE_DIR = 'core';
16
+ const LUMENFLOW_DOT_DIR = '.lumenflow';
17
+ const CONSTRAINTS_FILE = 'constraints.md';
18
+ const CONSTRAINTS_TEMPLATE = 'constraints.md.template';
19
+ describe('templates-sync', () => {
20
+ let tempDir;
21
+ beforeEach(() => {
22
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'templates-sync-test-'));
23
+ });
24
+ afterEach(() => {
25
+ if (tempDir && fs.existsSync(tempDir)) {
26
+ fs.rmSync(tempDir, { recursive: true, force: true });
27
+ }
28
+ });
29
+ describe('convertToTemplate', () => {
30
+ it('should replace dates with {{DATE}} placeholder', () => {
31
+ const content = 'Updated: 2026-02-02\nCreated: 2025-01-15';
32
+ const result = convertToTemplate(content, '/home/test/project');
33
+ expect(result).toBe('Updated: {{DATE}}\nCreated: {{DATE}}');
34
+ });
35
+ it('should preserve content without dates', () => {
36
+ const content = '# Title\n\nSome content without dates.';
37
+ const result = convertToTemplate(content, '/home/test/project');
38
+ expect(result).toBe(content);
39
+ });
40
+ });
41
+ describe('syncCoreDocs', () => {
42
+ beforeEach(() => {
43
+ // Set up directory structure
44
+ const templatesDir = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, LUMENFLOW_DOT_DIR);
45
+ fs.mkdirSync(templatesDir, { recursive: true });
46
+ // Create source constraints.md with v1.1 content
47
+ const lumenflowDir = path.join(tempDir, LUMENFLOW_DOT_DIR);
48
+ fs.mkdirSync(lumenflowDir, { recursive: true });
49
+ fs.writeFileSync(path.join(lumenflowDir, CONSTRAINTS_FILE), `# LumenFlow Constraints Capsule
50
+
51
+ **Version:** 1.1
52
+ **Last updated:** 2026-02-02
53
+
54
+ This document contains the 7 non-negotiable constraints.
55
+
56
+ ### 1. Worktree Discipline and Git Safety
57
+
58
+ **MANDATORY PRE-WRITE CHECK**
59
+
60
+ **NEVER "QUICK FIX" ON MAIN**
61
+ `);
62
+ // Create LUMENFLOW.md
63
+ fs.writeFileSync(path.join(tempDir, 'LUMENFLOW.md'), `# LumenFlow Workflow Guide
64
+
65
+ **Last updated:** 2026-02-02
66
+
67
+ ## Critical Rule: Use wu:prep Then wu:done
68
+ `);
69
+ });
70
+ it('should sync constraints.md to template', async () => {
71
+ const result = await syncCoreDocs(tempDir, false);
72
+ expect(result.errors).toHaveLength(0);
73
+ expect(result.synced).toContain(`${PACKAGES_DIR}/${LUMENFLOW_SCOPE}/${CLI_DIR}/${TEMPLATES_DIR}/${CORE_DIR}/${LUMENFLOW_DOT_DIR}/${CONSTRAINTS_TEMPLATE}`);
74
+ // Verify template content
75
+ const templatePath = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, LUMENFLOW_DOT_DIR, CONSTRAINTS_TEMPLATE);
76
+ const templateContent = fs.readFileSync(templatePath, 'utf-8');
77
+ // Should have {{DATE}} placeholder
78
+ expect(templateContent).toContain('{{DATE}}');
79
+ expect(templateContent).not.toContain('2026-02-02');
80
+ // Should have v1.1 content markers
81
+ expect(templateContent).toContain('Version:** 1.1');
82
+ expect(templateContent).toContain('7 non-negotiable constraints');
83
+ expect(templateContent).toContain('MANDATORY PRE-WRITE CHECK');
84
+ expect(templateContent).toContain('NEVER "QUICK FIX" ON MAIN');
85
+ });
86
+ it('should use dry-run mode without writing files', async () => {
87
+ // First, ensure no template exists
88
+ const templatePath = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, LUMENFLOW_DOT_DIR, CONSTRAINTS_TEMPLATE);
89
+ // Remove if it exists from beforeEach
90
+ if (fs.existsSync(templatePath)) {
91
+ fs.unlinkSync(templatePath);
92
+ }
93
+ const result = await syncCoreDocs(tempDir, true);
94
+ expect(result.errors).toHaveLength(0);
95
+ expect(result.synced.length).toBeGreaterThan(0);
96
+ expect(fs.existsSync(templatePath)).toBe(false);
97
+ });
98
+ });
99
+ describe('syncOnboardingDocs', () => {
100
+ const ONBOARDING_SUBPATH = [
101
+ 'docs',
102
+ '04-operations',
103
+ '_frameworks',
104
+ 'lumenflow',
105
+ 'agent',
106
+ 'onboarding',
107
+ ];
108
+ const FIRST_WU_MISTAKES_FILE = 'first-wu-mistakes.md';
109
+ beforeEach(() => {
110
+ // Set up onboarding source directory
111
+ const onboardingDir = path.join(tempDir, ...ONBOARDING_SUBPATH);
112
+ fs.mkdirSync(onboardingDir, { recursive: true });
113
+ // Create first-wu-mistakes.md with v1.1 content (11 mistakes)
114
+ fs.writeFileSync(path.join(onboardingDir, FIRST_WU_MISTAKES_FILE), `# First WU Mistakes
115
+
116
+ **Last updated:** 2026-02-02
117
+
118
+ ## Mistake 1: Not Using Worktrees
119
+
120
+ pnpm wu:prep --id WU-123
121
+
122
+ ## Mistake 11: "Quick Fixing" on Main
123
+
124
+ ## Quick Checklist
125
+
126
+ - [ ] Check spec_refs for plans
127
+ `);
128
+ // Set up target directory
129
+ const templatesDir = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, 'ai', 'onboarding');
130
+ fs.mkdirSync(templatesDir, { recursive: true });
131
+ });
132
+ it('should sync first-wu-mistakes.md to template', async () => {
133
+ const result = await syncOnboardingDocs(tempDir, false);
134
+ expect(result.errors).toHaveLength(0);
135
+ expect(result.synced).toContain(`${PACKAGES_DIR}/${LUMENFLOW_SCOPE}/${CLI_DIR}/${TEMPLATES_DIR}/${CORE_DIR}/ai/onboarding/${FIRST_WU_MISTAKES_FILE}.template`);
136
+ // Verify template content
137
+ const templatePath = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, 'ai', 'onboarding', `${FIRST_WU_MISTAKES_FILE}.template`);
138
+ const templateContent = fs.readFileSync(templatePath, 'utf-8');
139
+ // Should have {{DATE}} placeholder
140
+ expect(templateContent).toContain('{{DATE}}');
141
+ expect(templateContent).not.toContain('2026-02-02');
142
+ // Should have v1.1 content markers
143
+ expect(templateContent).toContain('Mistake 11:');
144
+ expect(templateContent).toContain('Quick Fixing" on Main');
145
+ expect(templateContent).toContain('wu:prep');
146
+ expect(templateContent).toContain('spec_refs');
147
+ });
148
+ });
149
+ describe('checkTemplateDrift', () => {
150
+ beforeEach(() => {
151
+ // Set up source files
152
+ const lumenflowDir = path.join(tempDir, LUMENFLOW_DOT_DIR);
153
+ fs.mkdirSync(lumenflowDir, { recursive: true });
154
+ fs.writeFileSync(path.join(lumenflowDir, CONSTRAINTS_FILE), `# Constraints
155
+ **Version:** 1.1
156
+ **Last updated:** 2026-02-02
157
+ 7 constraints`);
158
+ // Set up template directory
159
+ const templatesDir = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, LUMENFLOW_DOT_DIR);
160
+ fs.mkdirSync(templatesDir, { recursive: true });
161
+ });
162
+ it('should detect drift when template is outdated', async () => {
163
+ // Create outdated template (v1.0, 6 constraints)
164
+ const templatePath = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, LUMENFLOW_DOT_DIR, CONSTRAINTS_TEMPLATE);
165
+ fs.writeFileSync(templatePath, `# Constraints
166
+ **Version:** 1.0
167
+ **Last updated:** {{DATE}}
168
+ 6 constraints`);
169
+ const drift = await checkTemplateDrift(tempDir);
170
+ expect(drift.hasDrift).toBe(true);
171
+ expect(drift.driftingFiles.length).toBeGreaterThan(0);
172
+ expect(drift.driftingFiles.some((f) => f.includes(CONSTRAINTS_FILE))).toBe(true);
173
+ });
174
+ it('should report no drift when templates are in sync', async () => {
175
+ // First sync templates
176
+ await syncCoreDocs(tempDir, false);
177
+ // Then check for drift
178
+ const drift = await checkTemplateDrift(tempDir);
179
+ // After sync, constraints should not be drifting
180
+ expect(drift.driftingFiles.filter((f) => f.includes(CONSTRAINTS_FILE))).toHaveLength(0);
181
+ });
182
+ it('should return detailed drift report', async () => {
183
+ // Create outdated template
184
+ const templatePath = path.join(tempDir, PACKAGES_DIR, LUMENFLOW_SCOPE, CLI_DIR, TEMPLATES_DIR, CORE_DIR, LUMENFLOW_DOT_DIR, CONSTRAINTS_TEMPLATE);
185
+ fs.writeFileSync(templatePath, 'outdated content');
186
+ const drift = await checkTemplateDrift(tempDir);
187
+ expect(drift.hasDrift).toBe(true);
188
+ expect(drift.driftingFiles).toBeDefined();
189
+ expect(Array.isArray(drift.driftingFiles)).toBe(true);
190
+ });
191
+ });
192
+ describe('syncTemplates (full sync)', () => {
193
+ beforeEach(() => {
194
+ // Set up minimal directory structure
195
+ const lumenflowDir = path.join(tempDir, LUMENFLOW_DOT_DIR);
196
+ fs.mkdirSync(lumenflowDir, { recursive: true });
197
+ fs.writeFileSync(path.join(lumenflowDir, CONSTRAINTS_FILE), 'content');
198
+ fs.writeFileSync(path.join(tempDir, 'LUMENFLOW.md'), 'content');
199
+ const onboardingDir = path.join(tempDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
200
+ fs.mkdirSync(onboardingDir, { recursive: true });
201
+ fs.writeFileSync(path.join(onboardingDir, 'first-wu-mistakes.md'), 'content');
202
+ const skillsDir = path.join(tempDir, '.claude', 'skills', 'test-skill');
203
+ fs.mkdirSync(skillsDir, { recursive: true });
204
+ fs.writeFileSync(path.join(skillsDir, 'SKILL.md'), 'skill content');
205
+ });
206
+ it('should sync all template categories', async () => {
207
+ const result = await syncTemplates(tempDir, false);
208
+ expect(result.core.errors).toHaveLength(0);
209
+ expect(result.onboarding.errors).toHaveLength(0);
210
+ expect(result.skills.errors).toHaveLength(0);
211
+ // Should sync at least constraints and LUMENFLOW
212
+ expect(result.core.synced.length).toBeGreaterThanOrEqual(2);
213
+ // Should sync onboarding docs
214
+ expect(result.onboarding.synced.length).toBeGreaterThanOrEqual(1);
215
+ // Should sync skills
216
+ expect(result.skills.synced.length).toBeGreaterThanOrEqual(1);
217
+ });
218
+ });
219
+ });
package/dist/init.js CHANGED
@@ -1909,6 +1909,8 @@ export async function scaffoldProject(targetDir, options) {
1909
1909
  // Create .lumenflow/agents directory with .gitkeep
1910
1910
  await createDirectory(path.join(targetDir, LUMENFLOW_AGENTS_DIR), result, targetDir);
1911
1911
  await createFile(path.join(targetDir, LUMENFLOW_AGENTS_DIR, '.gitkeep'), '', options.force ? 'force' : 'skip', result, targetDir);
1912
+ // WU-1342: Create .gitignore with required exclusions
1913
+ await scaffoldGitignore(targetDir, options, result);
1912
1914
  // Optional: full docs scaffolding
1913
1915
  if (options.full) {
1914
1916
  await scaffoldFullDocs(targetDir, options, result, tokenDefaults);
@@ -1925,18 +1927,106 @@ export async function scaffoldProject(targetDir, options) {
1925
1927
  }
1926
1928
  return result;
1927
1929
  }
1930
+ /**
1931
+ * WU-1342: .gitignore template with required exclusions
1932
+ * Includes node_modules, .lumenflow/state, and worktrees
1933
+ */
1934
+ const GITIGNORE_TEMPLATE = `# Dependencies
1935
+ node_modules/
1936
+
1937
+ # LumenFlow state (local only, not shared)
1938
+ .lumenflow/state/
1939
+
1940
+ # Worktrees (isolated parallel work directories)
1941
+ worktrees/
1942
+
1943
+ # Build output
1944
+ dist/
1945
+ *.tsbuildinfo
1946
+
1947
+ # Environment files
1948
+ .env
1949
+ .env.local
1950
+ .env.*.local
1951
+
1952
+ # IDE
1953
+ .idea/
1954
+ .vscode/
1955
+ *.swp
1956
+ *.swo
1957
+
1958
+ # OS files
1959
+ .DS_Store
1960
+ Thumbs.db
1961
+ `;
1962
+ /** Gitignore file name constant to avoid duplicate string lint error */
1963
+ const GITIGNORE_FILE_NAME = '.gitignore';
1964
+ /**
1965
+ * WU-1342: Scaffold .gitignore file with LumenFlow exclusions
1966
+ * Supports merge mode to add exclusions to existing .gitignore
1967
+ */
1968
+ async function scaffoldGitignore(targetDir, options, result) {
1969
+ const gitignorePath = path.join(targetDir, GITIGNORE_FILE_NAME);
1970
+ const fileMode = getFileMode(options);
1971
+ if (fileMode === 'merge' && fs.existsSync(gitignorePath)) {
1972
+ // Merge mode: append LumenFlow exclusions if not already present
1973
+ const existingContent = fs.readFileSync(gitignorePath, 'utf-8');
1974
+ const linesToAdd = [];
1975
+ // Check each required exclusion
1976
+ const requiredExclusions = [
1977
+ { pattern: 'node_modules', line: 'node_modules/' },
1978
+ { pattern: '.lumenflow/state', line: '.lumenflow/state/' },
1979
+ { pattern: 'worktrees', line: 'worktrees/' },
1980
+ ];
1981
+ for (const { pattern, line } of requiredExclusions) {
1982
+ if (!existingContent.includes(pattern)) {
1983
+ linesToAdd.push(line);
1984
+ }
1985
+ }
1986
+ if (linesToAdd.length > 0) {
1987
+ const separator = existingContent.endsWith('\n') ? '' : '\n';
1988
+ const lumenflowBlock = `${separator}
1989
+ # LumenFlow (auto-added)
1990
+ ${linesToAdd.join('\n')}
1991
+ `;
1992
+ fs.writeFileSync(gitignorePath, existingContent + lumenflowBlock);
1993
+ result.merged?.push(GITIGNORE_FILE_NAME);
1994
+ }
1995
+ else {
1996
+ result.skipped.push(GITIGNORE_FILE_NAME);
1997
+ }
1998
+ return;
1999
+ }
2000
+ // Skip or force mode
2001
+ await createFile(gitignorePath, GITIGNORE_TEMPLATE, fileMode, result, targetDir);
2002
+ }
1928
2003
  /**
1929
2004
  * WU-1307: LumenFlow scripts to inject into package.json
2005
+ * WU-1342: Expanded to include all 17 essential commands
1930
2006
  * Uses standalone binaries (wu-claim, wu-done, gates) that work in consumer projects
1931
2007
  * after installing @lumenflow/cli.
1932
2008
  */
1933
2009
  const LUMENFLOW_SCRIPTS = {
2010
+ // Core WU lifecycle
1934
2011
  'wu:claim': 'wu-claim',
1935
2012
  'wu:done': 'wu-done',
1936
2013
  'wu:create': 'wu-create',
1937
2014
  'wu:status': 'wu-status',
1938
2015
  'wu:block': 'wu-block',
1939
2016
  'wu:unblock': 'wu-unblock',
2017
+ // WU-1342: Additional critical commands
2018
+ 'wu:prep': 'wu-prep',
2019
+ 'wu:recover': 'wu-recover',
2020
+ 'wu:spawn': 'wu-spawn',
2021
+ 'wu:validate': 'wu-validate',
2022
+ 'wu:infer-lane': 'wu-infer-lane',
2023
+ // Memory commands
2024
+ 'mem:init': 'mem-init',
2025
+ 'mem:checkpoint': 'mem-checkpoint',
2026
+ 'mem:inbox': 'mem-inbox',
2027
+ // Lane commands
2028
+ 'lane:suggest': 'lane-suggest',
2029
+ // Gates
1940
2030
  gates: 'gates',
1941
2031
  'gates:docs': 'gates --docs-only',
1942
2032
  };
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
2
3
  /**
3
4
  * Orchestrate Initiative Status CLI
4
5
  *
@@ -10,10 +11,14 @@
10
11
  */
11
12
  import { Command } from 'commander';
12
13
  import { existsSync, readdirSync } from 'node:fs';
13
- import { loadInitiativeWUs, calculateProgress, formatProgress } from '@lumenflow/initiatives';
14
+ import { loadInitiativeWUs, calculateProgress, formatProgress, getLaneAvailability, resolveLaneConfigsFromConfig, } from '@lumenflow/initiatives';
14
15
  import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/dist/wu-constants.js';
16
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
15
17
  import chalk from 'chalk';
16
18
  const LOG_PREFIX = '[orchestrate:init-status]';
19
+ function getErrorMessage(error) {
20
+ return error instanceof Error ? error.message : String(error);
21
+ }
17
22
  function getCompletedWUs(wuIds) {
18
23
  const completed = new Set();
19
24
  if (!existsSync(LUMENFLOW_PATHS.STAMPS_DIR)) {
@@ -27,6 +32,21 @@ function getCompletedWUs(wuIds) {
27
32
  }
28
33
  return completed;
29
34
  }
35
+ function formatLaneAvailability(availability, laneConfigs) {
36
+ const lanes = Object.keys(availability).sort((a, b) => a.localeCompare(b));
37
+ if (lanes.length === 0) {
38
+ return ' (no lanes found)';
39
+ }
40
+ return lanes
41
+ .map((lane) => {
42
+ const entry = availability[lane];
43
+ const wipLimit = laneConfigs[lane]?.wip_limit ?? 1;
44
+ const statusLabel = entry.available ? chalk.green('available') : chalk.red('occupied');
45
+ const occupiedBy = entry.occupiedBy ?? 'none';
46
+ return ` ${lane}: ${statusLabel} (wip_limit=${wipLimit}, lock_policy=${entry.policy}, in_progress=${entry.inProgressCount}, blocked=${entry.blockedCount}, occupied_by=${occupiedBy})`;
47
+ })
48
+ .join('\n');
49
+ }
30
50
  const program = new Command()
31
51
  .name('orchestrate:init-status')
32
52
  .description('Show initiative progress status')
@@ -45,18 +65,26 @@ const program = new Command()
45
65
  const completed = getCompletedWUs(wus.map((w) => w.id));
46
66
  console.log(chalk.bold('WUs:'));
47
67
  for (const wu of wus) {
48
- const status = completed.has(wu.id)
49
- ? chalk.green('✓ done')
50
- : wu.doc.status === 'in_progress'
51
- ? chalk.yellow('⟳ in_progress')
52
- : wu.doc.status === 'blocked'
53
- ? chalk.red(' blocked')
54
- : chalk.gray('○ ready');
68
+ let status = chalk.gray('○ ready');
69
+ if (completed.has(wu.id)) {
70
+ status = chalk.green('✓ done');
71
+ }
72
+ else if (wu.doc.status === 'in_progress') {
73
+ status = chalk.yellow(' in_progress');
74
+ }
75
+ else if (wu.doc.status === 'blocked') {
76
+ status = chalk.red('⛔ blocked');
77
+ }
55
78
  console.log(` ${wu.id}: ${wu.doc.title} [${status}]`);
56
79
  }
80
+ const laneConfigs = resolveLaneConfigsFromConfig(getConfig());
81
+ const availability = getLaneAvailability(wus, { laneConfigs });
82
+ console.log('');
83
+ console.log(chalk.bold('Lane Availability:'));
84
+ console.log(formatLaneAvailability(availability, laneConfigs));
57
85
  }
58
86
  catch (err) {
59
- console.error(chalk.red(`${LOG_PREFIX} Error: ${err.message}`));
87
+ console.error(chalk.red(`${LOG_PREFIX} Error: ${getErrorMessage(err)}`));
60
88
  process.exit(EXIT_CODES.ERROR);
61
89
  }
62
90
  });
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI tool requires console output */
2
3
  /**
3
4
  * Orchestrate Initiative CLI
4
5
  *
@@ -11,8 +12,12 @@
11
12
  */
12
13
  import { Command } from 'commander';
13
14
  import chalk from 'chalk';
14
- import { loadInitiativeWUs, loadMultipleInitiatives, buildExecutionPlanAsync, formatExecutionPlan, formatExecutionPlanWithEmbeddedSpawns, calculateProgress, formatProgress, buildCheckpointWave, formatCheckpointOutput, validateCheckpointFlags, resolveCheckpointModeAsync, LOG_PREFIX, } from '@lumenflow/initiatives';
15
+ import { loadInitiativeWUs, loadMultipleInitiatives, buildExecutionPlanWithLockPolicy, resolveLaneConfigsFromConfig, formatExecutionPlan, formatExecutionPlanWithEmbeddedSpawns, calculateProgress, formatProgress, buildCheckpointWave, formatCheckpointOutput, validateCheckpointFlags, resolveCheckpointModeAsync, LOG_PREFIX, } from '@lumenflow/initiatives';
15
16
  import { EXIT_CODES } from '@lumenflow/core/dist/wu-constants.js';
17
+ import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
18
+ function getErrorMessage(error) {
19
+ return error instanceof Error ? error.message : String(error);
20
+ }
16
21
  const program = new Command()
17
22
  .name('orchestrate-initiative')
18
23
  .description('Orchestrate initiative execution with parallel agent spawning')
@@ -28,7 +33,7 @@ const program = new Command()
28
33
  validateCheckpointFlags({ checkpointPerWave, dryRun, noCheckpoint });
29
34
  }
30
35
  catch (error) {
31
- console.error(chalk.red(`${LOG_PREFIX} Error: ${error.message}`));
36
+ console.error(chalk.red(`${LOG_PREFIX} Error: ${getErrorMessage(error)}`));
32
37
  process.exit(EXIT_CODES.ERROR);
33
38
  }
34
39
  if (!initIds || initIds.length === 0) {
@@ -76,7 +81,8 @@ const program = new Command()
76
81
  return;
77
82
  }
78
83
  console.log(chalk.cyan(`${LOG_PREFIX} Building execution plan...`));
79
- const plan = await buildExecutionPlanAsync(wus);
84
+ const laneConfigs = resolveLaneConfigsFromConfig(getConfig());
85
+ const plan = buildExecutionPlanWithLockPolicy(wus, { laneConfigs });
80
86
  if (plan.waves.length === 0) {
81
87
  console.log(chalk.green(`${LOG_PREFIX} All WUs are complete! Nothing to execute.`));
82
88
  return;
@@ -99,7 +105,7 @@ const program = new Command()
99
105
  console.log(chalk.cyan('Copy the spawn XML above to execute agents.'));
100
106
  }
101
107
  catch (error) {
102
- console.error(chalk.red(`${LOG_PREFIX} Error: ${error.message}`));
108
+ console.error(chalk.red(`${LOG_PREFIX} Error: ${getErrorMessage(error)}`));
103
109
  process.exit(EXIT_CODES.ERROR);
104
110
  }
105
111
  });