@mndrk/memx 0.3.2 → 0.3.4
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 +98 -77
- package/coverage/clover.xml +1160 -0
- package/coverage/coverage-final.json +3 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/index.js.html +7255 -0
- package/coverage/lcov-report/mcp.js.html +1009 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +2017 -0
- package/index.js +651 -243
- package/package.json +24 -2
- package/test/additional.test.js +373 -0
- package/test/branches.test.js +247 -0
- package/test/commands.test.js +663 -0
- package/test/context.test.js +185 -0
- package/test/coverage.test.js +366 -0
- package/test/dispatch.test.js +220 -0
- package/test/edge-coverage.test.js +250 -0
- package/test/edge.test.js +434 -0
- package/test/final-coverage.test.js +316 -0
- package/test/final-edges.test.js +199 -0
- package/test/init-local.test.js +316 -0
- package/test/init.test.js +122 -0
- package/test/interactive.test.js +229 -0
- package/test/main-dispatch.test.js +164 -0
- package/test/main-full.test.js +590 -0
- package/test/main.test.js +197 -0
- package/test/mcp-server.test.js +320 -0
- package/test/mcp.test.js +288 -0
- package/test/more.test.js +312 -0
- package/test/new.test.js +175 -0
- package/test/skill.test.js +247 -0
- package/test/tasks-interactive.test.js +243 -0
- package/test/utils.test.js +367 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge case tests to increase coverage
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
// Mock child_process before requiring index.js
|
|
10
|
+
jest.mock('child_process', () => ({
|
|
11
|
+
execSync: jest.fn(),
|
|
12
|
+
spawnSync: jest.fn(() => ({ status: 0, stdout: '', stderr: '' })),
|
|
13
|
+
spawn: jest.fn()
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Mock readline
|
|
17
|
+
jest.mock('readline', () => ({
|
|
18
|
+
createInterface: jest.fn(() => ({
|
|
19
|
+
question: jest.fn((q, cb) => cb('')),
|
|
20
|
+
close: jest.fn(),
|
|
21
|
+
on: jest.fn()
|
|
22
|
+
}))
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
cmdStatus,
|
|
27
|
+
cmdGoal,
|
|
28
|
+
cmdNext,
|
|
29
|
+
cmdCheckpoint,
|
|
30
|
+
cmdLearn,
|
|
31
|
+
cmdContext,
|
|
32
|
+
cmdProgress,
|
|
33
|
+
cmdConstraint,
|
|
34
|
+
cmdCriteria,
|
|
35
|
+
writeMemFile,
|
|
36
|
+
readMemFile,
|
|
37
|
+
parseFrontmatter,
|
|
38
|
+
serializeFrontmatter,
|
|
39
|
+
} = require('../index.js');
|
|
40
|
+
|
|
41
|
+
const { spawnSync, execSync } = require('child_process');
|
|
42
|
+
|
|
43
|
+
const testDir = path.join(os.tmpdir(), 'memx-test-edge-' + Date.now());
|
|
44
|
+
let memDir;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
memDir = path.join(testDir, '.mem');
|
|
48
|
+
fs.mkdirSync(memDir, { recursive: true });
|
|
49
|
+
fs.mkdirSync(path.join(memDir, '.git'), { recursive: true });
|
|
50
|
+
spawnSync.mockReset();
|
|
51
|
+
spawnSync.mockReturnValue({ status: 0, stdout: '', stderr: '' });
|
|
52
|
+
execSync.mockClear();
|
|
53
|
+
jest.spyOn(console, 'log').mockImplementation();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
if (fs.existsSync(testDir)) {
|
|
58
|
+
fs.rmSync(testDir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
jest.restoreAllMocks();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('cmdStatus edge cases', () => {
|
|
64
|
+
test('shows status with all sections', () => {
|
|
65
|
+
spawnSync.mockReturnValue({ status: 0, stdout: 'task/test', stderr: '' });
|
|
66
|
+
|
|
67
|
+
writeMemFile(memDir, 'goal.md', `---
|
|
68
|
+
task: test
|
|
69
|
+
status: active
|
|
70
|
+
created: 2024-01-01
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
# Goal
|
|
74
|
+
|
|
75
|
+
Build something amazing
|
|
76
|
+
|
|
77
|
+
## Definition of Done
|
|
78
|
+
|
|
79
|
+
- [x] First criterion
|
|
80
|
+
- [ ] Second criterion
|
|
81
|
+
|
|
82
|
+
## Progress: 50%`);
|
|
83
|
+
|
|
84
|
+
writeMemFile(memDir, 'state.md', `---
|
|
85
|
+
status: active
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
# State
|
|
89
|
+
|
|
90
|
+
## Next Step
|
|
91
|
+
|
|
92
|
+
Continue building
|
|
93
|
+
|
|
94
|
+
## Checkpoints
|
|
95
|
+
|
|
96
|
+
- [x] Started
|
|
97
|
+
- [x] First milestone`);
|
|
98
|
+
|
|
99
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
100
|
+
cmdStatus(memDir);
|
|
101
|
+
|
|
102
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
103
|
+
expect(output).toContain('test');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('shows blocked status', () => {
|
|
107
|
+
spawnSync.mockReturnValue({ status: 0, stdout: 'task/blocked-test', stderr: '' });
|
|
108
|
+
|
|
109
|
+
writeMemFile(memDir, 'state.md', `---
|
|
110
|
+
status: blocked
|
|
111
|
+
blocker: Waiting for review
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# State`);
|
|
115
|
+
|
|
116
|
+
writeMemFile(memDir, 'goal.md', `---
|
|
117
|
+
task: blocked-test
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
# Goal
|
|
121
|
+
|
|
122
|
+
Test`);
|
|
123
|
+
|
|
124
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
125
|
+
cmdStatus(memDir);
|
|
126
|
+
|
|
127
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
128
|
+
expect(output).toContain('blocked');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('shows done status', () => {
|
|
132
|
+
spawnSync.mockReturnValue({ status: 0, stdout: 'task/done-test', stderr: '' });
|
|
133
|
+
|
|
134
|
+
writeMemFile(memDir, 'state.md', `---
|
|
135
|
+
status: done
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
# State`);
|
|
139
|
+
|
|
140
|
+
writeMemFile(memDir, 'goal.md', `---
|
|
141
|
+
task: done-test
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
# Goal
|
|
145
|
+
|
|
146
|
+
Completed task`);
|
|
147
|
+
|
|
148
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
149
|
+
cmdStatus(memDir);
|
|
150
|
+
|
|
151
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('cmdGoal edge cases', () => {
|
|
156
|
+
test('updates existing goal text', () => {
|
|
157
|
+
writeMemFile(memDir, 'goal.md', `---
|
|
158
|
+
task: test
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
# Goal
|
|
162
|
+
|
|
163
|
+
Old goal text
|
|
164
|
+
|
|
165
|
+
## Definition of Done
|
|
166
|
+
|
|
167
|
+
- [ ] Criterion`);
|
|
168
|
+
|
|
169
|
+
cmdGoal(['New', 'goal', 'text'], memDir);
|
|
170
|
+
|
|
171
|
+
const content = readMemFile(memDir, 'goal.md');
|
|
172
|
+
expect(content).toContain('New goal text');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('cmdNext edge cases', () => {
|
|
177
|
+
test('updates existing next step', () => {
|
|
178
|
+
writeMemFile(memDir, 'state.md', `---
|
|
179
|
+
status: active
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
# State
|
|
183
|
+
|
|
184
|
+
## Next Step
|
|
185
|
+
|
|
186
|
+
Old next step
|
|
187
|
+
|
|
188
|
+
## Checkpoints
|
|
189
|
+
|
|
190
|
+
- [x] Started`);
|
|
191
|
+
|
|
192
|
+
cmdNext(['New', 'next', 'step'], memDir);
|
|
193
|
+
|
|
194
|
+
const content = readMemFile(memDir, 'state.md');
|
|
195
|
+
expect(content).toContain('New next step');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('cmdCheckpoint edge cases', () => {
|
|
200
|
+
test('adds checkpoint with existing ones', () => {
|
|
201
|
+
writeMemFile(memDir, 'state.md', `---
|
|
202
|
+
status: active
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
# State
|
|
206
|
+
|
|
207
|
+
## Next Step
|
|
208
|
+
|
|
209
|
+
Continue
|
|
210
|
+
|
|
211
|
+
## Checkpoints
|
|
212
|
+
|
|
213
|
+
- [x] First checkpoint`);
|
|
214
|
+
|
|
215
|
+
cmdCheckpoint(['Second', 'checkpoint'], memDir);
|
|
216
|
+
|
|
217
|
+
const content = readMemFile(memDir, 'state.md');
|
|
218
|
+
expect(content).toContain('Second checkpoint');
|
|
219
|
+
expect(content).toContain('First checkpoint');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('cmdLearn edge cases', () => {
|
|
224
|
+
test('learns to task memory', () => {
|
|
225
|
+
writeMemFile(memDir, 'memory.md', '# Learnings\n\n');
|
|
226
|
+
|
|
227
|
+
cmdLearn(['Task', 'specific', 'insight'], memDir);
|
|
228
|
+
|
|
229
|
+
const content = readMemFile(memDir, 'memory.md');
|
|
230
|
+
expect(content).toContain('Task specific insight');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('creates memory.md if not exists', () => {
|
|
234
|
+
cmdLearn(['First', 'learning'], memDir);
|
|
235
|
+
|
|
236
|
+
const content = readMemFile(memDir, 'memory.md');
|
|
237
|
+
expect(content).toContain('First learning');
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('cmdContext edge cases', () => {
|
|
242
|
+
test('outputs full context', () => {
|
|
243
|
+
spawnSync.mockReturnValue({ status: 0, stdout: 'task/context-test', stderr: '' });
|
|
244
|
+
|
|
245
|
+
writeMemFile(memDir, 'goal.md', `---
|
|
246
|
+
task: context-test
|
|
247
|
+
created: 2024-01-01
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
# Goal
|
|
251
|
+
|
|
252
|
+
Test goal for context
|
|
253
|
+
|
|
254
|
+
## Constraints
|
|
255
|
+
|
|
256
|
+
- Be fast
|
|
257
|
+
|
|
258
|
+
## Definition of Done
|
|
259
|
+
|
|
260
|
+
- [ ] Done criterion`);
|
|
261
|
+
|
|
262
|
+
writeMemFile(memDir, 'state.md', `---
|
|
263
|
+
status: active
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
# State
|
|
267
|
+
|
|
268
|
+
## Next Step
|
|
269
|
+
|
|
270
|
+
Work on it
|
|
271
|
+
|
|
272
|
+
## Checkpoints
|
|
273
|
+
|
|
274
|
+
- [x] Started`);
|
|
275
|
+
|
|
276
|
+
writeMemFile(memDir, 'memory.md', `# Learnings
|
|
277
|
+
|
|
278
|
+
- 2024-01-01: First learning`);
|
|
279
|
+
|
|
280
|
+
writeMemFile(memDir, 'playbook.md', `# Playbook
|
|
281
|
+
|
|
282
|
+
- Global rule`);
|
|
283
|
+
|
|
284
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
285
|
+
cmdContext(memDir);
|
|
286
|
+
|
|
287
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
288
|
+
expect(output).toContain('context-test');
|
|
289
|
+
expect(output).toContain('Test goal for context');
|
|
290
|
+
expect(output).toContain('First learning');
|
|
291
|
+
expect(output).toContain('Global rule');
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe('cmdProgress edge cases', () => {
|
|
296
|
+
test('calculates and updates progress', () => {
|
|
297
|
+
writeMemFile(memDir, 'goal.md', `# Goal
|
|
298
|
+
|
|
299
|
+
Test
|
|
300
|
+
|
|
301
|
+
## Definition of Done
|
|
302
|
+
|
|
303
|
+
- [x] First
|
|
304
|
+
- [x] Second
|
|
305
|
+
- [ ] Third
|
|
306
|
+
- [ ] Fourth
|
|
307
|
+
|
|
308
|
+
## Progress: 0%`);
|
|
309
|
+
|
|
310
|
+
cmdProgress([], memDir);
|
|
311
|
+
|
|
312
|
+
const content = readMemFile(memDir, 'goal.md');
|
|
313
|
+
expect(content).toContain('50%');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('shows 100% when all done', () => {
|
|
317
|
+
writeMemFile(memDir, 'goal.md', `# Goal
|
|
318
|
+
|
|
319
|
+
Test
|
|
320
|
+
|
|
321
|
+
## Definition of Done
|
|
322
|
+
|
|
323
|
+
- [x] First
|
|
324
|
+
- [x] Second
|
|
325
|
+
|
|
326
|
+
## Progress: 0%`);
|
|
327
|
+
|
|
328
|
+
cmdProgress([], memDir);
|
|
329
|
+
|
|
330
|
+
const content = readMemFile(memDir, 'goal.md');
|
|
331
|
+
expect(content).toContain('100%');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('cmdConstraint edge cases', () => {
|
|
336
|
+
test('lists multiple constraints', () => {
|
|
337
|
+
writeMemFile(memDir, 'goal.md', `# Goal
|
|
338
|
+
|
|
339
|
+
Test
|
|
340
|
+
|
|
341
|
+
## Constraints
|
|
342
|
+
|
|
343
|
+
- No breaking changes
|
|
344
|
+
- Test coverage > 80%
|
|
345
|
+
- Performance < 100ms`);
|
|
346
|
+
|
|
347
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
348
|
+
cmdConstraint([], memDir);
|
|
349
|
+
|
|
350
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
351
|
+
expect(output).toContain('No breaking changes');
|
|
352
|
+
expect(output).toContain('Test coverage');
|
|
353
|
+
expect(output).toContain('Performance');
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('cmdCriteria edge cases', () => {
|
|
358
|
+
test('shows criteria when present', () => {
|
|
359
|
+
writeMemFile(memDir, 'goal.md', `# Goal
|
|
360
|
+
|
|
361
|
+
Test
|
|
362
|
+
|
|
363
|
+
## Definition of Done
|
|
364
|
+
|
|
365
|
+
- [ ] First criterion
|
|
366
|
+
- [x] Second criterion done
|
|
367
|
+
- [ ] Third criterion
|
|
368
|
+
|
|
369
|
+
## Progress: 33%`);
|
|
370
|
+
|
|
371
|
+
const consoleSpy = jest.spyOn(console, 'log');
|
|
372
|
+
cmdCriteria([], memDir);
|
|
373
|
+
|
|
374
|
+
const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
|
|
375
|
+
expect(output).toContain('criteria');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('parseFrontmatter edge cases', () => {
|
|
380
|
+
test('parses frontmatter with dates', () => {
|
|
381
|
+
const content = `---
|
|
382
|
+
task: test
|
|
383
|
+
created: 2024-01-15
|
|
384
|
+
status: active
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
# Goal
|
|
388
|
+
|
|
389
|
+
Body text`;
|
|
390
|
+
|
|
391
|
+
const result = parseFrontmatter(content);
|
|
392
|
+
expect(result.frontmatter.task).toBe('test');
|
|
393
|
+
expect(result.frontmatter.created).toBe('2024-01-15');
|
|
394
|
+
expect(result.frontmatter.status).toBe('active');
|
|
395
|
+
expect(result.body).toContain('Body text');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('handles frontmatter with boolean-like values', () => {
|
|
399
|
+
const content = `---
|
|
400
|
+
active: true
|
|
401
|
+
archived: false
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
Content`;
|
|
405
|
+
|
|
406
|
+
const result = parseFrontmatter(content);
|
|
407
|
+
// Simple YAML parser keeps values as strings
|
|
408
|
+
expect(result.frontmatter.active).toBe('true');
|
|
409
|
+
expect(result.frontmatter.archived).toBe('false');
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('serializeFrontmatter edge cases', () => {
|
|
414
|
+
test('serializes frontmatter with date', () => {
|
|
415
|
+
const result = serializeFrontmatter(
|
|
416
|
+
{ task: 'test', created: '2024-01-15' },
|
|
417
|
+
'# Goal\n\nBody'
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
expect(result).toContain('task: test');
|
|
421
|
+
expect(result).toContain('created: 2024-01-15');
|
|
422
|
+
expect(result).toContain('# Goal');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('serializes frontmatter with status', () => {
|
|
426
|
+
const result = serializeFrontmatter(
|
|
427
|
+
{ status: 'blocked', blocker: 'Waiting for API' },
|
|
428
|
+
'# State'
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
expect(result).toContain('status: blocked');
|
|
432
|
+
expect(result).toContain('blocker: Waiting for API');
|
|
433
|
+
});
|
|
434
|
+
});
|