oh-my-claude-sisyphus 2.5.0 → 2.6.0

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 (80) hide show
  1. package/dist/__tests__/hooks.test.js +255 -1
  2. package/dist/__tests__/hooks.test.js.map +1 -1
  3. package/dist/__tests__/installer.test.js +1 -1
  4. package/dist/__tests__/notepad.test.d.ts +2 -0
  5. package/dist/__tests__/notepad.test.d.ts.map +1 -0
  6. package/dist/__tests__/notepad.test.js +374 -0
  7. package/dist/__tests__/notepad.test.js.map +1 -0
  8. package/dist/__tests__/ralph-prd.test.d.ts +2 -0
  9. package/dist/__tests__/ralph-prd.test.d.ts.map +1 -0
  10. package/dist/__tests__/ralph-prd.test.js +308 -0
  11. package/dist/__tests__/ralph-prd.test.js.map +1 -0
  12. package/dist/__tests__/ralph-progress.test.d.ts +2 -0
  13. package/dist/__tests__/ralph-progress.test.d.ts.map +1 -0
  14. package/dist/__tests__/ralph-progress.test.js +312 -0
  15. package/dist/__tests__/ralph-progress.test.js.map +1 -0
  16. package/dist/__tests__/skills.test.js +5 -3
  17. package/dist/__tests__/skills.test.js.map +1 -1
  18. package/dist/agents/definitions.d.ts +4 -0
  19. package/dist/agents/definitions.d.ts.map +1 -1
  20. package/dist/agents/definitions.js +147 -3
  21. package/dist/agents/definitions.js.map +1 -1
  22. package/dist/agents/index.d.ts +1 -0
  23. package/dist/agents/index.d.ts.map +1 -1
  24. package/dist/agents/index.js +2 -0
  25. package/dist/agents/index.js.map +1 -1
  26. package/dist/agents/prometheus.js +2 -2
  27. package/dist/agents/prometheus.js.map +1 -1
  28. package/dist/cli/index.js +0 -0
  29. package/dist/features/builtin-skills/skills.d.ts.map +1 -1
  30. package/dist/features/builtin-skills/skills.js +61 -0
  31. package/dist/features/builtin-skills/skills.js.map +1 -1
  32. package/dist/features/magic-keywords.js +1 -1
  33. package/dist/hooks/index.d.ts +5 -1
  34. package/dist/hooks/index.d.ts.map +1 -1
  35. package/dist/hooks/index.js +15 -1
  36. package/dist/hooks/index.js.map +1 -1
  37. package/dist/hooks/notepad/index.d.ts +114 -0
  38. package/dist/hooks/notepad/index.d.ts.map +1 -0
  39. package/dist/hooks/notepad/index.js +372 -0
  40. package/dist/hooks/notepad/index.js.map +1 -0
  41. package/dist/hooks/persistent-mode/index.d.ts +5 -0
  42. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  43. package/dist/hooks/persistent-mode/index.js +71 -5
  44. package/dist/hooks/persistent-mode/index.js.map +1 -1
  45. package/dist/hooks/ralph-loop/index.d.ts +48 -0
  46. package/dist/hooks/ralph-loop/index.d.ts.map +1 -1
  47. package/dist/hooks/ralph-loop/index.js +127 -0
  48. package/dist/hooks/ralph-loop/index.js.map +1 -1
  49. package/dist/hooks/ralph-prd/index.d.ts +130 -0
  50. package/dist/hooks/ralph-prd/index.d.ts.map +1 -0
  51. package/dist/hooks/ralph-prd/index.js +310 -0
  52. package/dist/hooks/ralph-prd/index.js.map +1 -0
  53. package/dist/hooks/ralph-progress/index.d.ts +102 -0
  54. package/dist/hooks/ralph-progress/index.d.ts.map +1 -0
  55. package/dist/hooks/ralph-progress/index.js +408 -0
  56. package/dist/hooks/ralph-progress/index.js.map +1 -0
  57. package/dist/hooks/sisyphus-orchestrator/index.d.ts.map +1 -1
  58. package/dist/hooks/sisyphus-orchestrator/index.js +26 -0
  59. package/dist/hooks/sisyphus-orchestrator/index.js.map +1 -1
  60. package/dist/hooks/ultraqa-loop/index.d.ts +94 -0
  61. package/dist/hooks/ultraqa-loop/index.d.ts.map +1 -0
  62. package/dist/hooks/ultraqa-loop/index.js +216 -0
  63. package/dist/hooks/ultraqa-loop/index.js.map +1 -0
  64. package/dist/installer/hooks.d.ts +28 -0
  65. package/dist/installer/hooks.d.ts.map +1 -1
  66. package/dist/installer/hooks.js +262 -2
  67. package/dist/installer/hooks.js.map +1 -1
  68. package/dist/installer/index.d.ts +1 -1
  69. package/dist/installer/index.d.ts.map +1 -1
  70. package/dist/installer/index.js +426 -12
  71. package/dist/installer/index.js.map +1 -1
  72. package/package.json +1 -1
  73. package/scripts/persistent-mode.mjs +167 -6
  74. package/scripts/post-tool-verifier.mjs +62 -1
  75. package/scripts/session-start.mjs +22 -0
  76. package/scripts/test-max-attempts.ts +94 -0
  77. package/scripts/test-mutual-exclusion.ts +152 -0
  78. package/scripts/test-notepad-integration.ts +495 -0
  79. package/scripts/test-remember-tags.ts +121 -0
  80. package/scripts/test-session-injection.ts +41 -0
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Integration test for notepad auto-capture functionality
4
+ *
5
+ * Tests:
6
+ * - Notepad initialization
7
+ * - Working memory entries
8
+ * - Priority context
9
+ * - Context formatting
10
+ * - Entry pruning
11
+ * - Remember tag processing
12
+ */
13
+
14
+ import { tmpdir } from 'os';
15
+ import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from 'fs';
16
+ import { join } from 'path';
17
+
18
+ // Import notepad functions
19
+ import {
20
+ initNotepad,
21
+ addWorkingMemoryEntry,
22
+ setPriorityContext,
23
+ getPriorityContext,
24
+ getWorkingMemory,
25
+ pruneOldEntries,
26
+ formatNotepadContext,
27
+ getNotepadStats,
28
+ getNotepadPath,
29
+ DEFAULT_CONFIG
30
+ } from '../dist/hooks/notepad/index.js';
31
+
32
+ // Import remember tag processing
33
+ import { processOrchestratorPostTool } from '../dist/hooks/sisyphus-orchestrator/index.js';
34
+
35
+ // ============================================================================
36
+ // Test Infrastructure
37
+ // ============================================================================
38
+
39
+ interface TestResult {
40
+ name: string;
41
+ passed: boolean;
42
+ error?: string;
43
+ details?: string;
44
+ }
45
+
46
+ const results: TestResult[] = [];
47
+
48
+ function test(name: string, fn: () => void | Promise<void>): void {
49
+ process.stdout.write(`\n🧪 ${name}... `);
50
+ try {
51
+ const result = fn();
52
+ if (result instanceof Promise) {
53
+ result
54
+ .then(() => {
55
+ results.push({ name, passed: true });
56
+ console.log('✅ PASS');
57
+ })
58
+ .catch((error) => {
59
+ results.push({
60
+ name,
61
+ passed: false,
62
+ error: error instanceof Error ? error.message : String(error)
63
+ });
64
+ console.log('❌ FAIL');
65
+ console.error(` Error: ${error instanceof Error ? error.message : String(error)}`);
66
+ });
67
+ } else {
68
+ results.push({ name, passed: true });
69
+ console.log('✅ PASS');
70
+ }
71
+ } catch (error) {
72
+ results.push({
73
+ name,
74
+ passed: false,
75
+ error: error instanceof Error ? error.message : String(error)
76
+ });
77
+ console.log('❌ FAIL');
78
+ console.error(` Error: ${error instanceof Error ? error.message : String(error)}`);
79
+ }
80
+ }
81
+
82
+ function assert(condition: boolean, message: string): void {
83
+ if (!condition) {
84
+ throw new Error(message);
85
+ }
86
+ }
87
+
88
+ function assertEquals(actual: unknown, expected: unknown, message?: string): void {
89
+ if (actual !== expected) {
90
+ throw new Error(
91
+ message || `Expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`
92
+ );
93
+ }
94
+ }
95
+
96
+ function assertContains(text: string, substring: string, message?: string): void {
97
+ if (!text.includes(substring)) {
98
+ throw new Error(
99
+ message || `Expected text to contain "${substring}" but it didn't.\nText: ${text}`
100
+ );
101
+ }
102
+ }
103
+
104
+ function assertNotNull<T>(value: T | null | undefined, message?: string): asserts value is T {
105
+ if (value === null || value === undefined) {
106
+ throw new Error(message || 'Expected value to not be null/undefined');
107
+ }
108
+ }
109
+
110
+ // ============================================================================
111
+ // Setup and Teardown
112
+ // ============================================================================
113
+
114
+ let testDir: string;
115
+
116
+ function setup(): void {
117
+ testDir = join(tmpdir(), `notepad-test-${Date.now()}`);
118
+ mkdirSync(testDir, { recursive: true });
119
+ console.log(`\n📁 Test directory: ${testDir}`);
120
+ }
121
+
122
+ function teardown(): void {
123
+ if (existsSync(testDir)) {
124
+ rmSync(testDir, { recursive: true, force: true });
125
+ console.log(`\n🧹 Cleaned up test directory`);
126
+ }
127
+ }
128
+
129
+ // ============================================================================
130
+ // Test Cases
131
+ // ============================================================================
132
+
133
+ function testInitialization(): void {
134
+ const success = initNotepad(testDir);
135
+ assert(success, 'initNotepad should return true');
136
+
137
+ const notepadPath = getNotepadPath(testDir);
138
+ assert(existsSync(notepadPath), 'notepad.md should exist after initialization');
139
+
140
+ const content = readFileSync(notepadPath, 'utf-8');
141
+ assertContains(content, '# Notepad', 'should contain header');
142
+ assertContains(content, '## Priority Context', 'should contain Priority Context section');
143
+ assertContains(content, '## Working Memory', 'should contain Working Memory section');
144
+ assertContains(content, '## MANUAL', 'should contain MANUAL section');
145
+ }
146
+
147
+ function testWorkingMemoryEntry(): void {
148
+ initNotepad(testDir);
149
+
150
+ const success = addWorkingMemoryEntry(testDir, 'Test discovery: This is a test entry');
151
+ assert(success, 'addWorkingMemoryEntry should return true');
152
+
153
+ const workingMemory = getWorkingMemory(testDir);
154
+ assertNotNull(workingMemory, 'working memory should not be null');
155
+ assertContains(workingMemory, 'Test discovery', 'should contain the added entry');
156
+ assertContains(workingMemory, '###', 'should contain timestamp header');
157
+ }
158
+
159
+ function testMultipleWorkingMemoryEntries(): void {
160
+ const localDir = join(tmpdir(), `notepad-test-multi-${Date.now()}`);
161
+ mkdirSync(localDir, { recursive: true });
162
+
163
+ initNotepad(localDir);
164
+
165
+ addWorkingMemoryEntry(localDir, 'First entry');
166
+ addWorkingMemoryEntry(localDir, 'Second entry');
167
+ addWorkingMemoryEntry(localDir, 'Third entry');
168
+
169
+ const workingMemory = getWorkingMemory(localDir);
170
+ assertNotNull(workingMemory, 'working memory should not be null');
171
+ assertContains(workingMemory, 'First entry', 'should contain first entry');
172
+ assertContains(workingMemory, 'Second entry', 'should contain second entry');
173
+ assertContains(workingMemory, 'Third entry', 'should contain third entry');
174
+
175
+ // Verify entries are separated
176
+ const entryCount = (workingMemory.match(/###/g) || []).length;
177
+ assertEquals(entryCount, 3, 'should have 3 timestamp headers');
178
+
179
+ rmSync(localDir, { recursive: true, force: true });
180
+ }
181
+
182
+ function testPriorityContext(): void {
183
+ initNotepad(testDir);
184
+
185
+ const content = 'CRITICAL: Auth system requires JWT tokens with 15-min expiry';
186
+ const result = setPriorityContext(testDir, content);
187
+ assert(result.success, 'setPriorityContext should succeed');
188
+ assert(!result.warning, 'should not have warning for short content');
189
+
190
+ const retrieved = getPriorityContext(testDir);
191
+ assertNotNull(retrieved, 'priority context should not be null');
192
+ assertEquals(retrieved, content, 'retrieved content should match original');
193
+ }
194
+
195
+ function testPriorityContextOversize(): void {
196
+ initNotepad(testDir);
197
+
198
+ const longContent = 'x'.repeat(600); // Over 500 char limit
199
+ const result = setPriorityContext(testDir, longContent);
200
+ assert(result.success, 'setPriorityContext should still succeed');
201
+ assert(result.warning !== undefined, 'should have warning for oversized content');
202
+ assertContains(result.warning!, 'exceeds', 'warning should mention exceeding limit');
203
+ }
204
+
205
+ function testPriorityContextReplacement(): void {
206
+ initNotepad(testDir);
207
+
208
+ setPriorityContext(testDir, 'First priority');
209
+ const first = getPriorityContext(testDir);
210
+ assertEquals(first, 'First priority', 'should store first priority');
211
+
212
+ setPriorityContext(testDir, 'Second priority');
213
+ const second = getPriorityContext(testDir);
214
+ assertEquals(second, 'Second priority', 'should replace with second priority');
215
+ assertNotNull(second, 'second priority should not be null');
216
+ assert(!second.includes('First priority'), 'should not contain first priority');
217
+ }
218
+
219
+ function testFormatNotepadContext(): void {
220
+ initNotepad(testDir);
221
+ setPriorityContext(testDir, 'Test priority content');
222
+
223
+ const formatted = formatNotepadContext(testDir);
224
+ assertNotNull(formatted, 'formatted context should not be null');
225
+ assertContains(formatted, '<notepad-priority>', 'should have opening tag');
226
+ assertContains(formatted, '</notepad-priority>', 'should have closing tag');
227
+ assertContains(formatted, 'Test priority content', 'should contain priority content');
228
+ assertContains(formatted, '## Priority Context', 'should contain section header');
229
+ }
230
+
231
+ function testFormatNotepadContextEmpty(): void {
232
+ const localDir = join(tmpdir(), `notepad-test-empty-${Date.now()}`);
233
+ mkdirSync(localDir, { recursive: true });
234
+
235
+ initNotepad(localDir);
236
+ // Don't set any priority context
237
+
238
+ const formatted = formatNotepadContext(localDir);
239
+ assertEquals(formatted, null, 'should return null when no priority context');
240
+
241
+ rmSync(localDir, { recursive: true, force: true });
242
+ }
243
+
244
+ function testGetNotepadStats(): void {
245
+ const localDir = join(tmpdir(), `notepad-test-stats-${Date.now()}`);
246
+ mkdirSync(localDir, { recursive: true });
247
+
248
+ initNotepad(localDir);
249
+ addWorkingMemoryEntry(localDir, 'Entry 1');
250
+ addWorkingMemoryEntry(localDir, 'Entry 2');
251
+ setPriorityContext(localDir, 'Priority info');
252
+
253
+ const stats = getNotepadStats(localDir);
254
+ assert(stats.exists, 'notepad should exist');
255
+ assert(stats.totalSize > 0, 'should have non-zero total size');
256
+ assert(stats.prioritySize > 0, 'should have non-zero priority size');
257
+ assertEquals(stats.workingMemoryEntries, 2, 'should have 2 working memory entries');
258
+ assertNotNull(stats.oldestEntry, 'should have oldest entry timestamp');
259
+
260
+ rmSync(localDir, { recursive: true, force: true });
261
+ }
262
+
263
+ function testPruningOldEntries(): void {
264
+ const localDir = join(tmpdir(), `notepad-test-prune-${Date.now()}`);
265
+ mkdirSync(localDir, { recursive: true });
266
+
267
+ initNotepad(localDir);
268
+
269
+ // Add entries with manipulated timestamps
270
+ const notepadPath = getNotepadPath(localDir);
271
+ let content = readFileSync(notepadPath, 'utf-8');
272
+
273
+ // Manually insert entries with old dates
274
+ const oldDate1 = new Date();
275
+ oldDate1.setDate(oldDate1.getDate() - 10); // 10 days ago
276
+ const oldDate2 = new Date();
277
+ oldDate2.setDate(oldDate2.getDate() - 8); // 8 days ago
278
+ const recentDate = new Date();
279
+ recentDate.setDate(recentDate.getDate() - 2); // 2 days ago
280
+
281
+ const formatDate = (d: Date) => d.toISOString().slice(0, 16).replace('T', ' ');
282
+
283
+ const oldEntry1 = `### ${formatDate(oldDate1)}\nOld entry 1\n`;
284
+ const oldEntry2 = `### ${formatDate(oldDate2)}\nOld entry 2\n`;
285
+ const recentEntry = `### ${formatDate(recentDate)}\nRecent entry\n`;
286
+
287
+ // Insert into Working Memory section
288
+ content = content.replace(
289
+ /## Working Memory\n<!-- Session notes\. Auto-pruned after 7 days\. -->\n/,
290
+ `## Working Memory\n<!-- Session notes. Auto-pruned after 7 days. -->\n${oldEntry1}\n${oldEntry2}\n${recentEntry}\n`
291
+ );
292
+ writeFileSync(notepadPath, content);
293
+
294
+ // Verify 3 entries before pruning
295
+ const statsBefore = getNotepadStats(localDir);
296
+ assertEquals(statsBefore.workingMemoryEntries, 3, 'should have 3 entries before pruning');
297
+
298
+ // Prune entries older than 7 days
299
+ const pruneResult = pruneOldEntries(localDir, 7);
300
+ assertEquals(pruneResult.pruned, 2, 'should prune 2 old entries');
301
+ assertEquals(pruneResult.remaining, 1, 'should have 1 remaining entry');
302
+
303
+ // Verify only recent entry remains
304
+ const workingMemory = getWorkingMemory(localDir);
305
+ assertNotNull(workingMemory, 'working memory should not be null');
306
+ assertContains(workingMemory, 'Recent entry', 'should contain recent entry');
307
+ assert(!workingMemory.includes('Old entry 1'), 'should not contain old entry 1');
308
+ assert(!workingMemory.includes('Old entry 2'), 'should not contain old entry 2');
309
+
310
+ rmSync(localDir, { recursive: true, force: true });
311
+ }
312
+
313
+ function testRememberTagProcessing(): void {
314
+ initNotepad(testDir);
315
+
316
+ // Simulate agent output with <remember> tags
317
+ const agentOutput = `
318
+ Here are my findings:
319
+
320
+ <remember>
321
+ Discovered that the API uses rate limiting of 100 req/min
322
+ </remember>
323
+
324
+ Some more text here.
325
+
326
+ <remember priority>
327
+ CRITICAL: Authentication tokens expire after 15 minutes
328
+ </remember>
329
+
330
+ Done!
331
+ `;
332
+
333
+ // Process the output (simulating post-tool hook)
334
+ processOrchestratorPostTool(
335
+ {
336
+ toolName: 'Task',
337
+ toolInput: {},
338
+ directory: testDir
339
+ },
340
+ agentOutput
341
+ );
342
+
343
+ // Verify priority context was captured
344
+ const priority = getPriorityContext(testDir);
345
+ assertNotNull(priority, 'priority context should be captured');
346
+ assertContains(priority, 'CRITICAL', 'should contain priority tag content');
347
+ assertContains(priority, '15 minutes', 'should contain specific priority detail');
348
+
349
+ // Verify working memory was captured
350
+ const workingMemory = getWorkingMemory(testDir);
351
+ assertNotNull(workingMemory, 'working memory should be captured');
352
+ assertContains(workingMemory, 'rate limiting', 'should contain working memory content');
353
+ assertContains(workingMemory, '100 req/min', 'should contain specific detail');
354
+ }
355
+
356
+ function testRememberTagWithMultipleMatches(): void {
357
+ const localDir = join(tmpdir(), `notepad-test-multi-remember-${Date.now()}`);
358
+ mkdirSync(localDir, { recursive: true });
359
+
360
+ initNotepad(localDir);
361
+
362
+ const agentOutput = `
363
+ <remember>First discovery about authentication</remember>
364
+ <remember>Second discovery about caching</remember>
365
+ <remember>Third discovery about error handling</remember>
366
+ `;
367
+
368
+ processOrchestratorPostTool(
369
+ {
370
+ toolName: 'Task',
371
+ toolInput: {},
372
+ directory: localDir
373
+ },
374
+ agentOutput
375
+ );
376
+
377
+ const workingMemory = getWorkingMemory(localDir);
378
+ assertNotNull(workingMemory, 'working memory should not be null');
379
+ assertContains(workingMemory, 'authentication', 'should contain first discovery');
380
+ assertContains(workingMemory, 'caching', 'should contain second discovery');
381
+ assertContains(workingMemory, 'error handling', 'should contain third discovery');
382
+
383
+ // Verify 3 separate entries
384
+ const entryCount = (workingMemory.match(/###/g) || []).length;
385
+ assertEquals(entryCount, 3, 'should have 3 separate timestamped entries');
386
+
387
+ rmSync(localDir, { recursive: true, force: true });
388
+ }
389
+
390
+ function testRememberTagIgnoresNonTaskTools(): void {
391
+ const localDir = join(tmpdir(), `notepad-test-non-task-${Date.now()}`);
392
+ mkdirSync(localDir, { recursive: true });
393
+
394
+ initNotepad(localDir);
395
+
396
+ const agentOutput = `
397
+ <remember>This should be ignored</remember>
398
+ `;
399
+
400
+ // Process with non-Task tool
401
+ processOrchestratorPostTool(
402
+ {
403
+ toolName: 'Read',
404
+ toolInput: {},
405
+ directory: localDir
406
+ },
407
+ agentOutput
408
+ );
409
+
410
+ const workingMemory = getWorkingMemory(localDir);
411
+ // Should be null or empty since notepad was just initialized and no Task tool was used
412
+ const isEmpty = workingMemory === null || workingMemory.trim() === '';
413
+ assert(isEmpty, 'should not capture remember tags from non-Task tools');
414
+
415
+ rmSync(localDir, { recursive: true, force: true });
416
+ }
417
+
418
+ // ============================================================================
419
+ // Main Test Runner
420
+ // ============================================================================
421
+
422
+ async function runTests(): Promise<void> {
423
+ console.log('\n═══════════════════════════════════════════════════════════');
424
+ console.log(' 🧪 NOTEPAD INTEGRATION TEST SUITE');
425
+ console.log('═══════════════════════════════════════════════════════════');
426
+
427
+ setup();
428
+
429
+ try {
430
+ // Basic operations
431
+ test('Notepad initialization', testInitialization);
432
+ test('Add working memory entry', testWorkingMemoryEntry);
433
+ test('Add multiple working memory entries', testMultipleWorkingMemoryEntries);
434
+
435
+ // Priority context
436
+ test('Set priority context', testPriorityContext);
437
+ test('Priority context oversize warning', testPriorityContextOversize);
438
+ test('Priority context replacement', testPriorityContextReplacement);
439
+
440
+ // Formatting
441
+ test('Format notepad context for injection', testFormatNotepadContext);
442
+ test('Format empty notepad context', testFormatNotepadContextEmpty);
443
+
444
+ // Stats and info
445
+ test('Get notepad stats', testGetNotepadStats);
446
+
447
+ // Pruning
448
+ test('Prune old entries', testPruningOldEntries);
449
+
450
+ // Remember tags
451
+ test('Process <remember> tags', testRememberTagProcessing);
452
+ test('Process multiple <remember> tags', testRememberTagWithMultipleMatches);
453
+ test('Ignore <remember> tags from non-Task tools', testRememberTagIgnoresNonTaskTools);
454
+
455
+ // Wait a bit for any async tests
456
+ await new Promise(resolve => setTimeout(resolve, 100));
457
+
458
+ } finally {
459
+ teardown();
460
+ }
461
+
462
+ // Print summary
463
+ console.log('\n═══════════════════════════════════════════════════════════');
464
+ console.log(' 📊 TEST SUMMARY');
465
+ console.log('═══════════════════════════════════════════════════════════');
466
+
467
+ const passed = results.filter(r => r.passed).length;
468
+ const failed = results.filter(r => !r.passed).length;
469
+ const total = results.length;
470
+
471
+ console.log(`\n Total: ${total}`);
472
+ console.log(` ✅ Pass: ${passed}`);
473
+ console.log(` ❌ Fail: ${failed}`);
474
+
475
+ if (failed > 0) {
476
+ console.log('\n Failed tests:');
477
+ results.filter(r => !r.passed).forEach(r => {
478
+ console.log(` - ${r.name}`);
479
+ if (r.error) {
480
+ console.log(` ${r.error}`);
481
+ }
482
+ });
483
+ }
484
+
485
+ console.log('\n═══════════════════════════════════════════════════════════\n');
486
+
487
+ // Exit with appropriate code
488
+ process.exit(failed > 0 ? 1 : 0);
489
+ }
490
+
491
+ // Run tests
492
+ runTests().catch((error) => {
493
+ console.error('\n❌ Test runner failed:', error);
494
+ process.exit(1);
495
+ });
@@ -0,0 +1,121 @@
1
+ import { tmpdir } from 'os';
2
+ import { mkdirSync, rmSync, existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { spawn } from 'child_process';
5
+
6
+ // Create test directory
7
+ const testDir = join(tmpdir(), `remember-tag-test-${Date.now()}`);
8
+ const sisyphusDir = join(testDir, '.sisyphus');
9
+ mkdirSync(sisyphusDir, { recursive: true });
10
+
11
+ console.log('Testing remember tag processing in post-tool-verifier.mjs\n');
12
+
13
+ // Helper to run the post-tool-verifier
14
+ async function runHook(input: object): Promise<string> {
15
+ return new Promise((resolve, reject) => {
16
+ const proc = spawn('node', [
17
+ join(import.meta.dirname, 'post-tool-verifier.mjs')
18
+ ], {
19
+ cwd: testDir
20
+ });
21
+
22
+ let stdout = '';
23
+ let stderr = '';
24
+
25
+ proc.stdout.on('data', (data) => { stdout += data; });
26
+ proc.stderr.on('data', (data) => { stderr += data; });
27
+
28
+ proc.on('close', (code) => {
29
+ if (code !== 0) {
30
+ reject(new Error(`Process exited with code ${code}: ${stderr}`));
31
+ } else {
32
+ resolve(stdout);
33
+ }
34
+ });
35
+
36
+ proc.stdin.write(JSON.stringify(input));
37
+ proc.stdin.end();
38
+ });
39
+ }
40
+
41
+ // Test 1: Regular remember tag
42
+ console.log('Test 1: Regular <remember> tag');
43
+ try {
44
+ const input1 = {
45
+ toolName: 'Task',
46
+ toolOutput: 'Agent completed task.\n<remember>This project uses pnpm</remember>\nDone.',
47
+ sessionId: 'test-session',
48
+ directory: testDir
49
+ };
50
+
51
+ await runHook(input1);
52
+
53
+ const notepadPath = join(sisyphusDir, 'notepad.md');
54
+ if (existsSync(notepadPath)) {
55
+ const content = readFileSync(notepadPath, 'utf-8');
56
+ if (content.includes('pnpm') && content.includes('Working Memory')) {
57
+ console.log('✓ PASS: Regular remember tag saved to Working Memory\n');
58
+ } else {
59
+ console.log('✗ FAIL: Remember tag not saved correctly');
60
+ console.log('Content:', content.slice(0, 200));
61
+ }
62
+ } else {
63
+ console.log('✗ FAIL: notepad.md not created\n');
64
+ }
65
+ } catch (err) {
66
+ console.log('✗ FAIL:', (err as Error).message);
67
+ }
68
+
69
+ // Test 2: Priority remember tag
70
+ console.log('Test 2: Priority <remember priority> tag');
71
+ try {
72
+ const input2 = {
73
+ toolName: 'Task',
74
+ toolOutput: '<remember priority>API endpoint is /api/v2</remember>',
75
+ sessionId: 'test-session',
76
+ directory: testDir
77
+ };
78
+
79
+ await runHook(input2);
80
+
81
+ const notepadPath = join(sisyphusDir, 'notepad.md');
82
+ const content = readFileSync(notepadPath, 'utf-8');
83
+ if (content.includes('API endpoint') && content.includes('Priority Context')) {
84
+ console.log('✓ PASS: Priority remember tag saved to Priority Context\n');
85
+ } else {
86
+ console.log('✗ FAIL: Priority tag not saved correctly');
87
+ console.log('Content:', content.slice(0, 300));
88
+ }
89
+ } catch (err) {
90
+ console.log('✗ FAIL:', (err as Error).message);
91
+ }
92
+
93
+ // Test 3: Non-Task tool should not process tags
94
+ console.log('Test 3: Non-Task tool should not process tags');
95
+ try {
96
+ // Clean up first
97
+ rmSync(testDir, { recursive: true });
98
+ mkdirSync(sisyphusDir, { recursive: true });
99
+
100
+ const input3 = {
101
+ toolName: 'Bash',
102
+ toolOutput: '<remember>Should not be saved</remember>',
103
+ sessionId: 'test-session',
104
+ directory: testDir
105
+ };
106
+
107
+ await runHook(input3);
108
+
109
+ const notepadPath = join(sisyphusDir, 'notepad.md');
110
+ if (!existsSync(notepadPath)) {
111
+ console.log('✓ PASS: Bash tool did not trigger remember tag processing\n');
112
+ } else {
113
+ console.log('✗ FAIL: Bash tool incorrectly triggered remember processing\n');
114
+ }
115
+ } catch (err) {
116
+ console.log('✗ FAIL:', (err as Error).message);
117
+ }
118
+
119
+ // Clean up
120
+ rmSync(testDir, { recursive: true });
121
+ console.log('All tests completed.');
@@ -0,0 +1,41 @@
1
+ import { tmpdir } from 'os';
2
+ import { mkdirSync, rmSync, writeFileSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ // Create test notepad
6
+ const testDir = join(tmpdir(), `session-test-${Date.now()}`);
7
+ const sisyphusDir = join(testDir, '.sisyphus');
8
+ mkdirSync(sisyphusDir, { recursive: true });
9
+
10
+ const notepadContent = `# Notepad
11
+
12
+ ## Priority Context
13
+ Project uses pnpm not npm
14
+ API client at src/api/client.ts
15
+
16
+ ## Working Memory
17
+
18
+ ### 2026-01-19 12:00
19
+ Some working memory entry
20
+
21
+ ## MANUAL
22
+ User notes here
23
+ `;
24
+
25
+ writeFileSync(join(sisyphusDir, 'notepad.md'), notepadContent);
26
+
27
+ // Test priority context extraction (mimics session-start.mjs logic)
28
+ const content = readFileSync(join(sisyphusDir, 'notepad.md'), 'utf-8');
29
+ const priorityMatch = content.match(/## Priority Context\n([\s\S]*?)(?=\n## [^#]|$)/);
30
+ const cleanContent = priorityMatch ? priorityMatch[1].replace(/<!--[\s\S]*?-->/g, '').trim() : '';
31
+
32
+ // Verify extraction
33
+ if (cleanContent.includes('pnpm') && cleanContent.includes('API client')) {
34
+ console.log('✓ PASS: Priority Context extracted correctly');
35
+ } else {
36
+ console.log('✗ FAIL: Priority Context not extracted');
37
+ console.log('Got:', cleanContent);
38
+ }
39
+
40
+ // Clean up
41
+ rmSync(testDir, { recursive: true });