@lumenflow/cli 1.1.0 → 1.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.
Files changed (118) hide show
  1. package/dist/__tests__/cli-entry-point.test.js +50 -0
  2. package/dist/__tests__/cli-subprocess.test.js +64 -0
  3. package/dist/cli-entry-point.js +46 -0
  4. package/dist/gates.js +102 -39
  5. package/dist/init.js +241 -195
  6. package/dist/initiative-add-wu.js +2 -1
  7. package/dist/initiative-create.js +5 -8
  8. package/dist/initiative-edit.js +3 -3
  9. package/dist/initiative-list.js +2 -1
  10. package/dist/initiative-status.js +2 -1
  11. package/dist/wu-claim.js +297 -110
  12. package/dist/wu-cleanup.js +129 -57
  13. package/dist/wu-create.js +197 -122
  14. package/dist/wu-deps.js +2 -1
  15. package/dist/wu-done.js +46 -14
  16. package/dist/wu-edit.js +152 -61
  17. package/dist/wu-infer-lane.js +5 -4
  18. package/dist/wu-preflight.js +2 -1
  19. package/dist/wu-prune.js +12 -3
  20. package/dist/wu-repair.js +2 -1
  21. package/dist/wu-spawn.js +79 -159
  22. package/dist/wu-unlock-lane.js +6 -1
  23. package/dist/wu-validate.js +2 -1
  24. package/package.json +14 -14
  25. package/dist/gates.d.ts +0 -41
  26. package/dist/gates.d.ts.map +0 -1
  27. package/dist/gates.js.map +0 -1
  28. package/dist/initiative-add-wu.d.ts +0 -22
  29. package/dist/initiative-add-wu.d.ts.map +0 -1
  30. package/dist/initiative-add-wu.js.map +0 -1
  31. package/dist/initiative-create.d.ts +0 -28
  32. package/dist/initiative-create.d.ts.map +0 -1
  33. package/dist/initiative-create.js.map +0 -1
  34. package/dist/initiative-edit.d.ts +0 -34
  35. package/dist/initiative-edit.d.ts.map +0 -1
  36. package/dist/initiative-edit.js.map +0 -1
  37. package/dist/initiative-list.d.ts +0 -12
  38. package/dist/initiative-list.d.ts.map +0 -1
  39. package/dist/initiative-list.js.map +0 -1
  40. package/dist/initiative-status.d.ts +0 -11
  41. package/dist/initiative-status.d.ts.map +0 -1
  42. package/dist/initiative-status.js.map +0 -1
  43. package/dist/mem-checkpoint.d.ts +0 -16
  44. package/dist/mem-checkpoint.d.ts.map +0 -1
  45. package/dist/mem-checkpoint.js.map +0 -1
  46. package/dist/mem-cleanup.d.ts +0 -29
  47. package/dist/mem-cleanup.d.ts.map +0 -1
  48. package/dist/mem-cleanup.js.map +0 -1
  49. package/dist/mem-create.d.ts +0 -17
  50. package/dist/mem-create.d.ts.map +0 -1
  51. package/dist/mem-create.js.map +0 -1
  52. package/dist/mem-inbox.d.ts +0 -35
  53. package/dist/mem-inbox.d.ts.map +0 -1
  54. package/dist/mem-inbox.js.map +0 -1
  55. package/dist/mem-init.d.ts +0 -15
  56. package/dist/mem-init.d.ts.map +0 -1
  57. package/dist/mem-init.js.map +0 -1
  58. package/dist/mem-ready.d.ts +0 -16
  59. package/dist/mem-ready.d.ts.map +0 -1
  60. package/dist/mem-ready.js.map +0 -1
  61. package/dist/mem-signal.d.ts +0 -16
  62. package/dist/mem-signal.d.ts.map +0 -1
  63. package/dist/mem-signal.js.map +0 -1
  64. package/dist/mem-start.d.ts +0 -16
  65. package/dist/mem-start.d.ts.map +0 -1
  66. package/dist/mem-start.js.map +0 -1
  67. package/dist/mem-summarize.d.ts +0 -22
  68. package/dist/mem-summarize.d.ts.map +0 -1
  69. package/dist/mem-summarize.js.map +0 -1
  70. package/dist/mem-triage.d.ts +0 -22
  71. package/dist/mem-triage.d.ts.map +0 -1
  72. package/dist/mem-triage.js.map +0 -1
  73. package/dist/spawn-list.d.ts +0 -16
  74. package/dist/spawn-list.d.ts.map +0 -1
  75. package/dist/spawn-list.js.map +0 -1
  76. package/dist/wu-block.d.ts +0 -16
  77. package/dist/wu-block.d.ts.map +0 -1
  78. package/dist/wu-block.js.map +0 -1
  79. package/dist/wu-claim.d.ts +0 -32
  80. package/dist/wu-claim.d.ts.map +0 -1
  81. package/dist/wu-claim.js.map +0 -1
  82. package/dist/wu-cleanup.d.ts +0 -17
  83. package/dist/wu-cleanup.d.ts.map +0 -1
  84. package/dist/wu-cleanup.js.map +0 -1
  85. package/dist/wu-create.d.ts +0 -38
  86. package/dist/wu-create.d.ts.map +0 -1
  87. package/dist/wu-create.js.map +0 -1
  88. package/dist/wu-deps.d.ts +0 -13
  89. package/dist/wu-deps.d.ts.map +0 -1
  90. package/dist/wu-deps.js.map +0 -1
  91. package/dist/wu-done.d.ts +0 -153
  92. package/dist/wu-done.d.ts.map +0 -1
  93. package/dist/wu-done.js.map +0 -1
  94. package/dist/wu-edit.d.ts +0 -29
  95. package/dist/wu-edit.d.ts.map +0 -1
  96. package/dist/wu-edit.js.map +0 -1
  97. package/dist/wu-infer-lane.d.ts +0 -17
  98. package/dist/wu-infer-lane.d.ts.map +0 -1
  99. package/dist/wu-infer-lane.js.map +0 -1
  100. package/dist/wu-preflight.d.ts +0 -47
  101. package/dist/wu-preflight.d.ts.map +0 -1
  102. package/dist/wu-preflight.js.map +0 -1
  103. package/dist/wu-prune.d.ts +0 -16
  104. package/dist/wu-prune.d.ts.map +0 -1
  105. package/dist/wu-prune.js.map +0 -1
  106. package/dist/wu-repair.d.ts +0 -60
  107. package/dist/wu-repair.d.ts.map +0 -1
  108. package/dist/wu-repair.js.map +0 -1
  109. package/dist/wu-spawn-completion.d.ts +0 -10
  110. package/dist/wu-spawn.d.ts +0 -168
  111. package/dist/wu-spawn.d.ts.map +0 -1
  112. package/dist/wu-spawn.js.map +0 -1
  113. package/dist/wu-unblock.d.ts +0 -16
  114. package/dist/wu-unblock.d.ts.map +0 -1
  115. package/dist/wu-unblock.js.map +0 -1
  116. package/dist/wu-validate.d.ts +0 -16
  117. package/dist/wu-validate.d.ts.map +0 -1
  118. package/dist/wu-validate.js.map +0 -1
