oh-my-claude-sisyphus 2.4.1 → 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.
- package/README.md +56 -29
- package/dist/__tests__/hooks.test.js +255 -1
- package/dist/__tests__/hooks.test.js.map +1 -1
- package/dist/__tests__/installer.test.js +3 -1
- package/dist/__tests__/installer.test.js.map +1 -1
- package/dist/__tests__/notepad.test.d.ts +2 -0
- package/dist/__tests__/notepad.test.d.ts.map +1 -0
- package/dist/__tests__/notepad.test.js +374 -0
- package/dist/__tests__/notepad.test.js.map +1 -0
- package/dist/__tests__/ralph-prd.test.d.ts +2 -0
- package/dist/__tests__/ralph-prd.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-prd.test.js +308 -0
- package/dist/__tests__/ralph-prd.test.js.map +1 -0
- package/dist/__tests__/ralph-progress.test.d.ts +2 -0
- package/dist/__tests__/ralph-progress.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-progress.test.js +312 -0
- package/dist/__tests__/ralph-progress.test.js.map +1 -0
- package/dist/__tests__/skills.test.js +5 -3
- package/dist/__tests__/skills.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +4 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +147 -3
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/prometheus.js +2 -2
- package/dist/agents/prometheus.js.map +1 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/features/builtin-skills/skills.d.ts.map +1 -1
- package/dist/features/builtin-skills/skills.js +61 -0
- package/dist/features/builtin-skills/skills.js.map +1 -1
- package/dist/features/magic-keywords.js +1 -1
- package/dist/hooks/index.d.ts +5 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +15 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/notepad/index.d.ts +114 -0
- package/dist/hooks/notepad/index.d.ts.map +1 -0
- package/dist/hooks/notepad/index.js +372 -0
- package/dist/hooks/notepad/index.js.map +1 -0
- package/dist/hooks/persistent-mode/index.d.ts +5 -0
- package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
- package/dist/hooks/persistent-mode/index.js +71 -5
- package/dist/hooks/persistent-mode/index.js.map +1 -1
- package/dist/hooks/ralph-loop/index.d.ts +48 -0
- package/dist/hooks/ralph-loop/index.d.ts.map +1 -1
- package/dist/hooks/ralph-loop/index.js +127 -0
- package/dist/hooks/ralph-loop/index.js.map +1 -1
- package/dist/hooks/ralph-prd/index.d.ts +130 -0
- package/dist/hooks/ralph-prd/index.d.ts.map +1 -0
- package/dist/hooks/ralph-prd/index.js +310 -0
- package/dist/hooks/ralph-prd/index.js.map +1 -0
- package/dist/hooks/ralph-progress/index.d.ts +102 -0
- package/dist/hooks/ralph-progress/index.d.ts.map +1 -0
- package/dist/hooks/ralph-progress/index.js +408 -0
- package/dist/hooks/ralph-progress/index.js.map +1 -0
- package/dist/hooks/sisyphus-orchestrator/index.d.ts.map +1 -1
- package/dist/hooks/sisyphus-orchestrator/index.js +26 -0
- package/dist/hooks/sisyphus-orchestrator/index.js.map +1 -1
- package/dist/hooks/ultraqa-loop/index.d.ts +94 -0
- package/dist/hooks/ultraqa-loop/index.d.ts.map +1 -0
- package/dist/hooks/ultraqa-loop/index.js +216 -0
- package/dist/hooks/ultraqa-loop/index.js.map +1 -0
- package/dist/installer/hooks.d.ts +28 -0
- package/dist/installer/hooks.d.ts.map +1 -1
- package/dist/installer/hooks.js +262 -2
- package/dist/installer/hooks.js.map +1 -1
- package/dist/installer/index.d.ts +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +533 -23
- package/dist/installer/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install.sh +119 -16
- package/scripts/persistent-mode.mjs +167 -6
- package/scripts/post-tool-verifier.mjs +62 -1
- package/scripts/session-start.mjs +22 -0
- package/scripts/test-max-attempts.ts +94 -0
- package/scripts/test-mutual-exclusion.ts +152 -0
- package/scripts/test-notepad-integration.ts +495 -0
- package/scripts/test-remember-tags.ts +121 -0
- package/scripts/test-session-injection.ts +41 -0
- package/scripts/uninstall.sh +1 -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 });
|
package/scripts/uninstall.sh
CHANGED
|
@@ -64,6 +64,7 @@ rm -f "$CLAUDE_CONFIG_DIR/agents/prometheus.md"
|
|
|
64
64
|
echo -e "${BLUE}Removing commands...${NC}"
|
|
65
65
|
rm -f "$CLAUDE_CONFIG_DIR/commands/sisyphus.md"
|
|
66
66
|
rm -f "$CLAUDE_CONFIG_DIR/commands/sisyphus-default.md"
|
|
67
|
+
rm -f "$CLAUDE_CONFIG_DIR/commands/sisyphus-default-global.md"
|
|
67
68
|
rm -f "$CLAUDE_CONFIG_DIR/commands/ultrawork.md"
|
|
68
69
|
rm -f "$CLAUDE_CONFIG_DIR/commands/deepsearch.md"
|
|
69
70
|
rm -f "$CLAUDE_CONFIG_DIR/commands/analyze.md"
|