@lumenflow/cli 2.2.2 → 2.3.2
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 +147 -57
- package/dist/__tests__/agent-log-issue.test.js +56 -0
- package/dist/__tests__/cli-entry-point.test.js +66 -17
- package/dist/__tests__/cli-subprocess.test.js +25 -0
- package/dist/__tests__/init.test.js +298 -0
- package/dist/__tests__/initiative-plan.test.js +340 -0
- package/dist/__tests__/mem-cleanup-execution.test.js +19 -0
- package/dist/__tests__/merge-block.test.js +220 -0
- package/dist/__tests__/release.test.js +61 -0
- package/dist/__tests__/safe-git.test.js +191 -0
- package/dist/__tests__/state-doctor.test.js +274 -0
- package/dist/__tests__/wu-done.test.js +36 -0
- package/dist/__tests__/wu-edit.test.js +119 -0
- package/dist/__tests__/wu-prep.test.js +108 -0
- package/dist/agent-issues-query.js +4 -3
- package/dist/agent-log-issue.js +25 -4
- package/dist/backlog-prune.js +5 -4
- package/dist/cli-entry-point.js +11 -1
- package/dist/doctor.js +368 -0
- package/dist/flow-bottlenecks.js +6 -5
- package/dist/flow-report.js +4 -3
- package/dist/gates.js +356 -101
- package/dist/guard-locked.js +4 -3
- package/dist/guard-worktree-commit.js +4 -3
- package/dist/init.js +517 -86
- package/dist/initiative-add-wu.js +4 -3
- package/dist/initiative-bulk-assign-wus.js +8 -5
- package/dist/initiative-create.js +73 -37
- package/dist/initiative-edit.js +37 -21
- package/dist/initiative-list.js +4 -3
- package/dist/initiative-plan.js +337 -0
- package/dist/initiative-status.js +4 -3
- package/dist/lane-health.js +377 -0
- package/dist/lane-suggest.js +382 -0
- package/dist/mem-checkpoint.js +2 -2
- package/dist/mem-cleanup.js +2 -2
- package/dist/mem-context.js +306 -0
- package/dist/mem-create.js +2 -2
- package/dist/mem-delete.js +293 -0
- package/dist/mem-inbox.js +2 -2
- package/dist/mem-index.js +211 -0
- package/dist/mem-init.js +1 -1
- package/dist/mem-profile.js +207 -0
- package/dist/mem-promote.js +254 -0
- package/dist/mem-ready.js +2 -2
- package/dist/mem-signal.js +2 -2
- package/dist/mem-start.js +2 -2
- package/dist/mem-summarize.js +2 -2
- package/dist/mem-triage.js +2 -2
- package/dist/merge-block.js +222 -0
- package/dist/metrics-cli.js +7 -4
- package/dist/metrics-snapshot.js +4 -3
- package/dist/orchestrate-initiative.js +10 -4
- package/dist/orchestrate-monitor.js +379 -31
- package/dist/release.js +69 -29
- package/dist/signal-cleanup.js +296 -0
- package/dist/spawn-list.js +6 -5
- package/dist/state-bootstrap.js +5 -4
- package/dist/state-cleanup.js +360 -0
- package/dist/state-doctor-fix.js +196 -0
- package/dist/state-doctor.js +501 -0
- package/dist/validate-agent-skills.js +4 -3
- package/dist/validate-agent-sync.js +4 -3
- package/dist/validate-backlog-sync.js +4 -3
- package/dist/validate-skills-spec.js +4 -3
- package/dist/validate.js +4 -3
- package/dist/wu-block.js +3 -3
- package/dist/wu-claim.js +208 -98
- package/dist/wu-cleanup.js +5 -4
- package/dist/wu-create.js +71 -46
- package/dist/wu-delete.js +88 -60
- package/dist/wu-deps.js +6 -5
- package/dist/wu-done-check.js +34 -0
- package/dist/wu-done.js +39 -12
- package/dist/wu-edit.js +63 -28
- package/dist/wu-infer-lane.js +7 -6
- package/dist/wu-preflight.js +23 -81
- package/dist/wu-prep.js +125 -0
- package/dist/wu-prune.js +4 -3
- package/dist/wu-recover.js +88 -22
- package/dist/wu-repair.js +7 -6
- package/dist/wu-spawn.js +226 -270
- package/dist/wu-status.js +4 -3
- package/dist/wu-unblock.js +5 -5
- package/dist/wu-unlock-lane.js +4 -3
- package/dist/wu-validate.js +5 -4
- package/package.json +16 -7
- package/templates/core/.lumenflow/constraints.md.template +192 -0
- package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
- package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
- package/templates/core/AGENTS.md.template +60 -0
- package/templates/core/LUMENFLOW.md.template +255 -0
- package/templates/core/UPGRADING.md.template +121 -0
- package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
- package/templates/core/ai/onboarding/release-process.md.template +362 -0
- package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
- package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
- package/templates/vendors/aider/.aider.conf.yml.template +27 -0
- package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
- package/templates/vendors/claude/.claude/settings.json.template +49 -0
- package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
- package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
- package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
- package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
- package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
- package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
- package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
- package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
- package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
- package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
- package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
- package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
- package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
- package/templates/vendors/cline/.clinerules.template +53 -0
- package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
- package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
- package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +34 -0
package/dist/init.js
CHANGED
|
@@ -4,31 +4,55 @@
|
|
|
4
4
|
* WU-1006: Library-First - use core defaults for config generation
|
|
5
5
|
* WU-1028: Vendor-agnostic core + vendor overlays
|
|
6
6
|
* WU-1085: Added createWUParser for proper --help support
|
|
7
|
+
* WU-1171: Added --merge mode, --client flag, AGENTS.md, updated vendor paths
|
|
7
8
|
*/
|
|
8
9
|
import * as fs from 'node:fs';
|
|
9
10
|
import * as path from 'node:path';
|
|
10
11
|
import * as yaml from 'yaml';
|
|
12
|
+
import { execFileSync } from 'node:child_process';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
11
14
|
import { getDefaultConfig, createWUParser, WU_OPTIONS } from '@lumenflow/core';
|
|
12
15
|
// WU-1067: Import GATE_PRESETS for --preset support
|
|
13
16
|
import { GATE_PRESETS } from '@lumenflow/core/dist/gates-config.js';
|
|
17
|
+
// WU-1171: Import merge block utilities
|
|
18
|
+
import { updateMergeBlock } from './merge-block.js';
|
|
14
19
|
/**
|
|
15
20
|
* WU-1085: CLI option definitions for init command
|
|
21
|
+
* WU-1171: Added --merge and --client options
|
|
16
22
|
*/
|
|
17
23
|
const INIT_OPTIONS = {
|
|
18
24
|
full: {
|
|
19
25
|
name: 'full',
|
|
20
26
|
flags: '--full',
|
|
21
|
-
description: 'Add docs + agent onboarding + task scaffolding',
|
|
27
|
+
description: 'Add docs + agent onboarding + task scaffolding (default: true)',
|
|
28
|
+
},
|
|
29
|
+
minimal: {
|
|
30
|
+
name: 'minimal',
|
|
31
|
+
flags: '--minimal',
|
|
32
|
+
description: 'Skip agent onboarding docs (only core files)',
|
|
22
33
|
},
|
|
23
34
|
framework: {
|
|
24
35
|
name: 'framework',
|
|
25
36
|
flags: '--framework <name>',
|
|
26
37
|
description: 'Add framework hint + overlay docs',
|
|
27
38
|
},
|
|
39
|
+
// WU-1171: --client is the new primary flag (wu:spawn vocabulary)
|
|
40
|
+
client: {
|
|
41
|
+
name: 'client',
|
|
42
|
+
flags: '--client <type>',
|
|
43
|
+
description: 'Client type (claude, cursor, windsurf, codex, all, none)',
|
|
44
|
+
},
|
|
45
|
+
// WU-1171: --vendor kept as backward-compatible alias
|
|
28
46
|
vendor: {
|
|
29
47
|
name: 'vendor',
|
|
30
48
|
flags: '--vendor <type>',
|
|
31
|
-
description: '
|
|
49
|
+
description: 'Alias for --client (deprecated)',
|
|
50
|
+
},
|
|
51
|
+
// WU-1171: --merge mode for safe insertion into existing files
|
|
52
|
+
merge: {
|
|
53
|
+
name: 'merge',
|
|
54
|
+
flags: '--merge',
|
|
55
|
+
description: 'Merge LumenFlow config into existing files using bounded markers',
|
|
32
56
|
},
|
|
33
57
|
preset: {
|
|
34
58
|
name: 'preset',
|
|
@@ -39,6 +63,7 @@ const INIT_OPTIONS = {
|
|
|
39
63
|
};
|
|
40
64
|
/**
|
|
41
65
|
* WU-1085: Parse init command options using createWUParser
|
|
66
|
+
* WU-1171: Added --merge, --client options
|
|
42
67
|
* Provides proper --help, --version, and option parsing
|
|
43
68
|
*/
|
|
44
69
|
export function parseInitOptions() {
|
|
@@ -47,20 +72,133 @@ export function parseInitOptions() {
|
|
|
47
72
|
description: 'Initialize LumenFlow in a project',
|
|
48
73
|
options: Object.values(INIT_OPTIONS),
|
|
49
74
|
});
|
|
75
|
+
// WU-1171: --client takes precedence, --vendor is alias
|
|
76
|
+
const clientValue = opts.client || opts.vendor;
|
|
77
|
+
// WU-1286: --full is now the default (true), use --minimal to disable
|
|
78
|
+
// --minimal explicitly sets full to false, otherwise full defaults to true
|
|
79
|
+
const fullMode = opts.minimal ? false : (opts.full ?? true);
|
|
50
80
|
return {
|
|
51
81
|
force: opts.force ?? false,
|
|
52
|
-
full:
|
|
82
|
+
full: fullMode,
|
|
83
|
+
merge: opts.merge ?? false,
|
|
53
84
|
framework: opts.framework,
|
|
54
|
-
|
|
85
|
+
client: clientValue,
|
|
86
|
+
vendor: clientValue,
|
|
55
87
|
preset: opts.preset,
|
|
56
88
|
};
|
|
57
89
|
}
|
|
90
|
+
const DEFAULT_CLIENT_CLAUDE = 'claude-code';
|
|
58
91
|
const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
|
|
59
92
|
const FRAMEWORK_HINT_FILE = '.lumenflow.framework.yaml';
|
|
60
93
|
const LUMENFLOW_DIR = '.lumenflow';
|
|
61
94
|
const LUMENFLOW_AGENTS_DIR = `${LUMENFLOW_DIR}/agents`;
|
|
62
95
|
const CLAUDE_DIR = '.claude';
|
|
63
96
|
const CLAUDE_AGENTS_DIR = path.join(CLAUDE_DIR, 'agents');
|
|
97
|
+
// Shared path segment for docs structure
|
|
98
|
+
const DOCS_OPERATIONS_DIR = '04-operations';
|
|
99
|
+
/**
|
|
100
|
+
* WU-1177: Detect IDE environment from environment variables
|
|
101
|
+
* Auto-detects which AI coding assistant is running
|
|
102
|
+
*/
|
|
103
|
+
export function detectIDEEnvironment() {
|
|
104
|
+
// Claude Code detection (highest priority - most specific)
|
|
105
|
+
if (process.env.CLAUDE_PROJECT_DIR || process.env.CLAUDE_CODE) {
|
|
106
|
+
return 'claude';
|
|
107
|
+
}
|
|
108
|
+
// Cursor detection
|
|
109
|
+
const cursorVars = Object.keys(process.env).filter((key) => key.startsWith('CURSOR_'));
|
|
110
|
+
if (cursorVars.length > 0) {
|
|
111
|
+
return 'cursor';
|
|
112
|
+
}
|
|
113
|
+
// Windsurf detection
|
|
114
|
+
const windsurfVars = Object.keys(process.env).filter((key) => key.startsWith('WINDSURF_'));
|
|
115
|
+
if (windsurfVars.length > 0) {
|
|
116
|
+
return 'windsurf';
|
|
117
|
+
}
|
|
118
|
+
// VS Code detection (lowest priority - most generic)
|
|
119
|
+
const vscodeVars = Object.keys(process.env).filter((key) => key.startsWith('VSCODE_'));
|
|
120
|
+
if (vscodeVars.length > 0) {
|
|
121
|
+
return 'vscode';
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get command version safely using execFileSync
|
|
127
|
+
*/
|
|
128
|
+
function getCommandVersion(command, args) {
|
|
129
|
+
try {
|
|
130
|
+
const output = execFileSync(command, args, {
|
|
131
|
+
encoding: 'utf-8',
|
|
132
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
133
|
+
}).trim();
|
|
134
|
+
return output;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return 'not found';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parse semver version string to compare
|
|
142
|
+
*/
|
|
143
|
+
function parseVersion(versionStr) {
|
|
144
|
+
// Extract version numbers using a non-backtracking pattern
|
|
145
|
+
const match = /^v?(\d+)\.(\d+)(?:\.(\d+))?/.exec(versionStr);
|
|
146
|
+
if (!match) {
|
|
147
|
+
return [0, 0, 0];
|
|
148
|
+
}
|
|
149
|
+
return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3] || '0', 10)];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Compare versions: returns true if actual >= required
|
|
153
|
+
*/
|
|
154
|
+
function compareVersions(actual, required) {
|
|
155
|
+
const actualParts = parseVersion(actual);
|
|
156
|
+
const requiredParts = parseVersion(required);
|
|
157
|
+
for (let i = 0; i < 3; i++) {
|
|
158
|
+
if (actualParts[i] > requiredParts[i]) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (actualParts[i] < requiredParts[i]) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* WU-1177: Check prerequisite versions
|
|
169
|
+
* Non-blocking - returns results but doesn't fail init
|
|
170
|
+
*/
|
|
171
|
+
export function checkPrerequisites() {
|
|
172
|
+
const nodeVersion = getCommandVersion('node', ['--version']);
|
|
173
|
+
const pnpmVersion = getCommandVersion('pnpm', ['--version']);
|
|
174
|
+
const gitVersion = getCommandVersion('git', ['--version']);
|
|
175
|
+
const requiredNode = '22.0.0';
|
|
176
|
+
const requiredPnpm = '9.0.0';
|
|
177
|
+
const requiredGit = '2.0.0';
|
|
178
|
+
const nodeOk = nodeVersion !== 'not found' && compareVersions(nodeVersion, requiredNode);
|
|
179
|
+
const pnpmOk = pnpmVersion !== 'not found' && compareVersions(pnpmVersion, requiredPnpm);
|
|
180
|
+
const gitOk = gitVersion !== 'not found' && compareVersions(gitVersion, requiredGit);
|
|
181
|
+
return {
|
|
182
|
+
node: {
|
|
183
|
+
passed: nodeOk,
|
|
184
|
+
version: nodeVersion,
|
|
185
|
+
required: `>=${requiredNode}`,
|
|
186
|
+
message: nodeOk ? undefined : `Node.js ${requiredNode}+ required`,
|
|
187
|
+
},
|
|
188
|
+
pnpm: {
|
|
189
|
+
passed: pnpmOk,
|
|
190
|
+
version: pnpmVersion,
|
|
191
|
+
required: `>=${requiredPnpm}`,
|
|
192
|
+
message: pnpmOk ? undefined : `pnpm ${requiredPnpm}+ required`,
|
|
193
|
+
},
|
|
194
|
+
git: {
|
|
195
|
+
passed: gitOk,
|
|
196
|
+
version: gitVersion,
|
|
197
|
+
required: `>=${requiredGit}`,
|
|
198
|
+
message: gitOk ? undefined : `Git ${requiredGit}+ required`,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
64
202
|
/**
|
|
65
203
|
* Generate YAML configuration with header comment
|
|
66
204
|
* WU-1067: Supports --preset option for config-driven gates
|
|
@@ -92,8 +230,11 @@ function normalizeFrameworkName(framework) {
|
|
|
92
230
|
const name = framework.trim();
|
|
93
231
|
const slug = name
|
|
94
232
|
.toLowerCase()
|
|
95
|
-
.replace(/[^a-z0-
|
|
96
|
-
|
|
233
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
234
|
+
// Remove leading dashes and trailing dashes separately (explicit precedence)
|
|
235
|
+
.replace(/^-+/, '')
|
|
236
|
+
// eslint-disable-next-line sonarjs/slow-regex -- Simple pattern, no catastrophic backtracking risk
|
|
237
|
+
.replace(/-+$/, '');
|
|
97
238
|
if (!slug) {
|
|
98
239
|
throw new Error(`Invalid framework name: "${framework}"`);
|
|
99
240
|
}
|
|
@@ -112,6 +253,66 @@ function processTemplate(content, tokens) {
|
|
|
112
253
|
function getRelativePath(targetDir, filePath) {
|
|
113
254
|
return path.relative(targetDir, filePath).split(path.sep).join('/');
|
|
114
255
|
}
|
|
256
|
+
// WU-1171: Template for AGENTS.md (universal entry point)
|
|
257
|
+
const AGENTS_MD_TEMPLATE = `# Universal Agent Instructions
|
|
258
|
+
|
|
259
|
+
**Last updated:** {{DATE}}
|
|
260
|
+
|
|
261
|
+
This project uses LumenFlow workflow. For complete documentation, see [LUMENFLOW.md](LUMENFLOW.md).
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Quick Start
|
|
266
|
+
|
|
267
|
+
\`\`\`bash
|
|
268
|
+
# 1. Claim a WU
|
|
269
|
+
pnpm wu:claim --id WU-XXXX --lane <Lane>
|
|
270
|
+
cd worktrees/<lane>-wu-xxxx
|
|
271
|
+
|
|
272
|
+
# 2. Work in worktree, run gates
|
|
273
|
+
pnpm gates
|
|
274
|
+
|
|
275
|
+
# 3. Complete (ALWAYS run this!)
|
|
276
|
+
cd {{PROJECT_ROOT}}
|
|
277
|
+
pnpm wu:done --id WU-XXXX
|
|
278
|
+
\`\`\`
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Critical: Always wu:done
|
|
283
|
+
|
|
284
|
+
After completing work, ALWAYS run \`pnpm wu:done --id WU-XXXX\` from the main checkout.
|
|
285
|
+
|
|
286
|
+
This is the single most forgotten step. See [LUMENFLOW.md](LUMENFLOW.md) for details.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Core Principles
|
|
291
|
+
|
|
292
|
+
1. **TDD**: Write tests first, then implementation
|
|
293
|
+
2. **Worktree Discipline**: After \`wu:claim\`, work ONLY in the worktree
|
|
294
|
+
3. **Gates Before Done**: Run \`pnpm gates\` before \`wu:done\`
|
|
295
|
+
4. **Never Bypass Hooks**: No \`--no-verify\`
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Forbidden Commands
|
|
300
|
+
|
|
301
|
+
- \`git reset --hard\`
|
|
302
|
+
- \`git push --force\`
|
|
303
|
+
- \`git stash\` (on main)
|
|
304
|
+
- \`--no-verify\`
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Vendor-Specific Overlays
|
|
309
|
+
|
|
310
|
+
This file provides universal guidance for all AI agents. Additional vendor-specific configuration:
|
|
311
|
+
|
|
312
|
+
- **Claude Code**: See \`CLAUDE.md\` (if present)
|
|
313
|
+
- **Cursor**: See \`.cursor/rules/lumenflow.md\` (if present)
|
|
314
|
+
- **Windsurf**: See \`.windsurf/rules/lumenflow.md\` (if present)
|
|
315
|
+
`;
|
|
115
316
|
// Template for LUMENFLOW.md (main entry point)
|
|
116
317
|
const LUMENFLOW_MD_TEMPLATE = `# LumenFlow Workflow Guide\n\n**Last updated:** {{DATE}}\n\nLumenFlow is a vendor-agnostic workflow framework for AI-native software development.\n\n---\n\n## Critical Rule: ALWAYS Run wu:done\n\n**After completing work on a WU, you MUST run \`pnpm wu:done --id WU-XXXX\` from the main checkout.**\n\nThis is the single most forgotten step. Do NOT:\n- Write "To Complete: pnpm wu:done" and stop\n- Ask if you should run wu:done\n- Forget to run wu:done\n\n**DO**: Run \`pnpm wu:done --id WU-XXXX\` immediately after gates pass.\n\n---\n\n## Quick Start\n\n\`\`\`bash\n# 1. Create a WU\npnpm wu:create --id WU-XXXX --lane <Lane> --title "Title"\n\n# 2. Edit WU spec with acceptance criteria, then claim:\npnpm wu:claim --id WU-XXXX --lane <Lane>\ncd worktrees/<lane>-wu-xxxx\n\n# 3. Implement in worktree\n\n# 4. Run gates\npnpm gates --docs-only # for docs changes\npnpm gates # for code changes\n\n# 5. Complete (from main checkout)\ncd {{PROJECT_ROOT}}\npnpm wu:done --id WU-XXXX\n\`\`\`\n\n---\n\n## Core Principles\n\n1. **TDD**: Failing test -> implementation -> passing test (>=90% coverage on new code)\n2. **Library-First**: Search existing libraries before custom code\n3. **DRY/SOLID/KISS/YAGNI**: No magic numbers, no hardcoded strings\n4. **Worktree Discipline**: After \`wu:claim\`, work ONLY in the worktree\n5. **Gates Before Done**: All gates must pass before \`wu:done\`\n6. **Do Not Bypass Hooks**: No \`--no-verify\`, fix issues properly\n7. **Always wu:done**: Complete every WU by running \`pnpm wu:done\`\n\n---\n\n## Documentation Structure\n\n### Core (Vendor-Agnostic)\n\n- **LUMENFLOW.md** - This file, main entry point\n- **.lumenflow/constraints.md** - Non-negotiable workflow constraints\n- **.lumenflow/agents/** - Agent instructions (vendor-agnostic)\n- **.lumenflow.config.yaml** - Workflow configuration\n\n### Optional Overlays\n\n- **CLAUDE.md + .claude/agents/** - Claude Code overlay (auto if Claude Code detected)\n- **docs/04-operations/tasks/** - Task boards and WU storage (\`lumenflow init --full\`)\n- **docs/04-operations/_frameworks/<framework>/** - Framework overlay docs (\`lumenflow init --framework <name>\`)\n- **.lumenflow.framework.yaml** - Framework hint file (created with \`--framework\`)\n\n---\n\n## Worktree Discipline (IMMUTABLE LAW)\n\nAfter claiming a WU, you MUST work in its worktree:\n\n\`\`\`bash\n# 1. Claim creates worktree\npnpm wu:claim --id WU-XXX --lane <lane>\n\n# 2. IMMEDIATELY cd to worktree\ncd worktrees/<lane>-wu-xxx\n\n# 3. ALL work happens here\n\n# 4. Return to main ONLY to complete\ncd {{PROJECT_ROOT}}\npnpm wu:done --id WU-XXX\n\`\`\`\n\n---\n\n## Definition of Done\n\n- Acceptance criteria satisfied\n- Gates green (\`pnpm gates\` or \`pnpm gates --docs-only\`)\n- WU YAML status = \`done\`\n- \`wu:done\` has been run\n\n---\n\n## Commands Reference\n\n| Command | Description |\n| ----------------- | ----------------------------------- |\n| \`pnpm wu:create\` | Create new WU spec |\n| \`pnpm wu:claim\` | Claim WU and create worktree |\n| \`pnpm wu:done\` | Complete WU (merge, stamp, cleanup) |\n| \`pnpm gates\` | Run quality gates |\n\n---\n\n## Constraints\n\nSee [.lumenflow/constraints.md](.lumenflow/constraints.md) for the 6 non-negotiable rules.\n\n---\n\n## Agent Onboarding\n\n- Start with **CLAUDE.md** if present (Claude Code overlay).\n- Add vendor-agnostic guidance in **.lumenflow/agents/**.\n- Add framework-specific notes in **docs/04-operations/_frameworks/<framework>/**.\n`;
|
|
117
318
|
// Template for .lumenflow/constraints.md
|
|
@@ -149,8 +350,108 @@ const CLAUDE_SETTINGS_TEMPLATE = `{
|
|
|
149
350
|
}
|
|
150
351
|
}
|
|
151
352
|
`;
|
|
152
|
-
// Template for .cursor/rules.md
|
|
153
|
-
const CURSOR_RULES_TEMPLATE = `# Cursor
|
|
353
|
+
// WU-1171: Template for .cursor/rules/lumenflow.md (updated path)
|
|
354
|
+
const CURSOR_RULES_TEMPLATE = `# Cursor LumenFlow Rules
|
|
355
|
+
|
|
356
|
+
This project uses LumenFlow workflow. See [LUMENFLOW.md](../../LUMENFLOW.md).
|
|
357
|
+
|
|
358
|
+
## Critical Rules
|
|
359
|
+
|
|
360
|
+
1. **Always run wu:done** - After gates pass, run \`pnpm wu:done --id WU-XXX\`
|
|
361
|
+
2. **Work in worktrees** - After \`wu:claim\`, work only in the worktree
|
|
362
|
+
3. **Never bypass hooks** - No \`--no-verify\`
|
|
363
|
+
4. **TDD** - Write tests first
|
|
364
|
+
|
|
365
|
+
## Forbidden Commands
|
|
366
|
+
|
|
367
|
+
- \`git reset --hard\`
|
|
368
|
+
- \`git push --force\`
|
|
369
|
+
- \`git stash\` (on main)
|
|
370
|
+
- \`--no-verify\`
|
|
371
|
+
|
|
372
|
+
## Quick Reference
|
|
373
|
+
|
|
374
|
+
\`\`\`bash
|
|
375
|
+
# Claim WU
|
|
376
|
+
pnpm wu:claim --id WU-XXX --lane <Lane>
|
|
377
|
+
cd worktrees/<lane>-wu-xxx
|
|
378
|
+
|
|
379
|
+
# Run gates
|
|
380
|
+
pnpm gates
|
|
381
|
+
|
|
382
|
+
# Complete (from main)
|
|
383
|
+
cd {{PROJECT_ROOT}}
|
|
384
|
+
pnpm wu:done --id WU-XXX
|
|
385
|
+
\`\`\`
|
|
386
|
+
`;
|
|
387
|
+
// WU-1171: Template for .windsurf/rules/lumenflow.md
|
|
388
|
+
const WINDSURF_RULES_TEMPLATE = `# Windsurf LumenFlow Rules
|
|
389
|
+
|
|
390
|
+
This project uses LumenFlow workflow. See [LUMENFLOW.md](../../LUMENFLOW.md).
|
|
391
|
+
|
|
392
|
+
## Critical Rules
|
|
393
|
+
|
|
394
|
+
1. **Always run wu:done** - After gates pass, run \`pnpm wu:done --id WU-XXX\`
|
|
395
|
+
2. **Work in worktrees** - After \`wu:claim\`, work only in the worktree
|
|
396
|
+
3. **Never bypass hooks** - No \`--no-verify\`
|
|
397
|
+
4. **TDD** - Write tests first
|
|
398
|
+
|
|
399
|
+
## Forbidden Commands
|
|
400
|
+
|
|
401
|
+
- \`git reset --hard\`
|
|
402
|
+
- \`git push --force\`
|
|
403
|
+
- \`git stash\` (on main)
|
|
404
|
+
- \`--no-verify\`
|
|
405
|
+
|
|
406
|
+
## Quick Reference
|
|
407
|
+
|
|
408
|
+
\`\`\`bash
|
|
409
|
+
# Claim WU
|
|
410
|
+
pnpm wu:claim --id WU-XXX --lane <Lane>
|
|
411
|
+
cd worktrees/<lane>-wu-xxx
|
|
412
|
+
|
|
413
|
+
# Run gates
|
|
414
|
+
pnpm gates
|
|
415
|
+
|
|
416
|
+
# Complete (from main)
|
|
417
|
+
cd {{PROJECT_ROOT}}
|
|
418
|
+
pnpm wu:done --id WU-XXX
|
|
419
|
+
\`\`\`
|
|
420
|
+
`;
|
|
421
|
+
// WU-1177: Template for .clinerules (Cline AI assistant)
|
|
422
|
+
const CLINE_RULES_TEMPLATE = `# Cline LumenFlow Rules
|
|
423
|
+
|
|
424
|
+
This project uses LumenFlow workflow. See [LUMENFLOW.md](LUMENFLOW.md).
|
|
425
|
+
|
|
426
|
+
## Critical Rules
|
|
427
|
+
|
|
428
|
+
1. **Always run wu:done** - After gates pass, run \`pnpm wu:done --id WU-XXX\`
|
|
429
|
+
2. **Work in worktrees** - After \`wu:claim\`, work only in the worktree
|
|
430
|
+
3. **Never bypass hooks** - No \`--no-verify\`
|
|
431
|
+
4. **TDD** - Write tests first
|
|
432
|
+
|
|
433
|
+
## Forbidden Commands
|
|
434
|
+
|
|
435
|
+
- \`git reset --hard\`
|
|
436
|
+
- \`git push --force\`
|
|
437
|
+
- \`git stash\` (on main)
|
|
438
|
+
- \`--no-verify\`
|
|
439
|
+
|
|
440
|
+
## Quick Reference
|
|
441
|
+
|
|
442
|
+
\`\`\`bash
|
|
443
|
+
# Claim WU
|
|
444
|
+
pnpm wu:claim --id WU-XXX --lane <Lane>
|
|
445
|
+
cd worktrees/<lane>-wu-xxx
|
|
446
|
+
|
|
447
|
+
# Run gates
|
|
448
|
+
pnpm gates
|
|
449
|
+
|
|
450
|
+
# Complete (from main)
|
|
451
|
+
cd {{PROJECT_ROOT}}
|
|
452
|
+
pnpm wu:done --id WU-XXX
|
|
453
|
+
\`\`\`
|
|
454
|
+
`;
|
|
154
455
|
// Template for .aider.conf.yml
|
|
155
456
|
const AIDER_CONF_TEMPLATE = `# Aider Configuration for LumenFlow Projects\n# See LUMENFLOW.md for workflow documentation\n\nmodel: gpt-4-turbo\nauto-commits: false\ndirty-commits: false\n\nread:\n - LUMENFLOW.md\n - .lumenflow/constraints.md\n`;
|
|
156
457
|
// Template for docs/04-operations/tasks/backlog.md
|
|
@@ -158,7 +459,7 @@ const BACKLOG_TEMPLATE = `---\nsections:\n ready:\n heading: '## 🚀 Ready
|
|
|
158
459
|
// Template for docs/04-operations/tasks/status.md
|
|
159
460
|
const STATUS_TEMPLATE = `# Status (active work)\n\n## In Progress\n\n(No items in progress)\n\n## Blocked\n\n(No items blocked)\n\n## Completed\n\n(No items completed yet)\n`;
|
|
160
461
|
// Template for docs/04-operations/tasks/templates/wu-template.yaml
|
|
161
|
-
const WU_TEMPLATE_YAML = `# Work Unit Template (LumenFlow WU Schema)\n#\n# Copy this template when creating new WUs. Fill in all required fields and\n# remove optional fields if not needed.\n#\n# If you used "lumenflow init --full", this template lives at:\n# docs/04-operations/tasks/templates/wu-template.yaml\n\n# Required: Unique work unit identifier (format: WU-NNN)\nid: WU-XXX\n\n# Required: Short, descriptive title (max 80 chars)\ntitle: 'Your WU title here'\n\n# Required: Lane (Parent: Sublane format)\nlane: 'Framework: CLI'\n\n# Required: Type of work\ntype: 'feature' # feature | bug | documentation | process | tooling | chore | refactor\n\n# Required: Current status\nstatus: 'ready' # ready | in_progress | blocked | done | cancelled\n\n# Required: Priority\npriority: P2 # P0 | P1 | P2 | P3\n\n# Required: Creation date (YYYY-MM-DD)\ncreated: {{DATE}}\n\n# Required: Owner/assignee (email)\nassigned_to: 'unassigned@example.com'\n\n# Required: Description\ndescription: |\n Context: ...\n Problem: ...\n Solution: ...\n\n# Required: Acceptance criteria (testable, binary)\nacceptance:\n - Criterion 1 (specific, measurable, testable)\n - Criterion 2 (binary pass/fail)\n - Documentation updated\n\n# Required: References to plans/specs (required for type: feature)\nspec_refs:\n - docs/04-operations/plans/WU-XXX-plan.md\n\n# Required: Code files changed or created (empty only for docs/process WUs)\ncode_paths:\n - path/to/file.ts\n\n# Required: Test paths (at least one of manual/unit/e2e/integration for non-doc WUs)\ntests:\n manual:\n - Manual test: Verify behavior\n unit:\n - path/to/test.test.ts\n e2e: []\n integration: []\n\n# Required: Exposure level\nexposure: 'backend-only' # ui | api | backend-only | documentation\n\n# Optional: User journey (recommended for ui/api)\n# user_journey: |\n# User navigates to ...\n# User performs ...\n\n# Optional: UI pairing WUs (for api exposure)\n# ui_pairing_wus:\n# - WU-1234\n\n# Optional: Navigation path (required when exposure=ui and no page file)\n# navigation_path: '/settings'\n\n# Required: Deliverable artifacts (stamps, docs, etc.)\nartifacts:\n - .lumenflow/stamps/WU-XXX.done\n\n# Optional: Dependencies (other WUs that must complete first)\ndependencies: []\n\n# Optional: Risks\nrisks:\n - Risk 1\n\n# Optional: Notes\nnotes: ''\n\n# Optional: Requires human review\nrequires_review: false\n\n# Optional: Claimed mode (worktree or branch-only)\n# Automatically set by wu:claim, usually don't need to specify\n# claimed_mode: worktree\n\n# Optional: Assigned to (email of current claimant)\n# Automatically set by wu:claim\n# assigned_to: engineer@example.com\n\n# Optional: Locked status (prevents concurrent edits)\n# Automatically set by wu:claim and wu:done\n# locked: false\n\n# Optional: Completion date (ISO 8601 format)\n# Automatically set by wu:done\n# completed: 2025-10-23\n\n# Optional: Completion notes (added by wu:done)\n# completion_notes: |\n# Additional notes added during wu:done.\n# Any deviations from original plan.\n# Lessons learned.\n\n# ============================================================================\n# GOVERNANCE BLOCK (WU Schema v2.0)\n# ============================================================================\n# Optional: COS governance rules that apply to this WU\n# Only include if this WU needs specific governance enforcement\n\n# governance:\n# # Rules that apply to this WU (evaluated during cos:gates)\n# rules:\n# - rule_id: UPAIN-01\n# satisfied: false # Initially false, set true when evidence provided\n# evidence:\n# - type: link\n# value: docs/product/voc/feature-user-pain.md\n# description: "Voice of Customer analysis showing user pain"\n# notes: |\n# VOC analysis shows 40% of support tickets request this feature.\n# Average time wasted: 15min/user/week.\n#\n# - rule_id: CASH-03\n# satisfied: false\n# evidence:\n# - type: link\n# value: docs/finance/spend-reviews/2025-10-cloud-infra.md\n# description: "Spend review for £1200/month cloud infrastructure"\n# - type: approval\n# value: owner@example.com\n# description: "Owner approval for spend commitment"\n# notes: |\n# New cloud infrastructure commitment: £1200/month for 12 months.\n# ROI: Reduces latency by 50%, improves user retention.\n#\n# # Gate checks (enforced by cos-gates.
|
|
462
|
+
const WU_TEMPLATE_YAML = `# Work Unit Template (LumenFlow WU Schema)\n#\n# Copy this template when creating new WUs. Fill in all required fields and\n# remove optional fields if not needed.\n#\n# If you used "lumenflow init --full", this template lives at:\n# docs/04-operations/tasks/templates/wu-template.yaml\n\n# Required: Unique work unit identifier (format: WU-NNN)\nid: WU-XXX\n\n# Required: Short, descriptive title (max 80 chars)\ntitle: 'Your WU title here'\n\n# Required: Lane (Parent: Sublane format)\nlane: 'Framework: CLI'\n\n# Required: Type of work\ntype: 'feature' # feature | bug | documentation | process | tooling | chore | refactor\n\n# Required: Current status\nstatus: 'ready' # ready | in_progress | blocked | done | cancelled\n\n# Required: Priority\npriority: P2 # P0 | P1 | P2 | P3\n\n# Required: Creation date (YYYY-MM-DD)\ncreated: {{DATE}}\n\n# Required: Owner/assignee (email)\nassigned_to: 'unassigned@example.com'\n\n# Required: Description\ndescription: |\n Context: ...\n Problem: ...\n Solution: ...\n\n# Required: Acceptance criteria (testable, binary)\nacceptance:\n - Criterion 1 (specific, measurable, testable)\n - Criterion 2 (binary pass/fail)\n - Documentation updated\n\n# Required: References to plans/specs (required for type: feature)\nspec_refs:\n - docs/04-operations/plans/WU-XXX-plan.md\n\n# Required: Code files changed or created (empty only for docs/process WUs)\ncode_paths:\n - path/to/file.ts\n\n# Required: Test paths (at least one of manual/unit/e2e/integration for non-doc WUs)\ntests:\n manual:\n - Manual test: Verify behavior\n unit:\n - path/to/test.test.ts\n e2e: []\n integration: []\n\n# Required: Exposure level\nexposure: 'backend-only' # ui | api | backend-only | documentation\n\n# Optional: User journey (recommended for ui/api)\n# user_journey: |\n# User navigates to ...\n# User performs ...\n\n# Optional: UI pairing WUs (for api exposure)\n# ui_pairing_wus:\n# - WU-1234\n\n# Optional: Navigation path (required when exposure=ui and no page file)\n# navigation_path: '/settings'\n\n# Required: Deliverable artifacts (stamps, docs, etc.)\nartifacts:\n - .lumenflow/stamps/WU-XXX.done\n\n# Optional: Dependencies (other WUs that must complete first)\ndependencies: []\n\n# Optional: Risks\nrisks:\n - Risk 1\n\n# Optional: Notes\nnotes: ''\n\n# Optional: Requires human review\nrequires_review: false\n\n# Optional: Claimed mode (worktree or branch-only)\n# Automatically set by wu:claim, usually don't need to specify\n# claimed_mode: worktree\n\n# Optional: Assigned to (email of current claimant)\n# Automatically set by wu:claim\n# assigned_to: engineer@example.com\n\n# Optional: Locked status (prevents concurrent edits)\n# Automatically set by wu:claim and wu:done\n# locked: false\n\n# Optional: Completion date (ISO 8601 format)\n# Automatically set by wu:done\n# completed: 2025-10-23\n\n# Optional: Completion notes (added by wu:done)\n# completion_notes: |\n# Additional notes added during wu:done.\n# Any deviations from original plan.\n# Lessons learned.\n\n# ============================================================================\n# GOVERNANCE BLOCK (WU Schema v2.0)\n# ============================================================================\n# Optional: COS governance rules that apply to this WU\n# Only include if this WU needs specific governance enforcement\n\n# governance:\n# # Rules that apply to this WU (evaluated during cos:gates)\n# rules:\n# - rule_id: UPAIN-01\n# satisfied: false # Initially false, set true when evidence provided\n# evidence:\n# - type: link\n# value: docs/product/voc/feature-user-pain.md\n# description: "Voice of Customer analysis showing user pain"\n# notes: |\n# VOC analysis shows 40% of support tickets request this feature.\n# Average time wasted: 15min/user/week.\n#\n# - rule_id: CASH-03\n# satisfied: false\n# evidence:\n# - type: link\n# value: docs/finance/spend-reviews/2025-10-cloud-infra.md\n# description: "Spend review for £1200/month cloud infrastructure"\n# - type: approval\n# value: owner@example.com\n# description: "Owner approval for spend commitment"\n# notes: |\n# New cloud infrastructure commitment: £1200/month for 12 months.\n# ROI: Reduces latency by 50%, improves user retention.\n#\n# # Gate checks (enforced by cos-gates.ts)\n# gates:\n# narrative: "pending" # Status: pending, passed, skipped, failed\n# finance: "pending"\n#\n# # Exemptions (only if rule doesn't apply)\n# exemptions:\n# - rule_id: FAIR-01\n# reason: "No user-facing pricing changes in this WU"\n# approved_by: product-owner@example.com\n# approved_at: 2025-10-23\n\n# ============================================================================\n# USAGE NOTES\n# ============================================================================\n#\n# 1. Remove this entire governance block if no COS rules apply to your WU\n# 2. Only include rules that require enforcement (not all rules apply to all WUs)\n# 3. Evidence types: link:, metric:, screenshot:, approval:\n# 4. Gates are checked during wu:done (before merge)\n# 5. Exemptions require approval from rule owner\n#\n# For more details, see:\n# - docs/04-operations/_frameworks/cos/system-prompt-v1.3.md\n# - docs/04-operations/_frameworks/cos/evidence-format.md\n`;
|
|
162
463
|
// Template for .lumenflow.framework.yaml
|
|
163
464
|
const FRAMEWORK_HINT_TEMPLATE = `# LumenFlow Framework Hint\n# Generated by: lumenflow init --framework {{FRAMEWORK_NAME}}\n\nframework: "{{FRAMEWORK_NAME}}"\nslug: "{{FRAMEWORK_SLUG}}"\n`;
|
|
164
465
|
// Template for docs/04-operations/_frameworks/<framework>/README.md
|
|
@@ -814,7 +1115,7 @@ version: 1.0.0
|
|
|
814
1115
|
|
|
815
1116
|
// WRONG - Absolute path bypasses worktree
|
|
816
1117
|
Write({
|
|
817
|
-
file_path: '
|
|
1118
|
+
file_path: '/<user-home>/source/project/apps/web/src/validator.ts',
|
|
818
1119
|
content: '...',
|
|
819
1120
|
});
|
|
820
1121
|
// Result: Written to MAIN checkout, not worktree!
|
|
@@ -840,9 +1141,9 @@ Write({
|
|
|
840
1141
|
|
|
841
1142
|
2. **Check file path format**:
|
|
842
1143
|
|
|
843
|
-
| Pattern | Safe? | Example
|
|
844
|
-
| --------------------------------- | ----- |
|
|
845
|
-
| Starts with
|
|
1144
|
+
| Pattern | Safe? | Example |
|
|
1145
|
+
| --------------------------------- | ----- | --------------------------- |
|
|
1146
|
+
| Starts with \`/<user-home>/\` | NO | \`/<user-home>/.../file.ts\` |
|
|
846
1147
|
| Contains full repo path | NO | \`/source/project/...\` |
|
|
847
1148
|
| Starts with package name | YES | \`apps/web/src/...\` |
|
|
848
1149
|
| Starts with \`./\` or \`../\` | YES | \`./src/lib/...\` |
|
|
@@ -935,76 +1236,77 @@ pnpm typecheck # Check types
|
|
|
935
1236
|
*/
|
|
936
1237
|
function detectDefaultClient() {
|
|
937
1238
|
if (process.env.CLAUDE_PROJECT_DIR || process.env.CLAUDE_CODE) {
|
|
938
|
-
return
|
|
1239
|
+
return DEFAULT_CLIENT_CLAUDE;
|
|
939
1240
|
}
|
|
940
1241
|
return 'none';
|
|
941
1242
|
}
|
|
942
1243
|
/**
|
|
943
|
-
*
|
|
1244
|
+
* WU-1171: Resolve client type from options
|
|
1245
|
+
* --client takes precedence over --vendor (backwards compat)
|
|
944
1246
|
*/
|
|
945
|
-
function
|
|
946
|
-
|
|
947
|
-
if (
|
|
948
|
-
|
|
949
|
-
if (['claude', 'cursor', 'aider', 'all', 'none'].includes(vendor)) {
|
|
950
|
-
return vendor;
|
|
951
|
-
}
|
|
1247
|
+
function resolveClientType(client, vendor, defaultClient) {
|
|
1248
|
+
// Explicit --client or --vendor takes precedence
|
|
1249
|
+
if (client) {
|
|
1250
|
+
return client;
|
|
952
1251
|
}
|
|
953
|
-
|
|
1252
|
+
if (vendor) {
|
|
1253
|
+
return vendor;
|
|
1254
|
+
}
|
|
1255
|
+
// Default based on environment
|
|
1256
|
+
return defaultClient === DEFAULT_CLIENT_CLAUDE ? 'claude' : 'none';
|
|
954
1257
|
}
|
|
955
1258
|
/**
|
|
956
|
-
*
|
|
1259
|
+
* WU-1171: Determine file mode from options
|
|
957
1260
|
*/
|
|
958
|
-
function
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
const [, value] = frameworkArg.split('=', 2);
|
|
962
|
-
return value?.trim() || undefined;
|
|
1261
|
+
function getFileMode(options) {
|
|
1262
|
+
if (options.force) {
|
|
1263
|
+
return 'force';
|
|
963
1264
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
return args[frameworkIndex + 1];
|
|
1265
|
+
if (options.merge) {
|
|
1266
|
+
return 'merge';
|
|
967
1267
|
}
|
|
968
|
-
return
|
|
1268
|
+
return 'skip';
|
|
969
1269
|
}
|
|
970
1270
|
/**
|
|
971
|
-
* WU-
|
|
1271
|
+
* WU-1171: Get templates directory path
|
|
972
1272
|
*/
|
|
973
|
-
function
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
return undefined;
|
|
1273
|
+
function getTemplatesDir() {
|
|
1274
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1275
|
+
const __dirname = path.dirname(__filename);
|
|
1276
|
+
// Check for dist/templates (production) or ../templates (development)
|
|
1277
|
+
const distTemplates = path.join(__dirname, '..', 'templates');
|
|
1278
|
+
if (fs.existsSync(distTemplates)) {
|
|
1279
|
+
return distTemplates;
|
|
982
1280
|
}
|
|
983
|
-
|
|
984
|
-
if (presetIndex !== -1 && args[presetIndex + 1]) {
|
|
985
|
-
const preset = args[presetIndex + 1].toLowerCase();
|
|
986
|
-
if (['node', 'python', 'go', 'rust', 'dotnet'].includes(preset)) {
|
|
987
|
-
return preset;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
return undefined;
|
|
1281
|
+
throw new Error(`Templates directory not found at ${distTemplates}`);
|
|
991
1282
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1283
|
+
/**
|
|
1284
|
+
* WU-1171: Load a template file
|
|
1285
|
+
*/
|
|
1286
|
+
function loadTemplate(templatePath) {
|
|
1287
|
+
const templatesDir = getTemplatesDir();
|
|
1288
|
+
const fullPath = path.join(templatesDir, templatePath);
|
|
1289
|
+
if (!fs.existsSync(fullPath)) {
|
|
1290
|
+
throw new Error(`Template not found: ${templatePath}`);
|
|
995
1291
|
}
|
|
996
|
-
return
|
|
1292
|
+
return fs.readFileSync(fullPath, 'utf-8');
|
|
997
1293
|
}
|
|
998
1294
|
/**
|
|
999
1295
|
* Scaffold a new LumenFlow project
|
|
1296
|
+
* WU-1171: Added AGENTS.md, --merge mode, updated vendor/client handling
|
|
1000
1297
|
*/
|
|
1001
1298
|
export async function scaffoldProject(targetDir, options) {
|
|
1002
1299
|
const result = {
|
|
1003
1300
|
created: [],
|
|
1004
1301
|
skipped: [],
|
|
1302
|
+
merged: [],
|
|
1303
|
+
warnings: [],
|
|
1005
1304
|
};
|
|
1006
1305
|
const defaultClient = options.defaultClient ?? detectDefaultClient();
|
|
1007
|
-
|
|
1306
|
+
// WU-1171: Use resolveClientType with both client and vendor (vendor is deprecated but kept for backwards compat)
|
|
1307
|
+
// eslint-disable-next-line sonarjs/deprecation -- Intentional backwards compatibility
|
|
1308
|
+
const client = resolveClientType(options.client, options.vendor, defaultClient);
|
|
1309
|
+
const fileMode = getFileMode(options);
|
|
1008
1310
|
// Ensure target directory exists
|
|
1009
1311
|
if (!fs.existsSync(targetDir)) {
|
|
1010
1312
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
@@ -1014,14 +1316,24 @@ export async function scaffoldProject(targetDir, options) {
|
|
|
1014
1316
|
PROJECT_ROOT: targetDir,
|
|
1015
1317
|
};
|
|
1016
1318
|
// Create .lumenflow.config.yaml (WU-1067: includes gate preset if specified)
|
|
1017
|
-
|
|
1319
|
+
// Note: Config files don't use merge mode (always skip or force)
|
|
1320
|
+
await createFile(path.join(targetDir, CONFIG_FILE_NAME), generateLumenflowConfigYaml(options.gatePreset), options.force ? 'force' : 'skip', result, targetDir);
|
|
1321
|
+
// WU-1171: Create AGENTS.md (universal entry point for all agents)
|
|
1322
|
+
try {
|
|
1323
|
+
const agentsTemplate = loadTemplate('core/AGENTS.md.template');
|
|
1324
|
+
await createFile(path.join(targetDir, 'AGENTS.md'), processTemplate(agentsTemplate, tokenDefaults), fileMode, result, targetDir);
|
|
1325
|
+
}
|
|
1326
|
+
catch {
|
|
1327
|
+
// Fallback to hardcoded template if template file not found
|
|
1328
|
+
await createFile(path.join(targetDir, 'AGENTS.md'), processTemplate(AGENTS_MD_TEMPLATE, tokenDefaults), fileMode, result, targetDir);
|
|
1329
|
+
}
|
|
1018
1330
|
// Create LUMENFLOW.md (main entry point)
|
|
1019
|
-
await createFile(path.join(targetDir, 'LUMENFLOW.md'), processTemplate(LUMENFLOW_MD_TEMPLATE, tokenDefaults),
|
|
1331
|
+
await createFile(path.join(targetDir, 'LUMENFLOW.md'), processTemplate(LUMENFLOW_MD_TEMPLATE, tokenDefaults), fileMode, result, targetDir);
|
|
1020
1332
|
// Create .lumenflow/constraints.md
|
|
1021
|
-
await createFile(path.join(targetDir, LUMENFLOW_DIR, 'constraints.md'), processTemplate(CONSTRAINTS_MD_TEMPLATE, tokenDefaults),
|
|
1333
|
+
await createFile(path.join(targetDir, LUMENFLOW_DIR, 'constraints.md'), processTemplate(CONSTRAINTS_MD_TEMPLATE, tokenDefaults), fileMode, result, targetDir);
|
|
1022
1334
|
// Create .lumenflow/agents directory with .gitkeep
|
|
1023
1335
|
await createDirectory(path.join(targetDir, LUMENFLOW_AGENTS_DIR), result, targetDir);
|
|
1024
|
-
await createFile(path.join(targetDir, LUMENFLOW_AGENTS_DIR, '.gitkeep'), '', options.force, result, targetDir);
|
|
1336
|
+
await createFile(path.join(targetDir, LUMENFLOW_AGENTS_DIR, '.gitkeep'), '', options.force ? 'force' : 'skip', result, targetDir);
|
|
1025
1337
|
// Optional: full docs scaffolding
|
|
1026
1338
|
if (options.full) {
|
|
1027
1339
|
await scaffoldFullDocs(targetDir, options, result, tokenDefaults);
|
|
@@ -1030,12 +1342,12 @@ export async function scaffoldProject(targetDir, options) {
|
|
|
1030
1342
|
if (options.framework) {
|
|
1031
1343
|
await scaffoldFrameworkOverlay(targetDir, options, result, tokenDefaults);
|
|
1032
1344
|
}
|
|
1033
|
-
// Scaffold
|
|
1034
|
-
await
|
|
1345
|
+
// Scaffold client-specific files (WU-1171: renamed from vendor)
|
|
1346
|
+
await scaffoldClientFiles(targetDir, options, result, tokenDefaults, client);
|
|
1035
1347
|
return result;
|
|
1036
1348
|
}
|
|
1037
1349
|
async function scaffoldFullDocs(targetDir, options, result, tokens) {
|
|
1038
|
-
const tasksDir = path.join(targetDir, 'docs',
|
|
1350
|
+
const tasksDir = path.join(targetDir, 'docs', DOCS_OPERATIONS_DIR, 'tasks');
|
|
1039
1351
|
const wuDir = path.join(tasksDir, 'wu');
|
|
1040
1352
|
const templatesDir = path.join(tasksDir, 'templates');
|
|
1041
1353
|
await createDirectory(wuDir, result, targetDir);
|
|
@@ -1051,7 +1363,7 @@ async function scaffoldFullDocs(targetDir, options, result, tokens) {
|
|
|
1051
1363
|
* WU-1083: Scaffold agent onboarding documentation
|
|
1052
1364
|
*/
|
|
1053
1365
|
async function scaffoldAgentOnboardingDocs(targetDir, options, result, tokens) {
|
|
1054
|
-
const onboardingDir = path.join(targetDir, 'docs',
|
|
1366
|
+
const onboardingDir = path.join(targetDir, 'docs', DOCS_OPERATIONS_DIR, '_frameworks', 'lumenflow', 'agent', 'onboarding');
|
|
1055
1367
|
await createDirectory(onboardingDir, result, targetDir);
|
|
1056
1368
|
await createFile(path.join(onboardingDir, 'quick-ref-commands.md'), processTemplate(QUICK_REF_COMMANDS_TEMPLATE, tokens), options.force, result, targetDir);
|
|
1057
1369
|
await createFile(path.join(onboardingDir, 'first-wu-mistakes.md'), processTemplate(FIRST_WU_MISTAKES_TEMPLATE, tokens), options.force, result, targetDir);
|
|
@@ -1088,34 +1400,73 @@ async function scaffoldFrameworkOverlay(targetDir, options, result, tokens) {
|
|
|
1088
1400
|
FRAMEWORK_SLUG: slug,
|
|
1089
1401
|
};
|
|
1090
1402
|
await createFile(path.join(targetDir, FRAMEWORK_HINT_FILE), processTemplate(FRAMEWORK_HINT_TEMPLATE, frameworkTokens), options.force, result, targetDir);
|
|
1091
|
-
const overlayDir = path.join(targetDir, 'docs',
|
|
1403
|
+
const overlayDir = path.join(targetDir, 'docs', DOCS_OPERATIONS_DIR, '_frameworks', slug);
|
|
1092
1404
|
await createDirectory(overlayDir, result, targetDir);
|
|
1093
1405
|
await createFile(path.join(overlayDir, 'README.md'), processTemplate(FRAMEWORK_OVERLAY_TEMPLATE, frameworkTokens), options.force, result, targetDir);
|
|
1094
1406
|
}
|
|
1095
1407
|
/**
|
|
1096
|
-
* Scaffold
|
|
1408
|
+
* WU-1171: Scaffold client-specific files based on --client option
|
|
1409
|
+
* Updated paths: Cursor uses .cursor/rules/lumenflow.md, Windsurf uses .windsurf/rules/lumenflow.md
|
|
1097
1410
|
*/
|
|
1098
|
-
async function
|
|
1411
|
+
async function scaffoldClientFiles(targetDir, options, result, tokens, client) {
|
|
1412
|
+
const fileMode = getFileMode(options);
|
|
1099
1413
|
// Claude Code
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1414
|
+
if (client === 'claude' || client === 'all') {
|
|
1415
|
+
// WU-1171: Single CLAUDE.md at root only (no .claude/CLAUDE.md duplication)
|
|
1416
|
+
await createFile(path.join(targetDir, 'CLAUDE.md'), processTemplate(CLAUDE_MD_TEMPLATE, tokens), fileMode, result, targetDir);
|
|
1102
1417
|
await createDirectory(path.join(targetDir, CLAUDE_AGENTS_DIR), result, targetDir);
|
|
1103
|
-
await createFile(path.join(targetDir, CLAUDE_AGENTS_DIR, '.gitkeep'), '', options.force, result, targetDir);
|
|
1104
|
-
await createFile(path.join(targetDir, CLAUDE_DIR, 'settings.json'), CLAUDE_SETTINGS_TEMPLATE, options.force, result, targetDir);
|
|
1418
|
+
await createFile(path.join(targetDir, CLAUDE_AGENTS_DIR, '.gitkeep'), '', options.force ? 'force' : 'skip', result, targetDir);
|
|
1419
|
+
await createFile(path.join(targetDir, CLAUDE_DIR, 'settings.json'), CLAUDE_SETTINGS_TEMPLATE, options.force ? 'force' : 'skip', result, targetDir);
|
|
1105
1420
|
// WU-1083: Scaffold Claude skills
|
|
1106
1421
|
await scaffoldClaudeSkills(targetDir, options, result, tokens);
|
|
1107
1422
|
// WU-1083: Scaffold agent onboarding docs for Claude vendor (even without --full)
|
|
1108
1423
|
await scaffoldAgentOnboardingDocs(targetDir, options, result, tokens);
|
|
1109
1424
|
}
|
|
1110
|
-
// Cursor
|
|
1111
|
-
if (
|
|
1112
|
-
const
|
|
1113
|
-
await createDirectory(
|
|
1114
|
-
|
|
1425
|
+
// WU-1171: Cursor uses .cursor/rules/lumenflow.md (not .cursor/rules.md)
|
|
1426
|
+
if (client === 'cursor' || client === 'all') {
|
|
1427
|
+
const cursorRulesDir = path.join(targetDir, '.cursor', 'rules');
|
|
1428
|
+
await createDirectory(cursorRulesDir, result, targetDir);
|
|
1429
|
+
// Try to load from template, fallback to hardcoded
|
|
1430
|
+
let cursorContent;
|
|
1431
|
+
try {
|
|
1432
|
+
cursorContent = loadTemplate('vendors/cursor/.cursor/rules/lumenflow.md.template');
|
|
1433
|
+
}
|
|
1434
|
+
catch {
|
|
1435
|
+
cursorContent = CURSOR_RULES_TEMPLATE;
|
|
1436
|
+
}
|
|
1437
|
+
await createFile(path.join(cursorRulesDir, 'lumenflow.md'), processTemplate(cursorContent, tokens), fileMode, result, targetDir);
|
|
1438
|
+
}
|
|
1439
|
+
// WU-1171: Windsurf uses .windsurf/rules/lumenflow.md (not .windsurfrules)
|
|
1440
|
+
if (client === 'windsurf' || client === 'all') {
|
|
1441
|
+
const windsurfRulesDir = path.join(targetDir, '.windsurf', 'rules');
|
|
1442
|
+
await createDirectory(windsurfRulesDir, result, targetDir);
|
|
1443
|
+
// Try to load from template, fallback to hardcoded
|
|
1444
|
+
let windsurfContent;
|
|
1445
|
+
try {
|
|
1446
|
+
windsurfContent = loadTemplate('vendors/windsurf/.windsurf/rules/lumenflow.md.template');
|
|
1447
|
+
}
|
|
1448
|
+
catch {
|
|
1449
|
+
windsurfContent = WINDSURF_RULES_TEMPLATE;
|
|
1450
|
+
}
|
|
1451
|
+
await createFile(path.join(windsurfRulesDir, 'lumenflow.md'), processTemplate(windsurfContent, tokens), fileMode, result, targetDir);
|
|
1452
|
+
}
|
|
1453
|
+
// WU-1171: Codex reads AGENTS.md directly - minimal extra config needed
|
|
1454
|
+
// AGENTS.md is always created, so nothing extra needed for codex
|
|
1455
|
+
// WU-1177: Cline uses .clinerules file at project root
|
|
1456
|
+
if (client === 'cline' || client === 'all') {
|
|
1457
|
+
// Try to load from template, fallback to hardcoded
|
|
1458
|
+
let clineContent;
|
|
1459
|
+
try {
|
|
1460
|
+
clineContent = loadTemplate('vendors/cline/.clinerules.template');
|
|
1461
|
+
}
|
|
1462
|
+
catch {
|
|
1463
|
+
clineContent = CLINE_RULES_TEMPLATE;
|
|
1464
|
+
}
|
|
1465
|
+
await createFile(path.join(targetDir, '.clinerules'), processTemplate(clineContent, tokens), fileMode, result, targetDir);
|
|
1115
1466
|
}
|
|
1116
1467
|
// Aider
|
|
1117
|
-
if (
|
|
1118
|
-
await createFile(path.join(targetDir, '.aider.conf.yml'), AIDER_CONF_TEMPLATE,
|
|
1468
|
+
if (client === 'aider' || client === 'all') {
|
|
1469
|
+
await createFile(path.join(targetDir, '.aider.conf.yml'), AIDER_CONF_TEMPLATE, fileMode, result, targetDir);
|
|
1119
1470
|
}
|
|
1120
1471
|
}
|
|
1121
1472
|
/**
|
|
@@ -1128,14 +1479,62 @@ async function createDirectory(dirPath, result, targetDir) {
|
|
|
1128
1479
|
}
|
|
1129
1480
|
}
|
|
1130
1481
|
/**
|
|
1131
|
-
* Create a file,
|
|
1482
|
+
* WU-1171: Create a file with support for skip, merge, and force modes
|
|
1483
|
+
*
|
|
1484
|
+
* @param filePath - Path to the file to create
|
|
1485
|
+
* @param content - Content to write (or merge block content in merge mode)
|
|
1486
|
+
* @param mode - 'skip' (default), 'merge', or 'force'
|
|
1487
|
+
* @param result - ScaffoldResult to track created/skipped/merged files
|
|
1488
|
+
* @param targetDir - Target directory for relative path calculation
|
|
1132
1489
|
*/
|
|
1133
|
-
async function createFile(filePath, content,
|
|
1490
|
+
async function createFile(filePath, content, mode, result, targetDir) {
|
|
1134
1491
|
const relativePath = getRelativePath(targetDir, filePath);
|
|
1135
|
-
|
|
1492
|
+
// Handle boolean for backwards compatibility (true = force, false = skip)
|
|
1493
|
+
const resolvedMode = resolveBooleanToFileMode(mode);
|
|
1494
|
+
// Ensure merged/warnings arrays exist
|
|
1495
|
+
result.merged = result.merged ?? [];
|
|
1496
|
+
result.warnings = result.warnings ?? [];
|
|
1497
|
+
const fileExists = fs.existsSync(filePath);
|
|
1498
|
+
if (fileExists && resolvedMode === 'skip') {
|
|
1499
|
+
result.skipped.push(relativePath);
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
if (fileExists && resolvedMode === 'merge') {
|
|
1503
|
+
handleMergeMode(filePath, content, result, relativePath);
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
// Force mode or file doesn't exist: write new content
|
|
1507
|
+
writeNewFile(filePath, content, result, relativePath);
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Convert boolean or FileMode to FileMode
|
|
1511
|
+
*/
|
|
1512
|
+
function resolveBooleanToFileMode(mode) {
|
|
1513
|
+
if (typeof mode === 'boolean') {
|
|
1514
|
+
return mode ? 'force' : 'skip';
|
|
1515
|
+
}
|
|
1516
|
+
return mode;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Handle merge mode file update
|
|
1520
|
+
*/
|
|
1521
|
+
function handleMergeMode(filePath, content, result, relativePath) {
|
|
1522
|
+
const existingContent = fs.readFileSync(filePath, 'utf-8');
|
|
1523
|
+
const mergeResult = updateMergeBlock(existingContent, content);
|
|
1524
|
+
if (mergeResult.unchanged) {
|
|
1136
1525
|
result.skipped.push(relativePath);
|
|
1137
1526
|
return;
|
|
1138
1527
|
}
|
|
1528
|
+
if (mergeResult.warning) {
|
|
1529
|
+
result.warnings?.push(`${relativePath}: ${mergeResult.warning}`);
|
|
1530
|
+
}
|
|
1531
|
+
fs.writeFileSync(filePath, mergeResult.content);
|
|
1532
|
+
result.merged?.push(relativePath);
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Write a new file, creating parent directories if needed
|
|
1536
|
+
*/
|
|
1537
|
+
function writeNewFile(filePath, content, result, relativePath) {
|
|
1139
1538
|
const parentDir = path.dirname(filePath);
|
|
1140
1539
|
if (!fs.existsSync(parentDir)) {
|
|
1141
1540
|
fs.mkdirSync(parentDir, { recursive: true });
|
|
@@ -1146,19 +1545,33 @@ async function createFile(filePath, content, force, result, targetDir) {
|
|
|
1146
1545
|
/**
|
|
1147
1546
|
* CLI entry point
|
|
1148
1547
|
* WU-1085: Updated to use parseInitOptions for proper --help support
|
|
1548
|
+
* WU-1171: Added --merge and --client support
|
|
1149
1549
|
*/
|
|
1150
1550
|
export async function main() {
|
|
1551
|
+
/* eslint-disable no-console -- CLI tool requires console output for user feedback */
|
|
1151
1552
|
const opts = parseInitOptions();
|
|
1152
1553
|
const targetDir = process.cwd();
|
|
1153
1554
|
console.log('[lumenflow init] Scaffolding LumenFlow project...');
|
|
1154
|
-
console.log(` Mode: ${opts.full ? 'full' : 'minimal'}`);
|
|
1555
|
+
console.log(` Mode: ${opts.full ? 'full' : 'minimal'}${opts.merge ? ' (merge)' : ''}`);
|
|
1155
1556
|
console.log(` Framework: ${opts.framework ?? 'none'}`);
|
|
1156
|
-
console.log(`
|
|
1557
|
+
console.log(` Client: ${opts.client ?? 'auto'}`);
|
|
1157
1558
|
console.log(` Gate preset: ${opts.preset ?? 'none (manual config)'}`);
|
|
1559
|
+
// WU-1177: Check prerequisites (non-blocking)
|
|
1560
|
+
const prereqs = checkPrerequisites();
|
|
1561
|
+
const failingPrereqs = Object.entries(prereqs)
|
|
1562
|
+
.filter(([, check]) => !check.passed)
|
|
1563
|
+
.map(([name, check]) => `${name}: ${check.version} (requires ${check.required})`);
|
|
1564
|
+
if (failingPrereqs.length > 0) {
|
|
1565
|
+
console.log('\nPrerequisite warnings (non-blocking):');
|
|
1566
|
+
failingPrereqs.forEach((msg) => console.log(` ! ${msg}`));
|
|
1567
|
+
console.log(' Run "lumenflow doctor" for details.\n');
|
|
1568
|
+
}
|
|
1158
1569
|
const result = await scaffoldProject(targetDir, {
|
|
1159
1570
|
force: opts.force,
|
|
1160
1571
|
full: opts.full,
|
|
1161
|
-
|
|
1572
|
+
merge: opts.merge,
|
|
1573
|
+
client: opts.client,
|
|
1574
|
+
vendor: opts.vendor, // Backwards compatibility
|
|
1162
1575
|
framework: opts.framework,
|
|
1163
1576
|
gatePreset: opts.preset,
|
|
1164
1577
|
});
|
|
@@ -1166,12 +1579,30 @@ export async function main() {
|
|
|
1166
1579
|
console.log('\nCreated:');
|
|
1167
1580
|
result.created.forEach((f) => console.log(` + ${f}`));
|
|
1168
1581
|
}
|
|
1582
|
+
if (result.merged && result.merged.length > 0) {
|
|
1583
|
+
console.log('\nMerged (LumenFlow block inserted/updated):');
|
|
1584
|
+
result.merged.forEach((f) => console.log(` ~ ${f}`));
|
|
1585
|
+
}
|
|
1169
1586
|
if (result.skipped.length > 0) {
|
|
1170
|
-
console.log('\nSkipped (already exists, use --force to overwrite):');
|
|
1587
|
+
console.log('\nSkipped (already exists, use --force to overwrite or --merge to insert block):');
|
|
1171
1588
|
result.skipped.forEach((f) => console.log(` - ${f}`));
|
|
1172
1589
|
}
|
|
1590
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
1591
|
+
console.log('\nWarnings:');
|
|
1592
|
+
result.warnings.forEach((w) => console.log(` ⚠ ${w}`));
|
|
1593
|
+
}
|
|
1173
1594
|
console.log('\n[lumenflow init] Done! Next steps:');
|
|
1174
|
-
console.log(' 1. Review LUMENFLOW.md for workflow documentation');
|
|
1595
|
+
console.log(' 1. Review AGENTS.md and LUMENFLOW.md for workflow documentation');
|
|
1175
1596
|
console.log(` 2. Edit ${CONFIG_FILE_NAME} to match your project structure`);
|
|
1176
1597
|
console.log(' 3. Run: pnpm wu:create --id WU-0001 --lane <lane> --title "First WU"');
|
|
1598
|
+
/* eslint-enable no-console */
|
|
1599
|
+
}
|
|
1600
|
+
// WU-1297: Use import.meta.main instead of exporting main() without calling it
|
|
1601
|
+
// This ensures main() runs when the script is executed as a CLI entry point
|
|
1602
|
+
if (import.meta.main) {
|
|
1603
|
+
main().catch((err) => {
|
|
1604
|
+
// eslint-disable-next-line no-console -- CLI error output
|
|
1605
|
+
console.error('[lumenflow init] Error:', err instanceof Error ? err.message : String(err));
|
|
1606
|
+
process.exit(1);
|
|
1607
|
+
});
|
|
1177
1608
|
}
|