package/dist/init.js CHANGED
@@ -1,178 +1,155 @@
1
1
  /**
2
2
  * @file init.ts
3
- * LumenFlow project scaffolding command (WU-1005)
4
- * WU-1006: Library-First - use js-yaml.dump() for YAML generation
5
- *
6
- * Scaffolds new projects with:
7
- * - .lumenflow.yaml configuration
8
- * - CLAUDE.md development guide
9
- * - AGENTS.md agent context
10
- * - .beacon/stamps directory
11
- * - docs/04-operations/tasks/wu directory
3
+ * LumenFlow project scaffolding command (WU-1045)
4
+ * WU-1006: Library-First - use core defaults for config generation
5
+ * WU-1028: Vendor-agnostic core + vendor overlays
12
6
  */
13
7
  import * as fs from 'node:fs';
14
8
  import * as path from 'node:path';
15
- import yaml from 'js-yaml';
9
+ import * as yaml from 'yaml';
10
+ import { getDefaultConfig } from '@lumenflow/core';
11
+ const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
12
+ const FRAMEWORK_HINT_FILE = '.lumenflow.framework.yaml';
13
+ const LUMENFLOW_DIR = '.lumenflow';
14
+ const LUMENFLOW_AGENTS_DIR = `${LUMENFLOW_DIR}/agents`;
15
+ const CLAUDE_DIR = '.claude';
16
+ const CLAUDE_AGENTS_DIR = path.join(CLAUDE_DIR, 'agents');
16
17
  /**
17
- * Default LumenFlow configuration object
18
- * WU-1006: Use structured object + js-yaml instead of string template
18
+ * Generate YAML configuration with header comment
19
19
  */
20
- const DEFAULT_LUMENFLOW_CONFIG = {
21
- version: '1.0',
22
- lanes: [
23
- { name: 'Core', paths: ['src/core/**'] },
24
- { name: 'API', paths: ['src/api/**'] },
25
- { name: 'UI', paths: ['src/ui/**', 'src/components/**'] },
26
- { name: 'Infrastructure', paths: ['infra/**', '.github/**'] },
27
- { name: 'Documentation', paths: ['docs/**', '*.md'] },
28
- ],
29
- gates: {
30
- format: true,
31
- lint: true,
32
- typecheck: true,
33
- test: true,
34
- },
35
- memory: {
36
- checkpoint_interval: 30,
37
- },
38
- };
20
+ function generateLumenflowConfigYaml() {
21
+ const header = `# LumenFlow Configuration\n# Generated by: lumenflow init\n# Customize paths based on your project structure\n\n`;
22
+ const config = getDefaultConfig();
23
+ config.directories.agentsDir = LUMENFLOW_AGENTS_DIR;
24
+ return header + yaml.stringify(config);
25
+ }
39
26
  /**
40
- * Generate YAML configuration with header comment
27
+ * Get current date in YYYY-MM-DD format
41
28
  */
