@lumenflow/cli 2.2.2 → 2.3.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 (118) hide show
  1. package/README.md +147 -57
  2. package/dist/__tests__/agent-log-issue.test.js +56 -0
  3. package/dist/__tests__/cli-entry-point.test.js +66 -17
  4. package/dist/__tests__/cli-subprocess.test.js +25 -0
  5. package/dist/__tests__/init.test.js +298 -0
  6. package/dist/__tests__/initiative-plan.test.js +340 -0
  7. package/dist/__tests__/mem-cleanup-execution.test.js +19 -0
  8. package/dist/__tests__/merge-block.test.js +220 -0
  9. package/dist/__tests__/safe-git.test.js +191 -0
  10. package/dist/__tests__/state-doctor.test.js +274 -0
  11. package/dist/__tests__/wu-done.test.js +36 -0
  12. package/dist/__tests__/wu-edit.test.js +119 -0
  13. package/dist/__tests__/wu-prep.test.js +108 -0
  14. package/dist/agent-issues-query.js +4 -3
  15. package/dist/agent-log-issue.js +25 -4
  16. package/dist/backlog-prune.js +5 -4
  17. package/dist/cli-entry-point.js +11 -1
  18. package/dist/doctor.js +368 -0
  19. package/dist/flow-bottlenecks.js +6 -5
  20. package/dist/flow-report.js +4 -3
  21. package/dist/gates.js +356 -101
  22. package/dist/guard-locked.js +4 -3
  23. package/dist/guard-worktree-commit.js +4 -3
  24. package/dist/init.js +508 -86
  25. package/dist/initiative-add-wu.js +4 -3
  26. package/dist/initiative-bulk-assign-wus.js +8 -5
  27. package/dist/initiative-create.js +73 -37
  28. package/dist/initiative-edit.js +37 -21
  29. package/dist/initiative-list.js +4 -3
  30. package/dist/initiative-plan.js +337 -0
  31. package/dist/initiative-status.js +4 -3
  32. package/dist/lane-health.js +377 -0
  33. package/dist/lane-suggest.js +382 -0
  34. package/dist/mem-checkpoint.js +2 -2
  35. package/dist/mem-cleanup.js +2 -2
  36. package/dist/mem-context.js +306 -0
  37. package/dist/mem-create.js +2 -2
  38. package/dist/mem-delete.js +293 -0
  39. package/dist/mem-inbox.js +2 -2
  40. package/dist/mem-index.js +211 -0
  41. package/dist/mem-init.js +1 -1
  42. package/dist/mem-profile.js +207 -0
  43. package/dist/mem-promote.js +254 -0
  44. package/dist/mem-ready.js +2 -2
  45. package/dist/mem-signal.js +2 -2
  46. package/dist/mem-start.js +2 -2
  47. package/dist/mem-summarize.js +2 -2
  48. package/dist/mem-triage.js +2 -2
  49. package/dist/merge-block.js +222 -0
  50. package/dist/metrics-cli.js +7 -4
  51. package/dist/metrics-snapshot.js +4 -3
  52. package/dist/orchestrate-initiative.js +10 -4
  53. package/dist/orchestrate-monitor.js +379 -31
  54. package/dist/signal-cleanup.js +296 -0
  55. package/dist/spawn-list.js +6 -5
  56. package/dist/state-bootstrap.js +5 -4
  57. package/dist/state-cleanup.js +360 -0
  58. package/dist/state-doctor-fix.js +196 -0
  59. package/dist/state-doctor.js +501 -0
  60. package/dist/validate-agent-skills.js +4 -3
  61. package/dist/validate-agent-sync.js +4 -3
  62. package/dist/validate-backlog-sync.js +4 -3
  63. package/dist/validate-skills-spec.js +4 -3
  64. package/dist/validate.js +4 -3
  65. package/dist/wu-block.js +3 -3
  66. package/dist/wu-claim.js +208 -98
  67. package/dist/wu-cleanup.js +5 -4
  68. package/dist/wu-create.js +71 -46
  69. package/dist/wu-delete.js +88 -60
  70. package/dist/wu-deps.js +6 -5
  71. package/dist/wu-done-check.js +34 -0
  72. package/dist/wu-done.js +39 -12
  73. package/dist/wu-edit.js +63 -28
  74. package/dist/wu-infer-lane.js +7 -6
  75. package/dist/wu-preflight.js +23 -81
  76. package/dist/wu-prep.js +125 -0
  77. package/dist/wu-prune.js +4 -3
  78. package/dist/wu-recover.js +88 -22
  79. package/dist/wu-repair.js +7 -6
  80. package/dist/wu-spawn.js +226 -270
  81. package/dist/wu-status.js +4 -3
  82. package/dist/wu-unblock.js +5 -5
  83. package/dist/wu-unlock-lane.js +4 -3
  84. package/dist/wu-validate.js +5 -4
  85. package/package.json +16 -7
  86. package/templates/core/.lumenflow/constraints.md.template +192 -0
  87. package/templates/core/.lumenflow/rules/git-safety.md.template +27 -0
  88. package/templates/core/.lumenflow/rules/wu-workflow.md.template +48 -0
  89. package/templates/core/AGENTS.md.template +60 -0
  90. package/templates/core/LUMENFLOW.md.template +255 -0
  91. package/templates/core/UPGRADING.md.template +121 -0
  92. package/templates/core/ai/onboarding/agent-safety-card.md.template +106 -0
  93. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +198 -0
  94. package/templates/core/ai/onboarding/quick-ref-commands.md.template +186 -0
  95. package/templates/core/ai/onboarding/release-process.md.template +362 -0
  96. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +159 -0
  97. package/templates/core/ai/onboarding/wu-create-checklist.md.template +117 -0
  98. package/templates/vendors/aider/.aider.conf.yml.template +27 -0
  99. package/templates/vendors/claude/.claude/CLAUDE.md.template +52 -0
  100. package/templates/vendors/claude/.claude/settings.json.template +49 -0
  101. package/templates/vendors/claude/.claude/skills/bug-classification/SKILL.md.template +192 -0
  102. package/templates/vendors/claude/.claude/skills/code-quality/SKILL.md.template +152 -0
  103. package/templates/vendors/claude/.claude/skills/context-management/SKILL.md.template +155 -0
  104. package/templates/vendors/claude/.claude/skills/execution-memory/SKILL.md.template +304 -0
  105. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +131 -0
  106. package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +164 -0
  107. package/templates/vendors/claude/.claude/skills/library-first/SKILL.md.template +98 -0
  108. package/templates/vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template +87 -0
  109. package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +84 -0
  110. package/templates/vendors/claude/.claude/skills/ops-maintenance/SKILL.md.template +254 -0
  111. package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +189 -0
  112. package/templates/vendors/claude/.claude/skills/tdd-workflow/SKILL.md.template +139 -0
  113. package/templates/vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template +138 -0
  114. package/templates/vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template +106 -0
  115. package/templates/vendors/cline/.clinerules.template +53 -0
  116. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +34 -0
  117. package/templates/vendors/cursor/.cursor/rules.md.template +28 -0
  118. 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: 'Vendor type (claude, cursor, aider, all, none)',
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: opts.full ?? false,
82
+ full: fullMode,
83
+ merge: opts.merge ?? false,
53
84
  framework: opts.framework,
