@lumenflow/cli 2.20.1 → 2.21.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.
Files changed (111) hide show
  1. package/README.md +8 -4
  2. package/dist/hooks/enforcement-checks.js +120 -0
  3. package/dist/hooks/enforcement-checks.js.map +1 -1
  4. package/dist/init-lane-validation.js +141 -0
  5. package/dist/init-lane-validation.js.map +1 -0
  6. package/dist/init-templates.js +36 -8
  7. package/dist/init-templates.js.map +1 -1
  8. package/dist/init.js +27 -58
  9. package/dist/init.js.map +1 -1
  10. package/dist/initiative-create.js +35 -4
  11. package/dist/initiative-create.js.map +1 -1
  12. package/dist/lane-lifecycle-process.js +364 -0
  13. package/dist/lane-lifecycle-process.js.map +1 -0
  14. package/dist/lane-lock.js +41 -0
  15. package/dist/lane-lock.js.map +1 -0
  16. package/dist/lane-setup.js +55 -0
  17. package/dist/lane-setup.js.map +1 -0
  18. package/dist/lane-status.js +38 -0
  19. package/dist/lane-status.js.map +1 -0
  20. package/dist/lane-validate.js +43 -0
  21. package/dist/lane-validate.js.map +1 -0
  22. package/dist/onboarding-smoke-test.js +17 -0
  23. package/dist/onboarding-smoke-test.js.map +1 -1
  24. package/dist/public-manifest.js +28 -0
  25. package/dist/public-manifest.js.map +1 -1
  26. package/dist/wu-claim-cloud.js +16 -0
  27. package/dist/wu-claim-cloud.js.map +1 -1
  28. package/dist/wu-claim.js +12 -2
  29. package/dist/wu-claim.js.map +1 -1
  30. package/dist/wu-create-content.js +8 -2
  31. package/dist/wu-create-content.js.map +1 -1
  32. package/dist/wu-create-validation.js +5 -3
  33. package/dist/wu-create-validation.js.map +1 -1
  34. package/dist/wu-create.js +21 -1
  35. package/dist/wu-create.js.map +1 -1
  36. package/dist/wu-done.js +57 -8
  37. package/dist/wu-done.js.map +1 -1
  38. package/dist/wu-prep.js +22 -0
  39. package/dist/wu-prep.js.map +1 -1
  40. package/package.json +15 -11
  41. package/dist/__tests__/agent-log-issue.test.js +0 -56
  42. package/dist/__tests__/agent-spawn-coordination.test.js +0 -451
  43. package/dist/__tests__/backlog-prune.test.js +0 -478
  44. package/dist/__tests__/cli-entry-point.test.js +0 -160
  45. package/dist/__tests__/cli-subprocess.test.js +0 -89
  46. package/dist/__tests__/commands/integrate.test.js +0 -165
  47. package/dist/__tests__/commands.test.js +0 -271
  48. package/dist/__tests__/deps-operations.test.js +0 -206
  49. package/dist/__tests__/doctor.test.js +0 -510
  50. package/dist/__tests__/file-operations.test.js +0 -906
  51. package/dist/__tests__/flow-report.test.js +0 -24
  52. package/dist/__tests__/gates-config.test.js +0 -303
  53. package/dist/__tests__/gates-integration-tests.test.js +0 -112
  54. package/dist/__tests__/git-operations.test.js +0 -668
  55. package/dist/__tests__/guard-main-branch.test.js +0 -79
  56. package/dist/__tests__/guards-validation.test.js +0 -416
  57. package/dist/__tests__/hooks/enforcement.test.js +0 -279
  58. package/dist/__tests__/init-config-lanes.test.js +0 -131
  59. package/dist/__tests__/init-docs-structure.test.js +0 -152
  60. package/dist/__tests__/init-greenfield.test.js +0 -247
  61. package/dist/__tests__/init-lane-inference.test.js +0 -125
  62. package/dist/__tests__/init-onboarding-docs.test.js +0 -132
  63. package/dist/__tests__/init-quick-ref.test.js +0 -144
  64. package/dist/__tests__/init-scripts.test.js +0 -207
  65. package/dist/__tests__/init-template-portability.test.js +0 -96
  66. package/dist/__tests__/init.test.js +0 -968
  67. package/dist/__tests__/initiative-add-wu.test.js +0 -490
  68. package/dist/__tests__/initiative-e2e.test.js +0 -442
  69. package/dist/__tests__/initiative-plan-replacement.test.js +0 -161
  70. package/dist/__tests__/initiative-plan.test.js +0 -340
  71. package/dist/__tests__/initiative-remove-wu.test.js +0 -458
  72. package/dist/__tests__/lumenflow-upgrade.test.js +0 -260
  73. package/dist/__tests__/mem-cleanup-execution.test.js +0 -19
  74. package/dist/__tests__/memory-integration.test.js +0 -333
  75. package/dist/__tests__/merge-block.test.js +0 -220
  76. package/dist/__tests__/metrics-cli.test.js +0 -619
  77. package/dist/__tests__/metrics-snapshot.test.js +0 -24
  78. package/dist/__tests__/no-beacon-references-docs.test.js +0 -30
  79. package/dist/__tests__/no-beacon-references.test.js +0 -39
  80. package/dist/__tests__/onboarding-smoke-test.test.js +0 -211
  81. package/dist/__tests__/path-centralization-cli.test.js +0 -234
  82. package/dist/__tests__/plan-create.test.js +0 -126
  83. package/dist/__tests__/plan-edit.test.js +0 -157
  84. package/dist/__tests__/plan-link.test.js +0 -239
  85. package/dist/__tests__/plan-promote.test.js +0 -181
  86. package/dist/__tests__/release.test.js +0 -372
  87. package/dist/__tests__/rotate-progress.test.js +0 -127
  88. package/dist/__tests__/safe-git.test.js +0 -190
  89. package/dist/__tests__/session-coordinator.test.js +0 -109
  90. package/dist/__tests__/state-bootstrap.test.js +0 -432
  91. package/dist/__tests__/state-doctor.test.js +0 -328
  92. package/dist/__tests__/sync-templates.test.js +0 -255
  93. package/dist/__tests__/templates-sync.test.js +0 -219
  94. package/dist/__tests__/trace-gen.test.js +0 -115
  95. package/dist/__tests__/wu-create-required-fields.test.js +0 -143
  96. package/dist/__tests__/wu-create-strict.test.js +0 -118
  97. package/dist/__tests__/wu-create.test.js +0 -121
  98. package/dist/__tests__/wu-done-auto-cleanup.test.js +0 -135
  99. package/dist/__tests__/wu-done-docs-only-policy.test.js +0 -20
  100. package/dist/__tests__/wu-done-staging-whitelist.test.js +0 -35
  101. package/dist/__tests__/wu-done.test.js +0 -36
  102. package/dist/__tests__/wu-edit-strict.test.js +0 -109
  103. package/dist/__tests__/wu-edit.test.js +0 -119
  104. package/dist/__tests__/wu-lifecycle-integration.test.js +0 -388
  105. package/dist/__tests__/wu-prep-default-exec.test.js +0 -35
  106. package/dist/__tests__/wu-prep.test.js +0 -140
  107. package/dist/__tests__/wu-proto.test.js +0 -97
  108. package/dist/__tests__/wu-validate-strict.test.js +0 -113
  109. package/dist/__tests__/wu-validate.test.js +0 -36
  110. package/dist/spawn-list.js +0 -143
  111. package/dist/spawn-list.js.map +0 -1