42
- function generateLumenflowYaml() {
43
- const header = `# LumenFlow Configuration
44
- # Generated by: lumenflow init
45
- # Customize lanes based on your project structure
46
-
47
- `;
48
- return header + yaml.dump(DEFAULT_LUMENFLOW_CONFIG, { lineWidth: -1, quotingType: "'" });
49
- }
50
- // Template for CLAUDE.md
51
- const CLAUDE_MD_TEMPLATE = `# Development Guide
52
-
53
- **Last updated:** ${new Date().toISOString().split('T')[0]}
54
-
55
- This project uses LumenFlow workflow for all changes.
56
-
57
- ---
58
-
59
- ## Quick Start
60
-
61
- \`\`\`bash
62
- # 1. Create a WU
63
- pnpm wu:create --id WU-XXXX --lane <Lane> --title "Title"
64
-
65
- # 2. Edit WU spec with acceptance criteria, then claim:
66
- pnpm wu:claim --id WU-XXXX --lane <Lane>
67
- cd worktrees/<lane>-wu-xxxx
68
-
69
- # 3. Implement in worktree
70
-
71
- # 4. Run gates
72
- pnpm gates
73
-
74
- # 5. Complete (from main)
75
- cd /path/to/main
76
- pnpm wu:done --id WU-XXXX
77
- \`\`\`
78
-
79
- ---
80
-
81
- ## Core Principles
82
-
83
- 1. **TDD**: Failing test → implementation → passing test (≥90% coverage on new code)
84
- 2. **Library-First**: Search existing libraries before custom code
85
- 3. **DRY/SOLID/KISS/YAGNI**: No magic numbers, no hardcoded strings
86
- 4. **Worktree Discipline**: After \`wu:claim\`, work ONLY in the worktree
87
- 5. **Gates Before Done**: All gates must pass before \`wu:done\`
88
- 6. **Do Not Bypass Hooks**: No \`--no-verify\`, fix issues properly
89
-
90
- ---
91
-
92
- ## Definition of Done
93
-
94
- - Acceptance criteria satisfied
95
- - Gates green (\`pnpm gates\`)
96
- - WU YAML status = \`done\`
97
- - \`.beacon/stamps/WU-<id>.done\` exists
98
-
99
- ---
100
-
101
- ## Commands Reference
102
-
103
- | Command | Description |
104
- | --------------------- | ----------------------------------- |
105
- | \`pnpm wu:create\` | Create new WU spec |
106
- | \`pnpm wu:claim\` | Claim WU and create worktree |
107
- | \`pnpm wu:done\` | Complete WU (merge, stamp, cleanup) |
108
- | \`pnpm gates\` | Run quality gates |
109
- | \`pnpm mem:checkpoint\` | Save memory checkpoint |
110
- `;
111
- // Template for AGENTS.md
112
- const AGENTS_MD_TEMPLATE = `# Agent Context
113
-
114
- This project uses LumenFlow workflow. Before starting any work:
115
-
116
- ## Context Loading Protocol
117
-
118
- 1. Read \`CLAUDE.md\` for workflow fundamentals
119
- 2. Read \`README.md\` for project structure
120
- 3. Read the specific WU YAML file from \`docs/04-operations/tasks/wu/\`
121
-
122
- ## Critical Rules
123
-
124
- ### Worktree Discipline (IMMUTABLE LAW)
125
-
126
- After claiming a WU, you MUST work in its worktree:
127
-
128
- \`\`\`bash
129
- # 1. Claim creates worktree
130
- pnpm wu:claim --id WU-XXX --lane <lane>
131
-
132
- # 2. IMMEDIATELY cd to worktree
133
- cd worktrees/<lane>-wu-xxx
134
-
135
- # 3. ALL work happens here (edits, git add/commit/push, tests, gates)
136
-
137
- # 4. Return to main ONLY to complete
138
- cd ../../
139
- pnpm wu:done --id WU-XXX
140
- \`\`\`
141
-
142
- Main checkout becomes read-only after claim. Hooks will block WU commits from main.
143
-
144
- ### Never Bypass Hooks
145
-
146
- If a git hook fails (pre-commit, commit-msg):
147
-
148
- 1. Read the error message (shows which gate failed)
149
- 2. Fix the underlying issue (format/lint/type errors)
150
- 3. Re-run the commit
151
-
152
- **NEVER use \`--no-verify\` or \`--no-gpg-sign\` to bypass hooks.**
153
-
154
- ### WIP = 1 Per Lane
155
-
156
- Only ONE work unit can be "in progress" per lane at any time.
157
-
158
- ## Coding Standards
159
-
160
- - **TDD:** Tests first, ≥90% coverage on new application code
161
- - **Conventional commits:** \`type: summary\` (e.g., \`feat: add feature\`, \`fix: resolve bug\`)
162
- - **Documentation:** Concise, clear Markdown
163
-
164
- ## Forbidden Git Commands (NEVER RUN on main)
165
-
166
- \`\`\`bash
167
- git reset --hard # Data loss
168
- git stash # Hides work
169
- git clean -fd # Deletes files
170
- git push --force # History rewrite
171
- --no-verify # Bypasses safety checks
172
- \`\`\`
173
-
174
- **Where allowed:** Inside your worktree on a lane branch (safe, isolated).
29
+ function getCurrentDate() {
30
+ return new Date().toISOString().split('T')[0];
31
+ }
32
+ /**
33
+ * Normalize a framework name into display + slug
34
+ */
35
+ function normalizeFrameworkName(framework) {
36
+ const name = framework.trim();
37
+ const slug = name
38
+ .toLowerCase()
39
+ .replace(/[^a-z0-9-_]+/g, '-')
40
+ .replace(/^-+|-+$/g, '');
41
+ if (!slug) {
42
+ throw new Error(`Invalid framework name: "${framework}"`);
43
+ }
44
+ return { name, slug };
45
+ }
46
+ /**
47
+ * Process template content by replacing placeholders
48
+ */
49
+ function processTemplate(content, tokens) {
50
+ let output = content;
51
+ for (const [key, value] of Object.entries(tokens)) {
52
+ output = output.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
53
+ }
54
+ return output;
55
+ }
56
+ function getRelativePath(targetDir, filePath) {
57
+ return path.relative(targetDir, filePath).split(path.sep).join('/');
58
+ }
59
+ // Template for LUMENFLOW.md (main entry point)
60
+ 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`;
61
+ // Template for .lumenflow/constraints.md
62
+ const CONSTRAINTS_MD_TEMPLATE = `# LumenFlow Constraints Capsule\n\n**Version:** 1.0\n**Last updated:** {{DATE}}\n\n## The 6 Non-Negotiable Constraints\n\n### 1. Worktree Discipline and Git Safety\nWork only in worktrees, treat main as read-only, never run destructive git commands on main.\n\n### 2. WUs Are Specs, Not Code\nRespect code_paths boundaries, no feature creep, no code blocks in WU YAML files.\n\n### 3. Docs-Only vs Code WUs\nDocumentation WUs use \`--docs-only\` gates, code WUs run full gates.\n\n### 4. LLM-First, Zero-Fallback Inference\nUse LLMs for semantic tasks, fall back to safe defaults (never regex/keywords).\n\n### 5. Gates and Skip-Gates\nComplete via \`pnpm wu:done\`; skip-gates only for pre-existing failures with \`--reason\` and \`--fix-wu\`.\n\n### 6. Safety and Governance\nRespect privacy rules, approved sources, security policies; when uncertain, choose safer path.\n\n---\n\n## Mini Audit Checklist\n\nBefore running \`wu:done\`, verify:\n\n- [ ] Working in worktree (not main)\n- [ ] Only modified files in \`code_paths\`\n- [ ] Gates pass\n- [ ] No forbidden git commands used\n- [ ] Acceptance criteria satisfied\n\n---\n\n## Escalation Triggers\n\nStop and ask a human when:\n- Same error repeats 3 times\n- Auth or permissions changes required\n- PII/PHI/safety issues discovered\n- Cloud spend or secrets involved\n`;
63
+ // Template for root CLAUDE.md
64
+ const CLAUDE_MD_TEMPLATE = `# Claude Code Instructions\n\n**Last updated:** {{DATE}}\n\nThis project uses LumenFlow workflow. For workflow documentation, see [LUMENFLOW.md](LUMENFLOW.md).\n\n---\n\n## Quick Start\n\n\`\`\`bash\n# 1. Claim a WU\npnpm wu:claim --id WU-XXXX --lane <Lane>\ncd worktrees/<lane>-wu-xxxx\n\n# 2. Work in worktree, run gates\npnpm gates\n\n# 3. Complete (ALWAYS run this!)\ncd {{PROJECT_ROOT}}\npnpm wu:done --id WU-XXXX\n\`\`\`\n\n---\n\n## Critical: Always wu:done\n\nAfter completing work, ALWAYS run \`pnpm wu:done --id WU-XXXX\`.\n\nSee [LUMENFLOW.md](LUMENFLOW.md) for full workflow documentation.\n`;
65
+ // Template for .claude/settings.json
66
+ const CLAUDE_SETTINGS_TEMPLATE = `{
67
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
68
+ "permissions": {
69
+ "allow": [
70
+ "Bash",
71
+ "Read",
72
+ "Write",
73
+ "Edit",
74
+ "WebFetch",
75
+ "WebSearch"
76
+ ],
77
+ "deny": [
78
+ "Read(./.env)",
79
+ "Read(./.env.*)",
80
+ "Write(./.env*)",
81
+ "Bash(git reset --hard *)",
82
+ "Bash(git stash *)",
83
+ "Bash(git clean -fd *)",
84
+ "Bash(git push --force *)",
85
+ "Bash(git push -f *)",
86
+ "Bash(git commit --no-verify *)",
87
+ "Bash(HUSKY=0 *)",
88
+ "Bash(rm -rf /*)",
89
+ "Bash(sudo *)",
90
+ "Bash(git worktree remove *)",
91
+ "Bash(git worktree prune *)"
92
+ ]
93
+ }
94
+ }
175
95
  `;
