@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.
- package/README.md +8 -4
- package/dist/hooks/enforcement-checks.js +120 -0
- package/dist/hooks/enforcement-checks.js.map +1 -1
- package/dist/init-lane-validation.js +141 -0
- package/dist/init-lane-validation.js.map +1 -0
- package/dist/init-templates.js +36 -8
- package/dist/init-templates.js.map +1 -1
- package/dist/init.js +27 -58
- package/dist/init.js.map +1 -1
- package/dist/initiative-create.js +35 -4
- package/dist/initiative-create.js.map +1 -1
- package/dist/lane-lifecycle-process.js +364 -0
- package/dist/lane-lifecycle-process.js.map +1 -0
- package/dist/lane-lock.js +41 -0
- package/dist/lane-lock.js.map +1 -0
- package/dist/lane-setup.js +55 -0
- package/dist/lane-setup.js.map +1 -0
- package/dist/lane-status.js +38 -0
- package/dist/lane-status.js.map +1 -0
- package/dist/lane-validate.js +43 -0
- package/dist/lane-validate.js.map +1 -0
- package/dist/onboarding-smoke-test.js +17 -0
- package/dist/onboarding-smoke-test.js.map +1 -1
- package/dist/public-manifest.js +28 -0
- package/dist/public-manifest.js.map +1 -1
- package/dist/wu-claim-cloud.js +16 -0
- package/dist/wu-claim-cloud.js.map +1 -1
- package/dist/wu-claim.js +12 -2
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-create-content.js +8 -2
- package/dist/wu-create-content.js.map +1 -1
- package/dist/wu-create-validation.js +5 -3
- package/dist/wu-create-validation.js.map +1 -1
- package/dist/wu-create.js +21 -1
- package/dist/wu-create.js.map +1 -1
- package/dist/wu-done.js +57 -8
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-prep.js +22 -0
- package/dist/wu-prep.js.map +1 -1
- package/package.json +15 -11
- package/dist/__tests__/agent-log-issue.test.js +0 -56
- package/dist/__tests__/agent-spawn-coordination.test.js +0 -451
- package/dist/__tests__/backlog-prune.test.js +0 -478
- package/dist/__tests__/cli-entry-point.test.js +0 -160
- package/dist/__tests__/cli-subprocess.test.js +0 -89
- package/dist/__tests__/commands/integrate.test.js +0 -165
- package/dist/__tests__/commands.test.js +0 -271
- package/dist/__tests__/deps-operations.test.js +0 -206
- package/dist/__tests__/doctor.test.js +0 -510
- package/dist/__tests__/file-operations.test.js +0 -906
- package/dist/__tests__/flow-report.test.js +0 -24
- package/dist/__tests__/gates-config.test.js +0 -303
- package/dist/__tests__/gates-integration-tests.test.js +0 -112
- package/dist/__tests__/git-operations.test.js +0 -668
- package/dist/__tests__/guard-main-branch.test.js +0 -79
- package/dist/__tests__/guards-validation.test.js +0 -416
- package/dist/__tests__/hooks/enforcement.test.js +0 -279
- package/dist/__tests__/init-config-lanes.test.js +0 -131
- package/dist/__tests__/init-docs-structure.test.js +0 -152
- package/dist/__tests__/init-greenfield.test.js +0 -247
- package/dist/__tests__/init-lane-inference.test.js +0 -125
- package/dist/__tests__/init-onboarding-docs.test.js +0 -132
- package/dist/__tests__/init-quick-ref.test.js +0 -144
- package/dist/__tests__/init-scripts.test.js +0 -207
- package/dist/__tests__/init-template-portability.test.js +0 -96
- package/dist/__tests__/init.test.js +0 -968
- package/dist/__tests__/initiative-add-wu.test.js +0 -490
- package/dist/__tests__/initiative-e2e.test.js +0 -442
- package/dist/__tests__/initiative-plan-replacement.test.js +0 -161
- package/dist/__tests__/initiative-plan.test.js +0 -340
- package/dist/__tests__/initiative-remove-wu.test.js +0 -458
- package/dist/__tests__/lumenflow-upgrade.test.js +0 -260
- package/dist/__tests__/mem-cleanup-execution.test.js +0 -19
- package/dist/__tests__/memory-integration.test.js +0 -333
- package/dist/__tests__/merge-block.test.js +0 -220
- package/dist/__tests__/metrics-cli.test.js +0 -619
- package/dist/__tests__/metrics-snapshot.test.js +0 -24
- package/dist/__tests__/no-beacon-references-docs.test.js +0 -30
- package/dist/__tests__/no-beacon-references.test.js +0 -39
- package/dist/__tests__/onboarding-smoke-test.test.js +0 -211
- package/dist/__tests__/path-centralization-cli.test.js +0 -234
- package/dist/__tests__/plan-create.test.js +0 -126
- package/dist/__tests__/plan-edit.test.js +0 -157
- package/dist/__tests__/plan-link.test.js +0 -239
- package/dist/__tests__/plan-promote.test.js +0 -181
- package/dist/__tests__/release.test.js +0 -372
- package/dist/__tests__/rotate-progress.test.js +0 -127
- package/dist/__tests__/safe-git.test.js +0 -190
- package/dist/__tests__/session-coordinator.test.js +0 -109
- package/dist/__tests__/state-bootstrap.test.js +0 -432
- package/dist/__tests__/state-doctor.test.js +0 -328
- package/dist/__tests__/sync-templates.test.js +0 -255
- package/dist/__tests__/templates-sync.test.js +0 -219
- package/dist/__tests__/trace-gen.test.js +0 -115
- package/dist/__tests__/wu-create-required-fields.test.js +0 -143
- package/dist/__tests__/wu-create-strict.test.js +0 -118
- package/dist/__tests__/wu-create.test.js +0 -121
- package/dist/__tests__/wu-done-auto-cleanup.test.js +0 -135
- package/dist/__tests__/wu-done-docs-only-policy.test.js +0 -20
- package/dist/__tests__/wu-done-staging-whitelist.test.js +0 -35
- package/dist/__tests__/wu-done.test.js +0 -36
- package/dist/__tests__/wu-edit-strict.test.js +0 -109
- package/dist/__tests__/wu-edit.test.js +0 -119
- package/dist/__tests__/wu-lifecycle-integration.test.js +0 -388
- package/dist/__tests__/wu-prep-default-exec.test.js +0 -35
- package/dist/__tests__/wu-prep.test.js +0 -140
- package/dist/__tests__/wu-proto.test.js +0 -97
- package/dist/__tests__/wu-validate-strict.test.js +0 -113
- package/dist/__tests__/wu-validate.test.js +0 -36
- package/dist/spawn-list.js +0 -143
- 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
|
-
});
|