@lumenflow/cli 2.8.0 → 2.9.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.
@@ -491,6 +491,172 @@ describe('lumenflow init', () => {
491
491
  });
492
492
  });
493
493
  });
494
+ // WU-1382: Improved templates for agent clarity
495
+ describe('WU-1382: improved templates for agent clarity', () => {
496
+ describe('CLAUDE.md template enhancements', () => {
497
+ it('should include CLI commands table inline in CLAUDE.md', async () => {
498
+ const options = {
499
+ force: false,
500
+ full: false,
501
+ client: 'claude',
502
+ };
503
+ await scaffoldProject(tempDir, options);
504
+ const claudeMdPath = path.join(tempDir, 'CLAUDE.md');
505
+ expect(fs.existsSync(claudeMdPath)).toBe(true);
506
+ const content = fs.readFileSync(claudeMdPath, 'utf-8');
507
+ // Should have CLI commands table with common commands
508
+ expect(content).toContain('| Command');
509
+ expect(content).toContain('wu:claim');
510
+ expect(content).toContain('wu:done');
511
+ expect(content).toContain('wu:status');
512
+ expect(content).toContain('gates');
513
+ });
514
+ it('should include warning about manual YAML editing in CLAUDE.md', async () => {
515
+ const options = {
516
+ force: false,
517
+ full: false,
518
+ client: 'claude',
519
+ };
520
+ await scaffoldProject(tempDir, options);
521
+ const claudeMdPath = path.join(tempDir, 'CLAUDE.md');
522
+ const content = fs.readFileSync(claudeMdPath, 'utf-8');
523
+ // Should warn against manual WU YAML edits
524
+ expect(content).toMatch(/do\s+not\s+(manually\s+)?edit|never\s+(manually\s+)?edit/i);
525
+ expect(content).toMatch(/wu.*yaml|yaml.*wu/i);
526
+ });
527
+ });
528
+ describe('config.yaml managed file header', () => {
529
+ it('should include managed file header in .lumenflow.config.yaml', async () => {
530
+ const options = {
531
+ force: false,
532
+ full: false,
533
+ };
534
+ await scaffoldProject(tempDir, options);
535
+ const configPath = path.join(tempDir, '.lumenflow.config.yaml');
536
+ expect(fs.existsSync(configPath)).toBe(true);
537
+ const content = fs.readFileSync(configPath, 'utf-8');
538
+ // Should have managed file header
539
+ expect(content).toMatch(/LUMENFLOW\s+MANAGED\s+FILE/i);
540
+ expect(content).toMatch(/do\s+not\s+(manually\s+)?edit/i);
541
+ });
542
+ });
543
+ describe('lane-inference.yaml managed file header', () => {
544
+ it('should include managed file header in .lumenflow.lane-inference.yaml', async () => {
545
+ const options = {
546
+ force: false,
547
+ full: true,
548
+ };
549
+ await scaffoldProject(tempDir, options);
550
+ const laneInferencePath = path.join(tempDir, '.lumenflow.lane-inference.yaml');
551
+ expect(fs.existsSync(laneInferencePath)).toBe(true);
552
+ const content = fs.readFileSync(laneInferencePath, 'utf-8');
553
+ // Should have managed file header
554
+ expect(content).toMatch(/LUMENFLOW\s+MANAGED\s+FILE/i);
555
+ expect(content).toMatch(/do\s+not\s+(manually\s+)?edit/i);
556
+ });
557
+ });
558
+ });
559
+ // WU-1383: CLI safeguards against manual file editing
560
+ describe('WU-1383: CLI safeguards for Claude client', () => {
561
+ const CONFIG_FILE_NAME = '.lumenflow.config.yaml';
562
+ describe('enforcement hooks enabled by default for --client claude', () => {
563
+ it('should add enforcement hooks config when --client claude is used', async () => {
564
+ const options = {
565
+ force: false,
566
+ full: false,
567
+ client: 'claude',
568
+ };
569
+ await scaffoldProject(tempDir, options);
570
+ const configPath = path.join(tempDir, CONFIG_FILE_NAME);
571
+ expect(fs.existsSync(configPath)).toBe(true);
572
+ const content = fs.readFileSync(configPath, 'utf-8');
573
+ // Should have enforcement hooks enabled for claude-code
574
+ expect(content).toContain('claude-code');
575
+ expect(content).toContain('enforcement');
576
+ expect(content).toContain('hooks: true');
577
+ });
578
+ it('should set block_outside_worktree to true by default for claude client', async () => {
579
+ const options = {
580
+ force: false,
581
+ full: false,
582
+ client: 'claude',
583
+ };
584
+ await scaffoldProject(tempDir, options);
585
+ const configPath = path.join(tempDir, CONFIG_FILE_NAME);
586
+ const content = fs.readFileSync(configPath, 'utf-8');
587
+ expect(content).toContain('block_outside_worktree: true');
588
+ });
589
+ it('should NOT add enforcement hooks for other clients like cursor', async () => {
590
+ const options = {
591
+ force: false,
592
+ full: false,
593
+ client: 'cursor',
594
+ };
595
+ await scaffoldProject(tempDir, options);
596
+ const configPath = path.join(tempDir, CONFIG_FILE_NAME);
597
+ const content = fs.readFileSync(configPath, 'utf-8');
598
+ // Should NOT have claude-code enforcement section (check for the nested enforcement block)
599
+ // Note: The default config has agents.defaultClient: claude-code, but no enforcement section
600
+ expect(content).not.toContain('block_outside_worktree');
601
+ expect(content).not.toMatch(/claude-code:\s*\n\s*enforcement/);
602
+ });
603
+ });
604
+ describe('warning when config already exists', () => {
605
+ it('should add warning to result when config yaml already exists', async () => {
606
+ // Create existing config file
607
+ fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), '# Existing config\ndirectories:\n tasksDir: docs/tasks\n');
608
+ const options = {
609
+ force: false,
610
+ full: false,
611
+ };
612
+ const result = await scaffoldProject(tempDir, options);
613
+ // Should have warning about existing config
614
+ expect(result.warnings).toBeDefined();
615
+ expect(result.warnings?.some((w) => w.includes('already exists'))).toBe(true);
616
+ // Warning should suggest CLI commands
617
+ expect(result.warnings?.some((w) => w.includes('CLI') || w.includes('lumenflow'))).toBe(true);
618
+ });
619
+ it('should skip config file when it already exists (not force)', async () => {
620
+ const existingContent = '# My custom config\n';
621
+ fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), existingContent);
622
+ const options = {
623
+ force: false,
624
+ full: false,
625
+ };
626
+ const result = await scaffoldProject(tempDir, options);
627
+ expect(result.skipped).toContain(CONFIG_FILE_NAME);
628
+ // Content should not be changed
629
+ const content = fs.readFileSync(path.join(tempDir, CONFIG_FILE_NAME), 'utf-8');
630
+ expect(content).toBe(existingContent);
631
+ });
632
+ });
633
+ describe('post-init output shows CLI commands prominently', () => {
634
+ // Note: These test the ScaffoldResult which contains info for the CLI output
635
+ // The main() function uses these to print output
636
+ it('should include CLI usage guidance in warnings when config exists', async () => {
637
+ fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), '# Existing\n');
638
+ const options = {
639
+ force: false,
640
+ full: false,
641
+ };
642
+ const result = await scaffoldProject(tempDir, options);
643
+ // Warning should mention CLI commands for editing config
644
+ expect(result.warnings?.some((w) => /pnpm|lumenflow|CLI/i.test(w))).toBe(true);
645
+ });
646
+ });
647
+ describe('warning message suggests CLI commands not manual editing', () => {
648
+ it('should warn users to use CLI commands instead of manual editing', async () => {
649
+ fs.writeFileSync(path.join(tempDir, CONFIG_FILE_NAME), '# Existing config\n');
650
+ const options = {
651
+ force: false,
652
+ full: false,
653
+ };
654
+ const result = await scaffoldProject(tempDir, options);
655
+ // Should have a warning that mentions not to manually edit
656
+ expect(result.warnings?.some((w) => w.includes('manual') || w.includes('CLI') || w.includes('lumenflow'))).toBe(true);
657
+ });
658
+ });
659
+ });
494
660
  // WU-1362: Branch guard tests for init.ts
