@lumenflow/cli 2.0.0 → 2.1.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.
package/README.md CHANGED
@@ -140,6 +140,25 @@ The CLI integrates with other LumenFlow packages:
140
140
 
141
141
  For complete documentation, see [lumenflow.dev](https://lumenflow.dev/reference/cli).
142
142
 
143
+ ## Upgrading
144
+
145
+ To upgrade LumenFlow packages:
146
+
147
+ ```bash
148
+ # Check for available updates
149
+ pnpm outdated @lumenflow/*
150
+
151
+ # Update all LumenFlow packages
152
+ pnpm update @lumenflow/cli @lumenflow/core @lumenflow/memory @lumenflow/agent @lumenflow/initiatives
153
+
154
+ # Sync documentation and templates
155
+ pnpm exec lumenflow docs:sync
156
+ ```
157
+
158
+ **Important**: Always run `docs:sync` after upgrading to update agent onboarding documentation, workflow rules, and vendor-specific configurations.
159
+
160
+ For detailed upgrade instructions, migration guides, and troubleshooting, see [UPGRADING.md](https://lumenflow.dev/upgrading).
161
+
143
162
  ## License
144
163
 
145
164
  Apache-2.0
@@ -3,18 +3,50 @@
3
3
  * Tests for lumenflow-upgrade CLI command
4
4
  *
5
5
  * WU-1112: INIT-003 Phase 6 - Migrate remaining Tier 1 tools
6
+ * WU-1127: Add micro-worktree isolation pattern
6
7
  *
7
8
  * lumenflow-upgrade updates all @lumenflow/* packages to latest versions.
8
9
  * Key requirements:
9
- * - Uses worktree pattern (runs pnpm install in worktree, not main)
10
+ * - Uses micro-worktree pattern (atomic changes to main without requiring user worktree)
10
11
  * - Checks all 7 @lumenflow/* packages
12
+ * - Supports --dry-run and --latest flags
11
13
  */
12
- import { describe, it, expect, vi, beforeEach } from 'vitest';
13
- // Import functions under test
14
- import { parseUpgradeArgs, LUMENFLOW_PACKAGES, buildUpgradeCommands, } from '../lumenflow-upgrade.js';
14
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
+ import { execSync } from 'node:child_process';
16
+ // Mock modules with inline factories (no external references)
17
+ vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
18
+ withMicroWorktree: vi.fn(),
19
+ }));
20
+ vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
21
+ getGitForCwd: vi.fn(),
22
+ }));
23
+ vi.mock('node:child_process', async (importOriginal) => {
24
+ const actual = await importOriginal();
25
+ return {
26
+ ...actual,
27
+ execSync: vi.fn(),
28
+ };
29
+ });
30
+ // Import mocked modules to access mock functions
31
+ import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
32
+ import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
33
+ // Import functions under test after mocks are set up
34
+ import { parseUpgradeArgs, LUMENFLOW_PACKAGES, buildUpgradeCommands, executeUpgradeInMicroWorktree, validateMainCheckout, } from '../lumenflow-upgrade.js';
35
+ // Cast mocks for TypeScript
36
+ const mockWithMicroWorktree = withMicroWorktree;
37
+ const mockGetGitForCwd = getGitForCwd;
38
+ const mockExecSync = execSync;
15
39
  describe('lumenflow-upgrade', () => {
16
40
  beforeEach(() => {
17
41
  vi.clearAllMocks();
42
+ // Default mock behavior for git adapter
43
+ mockGetGitForCwd.mockReturnValue({
44
+ raw: vi.fn().mockResolvedValue('main'),
45
+ getStatus: vi.fn().mockResolvedValue(''),
46
+ });
47
+ });
48
+ afterEach(() => {
49
+ vi.restoreAllMocks();
18
50
  });
19
51
  describe('LUMENFLOW_PACKAGES constant', () => {
20
52
  it('should include all 7 @lumenflow/* packages', () => {
@@ -94,14 +126,135 @@ describe('lumenflow-upgrade', () => {
94
126
  expect(packageCount).toBe(7);
95
127
  });
96
128
  });
97
- describe('worktree pattern enforcement', () => {
98
- it('should include note about worktree usage in commands', () => {
99
- const args = { version: '1.5.0' };
100
- const commands = buildUpgradeCommands(args);
101
- // The command should be designed to run in worktree
102
- // Actual execution is tested in integration tests
103
- expect(commands.addCommand).toBeDefined();
104
- expect(commands.addCommand.length).toBeGreaterThan(0);
129
+ // WU-1127: Tests for micro-worktree isolation pattern
130
+ describe('validateMainCheckout', () => {
131
+ let originalCwd;
132
+ beforeEach(() => {
133
+ originalCwd = process.cwd;
134
+ });
135
+ afterEach(() => {
136
+ process.cwd = originalCwd;
137
+ });
138
+ it('should return valid when on main branch and not in worktree', async () => {
139
+ // Mock process.cwd to be on main checkout (not worktree)
140
+ process.cwd = vi.fn().mockReturnValue('/path/to/repo');
141
+ mockGetGitForCwd.mockReturnValue({
142
+ raw: vi.fn().mockResolvedValue('main'),
143
+ getStatus: vi.fn().mockResolvedValue(''),
144
+ });
145
+ const result = await validateMainCheckout();
146
+ expect(result.valid).toBe(true);
147
+ });
148
+ it('should return invalid when not on main branch', async () => {
149
+ // Mock process.cwd to be on main checkout (not worktree)
150
+ process.cwd = vi.fn().mockReturnValue('/path/to/repo');
151
+ mockGetGitForCwd.mockReturnValue({
152
+ raw: vi.fn().mockResolvedValue('lane/framework-cli/wu-123'),
153
+ getStatus: vi.fn().mockResolvedValue(''),
154
+ });
155
+ const result = await validateMainCheckout();
156
+ expect(result.valid).toBe(false);
157
+ expect(result.error).toContain('must be run from main checkout');
158
+ });
159
+ it('should return invalid when in a worktree directory', async () => {
160
+ // Mock process.cwd() to be in a worktree
161
+ process.cwd = vi
162
+ .fn()
163
+ .mockReturnValue('/path/to/repo/worktrees/some-wu');
164
+ mockGetGitForCwd.mockReturnValue({
165
+ raw: vi.fn().mockResolvedValue('main'),
166
+ getStatus: vi.fn().mockResolvedValue(''),
167
+ });
168
+ const result = await validateMainCheckout();
169
+ expect(result.valid).toBe(false);
170
+ expect(result.error).toContain('worktree');
171
+ });
172
+ });
173
+ describe('executeUpgradeInMicroWorktree', () => {
174
+ it('should call withMicroWorktree with correct operation name', async () => {
175
+ mockWithMicroWorktree.mockResolvedValue({});
176
+ mockExecSync.mockReturnValue('');
177
+ const args = { version: '2.1.0' };
178
+ await executeUpgradeInMicroWorktree(args);
179
+ expect(mockWithMicroWorktree).toHaveBeenCalledTimes(1);
180
+ expect(mockWithMicroWorktree).toHaveBeenCalledWith(expect.objectContaining({
181
+ operation: 'lumenflow-upgrade',
182
+ }));
183
+ });
184
+ it('should use a unique ID for micro-worktree based on timestamp', async () => {
185
+ mockWithMicroWorktree.mockResolvedValue({});
186
+ mockExecSync.mockReturnValue('');
187
+ const args = { latest: true };
188
+ await executeUpgradeInMicroWorktree(args);
189
+ // ID should be a timestamp-like string
190
+ expect(mockWithMicroWorktree).toHaveBeenCalledWith(expect.objectContaining({
191
+ id: expect.stringMatching(/^upgrade-\d+$/),
192
+ }));
193
+ });
194
+ it('should execute pnpm add in the micro-worktree', async () => {
195
+ mockWithMicroWorktree.mockImplementation(async (options) => {
196
+ // Simulate calling the execute function with a worktree path
197
+ return options.execute({ worktreePath: '/tmp/test-worktree' });
198
+ });
199
+ const args = { version: '2.1.0' };
200
+ await executeUpgradeInMicroWorktree(args);
201
+ // Verify pnpm add was executed with correct cwd
202
+ expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('pnpm add'), expect.objectContaining({
203
+ cwd: '/tmp/test-worktree',
204
+ }));
205
+ });
206
+ it('should include all 7 packages in the pnpm add command', async () => {
207
+ mockWithMicroWorktree.mockImplementation(async (options) => {
208
+ return options.execute({ worktreePath: '/tmp/test-worktree' });
209
+ });
210
+ const args = { version: '2.1.0' };
211
+ await executeUpgradeInMicroWorktree(args);
212
+ // Get the command that was executed
213
+ const execCall = mockExecSync.mock.calls[0][0];
214
+ expect(typeof execCall).toBe('string');
215
+ // Verify all 7 packages are included
216
+ for (const pkg of LUMENFLOW_PACKAGES) {
217
+ expect(execCall).toContain(`${pkg}@2.1.0`);
218
+ }
219
+ });
220
+ it('should return appropriate commit message and files', async () => {
221
+ let executeResult;
222
+ mockWithMicroWorktree.mockImplementation(async (options) => {
223
+ executeResult = await options.execute({ worktreePath: '/tmp/test-worktree' });
224
+ return executeResult;
225
+ });
226
+ const args = { version: '2.1.0' };
227
+ await executeUpgradeInMicroWorktree(args);
228
+ expect(executeResult).toBeDefined();
229
+ expect(executeResult.commitMessage).toContain('upgrade @lumenflow packages');
230
+ expect(executeResult.files).toContain('package.json');
231
+ expect(executeResult.files).toContain('pnpm-lock.yaml');
232
+ });
233
+ it('should use --latest version specifier when latest flag is set', async () => {
234
+ mockWithMicroWorktree.mockImplementation(async (options) => {
235
+ return options.execute({ worktreePath: '/tmp/test-worktree' });
236
+ });
237
+ const args = { latest: true };
238
+ await executeUpgradeInMicroWorktree(args);
239
+ const execCall = mockExecSync.mock.calls[0][0];
240
+ expect(execCall).toContain('@lumenflow/core@latest');
241
+ });
242
+ });
243
+ describe('dry-run mode', () => {
244
+ it('should not call withMicroWorktree when dryRun is true', async () => {
245
+ const args = { version: '2.1.0', dryRun: true };
246
+ // In dry-run mode, executeUpgradeInMicroWorktree should not be called
247
+ // This is handled by the main() function checking dryRun before calling execute
248
+ // We just verify the function exists and can be called
249
+ expect(typeof executeUpgradeInMicroWorktree).toBe('function');
250
+ });
251
+ });
252
+ describe('legacy worktree validation removal', () => {
253
+ it('should not require user to be in a worktree', () => {
254
+ // The old implementation required users to be inside a worktree
255
+ // The new implementation uses micro-worktree and runs from main checkout
256
+ // This test verifies the old validateWorktreeContext is no longer used
257
+ expect(typeof validateMainCheckout).toBe('function');
105
258
  });
106
259
  });
107
260
  });
package/dist/docs-sync.js CHANGED
@@ -2,9 +2,11 @@
2
2
  * @file docs-sync.ts
3
3
  * LumenFlow docs:sync command for syncing agent docs to existing projects (WU-1083)
4
4
  * WU-1085: Added createWUParser for proper --help support
5
+ * WU-1124: Refactored to read templates from bundled files (INIT-004 Phase 2)
5
6
  */
6
7
  import * as fs from 'node:fs';
7
8
  import * as path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
8
10
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core';
9
11
  /**
10
12
  * WU-1085: CLI option definitions for docs-sync command
@@ -33,6 +35,40 @@ export function parseDocsSyncOptions() {
33
35
  vendor: opts.vendor ?? 'claude',
34
36
  };
35
37
  }
38
+ /**
39
+ * WU-1124: Get the templates directory path
40
+ * Templates are bundled with the CLI package at dist/templates/
41
+ * Falls back to src/templates/ for development
42
+ */
43
+ export function getTemplatesDir() {
44
+ const __filename = fileURLToPath(import.meta.url);
45
+ const __dirname = path.dirname(__filename);
46
+ // In production: dist/docs-sync.js -> templates/
47
+ // In development: src/docs-sync.ts -> ../templates/
48
+ const distTemplates = path.join(__dirname, '..', 'templates');
49
+ if (fs.existsSync(distTemplates)) {
50
+ return distTemplates;
51
+ }
52
+ // Fallback for tests running from src
53
+ const srcTemplates = path.join(__dirname, '..', 'templates');
54
+ if (fs.existsSync(srcTemplates)) {
55
+ return srcTemplates;
56
+ }
57
+ throw new Error(`Templates directory not found at ${distTemplates}`);
58
+ }
59
+ /**
60
+ * WU-1124: Load a template file from the bundled templates directory
61
+ * @param templatePath - Relative path from templates directory (e.g., 'core/ai/onboarding/quick-ref-commands.md.template')
62
+ * @returns Template content as string
63
+ */
64
+ export function loadTemplate(templatePath) {
65
+ const templatesDir = getTemplatesDir();
66
+ const fullPath = path.join(templatesDir, templatePath);
67
+ if (!fs.existsSync(fullPath)) {
68
+ throw new Error(`Template not found: ${templatePath} (looked at ${fullPath})`);
69
+ }
70
+ return fs.readFileSync(fullPath, 'utf-8');
71
+ }
36
72
  /**
37
73
  * Get current date in YYYY-MM-DD format
38
74
  */
@@ -77,317 +113,29 @@ async function createFile(filePath, content, force, result, targetDir) {
77
113
  fs.writeFileSync(filePath, content);
78
114
  result.created.push(relativePath);
79
115
  }
80
- // Agent onboarding docs templates (duplicated from init.ts for modularity)
81
- const QUICK_REF_COMMANDS_TEMPLATE = `# Quick Reference: LumenFlow Commands
82
-
83
- **Last updated:** {{DATE}}
84
-
85
- ---
86
-
87
- ## Project Setup
88
-
89
- | Command | Description |
90
- | --------------------------------------------- | --------------------------------------- |
91
- | \`pnpm exec lumenflow init\` | Scaffold minimal LumenFlow core |
92
- | \`pnpm exec lumenflow init --full\` | Add docs/04-operations task scaffolding |
93
- | \`pnpm exec lumenflow init --framework <name>\` | Add framework hint + overlay docs |
94
- | \`pnpm exec lumenflow init --force\` | Overwrite existing files |
95
-
96
- ---
97
-
98
- ## WU Management
99
-
100
- | Command | Description |
101
- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
102
- | \`pnpm wu:create --id WU-XXX --lane <Lane> --title "Title" --description "..." --acceptance "..." --code-paths "path" --test-paths-unit "path" --exposure backend-only --spec-refs "~/.lumenflow/plans/WU-XXX.md"\` | Create new WU |
103
- | \`pnpm wu:claim --id WU-XXX --lane <Lane>\` | Claim WU (creates worktree) |
104
- | \`pnpm wu:done --id WU-XXX\` | Complete WU (merge, stamp, cleanup) |
105
- | \`pnpm wu:block --id WU-XXX --reason "Reason"\` | Block a WU |
106
- | \`pnpm wu:unblock --id WU-XXX\` | Unblock a WU |
107
-
108
- ---
109
-
110
- ## Gates
111
-
112
- | Command | Description |
113
- | ------------------------ | -------------------------- |
114
- | \`pnpm gates\` | Run all quality gates |
115
- | \`pnpm gates --docs-only\` | Run gates for docs changes |
116
- | \`pnpm format\` | Format all files |
117
- | \`pnpm lint\` | Run linter |
118
- | \`pnpm typecheck\` | Run TypeScript check |
119
-
120
- ---
121
-
122
- ## File Paths
123
-
124
- | Path | Description |
125
- | ----------------------------------------- | -------------------- |
126
- | \`docs/04-operations/tasks/wu/WU-XXX.yaml\` | WU specification |
127
- | \`docs/04-operations/tasks/status.md\` | Current status board |
128
- | \`.lumenflow/stamps/WU-XXX.done\` | Completion stamp |
129
- | \`worktrees/<lane>-wu-xxx/\` | Worktree directory |
130
- `;
131
- const FIRST_WU_MISTAKES_TEMPLATE = `# First WU Mistakes
132
-
133
- **Last updated:** {{DATE}}
134
-
135
- Common mistakes agents make on their first WU, and how to avoid them.
136
-
137
- ---
138
-
139
- ## Mistake 1: Not Using Worktrees
140
-
141
- ### Wrong
142
-
143
- \`\`\`bash
144
- # Working directly in main
145
- vim src/feature.ts
146
- git commit -m "feat: add feature"
147
- git push origin main
148
- \`\`\`
149
-
150
- ### Right
151
-
152
- \`\`\`bash
153
- # Claim first, then work in worktree
154
- pnpm wu:claim --id WU-123 --lane Core
155
- cd worktrees/core-wu-123
156
- vim src/feature.ts
157
- git commit -m "feat: add feature"
158
- git push origin lane/core/wu-123
159
- cd /path/to/main
160
- pnpm wu:done --id WU-123
161
- \`\`\`
162
-
163
- ---
164
-
165
- ## Mistake 2: Forgetting to Run wu:done
166
-
167
- **TL;DR:** After gates pass, ALWAYS run \`pnpm wu:done --id WU-XXX\`.
168
-
169
- ---
170
-
171
- ## Mistake 3: Working Outside code_paths
172
-
173
- Only edit files within the specified \`code_paths\`.
174
-
175
- ---
176
-
177
- ## Quick Checklist
178
-
179
- - [ ] Claim the WU with \`pnpm wu:claim\`
180
- - [ ] cd to the worktree IMMEDIATELY
181
- - [ ] Work only in the worktree
182
- - [ ] Run gates before wu:done
183
- - [ ] ALWAYS run wu:done
184
- `;
185
- const TROUBLESHOOTING_WU_DONE_TEMPLATE = `# Troubleshooting: wu:done Not Run
186
-
187
- **Last updated:** {{DATE}}
188
-
189
- This is the most common mistake agents make.
190
-
191
- ---
192
-
193
- ## The Fix
194
-
195
- ### Rule: ALWAYS Run wu:done
196
-
197
- After gates pass, you MUST run:
198
-
199
- \`\`\`bash
200
- cd /path/to/main
201
- pnpm wu:done --id WU-XXX
202
- \`\`\`
203
-
204
- Do NOT:
205
-
206
- - Ask "Should I run wu:done?"
207
- - Write "To Complete: pnpm wu:done"
208
- - Wait for permission
209
-
210
- ---
211
-
212
- ## What wu:done Does
213
-
214
- 1. Validates the worktree exists and has commits
215
- 2. Runs gates in the worktree (not main)
216
- 3. Fast-forward merges to main
217
- 4. Creates the done stamp
218
- 5. Updates status and backlog docs
219
- 6. Removes the worktree
220
- 7. Pushes to origin
221
- `;
222
- const AGENT_SAFETY_CARD_TEMPLATE = `# Agent Safety Card
223
-
224
- **Last updated:** {{DATE}}
225
-
226
- Quick reference for AI agents working in LumenFlow projects.
227
-
228
- ---
229
-
230
- ## Stop and Ask When
231
-
232
- - Same error repeats 3 times
233
- - Auth or permissions changes needed
234
- - PII/PHI/secrets involved
235
- - Cloud spend decisions
236
-
237
- ---
238
-
239
- ## Never Do
240
-
241
- | Action | Why |
242
- | ------------------------ | ---------------- |
243
- | \`git reset --hard\` | Data loss |
244
- | \`git push --force\` | History rewrite |
245
- | \`--no-verify\` | Bypasses safety |
246
- | Work in main after claim | Breaks isolation |
247
- | Skip wu:done | Incomplete WU |
248
-
249
- ---
250
-
251
- ## Always Do
252
-
253
- | Action | Why |
254
- | -------------------------- | ---------------- |
255
- | Read WU spec first | Understand scope |
256
- | cd to worktree after claim | Isolation |
257
- | Write tests before code | TDD |
258
- | Run gates before wu:done | Quality |
259
- | Run wu:done | Complete WU |
260
- `;
261
- const WU_CREATE_CHECKLIST_TEMPLATE = `# WU Creation Checklist
262
-
263
- **Last updated:** {{DATE}}
264
-
265
- Before running \`pnpm wu:create\`, verify these items.
266
-
267
- ---
268
-
269
- ## Step 1: Check Valid Lanes
270
-
271
- \`\`\`bash
272
- grep -A 30 "lanes:" .lumenflow.config.yaml
273
- \`\`\`
274
-
275
- **Format:** \`"Parent: Sublane"\` (colon + single space)
276
-
277
- ---
278
-
279
- ## Step 2: Required Fields
280
-
281
- | Field | Required For | Example |
282
- |-------|--------------|---------|
283
- | \`--id\` | All | \`WU-1234\` |
284
- | \`--lane\` | All | \`"Experience: Chat"\` |
285
- | \`--title\` | All | \`"Add feature"\` |
286
- | \`--description\` | All | \`"Context: ... Problem: ... Solution: ..."\` |
287
- | \`--acceptance\` | All | \`--acceptance "Works"\` (repeatable) |
288
- | \`--exposure\` | All | \`ui\`, \`api\`, \`backend-only\`, \`documentation\` |
289
- | \`--code-paths\` | Code WUs | \`"src/a.ts,src/b.ts"\` |
290
- | \`--test-paths-unit\` | Code WUs | \`"src/__tests__/a.test.ts"\` |
291
- | \`--spec-refs\` | Feature WUs | \`"~/.lumenflow/plans/WU-XXX.md"\` |
292
-
293
- ---
294
-
295
- ## Step 3: Plan Storage
296
-
297
- Plans go in \`~/.lumenflow/plans/\` (NOT in project):
298
-
299
- \`\`\`bash
300
- mkdir -p ~/.lumenflow/plans
301
- vim ~/.lumenflow/plans/WU-XXX-plan.md
302
- \`\`\`
303
-
304
- Reference in wu:create:
305
- \`\`\`bash
306
- --spec-refs "~/.lumenflow/plans/WU-XXX-plan.md"
307
- \`\`\`
308
-
309
- ---
310
-
311
- ## Step 4: Validate First
312
-
313
- \`\`\`bash
314
- pnpm wu:create --id WU-XXX ... --validate
315
- \`\`\`
316
-
317
- Fix errors, then remove \`--validate\` to create.
318
- `;
319
- // Claude skills templates
320
- const WU_LIFECYCLE_SKILL_TEMPLATE = `---
321
- name: wu-lifecycle
322
- description: Work Unit claim/block/done workflow automation.
323
- version: 1.0.0
324
- ---
325
-
326
- # WU Lifecycle Skill
327
-
328
- ## State Machine
329
-
330
- \`\`\`
331
- ready -> in_progress -> waiting/blocked -> done
332
- \`\`\`
333
-
334
- ## Core Commands
335
-
336
- \`\`\`bash
337
- # Claim WU
338
- pnpm wu:claim --id WU-XXX --lane <lane>
339
- cd worktrees/<lane>-wu-xxx # IMMEDIATELY
340
-
341
- # Complete WU (from main)
342
- cd ../..
343
- pnpm wu:done --id WU-XXX
344
- \`\`\`
345
- `;
346
- const WORKTREE_DISCIPLINE_SKILL_TEMPLATE = `---
347
- name: worktree-discipline
348
- description: Prevents the "absolute path trap" in Write/Edit/Read tools.
349
- version: 1.0.0
350
- ---
351
-
352
- # Worktree Discipline: Absolute Path Trap Prevention
353
-
354
- **Purpose**: Prevent AI agents from bypassing worktree isolation via absolute file paths.
355
-
356
- ## The Absolute Path Trap
357
-
358
- **Problem**: AI agents using Write/Edit/Read tools can bypass worktree isolation by passing absolute paths.
359
-
360
- ## Golden Rules
361
-
362
- 1. **Always verify pwd** before file operations
363
- 2. **Never use absolute paths** in Write/Edit/Read tools
364
- 3. **When in doubt, use relative paths**
365
- `;
366
- const LUMENFLOW_GATES_SKILL_TEMPLATE = `---
367
- name: lumenflow-gates
368
- description: Quality gates troubleshooting (format, lint, typecheck, tests).
369
- version: 1.0.0
370
- ---
371
-
372
- # LumenFlow Gates Skill
373
-
374
- ## Gate Sequence
375
-
376
- \`\`\`
377
- pnpm gates = format:check -> lint -> typecheck -> spec:linter -> tests
378
- \`\`\`
379
-
380
- ## Fix Patterns
381
-
382
- | Gate | Auto-fix | Manual |
383
- | --------- | --------------- | ----------------------------------- |
384
- | Format | \`pnpm format\` | - |
385
- | Lint | \`pnpm lint:fix\` | Fix reported issues |
386
- | Typecheck | - | Fix type errors (first error first) |
387
- | Tests | - | Debug, fix mocks, update snapshots |
388
- `;
116
+ /**
117
+ * WU-1124: Template paths for agent onboarding docs
118
+ * Maps output file names to template paths
119
+ */
120
+ const ONBOARDING_TEMPLATE_PATHS = {
121
+ 'quick-ref-commands.md': 'core/ai/onboarding/quick-ref-commands.md.template',
122
+ 'first-wu-mistakes.md': 'core/ai/onboarding/first-wu-mistakes.md.template',
123
+ 'troubleshooting-wu-done.md': 'core/ai/onboarding/troubleshooting-wu-done.md.template',
124
+ 'agent-safety-card.md': 'core/ai/onboarding/agent-safety-card.md.template',
125
+ 'wu-create-checklist.md': 'core/ai/onboarding/wu-create-checklist.md.template',
126
+ };
127
+ /**
128
+ * WU-1124: Template paths for Claude skills
129
+ * Maps skill names to template paths
130
+ */
131
+ const SKILL_TEMPLATE_PATHS = {
132
+ 'wu-lifecycle': 'vendors/claude/.claude/skills/wu-lifecycle/SKILL.md.template',
133
+ 'worktree-discipline': 'vendors/claude/.claude/skills/worktree-discipline/SKILL.md.template',
134
+ 'lumenflow-gates': 'vendors/claude/.claude/skills/lumenflow-gates/SKILL.md.template',
135
+ };
389
136
  /**
390
137
  * Sync agent onboarding docs to an existing project
138
+ * WU-1124: Now reads templates from bundled files instead of hardcoded strings
391
139
  */
392
140
  export async function syncAgentDocs(targetDir, options) {
393
141
  const result = {
@@ -399,15 +147,17 @@ export async function syncAgentDocs(targetDir, options) {
399
147
  };
400
148
  const onboardingDir = path.join(targetDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
401
149
  await createDirectory(onboardingDir, result, targetDir);
402
- await createFile(path.join(onboardingDir, 'quick-ref-commands.md'), processTemplate(QUICK_REF_COMMANDS_TEMPLATE, tokens), options.force, result, targetDir);
403
- await createFile(path.join(onboardingDir, 'first-wu-mistakes.md'), processTemplate(FIRST_WU_MISTAKES_TEMPLATE, tokens), options.force, result, targetDir);
404
- await createFile(path.join(onboardingDir, 'troubleshooting-wu-done.md'), processTemplate(TROUBLESHOOTING_WU_DONE_TEMPLATE, tokens), options.force, result, targetDir);
405
- await createFile(path.join(onboardingDir, 'agent-safety-card.md'), processTemplate(AGENT_SAFETY_CARD_TEMPLATE, tokens), options.force, result, targetDir);
406
- await createFile(path.join(onboardingDir, 'wu-create-checklist.md'), processTemplate(WU_CREATE_CHECKLIST_TEMPLATE, tokens), options.force, result, targetDir);
150
+ // WU-1124: Load and process templates from bundled files
151
+ for (const [outputFile, templatePath] of Object.entries(ONBOARDING_TEMPLATE_PATHS)) {
152
+ const templateContent = loadTemplate(templatePath);
153
+ const processedContent = processTemplate(templateContent, tokens);
154
+ await createFile(path.join(onboardingDir, outputFile), processedContent, options.force, result, targetDir);
155
+ }
407
156
  return result;
408
157
  }
409
158
  /**
410
159
  * Sync Claude skills to an existing project
160
+ * WU-1124: Now reads templates from bundled files instead of hardcoded strings
411
161
  */
412
162
  export async function syncSkills(targetDir, options) {
413
163
  const result = {
@@ -422,18 +172,14 @@ export async function syncSkills(targetDir, options) {
422
172
  DATE: getCurrentDate(),
423
173
  };
424
174
  const skillsDir = path.join(targetDir, '.claude', 'skills');
425
- // wu-lifecycle skill
426
- const wuLifecycleDir = path.join(skillsDir, 'wu-lifecycle');
427
- await createDirectory(wuLifecycleDir, result, targetDir);
428
- await createFile(path.join(wuLifecycleDir, 'SKILL.md'), processTemplate(WU_LIFECYCLE_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
429
- // worktree-discipline skill
430
- const worktreeDir = path.join(skillsDir, 'worktree-discipline');
431
- await createDirectory(worktreeDir, result, targetDir);
432
- await createFile(path.join(worktreeDir, 'SKILL.md'), processTemplate(WORKTREE_DISCIPLINE_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
433
- // lumenflow-gates skill
434
- const gatesDir = path.join(skillsDir, 'lumenflow-gates');
435
- await createDirectory(gatesDir, result, targetDir);
436
- await createFile(path.join(gatesDir, 'SKILL.md'), processTemplate(LUMENFLOW_GATES_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
175
+ // WU-1124: Load and process skill templates from bundled files
176
+ for (const [skillName, templatePath] of Object.entries(SKILL_TEMPLATE_PATHS)) {
177
+ const skillDir = path.join(skillsDir, skillName);
178
+ await createDirectory(skillDir, result, targetDir);
179
+ const templateContent = loadTemplate(templatePath);
180
+ const processedContent = processTemplate(templateContent, tokens);
181
+ await createFile(path.join(skillDir, 'SKILL.md'), processedContent, options.force, result, targetDir);
182
+ }
437
183
  return result;
438
184
  }
439
185
  /**
@@ -3,13 +3,16 @@
3
3
  * LumenFlow Upgrade CLI Command
4
4
  *
5
5
  * Updates all @lumenflow/* packages to a specified version or latest.
6
- * Uses worktree pattern to ensure pnpm install runs in worktree, not main.
6
+ * Uses micro-worktree pattern for atomic changes to main without requiring
7
+ * users to be in a worktree.
7
8
  *
8
9
  * WU-1112: INIT-003 Phase 6 - Migrate remaining Tier 1 tools
10
+ * WU-1127: Use micro-worktree isolation pattern (fixes user blocking issue)
9
11
  *
10
- * Key requirements (from WU acceptance criteria):
11
- * - Uses worktree pattern (install runs in worktree, not main)
12
- * - Checks all 7 @lumenflow/* packages (not just 4)
12
+ * Key requirements:
13
+ * - Uses micro-worktree pattern (atomic changes, no user worktree needed)
14
+ * - Runs from main checkout (not inside a worktree)
15
+ * - Checks all 7 @lumenflow/* packages
13
16
  *
14
17
  * Usage:
15
18
  * pnpm lumenflow:upgrade --version 1.5.0
@@ -17,11 +20,14 @@
17
20
  * pnpm lumenflow:upgrade --latest --dry-run
18
21
  */
19
22
  import { execSync } from 'node:child_process';
20
- import { STDIO_MODES, EXIT_CODES, PKG_MANAGER, } from '@lumenflow/core/dist/wu-constants.js';
23
+ import { STDIO_MODES, EXIT_CODES, PKG_MANAGER, DEFAULTS, BRANCHES, } from '@lumenflow/core/dist/wu-constants.js';
24
+ import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
25
+ import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
21
26
  import { runCLI } from './cli-entry-point.js';
22
- import { validateWorktreeContext } from './deps-add.js';
23
27
  /** Log prefix for console output */
24
28
  const LOG_PREFIX = '[lumenflow:upgrade]';
29
+ /** Operation name for micro-worktree */
30
+ const OPERATION_NAME = 'lumenflow-upgrade';
25
31
  /**
26
32
  * All @lumenflow/* packages that should be upgraded together
27
33
  *
@@ -85,6 +91,98 @@ export function buildUpgradeCommands(args) {
85
91
  versionSpec,
86
92
  };
87
93
  }
94
+ /**
95
+ * WU-1127: Validate that the command is run from main checkout
96
+ *
97
+ * The micro-worktree pattern requires the command to be run from the main
98
+ * checkout (not inside a worktree). This is the inverse of the old behavior
99
+ * which required users to be IN a worktree.
100
+ *
101
+ * @returns Validation result with error and fix command if invalid
102
+ */
103
+ export async function validateMainCheckout() {
104
+ const cwd = process.cwd();
105
+ const worktreesDir = `/${DEFAULTS.WORKTREES_DIR}/`;
106
+ // Check if we're inside a worktree directory
107
+ if (cwd.includes(worktreesDir)) {
108
+ return {
109
+ valid: false,
110
+ error: `Cannot run lumenflow:upgrade from inside a worktree.\n\n` +
111
+ `This command must be run from main checkout because it uses\n` +
112
+ `micro-worktree isolation to atomically update package.json and lockfile.`,
113
+ fixCommand: `cd to main checkout and re-run:\n cd <main-checkout>\n pnpm lumenflow:upgrade --latest`,
114
+ };
115
+ }
116
+ // Check if we're on main branch
117
+ try {
118
+ const git = getGitForCwd();
119
+ const currentBranch = await git.raw(['rev-parse', '--abbrev-ref', 'HEAD']);
120
+ const branchName = currentBranch.trim();
121
+ if (branchName !== BRANCHES.MAIN) {
122
+ return {
123
+ valid: false,
124
+ error: `lumenflow:upgrade must be run from main checkout (on main branch).\n\n` +
125
+ `Current branch: ${branchName}\n` +
126
+ `Expected branch: main`,
127
+ fixCommand: `Switch to main branch:\n git checkout main\n pnpm lumenflow:upgrade --latest`,
128
+ };
129
+ }
130
+ }
131
+ catch (error) {
132
+ // If git fails, assume we're not in a valid git repo
133
+ return {
134
+ valid: false,
135
+ error: `Failed to detect git branch. Ensure you're in a git repository.`,
136
+ };
137
+ }
138
+ return { valid: true };
139
+ }
140
+ /**
141
+ * WU-1127: Execute the upgrade in a micro-worktree
142
+ *
143
+ * Uses the shared micro-worktree pattern (like wu:create, wu:edit) to:
144
+ * 1. Create a temporary worktree without switching main checkout
145
+ * 2. Run pnpm add in the temporary worktree
146
+ * 3. Commit the changes
147
+ * 4. FF-only merge to main
148
+ * 5. Push to origin
149
+ * 6. Cleanup
150
+ *
151
+ * @param args - Parsed upgrade arguments
152
+ * @returns Promise resolving when upgrade is complete
153
+ */
154
+ export async function executeUpgradeInMicroWorktree(args) {
155
+ const { addCommand, versionSpec } = buildUpgradeCommands(args);
156
+ // Generate unique ID for this upgrade operation using timestamp
157
+ const upgradeId = `upgrade-${Date.now()}`;
158
+ console.log(`${LOG_PREFIX} Using micro-worktree isolation (WU-1127)`);
159
+ console.log(`${LOG_PREFIX} Upgrading @lumenflow/* packages to ${versionSpec}`);
160
+ console.log(`${LOG_PREFIX} Packages: ${LUMENFLOW_PACKAGES.length} packages`);
161
+ await withMicroWorktree({
162
+ operation: OPERATION_NAME,
163
+ id: upgradeId,
164
+ logPrefix: LOG_PREFIX,
165
+ execute: async ({ worktreePath }) => {
166
+ console.log(`${LOG_PREFIX} Running: ${addCommand}`);
167
+ // Execute pnpm add in the micro-worktree
168
+ execSync(addCommand, {
169
+ stdio: STDIO_MODES.INHERIT,
170
+ cwd: worktreePath,
171
+ });
172
+ console.log(`${LOG_PREFIX} Package installation complete`);
173
+ // Return files to stage and commit message
174
+ return {
175
+ commitMessage: `chore: upgrade @lumenflow packages to ${versionSpec}`,
176
+ files: ['package.json', 'pnpm-lock.yaml'],
177
+ };
178
+ },
179
+ });
180
+ console.log(`\n${LOG_PREFIX} Upgrade complete!`);
181
+ console.log(`${LOG_PREFIX} Upgraded to ${versionSpec}`);
182
+ console.log(`\n${LOG_PREFIX} Next steps:`);
183
+ console.log(` 1. Run 'pnpm build' to rebuild with new versions`);
184
+ console.log(` 2. Run 'pnpm gates' to verify everything works`);
185
+ }
88
186
  /**
89
187
  * Print help message for lumenflow-upgrade
90
188
  */
@@ -94,7 +192,7 @@ function printHelp() {
94
192
  Usage: lumenflow-upgrade [options]
95
193
 
96
194
  Upgrade all @lumenflow/* packages to a specified version.
97
- Must be run from inside a worktree to enforce worktree discipline.
195
+ Uses micro-worktree isolation to atomically update packages from main checkout.
98
196
 
99
197
  Options:
100
198
  -v, --version <ver> Upgrade to specific version (e.g., 1.5.0)
@@ -110,13 +208,13 @@ Examples:
110
208
  lumenflow:upgrade --latest # Upgrade to latest
111
209
  lumenflow:upgrade --latest --dry-run # Preview upgrade commands
112
210
 
113
- Worktree Discipline:
114
- This command only works inside a worktree to prevent lockfile
115
- conflicts on main checkout. Claim a WU first:
211
+ Micro-Worktree Pattern (WU-1127):
212
+ This command uses micro-worktree isolation to atomically update
213
+ package.json and pnpm-lock.yaml without requiring you to claim a WU.
116
214
 
117
- pnpm wu:claim --id WU-XXXX --lane "Your Lane"
118
- cd worktrees/<lane>-wu-<id>/
119
- lumenflow:upgrade --latest
215
+ Run from your main checkout (NOT from inside a worktree):
216
+ cd /path/to/main
217
+ pnpm lumenflow:upgrade --latest
120
218
  `);
121
219
  }
122
220
  /**
@@ -135,40 +233,32 @@ async function main() {
135
233
  printHelp();
136
234
  process.exit(EXIT_CODES.ERROR);
137
235
  }
138
- // Validate worktree context (WU-1112 requirement: must run in worktree)
139
- const validation = validateWorktreeContext(process.cwd());
236
+ // WU-1127: Validate we're on main checkout (not in a worktree)
237
+ const validation = await validateMainCheckout();
140
238
  if (!validation.valid) {
141
239
  console.error(`${LOG_PREFIX} ${validation.error}`);
142
- console.error(`\nTo fix:\n${validation.fixCommand}`);
240
+ if (validation.fixCommand) {
241
+ console.error(`\nTo fix:\n${validation.fixCommand}`);
242
+ }
143
243
  process.exit(EXIT_CODES.ERROR);
144
244
  }
145
- // Build upgrade commands
245
+ // Build upgrade commands for dry-run display
146
246
  const { addCommand, versionSpec } = buildUpgradeCommands(args);
147
- console.log(`${LOG_PREFIX} Upgrading @lumenflow/* packages to ${versionSpec}`);
148
- console.log(`${LOG_PREFIX} Packages: ${LUMENFLOW_PACKAGES.length} packages`);
149
247
  if (args.dryRun) {
150
- console.log(`\n${LOG_PREFIX} DRY RUN - Commands that would be executed:`);
248
+ console.log(`${LOG_PREFIX} DRY RUN - Commands that would be executed:`);
151
249
  console.log(` ${addCommand}`);
250
+ console.log(`\n${LOG_PREFIX} Packages: ${LUMENFLOW_PACKAGES.length}`);
251
+ console.log(`${LOG_PREFIX} Version: ${versionSpec}`);
152
252
  console.log(`\n${LOG_PREFIX} No changes made.`);
153
253
  process.exit(EXIT_CODES.SUCCESS);
154
254
  }
155
- // Execute upgrade
156
- console.log(`${LOG_PREFIX} Running: ${addCommand}`);
255
+ // Execute upgrade using micro-worktree
157
256
  try {
158
- execSync(addCommand, {
159
- stdio: STDIO_MODES.INHERIT,
160
- cwd: process.cwd(),
161
- });
162
- console.log(`\n${LOG_PREFIX} ✅ Upgrade complete!`);
163
- console.log(`${LOG_PREFIX} Upgraded to ${versionSpec}`);
164
- console.log(`\n${LOG_PREFIX} Next steps:`);
165
- console.log(` 1. Run 'pnpm build' to rebuild with new versions`);
166
- console.log(` 2. Run 'pnpm gates' to verify everything works`);
167
- console.log(` 3. Commit the changes`);
257
+ await executeUpgradeInMicroWorktree(args);
168
258
  }
169
259
  catch (error) {
170
- console.error(`\n${LOG_PREFIX} Upgrade failed`);
171
- console.error(`${LOG_PREFIX} Check the error above and try again.`);
260
+ console.error(`\n${LOG_PREFIX} Upgrade failed`);
261
+ console.error(`${LOG_PREFIX} ${error instanceof Error ? error.message : String(error)}`);
172
262
  process.exit(EXIT_CODES.ERROR);
173
263
  }
174
264
  }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @file sync-templates.ts
3
+ * Sync internal docs to CLI templates for release-cycle maintenance (WU-1123)
4
+ *
5
+ * This script syncs source docs from the hellmai/os repo to the templates
6
+ * directory, applying template variable substitutions:
7
+ * - Onboarding docs -> templates/core/ai/onboarding/
8
+ * - Claude skills -> templates/vendors/claude/.claude/skills/
9
+ * - Core docs (LUMENFLOW.md, constraints.md) -> templates/core/
10
+ */
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import { createWUParser } from '@lumenflow/core';
14
+ // Template variable patterns
15
+ const DATE_PATTERN = /\d{4}-\d{2}-\d{2}/g;
16
+ /**
17
+ * CLI option definitions for sync-templates command
18
+ */
19
+ const SYNC_TEMPLATES_OPTIONS = {
20
+ dryRun: {
21
+ name: 'dry-run',
22
+ flags: '--dry-run',
23
+ description: 'Show what would be synced without writing files',
24
+ default: false,
25
+ },
26
+ verbose: {
27
+ name: 'verbose',
28
+ flags: '--verbose',
29
+ description: 'Show detailed output',
30
+ default: false,
31
+ },
32
+ };
33
+ /**
34
+ * Parse sync-templates command options
35
+ */
36
+ export function parseSyncTemplatesOptions() {
37
+ const opts = createWUParser({
38
+ name: 'sync-templates',
39
+ description: 'Sync internal docs to CLI templates for release-cycle maintenance',
40
+ options: Object.values(SYNC_TEMPLATES_OPTIONS),
41
+ });
42
+ return {
43
+ dryRun: opts['dry-run'] ?? false,
44
+ verbose: opts.verbose ?? false,
45
+ };
46
+ }
47
+ /**
48
+ * Convert source content to template format by replacing:
49
+ * - YYYY-MM-DD dates with {{DATE}}
50
+ * - Absolute project paths with {{PROJECT_ROOT}}
51
+ */
52
+ export function convertToTemplate(content, projectRoot) {
53
+ let output = content;
54
+ // Replace dates with {{DATE}}
55
+ output = output.replace(DATE_PATTERN, '{{DATE}}');
56
+ // Replace absolute project paths with {{PROJECT_ROOT}}
57
+ // Escape special regex characters in the path
58
+ const escapedPath = projectRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
59
+ const pathPattern = new RegExp(escapedPath, 'g');
60
+ output = output.replace(pathPattern, '{{PROJECT_ROOT}}');
61
+ return output;
62
+ }
63
+ /**
64
+ * Get the templates directory path
65
+ */
66
+ function getTemplatesDir(projectRoot) {
67
+ return path.join(projectRoot, 'packages', '@lumenflow', 'cli', 'templates');
68
+ }
69
+ /**
70
+ * Ensure directory exists
71
+ */
72
+ function ensureDir(dirPath) {
73
+ if (!fs.existsSync(dirPath)) {
74
+ fs.mkdirSync(dirPath, { recursive: true });
75
+ }
76
+ }
77
+ /**
78
+ * Sync a single file to templates
79
+ */
80
+ function syncFile(sourcePath, targetPath, projectRoot, result, dryRun = false) {
81
+ try {
82
+ if (!fs.existsSync(sourcePath)) {
83
+ result.errors.push(`Source not found: ${sourcePath}`);
84
+ return;
85
+ }
86
+ const content = fs.readFileSync(sourcePath, 'utf-8');
87
+ const templateContent = convertToTemplate(content, projectRoot);
88
+ if (!dryRun) {
89
+ ensureDir(path.dirname(targetPath));
90
+ fs.writeFileSync(targetPath, templateContent);
91
+ }
92
+ result.synced.push(path.relative(projectRoot, targetPath));
93
+ }
94
+ catch (error) {
95
+ result.errors.push(`Error syncing ${sourcePath}: ${error.message}`);
96
+ }
97
+ }
98
+ /**
99
+ * Sync onboarding docs to templates/core/ai/onboarding/
100
+ */
101
+ export async function syncOnboardingDocs(projectRoot, dryRun = false) {
102
+ const result = { synced: [], errors: [] };
103
+ const sourceDir = path.join(projectRoot, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
104
+ const targetDir = path.join(getTemplatesDir(projectRoot), 'core', 'ai', 'onboarding');
105
+ if (!fs.existsSync(sourceDir)) {
106
+ result.errors.push(`Onboarding source directory not found: ${sourceDir}`);
107
+ return result;
108
+ }
109
+ const files = fs.readdirSync(sourceDir).filter((f) => f.endsWith('.md'));
110
+ for (const file of files) {
111
+ const sourcePath = path.join(sourceDir, file);
112
+ const targetPath = path.join(targetDir, `${file}.template`);
113
+ syncFile(sourcePath, targetPath, projectRoot, result, dryRun);
114
+ }
115
+ return result;
116
+ }
117
+ /**
118
+ * Sync Claude skills to templates/vendors/claude/.claude/skills/
119
+ */
120
+ export async function syncSkillsToTemplates(projectRoot, dryRun = false) {
121
+ const result = { synced: [], errors: [] };
122
+ const sourceDir = path.join(projectRoot, '.claude', 'skills');
123
+ const targetDir = path.join(getTemplatesDir(projectRoot), 'vendors', 'claude', '.claude', 'skills');
124
+ if (!fs.existsSync(sourceDir)) {
125
+ result.errors.push(`Skills source directory not found: ${sourceDir}`);
126
+ return result;
127
+ }
128
+ // Get all skill directories
129
+ const skillDirs = fs
130
+ .readdirSync(sourceDir, { withFileTypes: true })
131
+ .filter((d) => d.isDirectory())
132
+ .map((d) => d.name);
133
+ for (const skillName of skillDirs) {
134
+ const skillSourceDir = path.join(sourceDir, skillName);
135
+ const skillTargetDir = path.join(targetDir, skillName);
136
+ // Look for SKILL.md file
137
+ const skillFile = path.join(skillSourceDir, 'SKILL.md');
138
+ if (fs.existsSync(skillFile)) {
139
+ const targetPath = path.join(skillTargetDir, 'SKILL.md.template');
140
+ syncFile(skillFile, targetPath, projectRoot, result, dryRun);
141
+ }
142
+ }
143
+ return result;
144
+ }
145
+ /**
146
+ * Sync core docs (LUMENFLOW.md, constraints.md) to templates/core/
147
+ */
148
+ export async function syncCoreDocs(projectRoot, dryRun = false) {
149
+ const result = { synced: [], errors: [] };
150
+ const templatesDir = getTemplatesDir(projectRoot);
151
+ // Sync LUMENFLOW.md
152
+ const lumenflowSource = path.join(projectRoot, 'LUMENFLOW.md');
153
+ const lumenflowTarget = path.join(templatesDir, 'core', 'LUMENFLOW.md.template');
154
+ syncFile(lumenflowSource, lumenflowTarget, projectRoot, result, dryRun);
155
+ // Sync constraints.md
156
+ const constraintsSource = path.join(projectRoot, '.lumenflow', 'constraints.md');
157
+ const constraintsTarget = path.join(templatesDir, 'core', '.lumenflow', 'constraints.md.template');
158
+ syncFile(constraintsSource, constraintsTarget, projectRoot, result, dryRun);
159
+ return result;
160
+ }
161
+ /**
162
+ * Sync all templates
163
+ */
164
+ export async function syncTemplates(projectRoot, dryRun = false) {
165
+ const onboarding = await syncOnboardingDocs(projectRoot, dryRun);
166
+ const skills = await syncSkillsToTemplates(projectRoot, dryRun);
167
+ const core = await syncCoreDocs(projectRoot, dryRun);
168
+ return { onboarding, skills, core };
169
+ }
170
+ /**
171
+ * CLI entry point
172
+ */
173
+ export async function main() {
174
+ const opts = parseSyncTemplatesOptions();
175
+ const projectRoot = process.cwd();
176
+ console.log('[sync-templates] Syncing internal docs to CLI templates...');
177
+ if (opts.dryRun) {
178
+ console.log(' (dry-run mode - no files will be written)');
179
+ }
180
+ const result = await syncTemplates(projectRoot, opts.dryRun);
181
+ // Print results
182
+ const sections = [
183
+ { name: 'Onboarding docs', data: result.onboarding },
184
+ { name: 'Claude skills', data: result.skills },
185
+ { name: 'Core docs', data: result.core },
186
+ ];
187
+ let totalSynced = 0;
188
+ let totalErrors = 0;
189
+ for (const section of sections) {
190
+ if (section.data.synced.length > 0 || section.data.errors.length > 0) {
191
+ console.log(`\n${section.name}:`);
192
+ if (section.data.synced.length > 0) {
193
+ section.data.synced.forEach((f) => console.log(` + ${f}`));
194
+ totalSynced += section.data.synced.length;
195
+ }
196
+ if (section.data.errors.length > 0) {
197
+ section.data.errors.forEach((e) => console.log(` ! ${e}`));
198
+ totalErrors += section.data.errors.length;
199
+ }
200
+ }
201
+ }
202
+ console.log(`\n[sync-templates] Done! Synced ${totalSynced} files.`);
203
+ if (totalErrors > 0) {
204
+ console.log(` ${totalErrors} error(s) occurred.`);
205
+ process.exitCode = 1;
206
+ }
207
+ }
208
+ // CLI entry point
209
+ import { runCLI } from './cli-entry-point.js';
210
+ if (import.meta.main) {
211
+ runCLI(main);
212
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Command-line interface for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -89,6 +89,8 @@
89
89
  "lumenflow": "./dist/init.js",
90
90
  "lumenflow-release": "./dist/release.js",
91
91
  "lumenflow-docs-sync": "./dist/docs-sync.js",
92
+ "lumenflow-sync-templates": "./dist/sync-templates.js",
93
+ "sync-templates": "./dist/sync-templates.js",
92
94
  "backlog-prune": "./dist/backlog-prune.js",
93
95
  "file-read": "./dist/file-read.js",
94
96
  "file-write": "./dist/file-write.js",
@@ -130,11 +132,11 @@
130
132
  "pretty-ms": "^9.2.0",
131
133
  "simple-git": "^3.30.0",
132
134
  "yaml": "^2.8.2",
133
- "@lumenflow/memory": "2.0.0",
134
- "@lumenflow/metrics": "1.6.0",
135
- "@lumenflow/core": "2.0.0",
136
- "@lumenflow/initiatives": "2.0.0",
137
- "@lumenflow/agent": "2.0.0"
135
+ "@lumenflow/core": "2.1.1",
136
+ "@lumenflow/metrics": "2.1.1",
137
+ "@lumenflow/memory": "2.1.1",
138
+ "@lumenflow/initiatives": "2.1.1",
139
+ "@lumenflow/agent": "2.1.1"
138
140
  },
139
141
  "devDependencies": {
140
142
  "@vitest/coverage-v8": "^4.0.17",