96
+ // Template for .cursor/rules.md
97
+ 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`;
98
+ // Template for .aider.conf.yml
99
+ 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`;
100
+ // Template for docs/04-operations/tasks/backlog.md
101
+ const BACKLOG_TEMPLATE = `---\nsections:\n ready:\n heading: '## 🚀 Ready (pull from here)'\n insertion: after_heading_blank_line\n in_progress:\n heading: '## 🔧 In progress'\n insertion: after_heading_blank_line\n blocked:\n heading: '## ⛔ Blocked'\n insertion: after_heading_blank_line\n done:\n heading: '## ✅ Done'\n insertion: after_heading_blank_line\n---\n\n# Backlog (single source of truth)\n\n## 🚀 Ready (pull from here)\n\n(No items ready)\n\n## 🔧 In progress\n\n(No items in progress)\n\n## ⛔ Blocked\n\n(No items blocked)\n\n## ✅ Done\n\n(No items completed yet)\n`;
102
+ // Template for docs/04-operations/tasks/status.md
103
+ 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`;
104
+ // Template for docs/04-operations/tasks/templates/wu-template.yaml
105
+ 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 - .beacon/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`;
106
+ // Template for .lumenflow.framework.yaml
107
+ const FRAMEWORK_HINT_TEMPLATE = `# LumenFlow Framework Hint\n# Generated by: lumenflow init --framework {{FRAMEWORK_NAME}}\n\nframework: "{{FRAMEWORK_NAME}}"\nslug: "{{FRAMEWORK_SLUG}}"\n`;
108
+ // Template for docs/04-operations/_frameworks/<framework>/README.md
109
+ const FRAMEWORK_OVERLAY_TEMPLATE = `# {{FRAMEWORK_NAME}} Framework Overlay\n\n**Last updated:** {{DATE}}\n\nThis overlay captures framework-specific conventions, constraints, and references for {{FRAMEWORK_NAME}} projects.\n\n## Scope\n\n- Project structure conventions\n- Framework-specific testing guidance\n- Common pitfalls and mitigations\n\n## References\n\n- Add official docs links here\n`;
110
+ /**
111
+ * Detect default client from environment
112
+ */
113
+ function detectDefaultClient() {
114
+ if (process.env.CLAUDE_PROJECT_DIR || process.env.CLAUDE_CODE) {
115
+ return 'claude-code';
116
+ }
117
+ return 'none';
118
+ }
119
+ /**
120
+ * Parse vendor flag from arguments
121
+ */
122
+ function parseVendorArg(args) {
123
+ const vendorIndex = args.findIndex((arg) => arg === '--vendor');
124
+ if (vendorIndex !== -1 && args[vendorIndex + 1]) {
125
+ const vendor = args[vendorIndex + 1].toLowerCase();
126
+ if (['claude', 'cursor', 'aider', 'all', 'none'].includes(vendor)) {
127
+ return vendor;
128
+ }
129
+ }
130
+ return undefined;
131
+ }
132
+ /**
133
+ * Parse framework flag from arguments
134
+ */
135
+ function parseFrameworkArg(args) {
136
+ const frameworkArg = args.find((arg) => arg.startsWith('--framework='));
137
+ if (frameworkArg) {
138
+ const [, value] = frameworkArg.split('=', 2);
139
+ return value?.trim() || undefined;
140
+ }
141
+ const frameworkIndex = args.findIndex((arg) => arg === '--framework');
142
+ if (frameworkIndex !== -1 && args[frameworkIndex + 1]) {
143
+ return args[frameworkIndex + 1];
144
+ }
145
+ return undefined;
146
+ }
147
+ function shouldUseVendor(vendor, defaultClient) {
148
+ if (vendor) {
149
+ return vendor;
150
+ }
151
+ return defaultClient === 'claude-code' ? 'claude' : 'none';
152
+ }
176
153
  /**
177
154
  * Scaffold a new LumenFlow project
178
155
  */