54
- vendor: opts.vendor,
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-9-_]+/g, '-')
96
- .replace(/^-+|-+$/g, '');
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 Rules\n\nThis project uses LumenFlow workflow. See [LUMENFLOW.md](../LUMENFLOW.md).\n\n## Critical Rules\n\n1. **Always run wu:done** - After gates pass, run \`pnpm wu:done --id WU-XXX\`\n2. **Work in worktrees** - After \`wu:claim\`, work only in the worktree\n3. **Never bypass hooks** - No \`--no-verify\`\n4. **TDD** - Write tests first\n\n## Forbidden Commands\n\n- \`git reset --hard\`\n- \`git push --force\`\n- \`git stash\` (on main)\n- \`--no-verify\`\n`;
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.mjs)\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`;
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: '/home/user/source/project/apps/web/src/validator.ts',
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 \`/home/\` or \`/Users/\` | NO | \`/home/user/.../file.ts\` |
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 'claude-code';
1239
+ return DEFAULT_CLIENT_CLAUDE;
939
1240
  }
940
1241
  return 'none';
941
1242
  }
942
1243
  /**
943
- * Parse vendor flag from arguments
1244
+ * WU-1171: Resolve client type from options
1245
+ * --client takes precedence over --vendor (backwards compat)
944
1246
  */
945
- function parseVendorArg(args) {
946
- const vendorIndex = args.findIndex((arg) => arg === '--vendor');
947
- if (vendorIndex !== -1 && args[vendorIndex + 1]) {
948
- const vendor = args[vendorIndex + 1].toLowerCase();
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
- return undefined;
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
- * Parse framework flag from arguments
1259
+ * WU-1171: Determine file mode from options
957
1260
  */
958
- function parseFrameworkArg(args) {
959
- const frameworkArg = args.find((arg) => arg.startsWith('--framework='));
960
- if (frameworkArg) {
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
- const frameworkIndex = args.findIndex((arg) => arg === '--framework');
965
- if (frameworkIndex !== -1 && args[frameworkIndex + 1]) {
966
- return args[frameworkIndex + 1];
1265
+ if (options.merge) {
1266
+ return 'merge';
967
1267
  }
968
- return undefined;
1268
+ return 'skip';
969
1269
  }
970
1270
  /**
971
- * WU-1067: Parse --preset flag from arguments for config-driven gates
1271
+ * WU-1171: Get templates directory path
972
1272
  */
973
- function parsePresetArg(args) {
974
- const presetArg = args.find((arg) => arg.startsWith('--preset='));
975
- if (presetArg) {
976
- const [, value] = presetArg.split('=', 2);
977
- const preset = value?.trim().toLowerCase();
978
- if (preset && ['node', 'python', 'go', 'rust', 'dotnet'].includes(preset)) {
979
- return preset;
980
- }
981
- return undefined;
982
- }
983
- const presetIndex = args.findIndex((arg) => arg === '--preset');
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
- }
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;
989
1280
  }
990
- return undefined;
1281
+ throw new Error(`Templates directory not found at ${distTemplates}`);
991
1282
  }
992
- function shouldUseVendor(vendor, defaultClient) {
993
- if (vendor) {
994
- return vendor;
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 defaultClient === 'claude-code' ? 'claude' : 'none';
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
- const vendor = shouldUseVendor(options.vendor, defaultClient);
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
- await createFile(path.join(targetDir, CONFIG_FILE_NAME), generateLumenflowConfigYaml(options.gatePreset), options.force, result, targetDir);
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), options.force, result, targetDir);
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), options.force, result, targetDir);
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 vendor-specific files
1034
- await scaffoldVendorFiles(targetDir, options, result, tokenDefaults, vendor);
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', '04-operations', 'tasks');
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', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
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', '04-operations', '_frameworks', slug);
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 vendor-specific files based on --vendor option
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 scaffoldVendorFiles(targetDir, options, result, tokens, vendor) {
1411
+ async function scaffoldClientFiles(targetDir, options, result, tokens, client) {
1412
+ const fileMode = getFileMode(options);
1099
1413
  // Claude Code
1100
- if (vendor === 'claude' || vendor === 'all') {
1101
- await createFile(path.join(targetDir, 'CLAUDE.md'), processTemplate(CLAUDE_MD_TEMPLATE, tokens), options.force, result, targetDir);
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 (vendor === 'cursor' || vendor === 'all') {
1112
- const cursorDir = path.join(targetDir, '.cursor');
1113
- await createDirectory(cursorDir, result, targetDir);
1114
- await createFile(path.join(cursorDir, 'rules.md'), processTemplate(CURSOR_RULES_TEMPLATE, tokens), options.force, result, targetDir);
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 (vendor === 'aider' || vendor === 'all') {
1118
- await createFile(path.join(targetDir, '.aider.conf.yml'), AIDER_CONF_TEMPLATE, options.force, result, targetDir);
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, respecting force option
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, force, result, targetDir) {
1490
+ async function createFile(filePath, content, mode, result, targetDir) {
1134
1491
  const relativePath = getRelativePath(targetDir, filePath);
1135
- if (fs.existsSync(filePath) && !force) {
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(` Vendor overlays: ${opts.vendor ?? 'auto'}`);
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
- vendor: opts.vendor,
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,21 @@ 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 */
1177
1599
  }