@lumenflow/cli 1.3.5 → 1.4.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/README.md CHANGED
@@ -63,6 +63,13 @@ This package provides CLI commands for the LumenFlow workflow framework, includi
63
63
  | `initiative-status` | Show initiative status and progress |
64
64
  | `initiative-add-wu` | Link a WU to an initiative |
65
65
 
66
+ ### Setup Commands
67
+
68
+ | Command | Description |
69
+ | ----------- | --------------------------------------------------- |
70
+ | `init` | Scaffold LumenFlow into a project |
71
+ | `docs-sync` | Sync agent onboarding docs (for upgrading projects) |
72
+
66
73
  ### Other Commands
67
74
 
68
75
  | Command | Description |
@@ -12,7 +12,7 @@
12
12
  * WU-1074: Add release command for npm publishing
13
13
  */
14
14
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
15
- import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
15
+ import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
17
  import { tmpdir } from 'node:os';
18
18
  // Import functions under test
@@ -135,3 +135,149 @@ describe('release command integration', () => {
135
135
  expect(typeof module.updatePackageVersions).toBe('function');
136
136
  });
137
137
  });
138
+ /**
139
+ * WU-1077: Tests for release script bug fixes
140
+ *
141
+ * Verifies:
142
+ * - hasNpmAuth() detects auth from ~/.npmrc not just env vars
143
+ * - Changeset pre mode is detected and exited in micro-worktree
144
+ * - Tag push bypasses pre-push hooks via LUMENFLOW_FORCE
145
+ */
146
+ describe('WU-1077: release script bug fixes', () => {
147
+ describe('hasNpmAuth - ~/.npmrc detection', () => {
148
+ let testDir;
149
+ beforeEach(() => {
150
+ testDir = join(tmpdir(), `release-npmrc-test-${Date.now()}`);
151
+ mkdirSync(testDir, { recursive: true });
152
+ });
153
+ afterEach(() => {
154
+ rmSync(testDir, { recursive: true, force: true });
155
+ });
156
+ it('should detect auth from ~/.npmrc authToken line', async () => {
157
+ // Import the function we're testing
158
+ const { hasNpmAuth } = await import('../release.js');
159
+ // Create a mock .npmrc with auth token
160
+ const npmrcPath = join(testDir, '.npmrc');
161
+ writeFileSync(npmrcPath, '//registry.npmjs.org/:_authToken=npm_testToken123\n');
162
+ // Test that it detects auth from the file
163
+ const result = hasNpmAuth(npmrcPath);
164
+ expect(result).toBe(true);
165
+ });
166
+ it('should return false when ~/.npmrc has no auth token', async () => {
167
+ const { hasNpmAuth } = await import('../release.js');
168
+ // Create a mock .npmrc without auth token
169
+ const npmrcPath = join(testDir, '.npmrc');
170
+ writeFileSync(npmrcPath, 'registry=https://registry.npmjs.org\n');
171
+ const result = hasNpmAuth(npmrcPath);
172
+ expect(result).toBe(false);
173
+ });
174
+ it('should return false when ~/.npmrc does not exist', async () => {
175
+ const { hasNpmAuth } = await import('../release.js');
176
+ // Non-existent path
177
+ const npmrcPath = join(testDir, 'nonexistent', '.npmrc');
178
+ const result = hasNpmAuth(npmrcPath);
179
+ expect(result).toBe(false);
180
+ });
181
+ it('should still detect auth from NPM_TOKEN env var', async () => {
182
+ const { hasNpmAuth } = await import('../release.js');
183
+ // Set env var
184
+ const originalNpmToken = process.env.NPM_TOKEN;
185
+ process.env.NPM_TOKEN = 'test_token';
186
+ try {
187
+ // No npmrc file provided, should check env var
188
+ const result = hasNpmAuth();
189
+ expect(result).toBe(true);
190
+ }
191
+ finally {
192
+ // Restore
193
+ if (originalNpmToken === undefined) {
194
+ delete process.env.NPM_TOKEN;
195
+ }
196
+ else {
197
+ process.env.NPM_TOKEN = originalNpmToken;
198
+ }
199
+ }
200
+ });
201
+ });
202
+ describe('isInChangesetPreMode', () => {
203
+ let testDir;
204
+ beforeEach(() => {
205
+ testDir = join(tmpdir(), `release-pre-test-${Date.now()}`);
206
+ mkdirSync(testDir, { recursive: true });
207
+ });
208
+ afterEach(() => {
209
+ rmSync(testDir, { recursive: true, force: true });
210
+ });
211
+ it('should return true when .changeset/pre.json exists', async () => {
212
+ const { isInChangesetPreMode } = await import('../release.js');
213
+ // Create .changeset directory and pre.json
214
+ const changesetDir = join(testDir, '.changeset');
215
+ mkdirSync(changesetDir, { recursive: true });
216
+ writeFileSync(join(changesetDir, 'pre.json'), JSON.stringify({
217
+ mode: 'pre',
218
+ tag: 'next',
219
+ initialVersions: {},
220
+ changesets: [],
221
+ }));
222
+ const result = isInChangesetPreMode(testDir);
223
+ expect(result).toBe(true);
224
+ });
225
+ it('should return false when .changeset/pre.json does not exist', async () => {
226
+ const { isInChangesetPreMode } = await import('../release.js');
227
+ // Create .changeset directory without pre.json
228
+ const changesetDir = join(testDir, '.changeset');
229
+ mkdirSync(changesetDir, { recursive: true });
230
+ writeFileSync(join(changesetDir, 'config.json'), JSON.stringify({ access: 'public' }));
231
+ const result = isInChangesetPreMode(testDir);
232
+ expect(result).toBe(false);
233
+ });
234
+ it('should return false when .changeset directory does not exist', async () => {
235
+ const { isInChangesetPreMode } = await import('../release.js');
236
+ const result = isInChangesetPreMode(testDir);
237
+ expect(result).toBe(false);
238
+ });
239
+ });
240
+ describe('exitChangesetPreMode', () => {
241
+ let testDir;
242
+ beforeEach(() => {
243
+ testDir = join(tmpdir(), `release-exit-pre-test-${Date.now()}`);
244
+ mkdirSync(testDir, { recursive: true });
245
+ });
246
+ afterEach(() => {
247
+ rmSync(testDir, { recursive: true, force: true });
248
+ });
249
+ it('should delete .changeset/pre.json to exit pre mode', async () => {
250
+ const { exitChangesetPreMode, isInChangesetPreMode } = await import('../release.js');
251
+ // Create .changeset directory and pre.json
252
+ const changesetDir = join(testDir, '.changeset');
253
+ mkdirSync(changesetDir, { recursive: true });
254
+ const preJsonPath = join(changesetDir, 'pre.json');
255
+ writeFileSync(preJsonPath, JSON.stringify({
256
+ mode: 'pre',
257
+ tag: 'next',
258
+ initialVersions: {},
259
+ changesets: [],
260
+ }));
261
+ // Verify pre mode is active
262
+ expect(isInChangesetPreMode(testDir)).toBe(true);
263
+ // Exit pre mode
264
+ exitChangesetPreMode(testDir);
265
+ // Verify pre mode is no longer active
266
+ expect(isInChangesetPreMode(testDir)).toBe(false);
267
+ expect(existsSync(preJsonPath)).toBe(false);
268
+ });
269
+ it('should not throw when .changeset/pre.json does not exist', async () => {
270
+ const { exitChangesetPreMode } = await import('../release.js');
271
+ // No pre.json file exists
272
+ expect(() => exitChangesetPreMode(testDir)).not.toThrow();
273
+ });
274
+ });
275
+ describe('pushTagWithForce', () => {
276
+ it('should export pushTagWithForce function', async () => {
277
+ const { pushTagWithForce } = await import('../release.js');
278
+ expect(typeof pushTagWithForce).toBe('function');
279
+ });
280
+ // Integration test would require git setup - functional verification
281
+ // is done by checking the function uses LUMENFLOW_FORCE env var
282
+ });
283
+ });
@@ -0,0 +1,452 @@
1
+ /**
2
+ * @file docs-sync.ts
3
+ * LumenFlow docs:sync command for syncing agent docs to existing projects (WU-1083)
4
+ */
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ /**
8
+ * Get current date in YYYY-MM-DD format
9
+ */
10
+ function getCurrentDate() {
11
+ return new Date().toISOString().split('T')[0];
12
+ }
13
+ /**
14
+ * Process template content by replacing placeholders
15
+ */
16
+ function processTemplate(content, tokens) {
17
+ let output = content;
18
+ for (const [key, value] of Object.entries(tokens)) {
19
+ output = output.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
20
+ }
21
+ return output;
22
+ }
23
+ function getRelativePath(targetDir, filePath) {
24
+ return path.relative(targetDir, filePath).split(path.sep).join('/');
25
+ }
26
+ /**
27
+ * Create a directory if missing
28
+ */
29
+ async function createDirectory(dirPath, result, targetDir) {
30
+ if (!fs.existsSync(dirPath)) {
31
+ fs.mkdirSync(dirPath, { recursive: true });
32
+ result.created.push(getRelativePath(targetDir, dirPath));
33
+ }
34
+ }
35
+ /**
36
+ * Create a file, respecting force option
37
+ */
38
+ async function createFile(filePath, content, force, result, targetDir) {
39
+ const relativePath = getRelativePath(targetDir, filePath);
40
+ if (fs.existsSync(filePath) && !force) {
41
+ result.skipped.push(relativePath);
42
+ return;
43
+ }
44
+ const parentDir = path.dirname(filePath);
45
+ if (!fs.existsSync(parentDir)) {
46
+ fs.mkdirSync(parentDir, { recursive: true });
47
+ }
48
+ fs.writeFileSync(filePath, content);
49
+ result.created.push(relativePath);
50
+ }
51
+ // Agent onboarding docs templates (duplicated from init.ts for modularity)
52
+ const QUICK_REF_COMMANDS_TEMPLATE = `# Quick Reference: LumenFlow Commands
53
+
54
+ **Last updated:** {{DATE}}
55
+
56
+ ---
57
+
58
+ ## Project Setup
59
+
60
+ | Command | Description |
61
+ | --------------------------------------------- | --------------------------------------- |
62
+ | \`pnpm exec lumenflow init\` | Scaffold minimal LumenFlow core |
63
+ | \`pnpm exec lumenflow init --full\` | Add docs/04-operations task scaffolding |
64
+ | \`pnpm exec lumenflow init --framework <name>\` | Add framework hint + overlay docs |
65
+ | \`pnpm exec lumenflow init --force\` | Overwrite existing files |
66
+
67
+ ---
68
+
69
+ ## WU Management
70
+
71
+ | Command | Description |
72
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
73
+ | \`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 |
74
+ | \`pnpm wu:claim --id WU-XXX --lane <Lane>\` | Claim WU (creates worktree) |
75
+ | \`pnpm wu:done --id WU-XXX\` | Complete WU (merge, stamp, cleanup) |
76
+ | \`pnpm wu:block --id WU-XXX --reason "Reason"\` | Block a WU |
77
+ | \`pnpm wu:unblock --id WU-XXX\` | Unblock a WU |
78
+
79
+ ---
80
+
81
+ ## Gates
82
+
83
+ | Command | Description |
84
+ | ------------------------ | -------------------------- |
85
+ | \`pnpm gates\` | Run all quality gates |
86
+ | \`pnpm gates --docs-only\` | Run gates for docs changes |
87
+ | \`pnpm format\` | Format all files |
88
+ | \`pnpm lint\` | Run linter |
89
+ | \`pnpm typecheck\` | Run TypeScript check |
90
+
91
+ ---
92
+
93
+ ## File Paths
94
+
95
+ | Path | Description |
96
+ | ----------------------------------------- | -------------------- |
97
+ | \`docs/04-operations/tasks/wu/WU-XXX.yaml\` | WU specification |
98
+ | \`docs/04-operations/tasks/status.md\` | Current status board |
99
+ | \`.lumenflow/stamps/WU-XXX.done\` | Completion stamp |
100
+ | \`worktrees/<lane>-wu-xxx/\` | Worktree directory |
101
+ `;
102
+ const FIRST_WU_MISTAKES_TEMPLATE = `# First WU Mistakes
103
+
104
+ **Last updated:** {{DATE}}
105
+
106
+ Common mistakes agents make on their first WU, and how to avoid them.
107
+
108
+ ---
109
+
110
+ ## Mistake 1: Not Using Worktrees
111
+
112
+ ### Wrong
113
+
114
+ \`\`\`bash
115
+ # Working directly in main
116
+ vim src/feature.ts
117
+ git commit -m "feat: add feature"
118
+ git push origin main
119
+ \`\`\`
120
+
121
+ ### Right
122
+
123
+ \`\`\`bash
124
+ # Claim first, then work in worktree
125
+ pnpm wu:claim --id WU-123 --lane Core
126
+ cd worktrees/core-wu-123
127
+ vim src/feature.ts
128
+ git commit -m "feat: add feature"
129
+ git push origin lane/core/wu-123
130
+ cd /path/to/main
131
+ pnpm wu:done --id WU-123
132
+ \`\`\`
133
+
134
+ ---
135
+
136
+ ## Mistake 2: Forgetting to Run wu:done
137
+
138
+ **TL;DR:** After gates pass, ALWAYS run \`pnpm wu:done --id WU-XXX\`.
139
+
140
+ ---
141
+
142
+ ## Mistake 3: Working Outside code_paths
143
+
144
+ Only edit files within the specified \`code_paths\`.
145
+
146
+ ---
147
+
148
+ ## Quick Checklist
149
+
150
+ - [ ] Claim the WU with \`pnpm wu:claim\`
151
+ - [ ] cd to the worktree IMMEDIATELY
152
+ - [ ] Work only in the worktree
153
+ - [ ] Run gates before wu:done
154
+ - [ ] ALWAYS run wu:done
155
+ `;
156
+ const TROUBLESHOOTING_WU_DONE_TEMPLATE = `# Troubleshooting: wu:done Not Run
157
+
158
+ **Last updated:** {{DATE}}
159
+
160
+ This is the most common mistake agents make.
161
+
162
+ ---
163
+
164
+ ## The Fix
165
+
166
+ ### Rule: ALWAYS Run wu:done
167
+
168
+ After gates pass, you MUST run:
169
+
170
+ \`\`\`bash
171
+ cd /path/to/main
172
+ pnpm wu:done --id WU-XXX
173
+ \`\`\`
174
+
175
+ Do NOT:
176
+
177
+ - Ask "Should I run wu:done?"
178
+ - Write "To Complete: pnpm wu:done"
179
+ - Wait for permission
180
+
181
+ ---
182
+
183
+ ## What wu:done Does
184
+
185
+ 1. Validates the worktree exists and has commits
186
+ 2. Runs gates in the worktree (not main)
187
+ 3. Fast-forward merges to main
188
+ 4. Creates the done stamp
189
+ 5. Updates status and backlog docs
190
+ 6. Removes the worktree
191
+ 7. Pushes to origin
192
+ `;
193
+ const AGENT_SAFETY_CARD_TEMPLATE = `# Agent Safety Card
194
+
195
+ **Last updated:** {{DATE}}
196
+
197
+ Quick reference for AI agents working in LumenFlow projects.
198
+
199
+ ---
200
+
201
+ ## Stop and Ask When
202
+
203
+ - Same error repeats 3 times
204
+ - Auth or permissions changes needed
205
+ - PII/PHI/secrets involved
206
+ - Cloud spend decisions
207
+
208
+ ---
209
+
210
+ ## Never Do
211
+
212
+ | Action | Why |
213
+ | ------------------------ | ---------------- |
214
+ | \`git reset --hard\` | Data loss |
215
+ | \`git push --force\` | History rewrite |
216
+ | \`--no-verify\` | Bypasses safety |
217
+ | Work in main after claim | Breaks isolation |
218
+ | Skip wu:done | Incomplete WU |
219
+
220
+ ---
221
+
222
+ ## Always Do
223
+
224
+ | Action | Why |
225
+ | -------------------------- | ---------------- |
226
+ | Read WU spec first | Understand scope |
227
+ | cd to worktree after claim | Isolation |
228
+ | Write tests before code | TDD |
229
+ | Run gates before wu:done | Quality |
230
+ | Run wu:done | Complete WU |
231
+ `;
232
+ const WU_CREATE_CHECKLIST_TEMPLATE = `# WU Creation Checklist
233
+
234
+ **Last updated:** {{DATE}}
235
+
236
+ Before running \`pnpm wu:create\`, verify these items.
237
+
238
+ ---
239
+
240
+ ## Step 1: Check Valid Lanes
241
+
242
+ \`\`\`bash
243
+ grep -A 30 "lanes:" .lumenflow.config.yaml
244
+ \`\`\`
245
+
246
+ **Format:** \`"Parent: Sublane"\` (colon + single space)
247
+
248
+ ---
249
+
250
+ ## Step 2: Required Fields
251
+
252
+ | Field | Required For | Example |
253
+ |-------|--------------|---------|
254
+ | \`--id\` | All | \`WU-1234\` |
255
+ | \`--lane\` | All | \`"Experience: Chat"\` |
256
+ | \`--title\` | All | \`"Add feature"\` |
257
+ | \`--description\` | All | \`"Context: ... Problem: ... Solution: ..."\` |
258
+ | \`--acceptance\` | All | \`--acceptance "Works"\` (repeatable) |
259
+ | \`--exposure\` | All | \`ui\`, \`api\`, \`backend-only\`, \`documentation\` |
260
+ | \`--code-paths\` | Code WUs | \`"src/a.ts,src/b.ts"\` |
261
+ | \`--test-paths-unit\` | Code WUs | \`"src/__tests__/a.test.ts"\` |
262
+ | \`--spec-refs\` | Feature WUs | \`"~/.lumenflow/plans/WU-XXX.md"\` |
263
+
264
+ ---
265
+
266
+ ## Step 3: Plan Storage
267
+
268
+ Plans go in \`~/.lumenflow/plans/\` (NOT in project):
269
+
270
+ \`\`\`bash
271
+ mkdir -p ~/.lumenflow/plans
272
+ vim ~/.lumenflow/plans/WU-XXX-plan.md
273
+ \`\`\`
274
+
275
+ Reference in wu:create:
276
+ \`\`\`bash
277
+ --spec-refs "~/.lumenflow/plans/WU-XXX-plan.md"
278
+ \`\`\`
279
+
280
+ ---
281
+
282
+ ## Step 4: Validate First
283
+
284
+ \`\`\`bash
285
+ pnpm wu:create --id WU-XXX ... --validate
286
+ \`\`\`
287
+
288
+ Fix errors, then remove \`--validate\` to create.
289
+ `;
290
+ // Claude skills templates
291
+ const WU_LIFECYCLE_SKILL_TEMPLATE = `---
292
+ name: wu-lifecycle
293
+ description: Work Unit claim/block/done workflow automation.
294
+ version: 1.0.0
295
+ ---
296
+
297
+ # WU Lifecycle Skill
298
+
299
+ ## State Machine
300
+
301
+ \`\`\`
302
+ ready -> in_progress -> waiting/blocked -> done
303
+ \`\`\`
304
+
305
+ ## Core Commands
306
+
307
+ \`\`\`bash
308
+ # Claim WU
309
+ pnpm wu:claim --id WU-XXX --lane <lane>
310
+ cd worktrees/<lane>-wu-xxx # IMMEDIATELY
311
+
312
+ # Complete WU (from main)
313
+ cd ../..
314
+ pnpm wu:done --id WU-XXX
315
+ \`\`\`
316
+ `;
317
+ const WORKTREE_DISCIPLINE_SKILL_TEMPLATE = `---
318
+ name: worktree-discipline
319
+ description: Prevents the "absolute path trap" in Write/Edit/Read tools.
320
+ version: 1.0.0
321
+ ---
322
+
323
+ # Worktree Discipline: Absolute Path Trap Prevention
324
+
325
+ **Purpose**: Prevent AI agents from bypassing worktree isolation via absolute file paths.
326
+
327
+ ## The Absolute Path Trap
328
+
329
+ **Problem**: AI agents using Write/Edit/Read tools can bypass worktree isolation by passing absolute paths.
330
+
331
+ ## Golden Rules
332
+
333
+ 1. **Always verify pwd** before file operations
334
+ 2. **Never use absolute paths** in Write/Edit/Read tools
335
+ 3. **When in doubt, use relative paths**
336
+ `;
337
+ const LUMENFLOW_GATES_SKILL_TEMPLATE = `---
338
+ name: lumenflow-gates
339
+ description: Quality gates troubleshooting (format, lint, typecheck, tests).
340
+ version: 1.0.0
341
+ ---
342
+
343
+ # LumenFlow Gates Skill
344
+
345
+ ## Gate Sequence
346
+
347
+ \`\`\`
348
+ pnpm gates = format:check -> lint -> typecheck -> spec:linter -> tests
349
+ \`\`\`
350
+
351
+ ## Fix Patterns
352
+
353
+ | Gate | Auto-fix | Manual |
354
+ | --------- | --------------- | ----------------------------------- |
355
+ | Format | \`pnpm format\` | - |
356
+ | Lint | \`pnpm lint:fix\` | Fix reported issues |
357
+ | Typecheck | - | Fix type errors (first error first) |
358
+ | Tests | - | Debug, fix mocks, update snapshots |
359
+ `;
360
+ /**
361
+ * Sync agent onboarding docs to an existing project
362
+ */
363
+ export async function syncAgentDocs(targetDir, options) {
364
+ const result = {
365
+ created: [],
366
+ skipped: [],
367
+ };
368
+ const tokens = {
369
+ DATE: getCurrentDate(),
370
+ };
371
+ const onboardingDir = path.join(targetDir, 'docs', '04-operations', '_frameworks', 'lumenflow', 'agent', 'onboarding');
372
+ await createDirectory(onboardingDir, result, targetDir);
373
+ await createFile(path.join(onboardingDir, 'quick-ref-commands.md'), processTemplate(QUICK_REF_COMMANDS_TEMPLATE, tokens), options.force, result, targetDir);
374
+ await createFile(path.join(onboardingDir, 'first-wu-mistakes.md'), processTemplate(FIRST_WU_MISTAKES_TEMPLATE, tokens), options.force, result, targetDir);
375
+ await createFile(path.join(onboardingDir, 'troubleshooting-wu-done.md'), processTemplate(TROUBLESHOOTING_WU_DONE_TEMPLATE, tokens), options.force, result, targetDir);
376
+ await createFile(path.join(onboardingDir, 'agent-safety-card.md'), processTemplate(AGENT_SAFETY_CARD_TEMPLATE, tokens), options.force, result, targetDir);
377
+ await createFile(path.join(onboardingDir, 'wu-create-checklist.md'), processTemplate(WU_CREATE_CHECKLIST_TEMPLATE, tokens), options.force, result, targetDir);
378
+ return result;
379
+ }
380
+ /**
381
+ * Sync Claude skills to an existing project
382
+ */
383
+ export async function syncSkills(targetDir, options) {
384
+ const result = {
385
+ created: [],
386
+ skipped: [],
387
+ };
388
+ const vendor = options.vendor ?? 'none';
389
+ if (vendor !== 'claude' && vendor !== 'all') {
390
+ return result;
391
+ }
392
+ const tokens = {
393
+ DATE: getCurrentDate(),
394
+ };
395
+ const skillsDir = path.join(targetDir, '.claude', 'skills');
396
+ // wu-lifecycle skill
397
+ const wuLifecycleDir = path.join(skillsDir, 'wu-lifecycle');
398
+ await createDirectory(wuLifecycleDir, result, targetDir);
399
+ await createFile(path.join(wuLifecycleDir, 'SKILL.md'), processTemplate(WU_LIFECYCLE_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
400
+ // worktree-discipline skill
401
+ const worktreeDir = path.join(skillsDir, 'worktree-discipline');
402
+ await createDirectory(worktreeDir, result, targetDir);
403
+ await createFile(path.join(worktreeDir, 'SKILL.md'), processTemplate(WORKTREE_DISCIPLINE_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
404
+ // lumenflow-gates skill
405
+ const gatesDir = path.join(skillsDir, 'lumenflow-gates');
406
+ await createDirectory(gatesDir, result, targetDir);
407
+ await createFile(path.join(gatesDir, 'SKILL.md'), processTemplate(LUMENFLOW_GATES_SKILL_TEMPLATE, tokens), options.force, result, targetDir);
408
+ return result;
409
+ }
410
+ /**
411
+ * Parse vendor flag from arguments
412
+ */
413
+ function parseVendorArg(args) {
414
+ const vendorIndex = args.findIndex((arg) => arg === '--vendor');
415
+ if (vendorIndex !== -1 && args[vendorIndex + 1]) {
416
+ const vendor = args[vendorIndex + 1].toLowerCase();
417
+ if (['claude', 'cursor', 'aider', 'all', 'none'].includes(vendor)) {
418
+ return vendor;
419
+ }
420
+ }
421
+ return undefined;
422
+ }
423
+ /**
424
+ * CLI entry point for docs:sync command
425
+ */
426
+ export async function main() {
427
+ const args = process.argv.slice(2);
428
+ const force = args.includes('--force') || args.includes('-f');
429
+ const vendor = parseVendorArg(args) ?? 'claude'; // Default to claude
430
+ const targetDir = process.cwd();
431
+ console.log('[lumenflow docs:sync] Syncing agent documentation...');
432
+ console.log(` Vendor: ${vendor}`);
433
+ console.log(` Force: ${force}`);
434
+ const docsResult = await syncAgentDocs(targetDir, { force });
435
+ const skillsResult = await syncSkills(targetDir, { force, vendor });
436
+ const created = [...docsResult.created, ...skillsResult.created];
437
+ const skipped = [...docsResult.skipped, ...skillsResult.skipped];
438
+ if (created.length > 0) {
439
+ console.log('\nCreated:');
440
+ created.forEach((f) => console.log(` + ${f}`));
441
+ }
442
+ if (skipped.length > 0) {
443
+ console.log('\nSkipped (already exists, use --force to overwrite):');
444
+ skipped.forEach((f) => console.log(` - ${f}`));
445
+ }
446
+ console.log('\n[lumenflow docs:sync] Done!');
447
+ }
448
+ // CLI entry point (WU-1071 pattern: import.meta.main)
449
+ import { runCLI } from './cli-entry-point.js';
450
+ if (import.meta.main) {
451
+ runCLI(main);
452
+ }