@@ -181,45 +158,103 @@ export async function scaffoldProject(targetDir, options) {
181
158
  created: [],
182
159
  skipped: [],
183
160
  };
161
+ const defaultClient = options.defaultClient ?? detectDefaultClient();
162
+ const vendor = shouldUseVendor(options.vendor, defaultClient);
184
163
  // Ensure target directory exists
185
164
  if (!fs.existsSync(targetDir)) {
186
165
  fs.mkdirSync(targetDir, { recursive: true });
187
166
  }
188
- // Create .lumenflow.yaml (WU-1006: use js-yaml.dump() instead of string template)
189
- await createFile(path.join(targetDir, '.lumenflow.yaml'), generateLumenflowYaml(), options.force, result);
190
- // Create CLAUDE.md
191
- await createFile(path.join(targetDir, 'CLAUDE.md'), CLAUDE_MD_TEMPLATE, options.force, result);
192
- // Create AGENTS.md
193
- await createFile(path.join(targetDir, 'AGENTS.md'), AGENTS_MD_TEMPLATE, options.force, result);
194
- // Create .beacon/stamps directory
195
- const beaconStampsDir = path.join(targetDir, '.beacon', 'stamps');
196
- if (!fs.existsSync(beaconStampsDir)) {
197
- fs.mkdirSync(beaconStampsDir, { recursive: true });
198
- result.created.push('.beacon/stamps');
199
- }
200
- // Create docs/04-operations/tasks/wu directory with .gitkeep
201
- const wuDir = path.join(targetDir, 'docs', '04-operations', 'tasks', 'wu');
202
- if (!fs.existsSync(wuDir)) {
203
- fs.mkdirSync(wuDir, { recursive: true });
204
- result.created.push('docs/04-operations/tasks/wu');
205
- }
206
- // Create .gitkeep in WU directory
207
- const gitkeepPath = path.join(wuDir, '.gitkeep');
208
- if (!fs.existsSync(gitkeepPath)) {
209
- fs.writeFileSync(gitkeepPath, '');
167
+ const tokenDefaults = {
168
+ DATE: getCurrentDate(),
169
+ PROJECT_ROOT: targetDir,
170
+ };
171
+ // Create .lumenflow.config.yaml
172
+ await createFile(path.join(targetDir, CONFIG_FILE_NAME), generateLumenflowConfigYaml(), options.force, result, targetDir);
173
+ // Create LUMENFLOW.md (main entry point)
174
+ await createFile(path.join(targetDir, 'LUMENFLOW.md'), processTemplate(LUMENFLOW_MD_TEMPLATE, tokenDefaults), options.force, result, targetDir);
175
+ // Create .lumenflow/constraints.md
176
+ await createFile(path.join(targetDir, LUMENFLOW_DIR, 'constraints.md'), processTemplate(CONSTRAINTS_MD_TEMPLATE, tokenDefaults), options.force, result, targetDir);
177
+ // Create .lumenflow/agents directory with .gitkeep
178
+ await createDirectory(path.join(targetDir, LUMENFLOW_AGENTS_DIR), result, targetDir);
179
+ await createFile(path.join(targetDir, LUMENFLOW_AGENTS_DIR, '.gitkeep'), '', options.force, result, targetDir);
180
+ // Optional: full docs scaffolding
181
+ if (options.full) {
182
+ await scaffoldFullDocs(targetDir, options, result, tokenDefaults);
183
+ }
184
+ // Optional: framework overlay
185
+ if (options.framework) {
186
+ await scaffoldFrameworkOverlay(targetDir, options, result, tokenDefaults);
210
187
  }
188
+ // Scaffold vendor-specific files
189
+ await scaffoldVendorFiles(targetDir, options, result, tokenDefaults, vendor);
211
190
  return result;
212
191
  }
