@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,260 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Tests for lumenflow-upgrade CLI command
|
|
4
|
-
*
|
|
5
|
-
* WU-1112: INIT-003 Phase 6 - Migrate remaining Tier 1 tools
|
|
6
|
-
* WU-1127: Add micro-worktree isolation pattern
|
|
7
|
-
*
|
|
8
|
-
* lumenflow-upgrade updates all @lumenflow/* packages to latest versions.
|
|
9
|
-
* Key requirements:
|
|
10
|
-
* - Uses micro-worktree pattern (atomic changes to main without requiring user worktree)
|
|
11
|
-
* - Checks all 7 @lumenflow/* packages
|
|
12
|
-
* - Supports --dry-run and --latest flags
|
|
13
|
-
*/
|
|
14
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
15
|
-
import { execSync } from 'node:child_process';
|
|
16
|
-
// Mock modules with inline factories (no external references)
|
|
17
|
-
vi.mock('@lumenflow/core/dist/micro-worktree.js', () => ({
|
|
18
|
-
withMicroWorktree: vi.fn(),
|
|
19
|
-
}));
|
|
20
|
-
vi.mock('@lumenflow/core/dist/git-adapter.js', () => ({
|
|
21
|
-
getGitForCwd: vi.fn(),
|
|
22
|
-
}));
|
|
23
|
-
vi.mock('node:child_process', async (importOriginal) => {
|
|
24
|
-
const actual = await importOriginal();
|
|
25
|
-
return {
|
|
26
|
-
...actual,
|
|
27
|
-
execSync: vi.fn(),
|
|
28
|
-
};
|
|
29
|
-
});
|
|
30
|
-
// Import mocked modules to access mock functions
|
|
31
|
-
import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
32
|
-
import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
|
|
33
|
-
// Import functions under test after mocks are set up
|
|
34
|
-
import { parseUpgradeArgs, LUMENFLOW_PACKAGES, buildUpgradeCommands, executeUpgradeInMicroWorktree, validateMainCheckout, } from '../lumenflow-upgrade.js';
|
|
35
|
-
// Cast mocks for TypeScript
|
|
36
|
-
const mockWithMicroWorktree = withMicroWorktree;
|
|
37
|
-
const mockGetGitForCwd = getGitForCwd;
|
|
38
|
-
const mockExecSync = execSync;
|
|
39
|
-
describe('lumenflow-upgrade', () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
vi.clearAllMocks();
|
|
42
|
-
// Default mock behavior for git adapter
|
|
43
|
-
mockGetGitForCwd.mockReturnValue({
|
|
44
|
-
raw: vi.fn().mockResolvedValue('main'),
|
|
45
|
-
getStatus: vi.fn().mockResolvedValue(''),
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
vi.restoreAllMocks();
|
|
50
|
-
});
|
|
51
|
-
describe('LUMENFLOW_PACKAGES constant', () => {
|
|
52
|
-
it('should include all 7 @lumenflow/* packages', () => {
|
|
53
|
-
expect(LUMENFLOW_PACKAGES).toContain('@lumenflow/agent');
|
|
54
|
-
expect(LUMENFLOW_PACKAGES).toContain('@lumenflow/cli');
|
|
55
|
-
expect(LUMENFLOW_PACKAGES).toContain('@lumenflow/core');
|
|
56
|
-
expect(LUMENFLOW_PACKAGES).toContain('@lumenflow/initiatives');
|
|
57
|
-
expect(LUMENFLOW_PACKAGES).toContain('@lumenflow/memory');
|
|
58
|
-
expect(LUMENFLOW_PACKAGES).toContain('@lumenflow/metrics');
|
|
59
|
-
expect(LUMENFLOW_PACKAGES).toContain('@lumenflow/shims');
|
|
60
|
-
expect(LUMENFLOW_PACKAGES).toHaveLength(7);
|
|
61
|
-
});
|
|
62
|
-
it('should have packages in alphabetical order', () => {
|
|
63
|
-
const sorted = [...LUMENFLOW_PACKAGES].sort();
|
|
64
|
-
expect(LUMENFLOW_PACKAGES).toEqual(sorted);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
describe('parseUpgradeArgs', () => {
|
|
68
|
-
it('should parse --version flag', () => {
|
|
69
|
-
const args = parseUpgradeArgs(['node', 'lumenflow-upgrade.js', '--version', '1.5.0']);
|
|
70
|
-
expect(args.version).toBe('1.5.0');
|
|
71
|
-
});
|
|
72
|
-
it('should parse --latest flag', () => {
|
|
73
|
-
const args = parseUpgradeArgs(['node', 'lumenflow-upgrade.js', '--latest']);
|
|
74
|
-
expect(args.latest).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
it('should parse --dry-run flag', () => {
|
|
77
|
-
const args = parseUpgradeArgs(['node', 'lumenflow-upgrade.js', '--dry-run']);
|
|
78
|
-
expect(args.dryRun).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
it('should parse --help flag', () => {
|
|
81
|
-
const args = parseUpgradeArgs(['node', 'lumenflow-upgrade.js', '--help']);
|
|
82
|
-
expect(args.help).toBe(true);
|
|
83
|
-
});
|
|
84
|
-
it('should default latest to false', () => {
|
|
85
|
-
const args = parseUpgradeArgs(['node', 'lumenflow-upgrade.js']);
|
|
86
|
-
expect(args.latest).toBeFalsy();
|
|
87
|
-
});
|
|
88
|
-
it('should default dryRun to false', () => {
|
|
89
|
-
const args = parseUpgradeArgs(['node', 'lumenflow-upgrade.js']);
|
|
90
|
-
expect(args.dryRun).toBeFalsy();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
describe('buildUpgradeCommands', () => {
|
|
94
|
-
it('should build commands for specific version', () => {
|
|
95
|
-
const args = { version: '1.5.0' };
|
|
96
|
-
const commands = buildUpgradeCommands(args);
|
|
97
|
-
// Should have pnpm add command for all packages
|
|
98
|
-
expect(commands.addCommand).toContain('pnpm add');
|
|
99
|
-
expect(commands.addCommand).toContain('@lumenflow/agent@1.5.0');
|
|
100
|
-
expect(commands.addCommand).toContain('@lumenflow/cli@1.5.0');
|
|
101
|
-
expect(commands.addCommand).toContain('@lumenflow/core@1.5.0');
|
|
102
|
-
expect(commands.addCommand).toContain('@lumenflow/initiatives@1.5.0');
|
|
103
|
-
expect(commands.addCommand).toContain('@lumenflow/memory@1.5.0');
|
|
104
|
-
expect(commands.addCommand).toContain('@lumenflow/metrics@1.5.0');
|
|
105
|
-
expect(commands.addCommand).toContain('@lumenflow/shims@1.5.0');
|
|
106
|
-
});
|
|
107
|
-
it('should build commands for latest version', () => {
|
|
108
|
-
const args = { latest: true };
|
|
109
|
-
const commands = buildUpgradeCommands(args);
|
|
110
|
-
// Should have pnpm add command for all packages with @latest
|
|
111
|
-
expect(commands.addCommand).toContain('pnpm add');
|
|
112
|
-
expect(commands.addCommand).toContain('@lumenflow/agent@latest');
|
|
113
|
-
expect(commands.addCommand).toContain('@lumenflow/cli@latest');
|
|
114
|
-
});
|
|
115
|
-
it('should include dev dependencies flag', () => {
|
|
116
|
-
const args = { version: '1.5.0' };
|
|
117
|
-
const commands = buildUpgradeCommands(args);
|
|
118
|
-
// LumenFlow packages are dev dependencies
|
|
119
|
-
expect(commands.addCommand).toContain('--save-dev');
|
|
120
|
-
});
|
|
121
|
-
it('should include all 7 packages in the command', () => {
|
|
122
|
-
const args = { version: '1.5.0' };
|
|
123
|
-
const commands = buildUpgradeCommands(args);
|
|
124
|
-
// Count how many packages are in the command
|
|
125
|
-
const packageCount = LUMENFLOW_PACKAGES.filter((pkg) => commands.addCommand.includes(pkg)).length;
|
|
126
|
-
expect(packageCount).toBe(7);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
// WU-1127: Tests for micro-worktree isolation pattern
|
|
130
|
-
describe('validateMainCheckout', () => {
|
|
131
|
-
let originalCwd;
|
|
132
|
-
beforeEach(() => {
|
|
133
|
-
originalCwd = process.cwd;
|
|
134
|
-
});
|
|
135
|
-
afterEach(() => {
|
|
136
|
-
process.cwd = originalCwd;
|
|
137
|
-
});
|
|
138
|
-
it('should return valid when on main branch and not in worktree', async () => {
|
|
139
|
-
// Mock process.cwd to be on main checkout (not worktree)
|
|
140
|
-
process.cwd = vi.fn().mockReturnValue('/path/to/repo');
|
|
141
|
-
mockGetGitForCwd.mockReturnValue({
|
|
142
|
-
raw: vi.fn().mockResolvedValue('main'),
|
|
143
|
-
getStatus: vi.fn().mockResolvedValue(''),
|
|
144
|
-
});
|
|
145
|
-
const result = await validateMainCheckout();
|
|
146
|
-
expect(result.valid).toBe(true);
|
|
147
|
-
});
|
|
148
|
-
it('should return invalid when not on main branch', async () => {
|
|
149
|
-
// Mock process.cwd to be on main checkout (not worktree)
|
|
150
|
-
process.cwd = vi.fn().mockReturnValue('/path/to/repo');
|
|
151
|
-
mockGetGitForCwd.mockReturnValue({
|
|
152
|
-
raw: vi.fn().mockResolvedValue('lane/framework-cli/wu-123'),
|
|
153
|
-
getStatus: vi.fn().mockResolvedValue(''),
|
|
154
|
-
});
|
|
155
|
-
const result = await validateMainCheckout();
|
|
156
|
-
expect(result.valid).toBe(false);
|
|
157
|
-
expect(result.error).toContain('must be run from main checkout');
|
|
158
|
-
});
|
|
159
|
-
it('should return invalid when in a worktree directory', async () => {
|
|
160
|
-
// Mock process.cwd() to be in a worktree
|
|
161
|
-
process.cwd = vi
|
|
162
|
-
.fn()
|
|
163
|
-
.mockReturnValue('/path/to/repo/worktrees/some-wu');
|
|
164
|
-
mockGetGitForCwd.mockReturnValue({
|
|
165
|
-
raw: vi.fn().mockResolvedValue('main'),
|
|
166
|
-
getStatus: vi.fn().mockResolvedValue(''),
|
|
167
|
-
});
|
|
168
|
-
const result = await validateMainCheckout();
|
|
169
|
-
expect(result.valid).toBe(false);
|
|
170
|
-
expect(result.error).toContain('worktree');
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
describe('executeUpgradeInMicroWorktree', () => {
|
|
174
|
-
it('should call withMicroWorktree with correct operation name', async () => {
|
|
175
|
-
mockWithMicroWorktree.mockResolvedValue({});
|
|
176
|
-
mockExecSync.mockReturnValue('');
|
|
177
|
-
const args = { version: '2.1.0' };
|
|
178
|
-
await executeUpgradeInMicroWorktree(args);
|
|
179
|
-
expect(mockWithMicroWorktree).toHaveBeenCalledTimes(1);
|
|
180
|
-
expect(mockWithMicroWorktree).toHaveBeenCalledWith(expect.objectContaining({
|
|
181
|
-
operation: 'lumenflow-upgrade',
|
|
182
|
-
}));
|
|
183
|
-
});
|
|
184
|
-
it('should use a unique ID for micro-worktree based on timestamp', async () => {
|
|
185
|
-
mockWithMicroWorktree.mockResolvedValue({});
|
|
186
|
-
mockExecSync.mockReturnValue('');
|
|
187
|
-
const args = { latest: true };
|
|
188
|
-
await executeUpgradeInMicroWorktree(args);
|
|
189
|
-
// ID should be a timestamp-like string
|
|
190
|
-
expect(mockWithMicroWorktree).toHaveBeenCalledWith(expect.objectContaining({
|
|
191
|
-
id: expect.stringMatching(/^upgrade-\d+$/),
|
|
192
|
-
}));
|
|
193
|
-
});
|
|
194
|
-
it('should execute pnpm add in the micro-worktree', async () => {
|
|
195
|
-
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
196
|
-
// Simulate calling the execute function with a worktree path
|
|
197
|
-
return options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
198
|
-
});
|
|
199
|
-
const args = { version: '2.1.0' };
|
|
200
|
-
await executeUpgradeInMicroWorktree(args);
|
|
201
|
-
// Verify pnpm add was executed with correct cwd
|
|
202
|
-
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining('pnpm add'), expect.objectContaining({
|
|
203
|
-
cwd: '/tmp/test-worktree',
|
|
204
|
-
}));
|
|
205
|
-
});
|
|
206
|
-
it('should include all 7 packages in the pnpm add command', async () => {
|
|
207
|
-
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
208
|
-
return options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
209
|
-
});
|
|
210
|
-
const args = { version: '2.1.0' };
|
|
211
|
-
await executeUpgradeInMicroWorktree(args);
|
|
212
|
-
// Get the command that was executed
|
|
213
|
-
const execCall = mockExecSync.mock.calls[0][0];
|
|
214
|
-
expect(typeof execCall).toBe('string');
|
|
215
|
-
// Verify all 7 packages are included
|
|
216
|
-
for (const pkg of LUMENFLOW_PACKAGES) {
|
|
217
|
-
expect(execCall).toContain(`${pkg}@2.1.0`);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
it('should return appropriate commit message and files', async () => {
|
|
221
|
-
let executeResult;
|
|
222
|
-
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
223
|
-
executeResult = await options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
224
|
-
return executeResult;
|
|
225
|
-
});
|
|
226
|
-
const args = { version: '2.1.0' };
|
|
227
|
-
await executeUpgradeInMicroWorktree(args);
|
|
228
|
-
expect(executeResult).toBeDefined();
|
|
229
|
-
expect(executeResult.commitMessage).toContain('upgrade @lumenflow packages');
|
|
230
|
-
expect(executeResult.files).toContain('package.json');
|
|
231
|
-
expect(executeResult.files).toContain('pnpm-lock.yaml');
|
|
232
|
-
});
|
|
233
|
-
it('should use --latest version specifier when latest flag is set', async () => {
|
|
234
|
-
mockWithMicroWorktree.mockImplementation(async (options) => {
|
|
235
|
-
return options.execute({ worktreePath: '/tmp/test-worktree' });
|
|
236
|
-
});
|
|
237
|
-
const args = { latest: true };
|
|
238
|
-
await executeUpgradeInMicroWorktree(args);
|
|
239
|
-
const execCall = mockExecSync.mock.calls[0][0];
|
|
240
|
-
expect(execCall).toContain('@lumenflow/core@latest');
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
describe('dry-run mode', () => {
|
|
244
|
-
it('should not call withMicroWorktree when dryRun is true', async () => {
|
|
245
|
-
const args = { version: '2.1.0', dryRun: true };
|
|
246
|
-
// In dry-run mode, executeUpgradeInMicroWorktree should not be called
|
|
247
|
-
// This is handled by the main() function checking dryRun before calling execute
|
|
248
|
-
// We just verify the function exists and can be called
|
|
249
|
-
expect(typeof executeUpgradeInMicroWorktree).toBe('function');
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
describe('legacy worktree validation removal', () => {
|
|
253
|
-
it('should not require user to be in a worktree', () => {
|
|
254
|
-
// The old implementation required users to be inside a worktree
|
|
255
|
-
// The new implementation uses micro-worktree and runs from main checkout
|
|
256
|
-
// This test verifies the old validateWorktreeContext is no longer used
|
|
257
|
-
expect(typeof validateMainCheckout).toBe('function');
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
const execAsync = promisify(exec);
|
|
6
|
-
const CLI_DIST_PATH = path.resolve(__dirname, '../../dist/mem-cleanup.js');
|
|
7
|
-
describe('mem:cleanup CLI execution', () => {
|
|
8
|
-
it('should run --help without crashing', async () => {
|
|
9
|
-
try {
|
|
10
|
-
// We run the actual built JS file to catch ESM/CJS compatibility issues
|
|
11
|
-
const { stdout } = await execAsync(`node ${CLI_DIST_PATH} --help`);
|
|
12
|
-
expect(stdout).toContain('Usage: mem-cleanup');
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
// If it fails, we want to see why (expecting ReferenceError: require is not defined)
|
|
16
|
-
throw new Error(`Command failed: ${error.message}\nStderr: ${error.stderr}`);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
});
|
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory Layer Integration Tests (WU-1363)
|
|
3
|
-
*
|
|
4
|
-
* Integration tests for memory layer operations:
|
|
5
|
-
* - AC3: mem:checkpoint, mem:signal, mem:inbox
|
|
6
|
-
*
|
|
7
|
-
* These tests validate the memory layer's ability to:
|
|
8
|
-
* - Create checkpoints for context preservation
|
|
9
|
-
* - Send and receive signals for agent coordination
|
|
10
|
-
* - Filter and query signals from inbox
|
|
11
|
-
*
|
|
12
|
-
* TDD: Tests written BEFORE implementation verification.
|
|
13
|
-
*/
|
|
14
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
15
|
-
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
16
|
-
import { join } from 'node:path';
|
|
17
|
-
import { tmpdir } from 'node:os';
|
|
18
|
-
import { createCheckpoint, createSignal, loadSignals, markSignalsAsRead } from '@lumenflow/memory';
|
|
19
|
-
// Test constants
|
|
20
|
-
const TEST_WU_ID = 'WU-9910';
|
|
21
|
-
const TEST_LANE = 'Framework: CLI';
|
|
22
|
-
// Session ID must be a valid UUID
|
|
23
|
-
const TEST_SESSION_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
|
24
|
-
/**
|
|
25
|
-
* Helper to create minimal memory directory structure
|
|
26
|
-
*/
|
|
27
|
-
function createMemoryProject(baseDir) {
|
|
28
|
-
const dirs = ['.lumenflow/memory', '.lumenflow/state'];
|
|
29
|
-
for (const dir of dirs) {
|
|
30
|
-
mkdirSync(join(baseDir, dir), { recursive: true });
|
|
31
|
-
}
|
|
32
|
-
// Create minimal config
|
|
33
|
-
const configContent = `
|
|
34
|
-
version: 1
|
|
35
|
-
memory:
|
|
36
|
-
enabled: true
|
|
37
|
-
decay:
|
|
38
|
-
enabled: false
|
|
39
|
-
`;
|
|
40
|
-
writeFileSync(join(baseDir, '.lumenflow.config.yaml'), configContent);
|
|
41
|
-
}
|
|
42
|
-
describe('Memory Layer Integration Tests (WU-1363)', () => {
|
|
43
|
-
let tempDir;
|
|
44
|
-
let originalCwd;
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
tempDir = join(tmpdir(), `memory-integration-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
47
|
-
mkdirSync(tempDir, { recursive: true });
|
|
48
|
-
originalCwd = process.cwd();
|
|
49
|
-
createMemoryProject(tempDir);
|
|
50
|
-
});
|
|
51
|
-
afterEach(() => {
|
|
52
|
-
process.chdir(originalCwd);
|
|
53
|
-
if (existsSync(tempDir)) {
|
|
54
|
-
try {
|
|
55
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Ignore cleanup errors
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
describe('AC3: Integration tests for memory checkpoint, signal, inbox', () => {
|
|
63
|
-
describe('mem:checkpoint functionality', () => {
|
|
64
|
-
it('should create a checkpoint node with correct structure', async () => {
|
|
65
|
-
// Arrange
|
|
66
|
-
process.chdir(tempDir);
|
|
67
|
-
const note = 'Checkpoint before gates';
|
|
68
|
-
// Act
|
|
69
|
-
const result = await createCheckpoint(tempDir, {
|
|
70
|
-
note,
|
|
71
|
-
wuId: TEST_WU_ID,
|
|
72
|
-
sessionId: TEST_SESSION_ID,
|
|
73
|
-
});
|
|
74
|
-
// Assert
|
|
75
|
-
expect(result.success).toBe(true);
|
|
76
|
-
expect(result.checkpoint).toBeDefined();
|
|
77
|
-
expect(result.checkpoint.id).toMatch(/^mem-/);
|
|
78
|
-
expect(result.checkpoint.type).toBe('checkpoint');
|
|
79
|
-
expect(result.checkpoint.content).toContain(note);
|
|
80
|
-
expect(result.checkpoint.wu_id).toBe(TEST_WU_ID);
|
|
81
|
-
expect(result.checkpoint.session_id).toBe(TEST_SESSION_ID);
|
|
82
|
-
});
|
|
83
|
-
it('should include progress and nextSteps in metadata', async () => {
|
|
84
|
-
// Arrange
|
|
85
|
-
process.chdir(tempDir);
|
|
86
|
-
const progress = 'Completed AC1 and AC2';
|
|
87
|
-
const nextSteps = 'Run gates and complete wu:done';
|
|
88
|
-
// Act
|
|
89
|
-
const result = await createCheckpoint(tempDir, {
|
|
90
|
-
note: 'Progress checkpoint',
|
|
91
|
-
wuId: TEST_WU_ID,
|
|
92
|
-
progress,
|
|
93
|
-
nextSteps,
|
|
94
|
-
});
|
|
95
|
-
// Assert
|
|
96
|
-
expect(result.checkpoint.metadata).toBeDefined();
|
|
97
|
-
expect(result.checkpoint.metadata?.progress).toBe(progress);
|
|
98
|
-
expect(result.checkpoint.metadata?.nextSteps).toBe(nextSteps);
|
|
99
|
-
});
|
|
100
|
-
it('should persist checkpoint to memory store', async () => {
|
|
101
|
-
// Arrange
|
|
102
|
-
process.chdir(tempDir);
|
|
103
|
-
// Act
|
|
104
|
-
await createCheckpoint(tempDir, {
|
|
105
|
-
note: 'Persisted checkpoint',
|
|
106
|
-
wuId: TEST_WU_ID,
|
|
107
|
-
});
|
|
108
|
-
// Assert - Check memory file exists (memory store uses memory.jsonl)
|
|
109
|
-
const memoryFile = join(tempDir, '.lumenflow/memory/memory.jsonl');
|
|
110
|
-
expect(existsSync(memoryFile)).toBe(true);
|
|
111
|
-
const content = readFileSync(memoryFile, 'utf-8');
|
|
112
|
-
expect(content).toContain('Persisted checkpoint');
|
|
113
|
-
});
|
|
114
|
-
it('should validate note is required', async () => {
|
|
115
|
-
// Arrange
|
|
116
|
-
process.chdir(tempDir);
|
|
117
|
-
// Act & Assert
|
|
118
|
-
await expect(createCheckpoint(tempDir, {
|
|
119
|
-
note: '',
|
|
120
|
-
wuId: TEST_WU_ID,
|
|
121
|
-
})).rejects.toThrow(/empty/i);
|
|
122
|
-
});
|
|
123
|
-
it('should validate WU ID format if provided', async () => {
|
|
124
|
-
// Arrange
|
|
125
|
-
process.chdir(tempDir);
|
|
126
|
-
// Act & Assert
|
|
127
|
-
await expect(createCheckpoint(tempDir, {
|
|
128
|
-
note: 'Test checkpoint',
|
|
129
|
-
wuId: 'INVALID-ID',
|
|
130
|
-
})).rejects.toThrow(/WU/i);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
describe('mem:signal functionality', () => {
|
|
134
|
-
it('should create a signal with correct structure', async () => {
|
|
135
|
-
// Arrange
|
|
136
|
-
process.chdir(tempDir);
|
|
137
|
-
const message = 'Starting implementation';
|
|
138
|
-
// Act
|
|
139
|
-
const result = await createSignal(tempDir, {
|
|
140
|
-
message,
|
|
141
|
-
wuId: TEST_WU_ID,
|
|
142
|
-
lane: TEST_LANE,
|
|
143
|
-
});
|
|
144
|
-
// Assert
|
|
145
|
-
expect(result.success).toBe(true);
|
|
146
|
-
expect(result.signal).toBeDefined();
|
|
147
|
-
expect(result.signal.id).toMatch(/^sig-/);
|
|
148
|
-
expect(result.signal.message).toBe(message);
|
|
149
|
-
expect(result.signal.wu_id).toBe(TEST_WU_ID);
|
|
150
|
-
expect(result.signal.lane).toBe(TEST_LANE);
|
|
151
|
-
expect(result.signal.read).toBe(false);
|
|
152
|
-
});
|
|
153
|
-
it('should persist signal to signals.jsonl', async () => {
|
|
154
|
-
// Arrange
|
|
155
|
-
process.chdir(tempDir);
|
|
156
|
-
// Act
|
|
157
|
-
await createSignal(tempDir, {
|
|
158
|
-
message: 'Persisted signal',
|
|
159
|
-
wuId: TEST_WU_ID,
|
|
160
|
-
});
|
|
161
|
-
// Assert
|
|
162
|
-
const signalsFile = join(tempDir, '.lumenflow/memory/signals.jsonl');
|
|
163
|
-
expect(existsSync(signalsFile)).toBe(true);
|
|
164
|
-
const content = readFileSync(signalsFile, 'utf-8');
|
|
165
|
-
expect(content).toContain('Persisted signal');
|
|
166
|
-
});
|
|
167
|
-
it('should validate message is required', async () => {
|
|
168
|
-
// Arrange
|
|
169
|
-
process.chdir(tempDir);
|
|
170
|
-
// Act & Assert
|
|
171
|
-
await expect(createSignal(tempDir, {
|
|
172
|
-
message: '',
|
|
173
|
-
wuId: TEST_WU_ID,
|
|
174
|
-
})).rejects.toThrow(/required/i);
|
|
175
|
-
});
|
|
176
|
-
it('should validate WU ID format if provided', async () => {
|
|
177
|
-
// Arrange
|
|
178
|
-
process.chdir(tempDir);
|
|
179
|
-
// Act & Assert
|
|
180
|
-
await expect(createSignal(tempDir, {
|
|
181
|
-
message: 'Test signal',
|
|
182
|
-
wuId: 'INVALID-123',
|
|
183
|
-
})).rejects.toThrow(/WU/i);
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
describe('mem:inbox functionality', () => {
|
|
187
|
-
it('should load all signals', async () => {
|
|
188
|
-
// Arrange
|
|
189
|
-
process.chdir(tempDir);
|
|
190
|
-
await createSignal(tempDir, { message: 'Signal 1', wuId: TEST_WU_ID });
|
|
191
|
-
await createSignal(tempDir, { message: 'Signal 2', wuId: TEST_WU_ID });
|
|
192
|
-
await createSignal(tempDir, { message: 'Signal 3', wuId: 'WU-9999' });
|
|
193
|
-
// Act
|
|
194
|
-
const signals = await loadSignals(tempDir);
|
|
195
|
-
// Assert
|
|
196
|
-
expect(signals).toHaveLength(3);
|
|
197
|
-
});
|
|
198
|
-
it('should filter signals by WU ID', async () => {
|
|
199
|
-
// Arrange
|
|
200
|
-
process.chdir(tempDir);
|
|
201
|
-
await createSignal(tempDir, { message: 'Signal 1', wuId: TEST_WU_ID });
|
|
202
|
-
await createSignal(tempDir, { message: 'Signal 2', wuId: TEST_WU_ID });
|
|
203
|
-
await createSignal(tempDir, { message: 'Other WU', wuId: 'WU-9999' });
|
|
204
|
-
// Act
|
|
205
|
-
const signals = await loadSignals(tempDir, { wuId: TEST_WU_ID });
|
|
206
|
-
// Assert
|
|
207
|
-
expect(signals).toHaveLength(2);
|
|
208
|
-
signals.forEach((sig) => expect(sig.wu_id).toBe(TEST_WU_ID));
|
|
209
|
-
});
|
|
210
|
-
it('should filter signals by lane', async () => {
|
|
211
|
-
// Arrange
|
|
212
|
-
process.chdir(tempDir);
|
|
213
|
-
await createSignal(tempDir, { message: 'CLI signal', lane: TEST_LANE });
|
|
214
|
-
await createSignal(tempDir, { message: 'Other lane', lane: 'Framework: Core' });
|
|
215
|
-
// Act
|
|
216
|
-
const signals = await loadSignals(tempDir, { lane: TEST_LANE });
|
|
217
|
-
// Assert
|
|
218
|
-
expect(signals).toHaveLength(1);
|
|
219
|
-
expect(signals[0].lane).toBe(TEST_LANE);
|
|
220
|
-
});
|
|
221
|
-
it('should filter unread signals only', async () => {
|
|
222
|
-
// Arrange
|
|
223
|
-
process.chdir(tempDir);
|
|
224
|
-
const result1 = await createSignal(tempDir, { message: 'Unread signal' });
|
|
225
|
-
await createSignal(tempDir, { message: 'Another unread' });
|
|
226
|
-
// Mark first as read
|
|
227
|
-
await markSignalsAsRead(tempDir, [result1.signal.id]);
|
|
228
|
-
// Act
|
|
229
|
-
const signals = await loadSignals(tempDir, { unreadOnly: true });
|
|
230
|
-
// Assert
|
|
231
|
-
expect(signals).toHaveLength(1);
|
|
232
|
-
expect(signals[0].message).toBe('Another unread');
|
|
233
|
-
});
|
|
234
|
-
it('should filter signals since a specific time', async () => {
|
|
235
|
-
// Arrange
|
|
236
|
-
process.chdir(tempDir);
|
|
237
|
-
const beforeTime = new Date();
|
|
238
|
-
// Wait a bit to ensure time difference
|
|
239
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
240
|
-
await createSignal(tempDir, { message: 'Recent signal' });
|
|
241
|
-
// Act
|
|
242
|
-
const signals = await loadSignals(tempDir, { since: beforeTime });
|
|
243
|
-
// Assert
|
|
244
|
-
expect(signals).toHaveLength(1);
|
|
245
|
-
expect(signals[0].message).toBe('Recent signal');
|
|
246
|
-
});
|
|
247
|
-
it('should return empty array when no signals exist', async () => {
|
|
248
|
-
// Arrange
|
|
249
|
-
process.chdir(tempDir);
|
|
250
|
-
// Act
|
|
251
|
-
const signals = await loadSignals(tempDir);
|
|
252
|
-
// Assert
|
|
253
|
-
expect(signals).toHaveLength(0);
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
describe('mark signals as read', () => {
|
|
257
|
-
it('should mark signals as read', async () => {
|
|
258
|
-
// Arrange
|
|
259
|
-
process.chdir(tempDir);
|
|
260
|
-
const result1 = await createSignal(tempDir, { message: 'Signal 1' });
|
|
261
|
-
const result2 = await createSignal(tempDir, { message: 'Signal 2' });
|
|
262
|
-
// Act
|
|
263
|
-
const markResult = await markSignalsAsRead(tempDir, [result1.signal.id, result2.signal.id]);
|
|
264
|
-
// Assert
|
|
265
|
-
expect(markResult.markedCount).toBe(2);
|
|
266
|
-
// Verify signals are now read
|
|
267
|
-
const allSignals = await loadSignals(tempDir);
|
|
268
|
-
expect(allSignals.every((sig) => sig.read)).toBe(true);
|
|
269
|
-
});
|
|
270
|
-
it('should not count already-read signals', async () => {
|
|
271
|
-
// Arrange
|
|
272
|
-
process.chdir(tempDir);
|
|
273
|
-
const result = await createSignal(tempDir, { message: 'Signal 1' });
|
|
274
|
-
// Mark as read first time
|
|
275
|
-
await markSignalsAsRead(tempDir, [result.signal.id]);
|
|
276
|
-
// Act - Try to mark again
|
|
277
|
-
const secondMarkResult = await markSignalsAsRead(tempDir, [result.signal.id]);
|
|
278
|
-
// Assert
|
|
279
|
-
expect(secondMarkResult.markedCount).toBe(0);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
describe('complete memory workflow', () => {
|
|
283
|
-
it('should support full checkpoint and signal workflow', async () => {
|
|
284
|
-
// This test validates the complete memory workflow:
|
|
285
|
-
// 1. Create initial checkpoint
|
|
286
|
-
// 2. Send progress signals
|
|
287
|
-
// 3. Check inbox for signals
|
|
288
|
-
// 4. Mark signals as read
|
|
289
|
-
// 5. Create final checkpoint
|
|
290
|
-
// Arrange
|
|
291
|
-
process.chdir(tempDir);
|
|
292
|
-
// Step 1: Initial checkpoint
|
|
293
|
-
const initialCheckpoint = await createCheckpoint(tempDir, {
|
|
294
|
-
note: 'Starting work on WU',
|
|
295
|
-
wuId: TEST_WU_ID,
|
|
296
|
-
sessionId: TEST_SESSION_ID,
|
|
297
|
-
});
|
|
298
|
-
expect(initialCheckpoint.success).toBe(true);
|
|
299
|
-
// Step 2: Send progress signals
|
|
300
|
-
await createSignal(tempDir, {
|
|
301
|
-
message: 'AC1 complete',
|
|
302
|
-
wuId: TEST_WU_ID,
|
|
303
|
-
lane: TEST_LANE,
|
|
304
|
-
});
|
|
305
|
-
await createSignal(tempDir, {
|
|
306
|
-
message: 'AC2 in progress',
|
|
307
|
-
wuId: TEST_WU_ID,
|
|
308
|
-
lane: TEST_LANE,
|
|
309
|
-
});
|
|
310
|
-
// Step 3: Check inbox
|
|
311
|
-
const inbox = await loadSignals(tempDir, { wuId: TEST_WU_ID, unreadOnly: true });
|
|
312
|
-
expect(inbox).toHaveLength(2);
|
|
313
|
-
// Step 4: Mark as read
|
|
314
|
-
const signalIds = inbox.map((sig) => sig.id);
|
|
315
|
-
await markSignalsAsRead(tempDir, signalIds);
|
|
316
|
-
const unreadAfter = await loadSignals(tempDir, { unreadOnly: true });
|
|
317
|
-
expect(unreadAfter).toHaveLength(0);
|
|
318
|
-
// Step 5: Final checkpoint
|
|
319
|
-
const finalCheckpoint = await createCheckpoint(tempDir, {
|
|
320
|
-
note: 'Work complete, ready for wu:done',
|
|
321
|
-
wuId: TEST_WU_ID,
|
|
322
|
-
sessionId: TEST_SESSION_ID,
|
|
323
|
-
progress: 'All acceptance criteria met',
|
|
324
|
-
nextSteps: 'Run pnpm wu:done --id ' + TEST_WU_ID,
|
|
325
|
-
});
|
|
326
|
-
expect(finalCheckpoint.success).toBe(true);
|
|
327
|
-
// Verify memory store has all data (memory store uses memory.jsonl)
|
|
328
|
-
const memoryFile = join(tempDir, '.lumenflow/memory/memory.jsonl');
|
|
329
|
-
expect(existsSync(memoryFile)).toBe(true);
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
});
|