@lumenflow/cli 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gates.js +102 -39
- package/dist/init.js +241 -195
- package/dist/initiative-create.js +3 -7
- package/dist/initiative-edit.js +3 -3
- package/dist/wu-claim.js +295 -109
- package/dist/wu-cleanup.js +49 -3
- package/dist/wu-create.js +195 -121
- package/dist/wu-done.js +44 -13
- package/dist/wu-edit.js +152 -61
- package/dist/wu-infer-lane.js +2 -2
- package/dist/wu-spawn.js +77 -158
- package/package.json +14 -14
package/dist/init.js
CHANGED
|
@@ -1,178 +1,155 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file init.ts
|
|
3
|
-
* LumenFlow project scaffolding command (WU-
|
|
4
|
-
* WU-1006: Library-First - use
|
|
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 '
|
|
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
|
-
*
|
|
18
|
-
* WU-1006: Use structured object + js-yaml instead of string template
|
|
18
|
+
* Generate YAML configuration with header comment
|
|
19
19
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
*
|
|
27
|
+
* Get current date in YYYY-MM-DD format
|
|
41
28
|
*/
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
\`\`\`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// Create
|
|
193
|
-
await createFile(path.join(targetDir,
|
|
194
|
-
// Create .
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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 =
|
|
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
|
-
|
|
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(`
|
|
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(`
|
|
291
|
+
result.skipped.forEach((f) => console.log(` - ${f}`));
|
|
246
292
|
}
|
|
247
293
|
console.log('\n[lumenflow init] Done! Next steps:');
|
|
248
|
-
console.log(' 1.
|
|
249
|
-
console.log(
|
|
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
|
}
|
|
@@ -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
|
|
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
|
|
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 =
|
|
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;
|
package/dist/initiative-edit.js
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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`);
|