192
+ async function scaffoldFullDocs(targetDir, options, result, tokens) {
193
+ const tasksDir = path.join(targetDir, 'docs', '04-operations', 'tasks');
194
+ const wuDir = path.join(tasksDir, 'wu');
195
+ const templatesDir = path.join(tasksDir, 'templates');
196
+ await createDirectory(wuDir, result, targetDir);
197
+ await createDirectory(templatesDir, result, targetDir);
198
+ await createFile(path.join(wuDir, '.gitkeep'), '', options.force, result, targetDir);
199
+ await createFile(path.join(tasksDir, 'backlog.md'), BACKLOG_TEMPLATE, options.force, result, targetDir);
200
+ await createFile(path.join(tasksDir, 'status.md'), STATUS_TEMPLATE, options.force, result, targetDir);
201
+ await createFile(path.join(templatesDir, 'wu-template.yaml'), processTemplate(WU_TEMPLATE_YAML, tokens), options.force, result, targetDir);
202
+ }
203
+ async function scaffoldFrameworkOverlay(targetDir, options, result, tokens) {
204
+ if (!options.framework) {
205
+ return;
206
+ }
207
+ const { name, slug } = normalizeFrameworkName(options.framework);
208
+ const frameworkTokens = {
209
+ ...tokens,
210
+ FRAMEWORK_NAME: name,
211
+ FRAMEWORK_SLUG: slug,
212
+ };
213
+ await createFile(path.join(targetDir, FRAMEWORK_HINT_FILE), processTemplate(FRAMEWORK_HINT_TEMPLATE, frameworkTokens), options.force, result, targetDir);
214
+ const overlayDir = path.join(targetDir, 'docs', '04-operations', '_frameworks', slug);
215
+ await createDirectory(overlayDir, result, targetDir);
216
+ await createFile(path.join(overlayDir, 'README.md'), processTemplate(FRAMEWORK_OVERLAY_TEMPLATE, frameworkTokens), options.force, result, targetDir);
217
+ }
218
+ /**
219
+ * Scaffold vendor-specific files based on --vendor option
220
+ */
221
+ async function scaffoldVendorFiles(targetDir, options, result, tokens, vendor) {
222
+ // Claude Code
223
+ if (vendor === 'claude' || vendor === 'all') {
224
+ await createFile(path.join(targetDir, 'CLAUDE.md'), processTemplate(CLAUDE_MD_TEMPLATE, tokens), options.force, result, targetDir);
225
+ await createDirectory(path.join(targetDir, CLAUDE_AGENTS_DIR), result, targetDir);
226
+ await createFile(path.join(targetDir, CLAUDE_AGENTS_DIR, '.gitkeep'), '', options.force, result, targetDir);
227
+ await createFile(path.join(targetDir, CLAUDE_DIR, 'settings.json'), CLAUDE_SETTINGS_TEMPLATE, options.force, result, targetDir);
228
+ }
229
+ // Cursor
230
+ if (vendor === 'cursor' || vendor === 'all') {
231
+ const cursorDir = path.join(targetDir, '.cursor');
232
+ await createDirectory(cursorDir, result, targetDir);
233
+ await createFile(path.join(cursorDir, 'rules.md'), processTemplate(CURSOR_RULES_TEMPLATE, tokens), options.force, result, targetDir);
234
+ }
235
+ // Aider
236
+ if (vendor === 'aider' || vendor === 'all') {
237
+ await createFile(path.join(targetDir, '.aider.conf.yml'), AIDER_CONF_TEMPLATE, options.force, result, targetDir);
238
+ }
239
+ }
240
+ /**
241
+ * Create a directory if missing
242
+ */
243
+ async function createDirectory(dirPath, result, targetDir) {
244
+ if (!fs.existsSync(dirPath)) {
245
+ fs.mkdirSync(dirPath, { recursive: true });
246
+ result.created.push(getRelativePath(targetDir, dirPath));
247
+ }
248
+ }
213
249
  /**
214
250
  * Create a file, respecting force option
215
251
  */
