@mndrk/memx 0.3.3 → 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.
Files changed (39) hide show
  1. package/README.md +98 -77
  2. package/coverage/clover.xml +1160 -0
  3. package/coverage/coverage-final.json +3 -0
  4. package/coverage/lcov-report/base.css +224 -0
  5. package/coverage/lcov-report/block-navigation.js +87 -0
  6. package/coverage/lcov-report/favicon.png +0 -0
  7. package/coverage/lcov-report/index.html +131 -0
  8. package/coverage/lcov-report/index.js.html +7255 -0
  9. package/coverage/lcov-report/mcp.js.html +1009 -0
  10. package/coverage/lcov-report/prettify.css +1 -0
  11. package/coverage/lcov-report/prettify.js +2 -0
  12. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  13. package/coverage/lcov-report/sorter.js +210 -0
  14. package/coverage/lcov.info +2017 -0
  15. package/index.js +651 -243
  16. package/package.json +24 -2
  17. package/test/additional.test.js +373 -0
  18. package/test/branches.test.js +247 -0
  19. package/test/commands.test.js +663 -0
  20. package/test/context.test.js +185 -0
  21. package/test/coverage.test.js +366 -0
  22. package/test/dispatch.test.js +220 -0
  23. package/test/edge-coverage.test.js +250 -0
  24. package/test/edge.test.js +434 -0
  25. package/test/final-coverage.test.js +316 -0
  26. package/test/final-edges.test.js +199 -0
  27. package/test/init-local.test.js +316 -0
  28. package/test/init.test.js +122 -0
  29. package/test/interactive.test.js +229 -0
  30. package/test/main-dispatch.test.js +164 -0
  31. package/test/main-full.test.js +590 -0
  32. package/test/main.test.js +197 -0
  33. package/test/mcp-server.test.js +320 -0
  34. package/test/mcp.test.js +288 -0
  35. package/test/more.test.js +312 -0
  36. package/test/new.test.js +175 -0
  37. package/test/skill.test.js +247 -0
  38. package/test/tasks-interactive.test.js +243 -0
  39. package/test/utils.test.js +367 -0
