@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,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
- });