495
661
  describe('WU-1362: branch guard for tracked file writes', () => {
496
662
  it('should block scaffold when on main branch and targeting main checkout', async () => {
@@ -518,4 +684,60 @@ describe('lumenflow init', () => {
518
684
  expect(result.created.length).toBeGreaterThan(0);
519
685
  });
520
686
  });
687
+ // WU-1385: Include wu-sizing-guide.md in lumenflow init onboarding docs
688
+ describe('WU-1385: wu-sizing-guide.md scaffolding', () => {
689
+ describe('wu-sizing-guide.md creation with --full', () => {
690
+ it('should scaffold wu-sizing-guide.md in onboarding docs with --full', async () => {
691
+ const options = {
692
+ force: false,
693
+ full: true,
694
+ docsStructure: 'arc42',
695
+ };
696
+ await scaffoldProject(tempDir, options);
697
+ const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
698
+ const sizingGuidePath = path.join(onboardingDir, 'wu-sizing-guide.md');
699
+ expect(fs.existsSync(sizingGuidePath)).toBe(true);
700
+ });
701
+ it('should include key sizing guide content', async () => {
702
+ const options = {
703
+ force: false,
704
+ full: true,
705
+ docsStructure: 'arc42',
706
+ };
707
+ await scaffoldProject(tempDir, options);
708
+ const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
709
+ const sizingGuidePath = path.join(onboardingDir, 'wu-sizing-guide.md');
710
+ const content = fs.readFileSync(sizingGuidePath, 'utf-8');
711
+ // Should have key content from the sizing guide
712
+ expect(content).toContain('Complexity');
713
+ expect(content).toContain('Tool Calls');
714
+ expect(content).toContain('Context');
715
+ });
716
+ it('should not scaffold wu-sizing-guide.md with --minimal (full=false)', async () => {
717
+ const options = {
718
+ force: false,
719
+ full: false,
720
+ };
721
+ await scaffoldProject(tempDir, options);
722
+ const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
723
+ const sizingGuidePath = path.join(onboardingDir, 'wu-sizing-guide.md');
724
+ expect(fs.existsSync(sizingGuidePath)).toBe(false);
725
+ });
726
+ });
727
+ describe('starting-prompt.md references sizing guide', () => {
728
+ it('should reference wu-sizing-guide.md in starting-prompt.md', async () => {
729
+ const options = {
730
+ force: false,
731
+ full: true,
732
+ docsStructure: 'arc42',
733
+ };
734
+ await scaffoldProject(tempDir, options);
735
+ const onboardingDir = path.join(tempDir, ONBOARDING_DOCS_PATH);
736
+ const startingPromptPath = path.join(onboardingDir, 'starting-prompt.md');
737
+ const content = fs.readFileSync(startingPromptPath, 'utf-8');
738
+ // Should reference the sizing guide
739
+ expect(content).toContain('wu-sizing-guide.md');
740
+ });
741
+ });
742
+ });
521
743
  });