216
- async function createFile(filePath, content, force, result) {
217
- const relativePath = path.basename(filePath);
252
+ async function createFile(filePath, content, force, result, targetDir) {
253
+ const relativePath = getRelativePath(targetDir, filePath);
218
254
  if (fs.existsSync(filePath) && !force) {
219
255
  result.skipped.push(relativePath);
220
256
  return;
221
257
  }
222
- // Ensure parent directory exists
223
258
  const parentDir = path.dirname(filePath);
224
259
  if (!fs.existsSync(parentDir)) {
225
260
  fs.mkdirSync(parentDir, { recursive: true });
@@ -233,19 +268,30 @@ async function createFile(filePath, content, force, result) {
233
268
  export async function main() {
234
269
  const args = process.argv.slice(2);
235
270
  const force = args.includes('--force') || args.includes('-f');
271
+ const full = args.includes('--full');
272
+ const vendor = parseVendorArg(args);
273
+ const framework = parseFrameworkArg(args);
236
274
  const targetDir = process.cwd();
237
275
  console.log('[lumenflow init] Scaffolding LumenFlow project...');
238
- const result = await scaffoldProject(targetDir, { force });
276
+ console.log(` Mode: ${full ? 'full' : 'minimal'}`);
277
+ console.log(` Framework: ${framework ?? 'none'}`);
278
+ console.log(` Vendor overlays: ${vendor ?? 'auto'}`);
279
+ const result = await scaffoldProject(targetDir, {
280
+ force,
281
+ full,
282
+ vendor,
283
+ framework,
284
+ });
239
285
  if (result.created.length > 0) {
240
286
  console.log('\nCreated:');
241
- result.created.forEach((f) => console.log(` ${f}`));
287
+ result.created.forEach((f) => console.log(` + ${f}`));
242
288
  }
243
289
  if (result.skipped.length > 0) {
244
290
  console.log('\nSkipped (already exists, use --force to overwrite):');
245
- result.skipped.forEach((f) => console.log(` ⏭️ ${f}`));
291
+ result.skipped.forEach((f) => console.log(` - ${f}`));
246
292
  }
247
293
  console.log('\n[lumenflow init] Done! Next steps:');
248
- console.log(' 1. Edit .lumenflow.yaml to match your project structure');
249
- console.log(' 2. Review CLAUDE.md and AGENTS.md templates');
294
+ console.log(' 1. Review LUMENFLOW.md for workflow documentation');
295
+ console.log(` 2. Edit ${CONFIG_FILE_NAME} to match your project structure`);
250
296
  console.log(' 3. Run: pnpm wu:create --id WU-0001 --lane <lane> --title "First WU"');
251
297
  }
@@ -229,6 +229,7 @@ async function main() {
229
229
  }
230
230
  // Guard main() for testability
231
231
  import { fileURLToPath } from 'node:url';
232
+ import { runCLI } from './cli-entry-point.js';
232
233
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
233
- main();
234
+ runCLI(main);
234
235
  }
@@ -30,11 +30,11 @@ import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
30
30
  import { die } from '@lumenflow/core/dist/error-handler.js';
31
31
  import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
32
32
  import { join } from 'node:path';
33
- import yaml from 'js-yaml';
33
+ import { stringifyYAML } from '@lumenflow/core/dist/wu-yaml.js';
34
34
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
35
35
  import { INIT_PATHS } from '@lumenflow/initiatives/dist/initiative-paths.js';
36
36
  import { INIT_PATTERNS, INIT_COMMIT_FORMATS, INIT_DEFAULTS, } from '@lumenflow/initiatives/dist/initiative-constants.js';
37
- import { FILE_SYSTEM, YAML_OPTIONS } from '@lumenflow/core/dist/wu-constants.js';
37
+ import { FILE_SYSTEM } from '@lumenflow/core/dist/wu-constants.js';
38
38
  import { ensureOnMain } from '@lumenflow/core/dist/wu-helpers.js';
39
39
  import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
40
40
  // WU-1428: Use date-utils for consistent YYYY-MM-DD format (library-first)
@@ -97,11 +97,7 @@ function createInitiativeYamlInWorktree(worktreePath, id, slug, title, options =
97
97
  success_metrics: [],
98
98
  labels: [],
99
99
  };
100
- const yamlContent = yaml.dump(initContent, {
101
- lineWidth: YAML_OPTIONS.LINE_WIDTH,
102
- quotingType: '"',
103
- forceQuotes: false,
104
- });
100
+ const yamlContent = stringifyYAML(initContent);
105
101
  writeFileSync(initAbsolutePath, yamlContent, { encoding: FILE_SYSTEM.UTF8 });
106
102
  console.log(`${LOG_PREFIX} ✅ Created ${id}.yaml in micro-worktree`);
107
103
  return initRelativePath;
@@ -167,6 +163,7 @@ async function main() {
167
163
  }
168
164
  // Guard main() for testability
169
165
  import { fileURLToPath } from 'node:url';
166
+ import { runCLI } from './cli-entry-point.js';
170
167
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
171
- main();
168
+ runCLI(main);
172
169
  }
@@ -35,7 +35,7 @@ import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
35
35
  import { die } from '@lumenflow/core/dist/error-handler.js';
36
36
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
37
37
  import { join } from 'node:path';
38
- import yaml from 'js-yaml';
38
+ import { parseYAML, stringifyYAML } from '@lumenflow/core/dist/wu-yaml.js';
39
39
  import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
40
40
  import { INIT_PATHS } from '@lumenflow/initiatives/dist/initiative-paths.js';
41
41
  import { INIT_STATUSES, PHASE_STATUSES, INIT_PATTERNS, INIT_LOG_PREFIX, INIT_COMMIT_FORMATS, } from '@lumenflow/initiatives/dist/initiative-constants.js';
@@ -230,7 +230,7 @@ function loadInitiative(id) {
230
230
  }
231
231
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool validates init files
232
232
  const content = readFileSync(initPath, { encoding: FILE_SYSTEM.ENCODING });
233
- return yaml.load(content);
233
+ return parseYAML(content);
234
234
  }
235
235
  /**
236
236
  * Ensure working tree is clean
@@ -421,7 +421,7 @@ async function main() {
421
421
  execute: async ({ worktreePath }) => {
422
422
  // Write updated Initiative to micro-worktree
423
423
  const initPath = join(worktreePath, INIT_PATHS.INITIATIVE(id));
424
- const yamlContent = yaml.dump(updatedInit, { lineWidth: 100 });
424
+ const yamlContent = stringifyYAML(updatedInit);
425
425
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes init files
426
426
  writeFileSync(initPath, yamlContent, { encoding: FILE_SYSTEM.ENCODING });
427
427
  console.log(`${PREFIX} Updated ${id}.yaml in micro-worktree`);
@@ -96,6 +96,7 @@ async function main() {
96
96
  }
97
97
  // Guard main() for testability (WU-1366)
98
98
  import { fileURLToPath } from 'node:url';
99
+ import { runCLI } from './cli-entry-point.js';
99
100
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
100
- main();
101
+ runCLI(main);
101
102
  }
@@ -216,6 +216,7 @@ async function main() {
216
216
  }
217
217
  // Guard main() for testability (WU-1366)
218
218
  import { fileURLToPath } from 'node:url';
219
+ import { runCLI } from './cli-entry-point.js';
219
220
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
220
- main();
221
+ runCLI(main);
221
222
  }