@@ -0,0 +1,590 @@
1
+ /**
2
+ * Full main() dispatcher tests with process.argv mocking
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ // Mock child_process
10
+ jest.mock('child_process', () => ({
11
+ execSync: jest.fn(),
12
+ spawnSync: jest.fn(() => ({ status: 0, stdout: '', stderr: '' })),
13
+ spawn: jest.fn(() => ({
14
+ on: jest.fn((event, cb) => {
15
+ if (event === 'exit') setTimeout(() => cb(0), 10);
16
+ })
17
+ }))
18
+ }));
19
+
20
+ // Mock readline
21
+ jest.mock('readline', () => ({
22
+ createInterface: jest.fn(() => ({
23
+ question: jest.fn((q, cb) => cb('')),
24
+ close: jest.fn(),
25
+ on: jest.fn()
26
+ }))
27
+ }));
28
+
29
+ const { spawnSync, execSync, spawn } = require('child_process');
30
+
31
+ // Store original values
32
+ const originalArgv = process.argv;
33
+ const originalCwd = process.cwd;
34
+ const originalExit = process.exit;
35
+
36
+ const testDir = path.join(os.tmpdir(), 'memx-test-main-full-' + Date.now());
37
+
38
+ beforeEach(() => {
39
+ // Create test directories
40
+ fs.mkdirSync(path.join(testDir, '.mem', '.git'), { recursive: true });
41
+
42
+ // Reset mocks
43
+ spawnSync.mockReset();
44
+ spawnSync.mockReturnValue({ status: 0, stdout: '', stderr: '' });
45
+ execSync.mockReset();
46
+ spawn.mockReset();
47
+ spawn.mockReturnValue({
48
+ on: jest.fn((event, cb) => {
49
+ if (event === 'exit') setTimeout(() => cb(0), 10);
50
+ })
51
+ });
52
+
53
+ jest.spyOn(console, 'log').mockImplementation();
54
+ jest.spyOn(console, 'error').mockImplementation();
55
+
56
+ // Mock process.exit to not actually exit
57
+ process.exit = jest.fn();
58
+ });
59
+
60
+ afterEach(() => {
61
+ process.argv = originalArgv;
62
+ process.cwd = originalCwd;
63
+ process.exit = originalExit;
64
+
65
+ if (fs.existsSync(testDir)) {
66
+ fs.rmSync(testDir, { recursive: true });
67
+ }
68
+
69
+ jest.restoreAllMocks();
70
+ jest.resetModules();
71
+ });
72
+
73
+ describe('main() dispatcher', () => {
74
+ test('shows help with --help flag', async () => {
75
+ process.argv = ['node', 'index.js', '--help'];
76
+
77
+ const { main } = require('../index.js');
78
+ await main();
79
+
80
+ const consoleSpy = console.log;
81
+ expect(consoleSpy).toHaveBeenCalled();
82
+ });
83
+
84
+ test('shows help with -h flag', async () => {
85
+ jest.resetModules();
86
+ process.argv = ['node', 'index.js', '-h'];
87
+
88
+ const { main } = require('../index.js');
89
+ await main();
90
+
91
+ expect(console.log).toHaveBeenCalled();
92
+ });
93
+
94
+ test('shows help with help command', async () => {
95
+ jest.resetModules();
96
+ process.argv = ['node', 'index.js', 'help'];
97
+
98
+ const { main } = require('../index.js');
99
+ await main();
100
+
101
+ expect(console.log).toHaveBeenCalled();
102
+ });
103
+
104
+ test('dispatches status command', async () => {
105
+ jest.resetModules();
106
+ process.argv = ['node', 'index.js', 'status'];
107
+ spawnSync.mockReturnValue({ status: 0, stdout: 'main', stderr: '' });
108
+
109
+ const { main, writeMemFile, CENTRAL_MEM } = require('../index.js');
110
+
111
+ // Create necessary files
112
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
113
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
114
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '---\ntask: test\n---\n\n# Goal\n\nTest');
115
+
116
+ await main();
117
+
118
+ expect(console.log).toHaveBeenCalled();
119
+ });
120
+
121
+ test('dispatches goal command', async () => {
122
+ jest.resetModules();
123
+ process.argv = ['node', 'index.js', 'goal'];
124
+
125
+ const { main, CENTRAL_MEM } = require('../index.js');
126
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
127
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '# Goal\n\nTest goal');
128
+
129
+ await main();
130
+
131
+ expect(console.log).toHaveBeenCalled();
132
+ });
133
+
134
+ test('dispatches learn command', async () => {
135
+ jest.resetModules();
136
+ process.argv = ['node', 'index.js', 'learn', 'Test', 'insight'];
137
+
138
+ const { main, CENTRAL_MEM } = require('../index.js');
139
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
140
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'memory.md'), '# Learnings\n\n');
141
+
142
+ await main();
143
+
144
+ // Learn command may or may not call spawnSync depending on state
145
+ expect(console.log).toHaveBeenCalled();
146
+ });
147
+
148
+ test('dispatches checkpoint command', async () => {
149
+ jest.resetModules();
150
+ process.argv = ['node', 'index.js', 'checkpoint', 'Done', 'something'];
151
+
152
+ const { main, CENTRAL_MEM } = require('../index.js');
153
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
154
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n## Checkpoints\n\n- [ ] Started');
155
+
156
+ await main();
157
+
158
+ expect(console.log).toHaveBeenCalled();
159
+ });
160
+
161
+ test('dispatches cp alias', async () => {
162
+ jest.resetModules();
163
+ process.argv = ['node', 'index.js', 'cp', 'Done'];
164
+
165
+ const { main, CENTRAL_MEM } = require('../index.js');
166
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
167
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n## Checkpoints\n\n');
168
+
169
+ await main();
170
+
171
+ expect(console.log).toHaveBeenCalled();
172
+ });
173
+
174
+ test('dispatches stuck command', async () => {
175
+ jest.resetModules();
176
+ process.argv = ['node', 'index.js', 'stuck', 'Blocked', 'by', 'API'];
177
+
178
+ const { main, CENTRAL_MEM } = require('../index.js');
179
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
180
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
181
+
182
+ await main();
183
+
184
+ expect(console.log).toHaveBeenCalled();
185
+ });
186
+
187
+ test('dispatches learnings command', async () => {
188
+ jest.resetModules();
189
+ process.argv = ['node', 'index.js', 'learnings'];
190
+
191
+ const { main, CENTRAL_MEM } = require('../index.js');
192
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
193
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'memory.md'), '# Learnings\n\n- Test');
194
+
195
+ await main();
196
+
197
+ expect(console.log).toHaveBeenCalled();
198
+ });
199
+
200
+ test('dispatches playbook command', async () => {
201
+ jest.resetModules();
202
+ process.argv = ['node', 'index.js', 'playbook'];
203
+
204
+ const { main, CENTRAL_MEM } = require('../index.js');
205
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
206
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'playbook.md'), '# Playbook\n\n- Rule');
207
+
208
+ await main();
209
+
210
+ expect(console.log).toHaveBeenCalled();
211
+ });
212
+
213
+ test('dispatches pb alias', async () => {
214
+ jest.resetModules();
215
+ process.argv = ['node', 'index.js', 'pb'];
216
+
217
+ const { main, CENTRAL_MEM } = require('../index.js');
218
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
219
+
220
+ await main();
221
+
222
+ expect(console.log).toHaveBeenCalled();
223
+ });
224
+
225
+ test('dispatches context command', async () => {
226
+ jest.resetModules();
227
+ process.argv = ['node', 'index.js', 'context'];
228
+ spawnSync.mockReturnValue({ status: 0, stdout: 'task/test', stderr: '' });
229
+
230
+ const { main, CENTRAL_MEM } = require('../index.js');
231
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
232
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '# Goal\n\nTest');
233
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
234
+
235
+ await main();
236
+
237
+ expect(console.log).toHaveBeenCalled();
238
+ });
239
+
240
+ test('dispatches ctx alias', async () => {
241
+ jest.resetModules();
242
+ process.argv = ['node', 'index.js', 'ctx'];
243
+ spawnSync.mockReturnValue({ status: 0, stdout: 'task/test', stderr: '' });
244
+
245
+ const { main, CENTRAL_MEM } = require('../index.js');
246
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
247
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '# Goal\n\nTest');
248
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
249
+
250
+ await main();
251
+
252
+ expect(console.log).toHaveBeenCalled();
253
+ });
254
+
255
+ test('dispatches history command', async () => {
256
+ jest.resetModules();
257
+ process.argv = ['node', 'index.js', 'history'];
258
+ spawnSync.mockReturnValue({ status: 0, stdout: 'abc123 commit', stderr: '' });
259
+
260
+ const { main, CENTRAL_MEM } = require('../index.js');
261
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
262
+
263
+ await main();
264
+
265
+ expect(console.log).toHaveBeenCalled();
266
+ });
267
+
268
+ test('dispatches query command', async () => {
269
+ jest.resetModules();
270
+ process.argv = ['node', 'index.js', 'query', 'test'];
271
+ execSync.mockReturnValue('goal.md:1:test');
272
+
273
+ const { main, CENTRAL_MEM } = require('../index.js');
274
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
275
+
276
+ await main();
277
+
278
+ expect(console.log).toHaveBeenCalled();
279
+ });
280
+
281
+ test('dispatches q alias', async () => {
282
+ jest.resetModules();
283
+ process.argv = ['node', 'index.js', 'q', 'search'];
284
+ execSync.mockReturnValue('');
285
+
286
+ const { main, CENTRAL_MEM } = require('../index.js');
287
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
288
+
289
+ await main();
290
+
291
+ expect(console.log).toHaveBeenCalled();
292
+ });
293
+
294
+ test('dispatches tasks command', async () => {
295
+ jest.resetModules();
296
+ process.argv = ['node', 'index.js', 'tasks'];
297
+ spawnSync
298
+ .mockReturnValueOnce({ status: 0, stdout: 'main', stderr: '' })
299
+ .mockReturnValueOnce({ status: 0, stdout: '* main', stderr: '' });
300
+
301
+ const { main, CENTRAL_MEM } = require('../index.js');
302
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
303
+
304
+ await main();
305
+
306
+ expect(console.log).toHaveBeenCalled();
307
+ });
308
+
309
+ test('dispatches ls alias', async () => {
310
+ jest.resetModules();
311
+ process.argv = ['node', 'index.js', 'ls'];
312
+ spawnSync
313
+ .mockReturnValueOnce({ status: 0, stdout: 'main', stderr: '' })
314
+ .mockReturnValueOnce({ status: 0, stdout: '* main', stderr: '' });
315
+
316
+ const { main, CENTRAL_MEM } = require('../index.js');
317
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
318
+
319
+ await main();
320
+
321
+ expect(console.log).toHaveBeenCalled();
322
+ });
323
+
324
+ test('dispatches switch command', async () => {
325
+ jest.resetModules();
326
+ process.argv = ['node', 'index.js', 'switch', 'other'];
327
+
328
+ const { main, CENTRAL_MEM } = require('../index.js');
329
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
330
+
331
+ await main();
332
+
333
+ expect(console.log).toHaveBeenCalled();
334
+ });
335
+
336
+ test('dispatches sync command', async () => {
337
+ jest.resetModules();
338
+ process.argv = ['node', 'index.js', 'sync'];
339
+ spawnSync.mockReturnValue({ status: 0, stdout: '', stderr: '' });
340
+
341
+ const { main, CENTRAL_MEM } = require('../index.js');
342
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
343
+
344
+ await main();
345
+
346
+ expect(console.log).toHaveBeenCalled();
347
+ });
348
+
349
+ test('dispatches branch command', async () => {
350
+ jest.resetModules();
351
+ process.argv = ['node', 'index.js', 'branch'];
352
+ spawnSync.mockReturnValue({ status: 0, stdout: '* main', stderr: '' });
353
+
354
+ const { main, CENTRAL_MEM } = require('../index.js');
355
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
356
+
357
+ await main();
358
+
359
+ expect(console.log).toHaveBeenCalled();
360
+ });
361
+
362
+ test('dispatches commit command', async () => {
363
+ jest.resetModules();
364
+ process.argv = ['node', 'index.js', 'commit', 'test'];
365
+ spawnSync.mockReturnValue({ status: 0, stdout: '', stderr: '' });
366
+
367
+ const { main, CENTRAL_MEM } = require('../index.js');
368
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
369
+
370
+ await main();
371
+
372
+ expect(console.log).toHaveBeenCalled();
373
+ });
374
+
375
+ test('dispatches set command', async () => {
376
+ jest.resetModules();
377
+ process.argv = ['node', 'index.js', 'set', 'key', 'value'];
378
+
379
+ const { main, CENTRAL_MEM } = require('../index.js');
380
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
381
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
382
+
383
+ await main();
384
+
385
+ expect(console.log).toHaveBeenCalled();
386
+ });
387
+
388
+ test('dispatches get command', async () => {
389
+ jest.resetModules();
390
+ process.argv = ['node', 'index.js', 'get', 'status'];
391
+
392
+ const { main, CENTRAL_MEM } = require('../index.js');
393
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
394
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
395
+
396
+ await main();
397
+
398
+ expect(console.log).toHaveBeenCalled();
399
+ });
400
+
401
+ test('dispatches log command', async () => {
402
+ jest.resetModules();
403
+ process.argv = ['node', 'index.js', 'log'];
404
+ spawnSync.mockReturnValue({ status: 0, stdout: '* abc first', stderr: '' });
405
+
406
+ const { main, CENTRAL_MEM } = require('../index.js');
407
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
408
+
409
+ await main();
410
+
411
+ expect(console.log).toHaveBeenCalled();
412
+ });
413
+
414
+ test('dispatches wake command', async () => {
415
+ jest.resetModules();
416
+ process.argv = ['node', 'index.js', 'wake'];
417
+
418
+ const { main, CENTRAL_MEM } = require('../index.js');
419
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
420
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
421
+
422
+ await main();
423
+
424
+ expect(console.log).toHaveBeenCalled();
425
+ });
426
+
427
+ test('dispatches cron export command', async () => {
428
+ jest.resetModules();
429
+ process.argv = ['node', 'index.js', 'cron', 'export'];
430
+
431
+ const { main, CENTRAL_MEM } = require('../index.js');
432
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
433
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nwake: every 15m\n---\n\n');
434
+
435
+ await main();
436
+
437
+ expect(console.log).toHaveBeenCalled();
438
+ });
439
+
440
+ test('dispatches cron without subcommand', async () => {
441
+ jest.resetModules();
442
+ process.argv = ['node', 'index.js', 'cron'];
443
+
444
+ const { main, CENTRAL_MEM } = require('../index.js');
445
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
446
+
447
+ await main();
448
+
449
+ expect(console.log).toHaveBeenCalled();
450
+ });
451
+
452
+ test('dispatches progress command', async () => {
453
+ jest.resetModules();
454
+ process.argv = ['node', 'index.js', 'progress'];
455
+
456
+ const { main, CENTRAL_MEM } = require('../index.js');
457
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
458
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '# Goal\n\n## Definition of Done\n\n- [x] Done\n\n## Progress: 0%');
459
+
460
+ await main();
461
+
462
+ expect(console.log).toHaveBeenCalled();
463
+ });
464
+
465
+ test('dispatches constraint command', async () => {
466
+ jest.resetModules();
467
+ process.argv = ['node', 'index.js', 'constraint'];
468
+
469
+ const { main, CENTRAL_MEM } = require('../index.js');
470
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
471
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '# Goal\n\n## Constraints\n\n- Rule');
472
+
473
+ await main();
474
+
475
+ expect(console.log).toHaveBeenCalled();
476
+ });
477
+
478
+ test('dispatches criteria command', async () => {
479
+ jest.resetModules();
480
+ process.argv = ['node', 'index.js', 'criteria'];
481
+
482
+ const { main, CENTRAL_MEM } = require('../index.js');
483
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
484
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '# Goal\n\n## Definition of Done\n\n- [ ] Test');
485
+
486
+ await main();
487
+
488
+ expect(console.log).toHaveBeenCalled();
489
+ });
490
+
491
+ test('dispatches promote command', async () => {
492
+ jest.resetModules();
493
+ process.argv = ['node', 'index.js', 'promote', '1'];
494
+
495
+ const { main, CENTRAL_MEM } = require('../index.js');
496
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
497
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'memory.md'), '# Learnings\n\n- 2024-01-01: Test');
498
+
499
+ await main();
500
+
501
+ expect(console.log).toHaveBeenCalled();
502
+ });
503
+
504
+ test('dispatches append command', async () => {
505
+ jest.resetModules();
506
+ process.argv = ['node', 'index.js', 'append', 'learnings', 'test'];
507
+
508
+ const { main, CENTRAL_MEM } = require('../index.js');
509
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
510
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'memory.md'), '# Learnings\n\n');
511
+
512
+ await main();
513
+
514
+ expect(console.log).toHaveBeenCalled();
515
+ });
516
+
517
+ test('dispatches skill command', async () => {
518
+ jest.resetModules();
519
+ process.argv = ['node', 'index.js', 'skill'];
520
+
521
+ const { main, CENTRAL_MEM } = require('../index.js');
522
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
523
+
524
+ await main();
525
+
526
+ expect(console.log).toHaveBeenCalled();
527
+ });
528
+
529
+ test('dispatches mcp config command', async () => {
530
+ jest.resetModules();
531
+ process.argv = ['node', 'index.js', 'mcp', 'config'];
532
+
533
+ const { main, CENTRAL_MEM } = require('../index.js');
534
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
535
+
536
+ await main();
537
+
538
+ expect(console.log).toHaveBeenCalled();
539
+ });
540
+
541
+ test('shows unknown command error', async () => {
542
+ jest.resetModules();
543
+ process.argv = ['node', 'index.js', 'unknowncommand'];
544
+
545
+ const { main, CENTRAL_MEM } = require('../index.js');
546
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
547
+
548
+ await main();
549
+
550
+ const output = console.log.mock.calls.map(c => c[0]).join('\n');
551
+ expect(output).toContain('Unknown command');
552
+ });
553
+
554
+ test('shows status when no command and task mapped', async () => {
555
+ jest.resetModules();
556
+ process.argv = ['node', 'index.js'];
557
+ spawnSync.mockReturnValue({ status: 0, stdout: 'task/test', stderr: '' });
558
+
559
+ const { main, CENTRAL_MEM, saveIndex } = require('../index.js');
560
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
561
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'state.md'), '---\nstatus: active\n---\n\n');
562
+ fs.writeFileSync(path.join(CENTRAL_MEM, 'goal.md'), '---\ntask: test\n---\n\n# Goal\n\nTest');
563
+
564
+ // Save index mapping current directory to task
565
+ saveIndex({ [process.cwd()]: 'task/test' });
566
+
567
+ await main();
568
+
569
+ expect(console.log).toHaveBeenCalled();
570
+ });
571
+
572
+ test('shows no task message when no command and no mapping', async () => {
573
+ jest.resetModules();
574
+ process.argv = ['node', 'index.js'];
575
+
576
+ const { main, CENTRAL_MEM, saveIndex, loadIndex } = require('../index.js');
577
+ fs.mkdirSync(path.join(CENTRAL_MEM, '.git'), { recursive: true });
578
+
579
+ // Save empty index for current directory
580
+ const index = loadIndex();
581
+ delete index[process.cwd()];
582
+ saveIndex(index);
583
+
584
+ await main();
585
+
586
+ const output = console.log.mock.calls.map(c => c[0]).join('\n');
587
+ // Check for "No task mapped" message or status output (if mapped)
588
+ expect(output.length).toBeGreaterThan(0);
589
+ });
590
+ });