@@ -1,89 +0,0 @@
1
- /**
2
- * Integration tests for CLI subprocess execution
3
- *
4
- * Tests that CLI commands properly:
5
- * - Exit with non-zero code on errors
6
- * - Output error messages to stderr
7
- * - Don't silently fail
8
- *
9
- * These tests run CLI commands as subprocesses to verify
10
- * the entry point error handling works end-to-end.
11
- */
12
- import { describe, it, expect } from 'vitest';
13
- import { spawnSync } from 'node:child_process';
14
- import { resolve } from 'node:path';
15
- const CLI_DIR = resolve(__dirname, '../../dist');
16
- /**
17
- * Helper to run a CLI command as subprocess
18
- */
19
- function runCLI(command, args = []) {
20
- const result = spawnSync('node', [resolve(CLI_DIR, `${command}.js`), ...args], {
21
- encoding: 'utf-8',
22
- timeout: 10000,
23
- });
24
- return {
25
- code: result.status ?? -1,
26
- stdout: result.stdout ?? '',
27
- stderr: result.stderr ?? '',
28
- };
29
- }
30
- describe('CLI subprocess error handling', () => {
31
- describe('wu-claim', () => {
32
- it('should exit with non-zero code when required options are missing', () => {
33
- const result = runCLI('wu-claim', ['--id', 'WU-TEST']);
34
- // Should NOT exit 0 (silent failure)
35
- expect(result.code).not.toBe(0);
36
- // Should have some error output
37
- expect(result.stderr.length + result.stdout.length).toBeGreaterThan(0);
38
- });
39
- it('should output help when --help is passed', () => {
40
- const result = runCLI('wu-claim', ['--help']);
41
- // Help should work
42
- expect(result.code).toBe(0);
43
- expect(result.stdout).toContain('Usage');
44
- });
45
- });
46
- describe('wu-done', () => {
47
- it('should exit with non-zero code for non-existent WU', () => {
48
- const result = runCLI('wu-done', ['--id', 'WU-NONEXISTENT-99999']);
49
- // Should NOT exit 0 (silent failure)
50
- expect(result.code).not.toBe(0);
51
- // Should have some error output
52
- expect(result.stderr.length + result.stdout.length).toBeGreaterThan(0);
53
- });
54
- });
55
- describe('wu-create', () => {
56
- it('should exit with non-zero code when validation fails', () => {
57
- const result = runCLI('wu-create', ['--id', 'WU-TEST', '--lane', 'Invalid Lane']);
58
- // Should NOT exit 0 (silent failure)
59
- expect(result.code).not.toBe(0);
60
- // Should have some error output
61
- expect(result.stderr.length + result.stdout.length).toBeGreaterThan(0);
62
- });
63
- });
64
- describe('wu-preflight (WU-1180)', () => {
65
- it('should show proper Commander help when --help is passed', () => {
66
- const result = runCLI('wu-preflight', ['--help']);
67
- // Help should work
68
- expect(result.code).toBe(0);
69
- expect(result.stdout).toContain('Usage');
70
- expect(result.stdout).toContain('Options');
71
- // Should have the standard Commander format with -h, --help
72
- expect(result.stdout).toMatch(/-h,\s*--help/);
73
- });
74
- it('should show proper Commander help format with option descriptions', () => {
75
- const result = runCLI('wu-preflight', ['--help']);
76
- expect(result.code).toBe(0);
77
- // Should include option descriptions in Commander format
78
- expect(result.stdout).toContain('--id');
79
- expect(result.stdout).toContain('--worktree');
80
- });
81
- it('should exit with non-zero code when required --id option is missing', () => {
82
- const result = runCLI('wu-preflight', []);
83
- // Should NOT exit 0 (silent failure)
84
- expect(result.code).not.toBe(0);
85
- // Should have some error output
86
- expect(result.stderr.length + result.stdout.length).toBeGreaterThan(0);
87
- });
88
- });
89
- });
@@ -1,165 +0,0 @@
1
- /**
2
- * @file integrate.test.ts
3
- * Tests for Claude Code integration command (WU-1367)
4
- */
5
- // Test file lint exceptions
6
- import { describe, it, expect, beforeEach, vi } from 'vitest';
7
- import * as fs from 'node:fs';
8
- // Mock fs
9
- vi.mock('node:fs');
10
- const TEST_PROJECT_DIR = '/test/project';
11
- describe('WU-1367: Integrate Command', () => {
12
- beforeEach(() => {
13
- vi.resetAllMocks();
14
- vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
15
- });
16
- describe('integrateClaudeCode', () => {
17
- it('should skip integration when enforcement not enabled', async () => {
18
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
19
- mockWriteFileSync.mockClear();
20
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
21
- await integrateClaudeCode(TEST_PROJECT_DIR, {
22
- enforcement: {
23
- hooks: false,
24
- },
25
- });
26
- // Should not write any files
27
- expect(mockWriteFileSync).not.toHaveBeenCalled();
28
- });
29
- it('should create hooks directory when it does not exist', async () => {
30
- const mockMkdirSync = vi.mocked(fs.mkdirSync);
31
- vi.mocked(fs.writeFileSync);
32
- vi.mocked(fs.existsSync).mockReturnValue(false);
33
- vi.mocked(fs.readFileSync).mockReturnValue('{}');
34
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
35
- await integrateClaudeCode(TEST_PROJECT_DIR, {
36
- enforcement: {
37
- hooks: true,
38
- block_outside_worktree: true,
39
- },
40
- });
41
- expect(mockMkdirSync).toHaveBeenCalledWith(expect.stringContaining('.claude/hooks'), {
42
- recursive: true,
43
- });
44
- });
45
- it('should generate enforce-worktree.sh when block_outside_worktree=true', async () => {
46
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
47
- vi.mocked(fs.existsSync).mockReturnValue(false);
48
- vi.mocked(fs.readFileSync).mockReturnValue('{}');
49
- mockWriteFileSync.mockClear();
50
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
51
- await integrateClaudeCode(TEST_PROJECT_DIR, {
52
- enforcement: {
53
- hooks: true,
54
- block_outside_worktree: true,
55
- require_wu_for_edits: false,
56
- warn_on_stop_without_wu_done: false,
57
- },
58
- });
59
- const enforceWorktreeCall = mockWriteFileSync.mock.calls.find((call) => String(call[0]).includes('enforce-worktree.sh'));
60
- expect(enforceWorktreeCall).toBeDefined();
61
- expect(enforceWorktreeCall[1]).toContain('enforce-worktree.sh');
62
- });
63
- it('should generate require-wu.sh when require_wu_for_edits=true', async () => {
64
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
65
- vi.mocked(fs.existsSync).mockReturnValue(false);
66
- vi.mocked(fs.readFileSync).mockReturnValue('{}');
67
- mockWriteFileSync.mockClear();
68
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
69
- await integrateClaudeCode(TEST_PROJECT_DIR, {
70
- enforcement: {
71
- hooks: true,
72
- block_outside_worktree: false,
73
- require_wu_for_edits: true,
74
- warn_on_stop_without_wu_done: false,
75
- },
76
- });
77
- const requireWuCall = mockWriteFileSync.mock.calls.find((call) => String(call[0]).includes('require-wu.sh'));
78
- expect(requireWuCall).toBeDefined();
79
- });
80
- it('should generate warn-incomplete.sh when warn_on_stop_without_wu_done=true', async () => {
81
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
82
- vi.mocked(fs.existsSync).mockReturnValue(false);
83
- vi.mocked(fs.readFileSync).mockReturnValue('{}');
84
- mockWriteFileSync.mockClear();
85
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
86
- await integrateClaudeCode(TEST_PROJECT_DIR, {
87
- enforcement: {
88
- hooks: true,
89
- block_outside_worktree: false,
90
- require_wu_for_edits: false,
91
- warn_on_stop_without_wu_done: true,
92
- },
93
- });
94
- const warnIncompleteCall = mockWriteFileSync.mock.calls.find((call) => String(call[0]).includes('warn-incomplete.sh'));
95
- expect(warnIncompleteCall).toBeDefined();
96
- });
97
- it('should update settings.json with PreToolUse hooks', async () => {
98
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
99
- vi.mocked(fs.existsSync).mockReturnValue(true);
100
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
101
- $schema: 'https://json.schemastore.org/claude-code-settings.json',
102
- permissions: { allow: ['Bash'] },
103
- }));
104
- mockWriteFileSync.mockClear();
105
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
106
- await integrateClaudeCode(TEST_PROJECT_DIR, {
107
- enforcement: {
108
- hooks: true,
109
- block_outside_worktree: true,
110
- },
111
- });
112
- const settingsCall = mockWriteFileSync.mock.calls.find((call) => String(call[0]).includes('settings.json'));
113
- expect(settingsCall).toBeDefined();
114
- const settingsContent = JSON.parse(settingsCall[1]);
115
- expect(settingsContent.hooks).toBeDefined();
116
- expect(settingsContent.hooks.PreToolUse).toBeDefined();
117
- expect(settingsContent.hooks.PreToolUse[0].matcher).toBe('Write|Edit');
118
- });
119
- it('should update settings.json with Stop hooks', async () => {
120
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
121
- vi.mocked(fs.existsSync).mockReturnValue(true);
122
- vi.mocked(fs.readFileSync).mockReturnValue('{}');
123
- mockWriteFileSync.mockClear();
124
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
125
- await integrateClaudeCode(TEST_PROJECT_DIR, {
126
- enforcement: {
127
- hooks: true,
128
- block_outside_worktree: false,
129
- require_wu_for_edits: false,
130
- warn_on_stop_without_wu_done: true,
131
- },
132
- });
133
- const settingsCall = mockWriteFileSync.mock.calls.find((call) => String(call[0]).includes('settings.json'));
134
- expect(settingsCall).toBeDefined();
135
- const settingsContent = JSON.parse(settingsCall[1]);
136
- expect(settingsContent.hooks).toBeDefined();
137
- expect(settingsContent.hooks.Stop).toBeDefined();
138
- });
139
- it('should preserve existing permissions when updating settings.json', async () => {
140
- const mockWriteFileSync = vi.mocked(fs.writeFileSync);
141
- vi.mocked(fs.existsSync).mockReturnValue(true);
142
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
143
- $schema: 'https://json.schemastore.org/claude-code-settings.json',
144
- permissions: {
145
- allow: ['Bash', 'Read', 'Write'],
146
- deny: ['Bash(rm -rf /*)'],
147
- },
148
- }));
149
- mockWriteFileSync.mockClear();
150
- const { integrateClaudeCode } = await import('../../commands/integrate.js');
151
- await integrateClaudeCode(TEST_PROJECT_DIR, {
152
- enforcement: {
153
- hooks: true,
154
- block_outside_worktree: true,
155
- },
156
- });
157
- const settingsCall = mockWriteFileSync.mock.calls.find((call) => String(call[0]).includes('settings.json'));
158
- expect(settingsCall).toBeDefined();
159
- const settingsContent = JSON.parse(settingsCall[1]);
160
- expect(settingsContent.permissions).toBeDefined();
161
- expect(settingsContent.permissions.allow).toContain('Bash');
162
- expect(settingsContent.permissions.deny).toContain('Bash(rm -rf /*)');
163
- });
164
- });
165
- });
@@ -1,271 +0,0 @@
1
- /**
2
- * @file commands.test.ts
3
- * Tests for lumenflow commands discovery feature (WU-1378)
4
- * Extended for public CLI manifest alignment (WU-1432)
5
- *
6
- * Tests the commands subcommand that lists all available CLI commands
7
- * grouped by category with brief descriptions. Also verifies alignment
8
- * between public-manifest.ts, commands.ts, and package.json bin entries.
9
- */
10
- import { describe, it, expect } from 'vitest';
11
- import { getCommandsRegistry, formatCommandsOutput } from '../commands.js';
12
- import { getPublicManifest, getPublicCommandNames, getPublicBinNames, isPublicCommand, } from '../public-manifest.js';
13
- import { readFileSync } from 'node:fs';
14
- import { fileURLToPath } from 'node:url';
15
- import { dirname, join } from 'node:path';
16
- const __dirname = dirname(fileURLToPath(import.meta.url));
17
- const packageJsonPath = join(__dirname, '../../package.json');
18
- // Script commands that are NOT CLI binaries (pnpm scripts only)
19
- // These appear in the registry but should NOT be in the manifest
20
- const SCRIPT_COMMANDS = new Set(['format', 'lint', 'typecheck', 'test', 'setup']);
21
- describe('lumenflow commands', () => {
22
- describe('getCommandsRegistry', () => {
23
- it('should return command categories', () => {
24
- const registry = getCommandsRegistry();
25
- expect(registry).toBeDefined();
26
- expect(Array.isArray(registry)).toBe(true);
27
- expect(registry.length).toBeGreaterThan(0);
28
- });
29
- it('should include WU Lifecycle category with key commands', () => {
30
- const registry = getCommandsRegistry();
31
- const wuLifecycle = registry.find((cat) => cat.name === 'WU Lifecycle');
32
- expect(wuLifecycle).toBeDefined();
33
- expect(wuLifecycle.commands).toBeDefined();
34
- const commandNames = wuLifecycle.commands.map((cmd) => cmd.name);
35
- expect(commandNames).toContain('wu:create');
36
- expect(commandNames).toContain('wu:claim');
37
- });
38
- it('should include Initiatives category', () => {
39
- const registry = getCommandsRegistry();
40
- const initiatives = registry.find((cat) => cat.name === 'Initiatives');
41
- expect(initiatives).toBeDefined();
42
- expect(initiatives.commands.some((cmd) => cmd.name === 'initiative:create')).toBe(true);
43
- });
44
- it('should include Gates & Quality category with gates command', () => {
45
- const registry = getCommandsRegistry();
46
- const gatesCategory = registry.find((cat) => cat.name === 'Gates & Quality');
47
- expect(gatesCategory).toBeDefined();
48
- expect(gatesCategory.commands.some((cmd) => cmd.name === 'gates')).toBe(true);
49
- });
50
- it('should have description for each command', () => {
51
- const registry = getCommandsRegistry();
52
- for (const category of registry) {
53
- for (const cmd of category.commands) {
54
- expect(cmd.description).toBeDefined();
55
- expect(cmd.description.length).toBeGreaterThan(0);
56
- }
57
- }
58
- });
59
- });
60
- describe('formatCommandsOutput', () => {
61
- it('should include category headers', () => {
62
- const output = formatCommandsOutput();
63
- expect(output).toContain('WU Lifecycle');
64
- expect(output).toContain('Initiatives');
65
- expect(output).toContain('Gates & Quality');
66
- });
67
- it('should include command names and descriptions', () => {
68
- const output = formatCommandsOutput();
69
- expect(output).toContain('wu:create');
70
- expect(output).toContain('wu:claim');
71
- expect(output).toContain('initiative:create');
72
- expect(output).toContain('gates');
73
- });
74
- it('should include hint to run --help for details', () => {
75
- const output = formatCommandsOutput();
76
- expect(output).toMatch(/--help/i);
77
- });
78
- it('should format output with clear grouping', () => {
79
- const output = formatCommandsOutput();
80
- const lines = output.split('\n');
81
- // Should have multiple non-empty lines
82
- const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
83
- expect(nonEmptyLines.length).toBeGreaterThan(10);
84
- });
85
- });
86
- });
87
- // ============================================================================
88
- // WU-1432: Public CLI Manifest Tests
89
- // ============================================================================
90
- describe('public CLI manifest (WU-1432)', () => {
91
- describe('getPublicManifest', () => {
92
- it('should return an array of public commands', () => {
93
- const manifest = getPublicManifest();
94
- expect(manifest).toBeDefined();
95
- expect(Array.isArray(manifest)).toBe(true);
96
- expect(manifest.length).toBeGreaterThan(0);
97
- });
98
- it('should have required fields for each command', () => {
99
- const manifest = getPublicManifest();
100
- for (const cmd of manifest) {
101
- expect(cmd.name).toBeDefined();
102
- expect(typeof cmd.name).toBe('string');
103
- expect(cmd.name.length).toBeGreaterThan(0);
104
- expect(cmd.binName).toBeDefined();
105
- expect(typeof cmd.binName).toBe('string');
106
- expect(cmd.binName.length).toBeGreaterThan(0);
107
- expect(cmd.description).toBeDefined();
108
- expect(typeof cmd.description).toBe('string');
109
- expect(cmd.description.length).toBeGreaterThan(0);
110
- expect(cmd.category).toBeDefined();
111
- expect(typeof cmd.category).toBe('string');
112
- expect(cmd.category.length).toBeGreaterThan(0);
113
- }
114
- });
115
- it('should include core public commands', () => {
116
- const names = getPublicCommandNames();
117
- // WU lifecycle - must be public
118
- expect(names).toContain('wu:create');
119
- expect(names).toContain('wu:claim');
120
- expect(names).toContain('wu:done');
121
- expect(names).toContain('wu:prep');
122
- expect(names).toContain('wu:status');
123
- // Gates - must be public
124
- expect(names).toContain('gates');
125
- expect(names).toContain('lumenflow');
126
- // Memory - must be public
127
- expect(names).toContain('mem:checkpoint');
128
- expect(names).toContain('mem:inbox');
129
- // Initiatives - must be public
130
- expect(names).toContain('initiative:create');
131
- expect(names).toContain('initiative:status');
132
- });
133
- it('should NOT include internal/maintainer commands', () => {
134
- const names = getPublicCommandNames();
135
- // Guards are internal
136
- expect(names).not.toContain('guard-worktree-commit');
137
- expect(names).not.toContain('guard-locked');
138
- expect(names).not.toContain('guard-main-branch');
139
- // Validation internals
140
- expect(names).not.toContain('validate-agent-skills');
141
- expect(names).not.toContain('validate-agent-sync');
142
- expect(names).not.toContain('validate-backlog-sync');
143
- expect(names).not.toContain('validate-skills-spec');
144
- // Session internals
145
- expect(names).not.toContain('session-coordinator');
146
- expect(names).not.toContain('rotate-progress');
147
- // Trace/debug internals
148
- expect(names).not.toContain('trace-gen');
149
- });
150
- });
151
- describe('isPublicCommand', () => {
152
- it('should return true for public commands', () => {
153
- expect(isPublicCommand('wu:create')).toBe(true);
154
- expect(isPublicCommand('wu:claim')).toBe(true);
155
- expect(isPublicCommand('gates')).toBe(true);
156
- expect(isPublicCommand('lumenflow')).toBe(true);
157
- });
158
- it('should return false for internal commands', () => {
159
- expect(isPublicCommand('guard-worktree-commit')).toBe(false);
160
- expect(isPublicCommand('validate-agent-skills')).toBe(false);
161
- expect(isPublicCommand('session-coordinator')).toBe(false);
162
- });
163
- });
164
- });
165
- describe('manifest alignment (WU-1432)', () => {
166
- describe('commands.ts derives from manifest', () => {
167
- it('should have all CLI registry commands in the public manifest (excluding script commands)', () => {
168
- const registry = getCommandsRegistry();
169
- const publicNames = new Set(getPublicCommandNames());
170
- const registryNames = [];
171
- for (const category of registry) {
172
- for (const cmd of category.commands) {
173
- // Skip script commands - they're not CLI binaries
174
- if (!SCRIPT_COMMANDS.has(cmd.name)) {
175
- registryNames.push(cmd.name);
176
- }
177
- }
178
- }
179
- // Every CLI command in the registry must be a public command
180
- for (const name of registryNames) {
181
- expect(publicNames.has(name), `Command "${name}" in registry but not in public manifest`).toBe(true);
182
- }
183
- });
184
- it('should have matching descriptions between manifest and registry', () => {
185
- const manifest = getPublicManifest();
186
- const registry = getCommandsRegistry();
187
- // Build a map of registry descriptions
188
- const registryDescriptions = new Map();
189
- for (const category of registry) {
190
- for (const cmd of category.commands) {
191
- registryDescriptions.set(cmd.name, cmd.description);
192
- }
193
- }
194
- // For commands that appear in both, descriptions should match
195
- for (const cmd of manifest) {
196
- const registryDesc = registryDescriptions.get(cmd.name);
197
- if (registryDesc) {
198
- expect(cmd.description, `Description mismatch for "${cmd.name}"`).toBe(registryDesc);
199
- }
200
- }
201
- });
202
- it('should have matching categories between manifest and registry', () => {
203
- const manifest = getPublicManifest();
204
- const registry = getCommandsRegistry();
205
- // Build a map of registry categories
206
- const registryCategories = new Map();
207
- for (const category of registry) {
208
- for (const cmd of category.commands) {
209
- registryCategories.set(cmd.name, category.name);
210
- }
211
- }
212
- // For commands that appear in both, categories should match
213
- for (const cmd of manifest) {
214
- const registryCategory = registryCategories.get(cmd.name);
215
- if (registryCategory) {
216
- expect(cmd.category, `Category mismatch for "${cmd.name}"`).toBe(registryCategory);
217
- }
218
- }
219
- });
220
- });
221
- describe('package.json bin alignment', () => {
222
- it('should only include public commands in bin', () => {
223
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
224
- const binEntries = Object.keys(packageJson.bin || {});
225
- const publicBinNames = new Set(getPublicBinNames());
226
- // Every bin entry should be in the public manifest
227
- for (const binName of binEntries) {
228
- expect(publicBinNames.has(binName), `Bin "${binName}" is in package.json but not in public manifest - should it be internal?`).toBe(true);
229
- }
230
- });
231
- it('should have all public commands in bin', () => {
232
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
233
- const binEntries = new Set(Object.keys(packageJson.bin || {}));
234
- const publicManifest = getPublicManifest();
235
- // Every public manifest command should have a bin entry
236
- for (const cmd of publicManifest) {
237
- expect(binEntries.has(cmd.binName), `Public command "${cmd.name}" (bin: ${cmd.binName}) missing from package.json bin`).toBe(true);
238
- }
239
- });
240
- it('should have correct file paths in bin', () => {
241
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
242
- const publicManifest = getPublicManifest();
243
- for (const cmd of publicManifest) {
244
- const binPath = packageJson.bin?.[cmd.binName];
245
- expect(binPath, `Missing bin path for ${cmd.binName}`).toBeDefined();
246
- expect(binPath, `Bin path for ${cmd.binName} should start with ./dist/`).toMatch(/^\.\/dist\//);
247
- expect(binPath, `Bin path for ${cmd.binName} should end with .js`).toMatch(/\.js$/);
248
- }
249
- });
250
- it('should not include internal commands in bin', () => {
251
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
252
- const binEntries = Object.keys(packageJson.bin || {});
253
- // These internal commands should NOT be in bin
254
- const internalCommands = [
255
- 'guard-worktree-commit',
256
- 'guard-locked',
257
- 'guard-main-branch',
258
- 'validate-agent-skills',
259
- 'validate-agent-sync',
260
- 'validate-backlog-sync',
261
- 'validate-skills-spec',
262
- 'session-coordinator',
263
- 'rotate-progress',
264
- 'trace-gen',
265
- ];
266
- for (const internalCmd of internalCommands) {
267
- expect(binEntries.includes(internalCmd), `Internal command "${internalCmd}" should NOT be in package.json bin`).toBe(false);
268
- }
269
- });
270
- });
271
- });