@@ -0,0 +1,171 @@
1
+ /**
2
+ * @file commands.ts
3
+ * LumenFlow CLI commands discovery feature (WU-1378)
4
+ *
5
+ * Provides a way to discover all available CLI commands grouped by category.
6
+ * This helps agents and users find CLI workflows without reading docs.
7
+ */
8
+ import { createWUParser } from '@lumenflow/core';
9
+ import { runCLI } from './cli-entry-point.js';
10
+ /**
11
+ * Command categories organized by function
12
+ * Based on quick-ref-commands.md structure
13
+ */
14
+ const COMMAND_CATEGORIES = [
15
+ {
16
+ name: 'WU Lifecycle',
17
+ commands: [
18
+ { name: 'wu:create', description: 'Create new WU spec' },
19
+ { name: 'wu:claim', description: 'Claim WU and create worktree' },
20
+ { name: 'wu:prep', description: 'Run gates in worktree, prep for wu:done' },
21
+ { name: 'wu:done', description: 'Complete WU (merge, stamp, cleanup) from main' },
22
+ { name: 'wu:edit', description: 'Edit WU spec fields' },
23
+ { name: 'wu:block', description: 'Block WU with reason' },
24
+ { name: 'wu:unblock', description: 'Unblock WU' },
25
+ { name: 'wu:release', description: 'Release orphaned WU (in_progress to ready)' },
26
+ { name: 'wu:status', description: 'Show WU status, location, valid commands' },
27
+ { name: 'wu:spawn', description: 'Generate sub-agent spawn prompt' },
28
+ { name: 'wu:validate', description: 'Validate WU spec' },
29
+ { name: 'wu:recover', description: 'Analyze and fix WU state inconsistencies' },
30
+ ],
31
+ },
32
+ {
33
+ name: 'Gates & Quality',
34
+ commands: [
35
+ { name: 'gates', description: 'Run all quality gates' },
36
+ { name: 'format', description: 'Format all files (Prettier)' },
37
+ { name: 'lint', description: 'Run ESLint' },
38
+ { name: 'typecheck', description: 'Run TypeScript type checking' },
39
+ { name: 'test', description: 'Run all tests (Vitest)' },
40
+ { name: 'lane:health', description: 'Check lane config health' },
41
+ ],
42
+ },
43
+ {
44
+ name: 'Memory & Sessions',
45
+ commands: [
46
+ { name: 'mem:init', description: 'Initialize memory for WU' },
47
+ { name: 'mem:checkpoint', description: 'Save progress checkpoint' },
48
+ { name: 'mem:signal', description: 'Broadcast coordination signal' },
49
+ { name: 'mem:inbox', description: 'Check coordination signals' },
50
+ { name: 'mem:create', description: 'Create memory node (bug discovery)' },
51
+ { name: 'mem:context', description: 'Get context for current lane/WU' },
52
+ ],
53
+ },
54
+ {
55
+ name: 'Initiatives',
56
+ commands: [
57
+ { name: 'initiative:create', description: 'Create new initiative' },
58
+ { name: 'initiative:edit', description: 'Edit initiative fields' },
59
+ { name: 'initiative:list', description: 'List all initiatives' },
60
+ { name: 'initiative:status', description: 'Show initiative status' },
61
+ { name: 'initiative:add-wu', description: 'Add WU to initiative' },
62
+ ],
63
+ },
64
+ {
65
+ name: 'Orchestration',
66
+ commands: [
67
+ { name: 'orchestrate:initiative', description: 'Orchestrate initiative execution' },
68
+ { name: 'orchestrate:init-status', description: 'Compact initiative progress view' },
69
+ { name: 'orchestrate:monitor', description: 'Monitor spawn/agent activity' },
70
+ { name: 'spawn:list', description: 'List active spawned agents' },
71
+ ],
72
+ },
73
+ {
74
+ name: 'Setup & Development',
75
+ commands: [
76
+ { name: 'setup', description: 'Install deps and build CLI (first time)' },
77
+ { name: 'lumenflow', description: 'Initialize LumenFlow in a project' },
78
+ { name: 'lumenflow:doctor', description: 'Diagnose LumenFlow configuration' },
79
+ { name: 'lumenflow:upgrade', description: 'Upgrade LumenFlow packages' },
80
+ { name: 'docs:sync', description: 'Sync agent docs (for upgrades)' },
81
+ ],
82
+ },
83
+ {
84
+ name: 'Metrics & Flow',
85
+ commands: [
86
+ { name: 'flow:report', description: 'Generate flow metrics report' },
87
+ { name: 'flow:bottlenecks', description: 'Identify flow bottlenecks' },
88
+ { name: 'metrics:snapshot', description: 'Capture metrics snapshot' },
89
+ ],
90
+ },
91
+ {
92
+ name: 'State Management',
93
+ commands: [
94
+ { name: 'state:doctor', description: 'Diagnose state store issues' },
95
+ { name: 'state:cleanup', description: 'Clean up stale state data' },
96
+ { name: 'state:bootstrap', description: 'Bootstrap state store' },
97
+ ],
98
+ },
99
+ ];
100
+ /**
101
+ * Get the complete commands registry
102
+ * @returns Array of command categories with their commands
103
+ */
104
+ export function getCommandsRegistry() {
105
+ return COMMAND_CATEGORIES;
106
+ }
107
+ /**
108
+ * Format commands output for terminal display
109
+ * @returns Formatted string with all commands grouped by category
110
+ */
111
+ export function formatCommandsOutput() {
112
+ const lines = [];
113
+ lines.push('LumenFlow CLI Commands');
114
+ lines.push('======================');
115
+ lines.push('');
116
+ for (const category of COMMAND_CATEGORIES) {
117
+ lines.push(`## ${category.name}`);
118
+ lines.push('');
119
+ // Find the longest command name for alignment
120
+ const maxNameLength = Math.max(...category.commands.map((cmd) => cmd.name.length));
121
+ for (const cmd of category.commands) {
122
+ const padding = ' '.repeat(maxNameLength - cmd.name.length + 2);
123
+ lines.push(` ${cmd.name}${padding}${cmd.description}`);
124
+ }
125
+ lines.push('');
126
+ }
127
+ lines.push('---');
128
+ lines.push('Tip: Run `pnpm <command> --help` for detailed options.');
129
+ lines.push('');
130
+ return lines.join('\n');
131
+ }
132
+ /**
133
+ * CLI option definitions for commands command
134
+ */
135
+ const COMMANDS_OPTIONS = {
136
+ json: {
137
+ name: 'json',
138
+ flags: '--json',
139
+ description: 'Output commands as JSON',
140
+ },
141
+ };
142
+ /**
143
+ * Parse commands command options using createWUParser
144
+ */
145
+ export function parseCommandsOptions() {
146
+ const opts = createWUParser({
147
+ name: 'lumenflow-commands',
148
+ description: 'List all available LumenFlow CLI commands',
149
+ options: Object.values(COMMANDS_OPTIONS),
150
+ });
151
+ return {
152
+ json: opts.json ?? false,
153
+ };
154
+ }
155
+ /**
156
+ * Main function for the commands CLI
157
+ */
158
+ export async function main() {
159
+ const opts = parseCommandsOptions();
160
+ if (opts.json) {
161
+ console.log(JSON.stringify(getCommandsRegistry(), null, 2));
162
+ }
163
+ else {
164
+ console.log(formatCommandsOutput());
165
+ }
166
+ }
167
+ // CLI entry point
168
+ // WU-1071: Use import.meta.main for proper CLI detection with pnpm symlinks
169
+ if (import.meta.main) {
170
+ runCLI(main);
171
+ }