@stackmemoryai/stackmemory 1.5.9 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/src/cli/commands/orchestrate.js +506 -33
  2. package/dist/src/cli/commands/orchestrator.js +208 -31
  3. package/dist/src/daemon/daemon-config.js +2 -3
  4. package/dist/src/daemon/session-daemon.js +4 -5
  5. package/dist/src/daemon/unified-daemon.js +4 -5
  6. package/dist/src/features/sweep/sweep-server-manager.js +3 -6
  7. package/dist/src/hooks/daemon.js +5 -8
  8. package/dist/src/utils/process-cleanup.js +9 -0
  9. package/package.json +1 -2
  10. package/scripts/gepa/.before-optimize.md +0 -112
  11. package/scripts/gepa/README.md +0 -275
  12. package/scripts/gepa/config.json +0 -59
  13. package/scripts/gepa/evals/coding-tasks.jsonl +0 -5
  14. package/scripts/gepa/evals/fixtures/api-endpoint.ts +0 -31
  15. package/scripts/gepa/evals/fixtures/brittle-integration.ts +0 -38
  16. package/scripts/gepa/evals/fixtures/buggy-loop.js +0 -18
  17. package/scripts/gepa/evals/fixtures/callback-hell.js +0 -53
  18. package/scripts/gepa/evals/fixtures/fts5-triggers.sql +0 -23
  19. package/scripts/gepa/evals/fixtures/leaky-service.ts +0 -70
  20. package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +0 -39
  21. package/scripts/gepa/evals/fixtures/pr-diff.txt +0 -24
  22. package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +0 -34
  23. package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +0 -42
  24. package/scripts/gepa/evals/stackmemory-tasks.jsonl +0 -8
  25. package/scripts/gepa/generations/gen-000/baseline.md +0 -112
  26. package/scripts/gepa/generations/gen-001/baseline.md +0 -112
  27. package/scripts/gepa/generations/gen-001/variant-a.md +0 -107
  28. package/scripts/gepa/generations/gen-001/variant-b.md +0 -216
  29. package/scripts/gepa/generations/gen-001/variant-c.md +0 -83
  30. package/scripts/gepa/generations/gen-001/variant-d.md +0 -90
  31. package/scripts/gepa/hooks/auto-optimize.js +0 -494
  32. package/scripts/gepa/hooks/eval-tracker.js +0 -203
  33. package/scripts/gepa/hooks/reflect.js +0 -350
  34. package/scripts/gepa/optimize.js +0 -853
  35. package/scripts/gepa/results/eval-1-baseline.json +0 -218
  36. package/scripts/gepa/results/eval-1-variant-a.json +0 -218
  37. package/scripts/gepa/results/eval-1-variant-b.json +0 -218
  38. package/scripts/gepa/results/eval-1-variant-c.json +0 -218
  39. package/scripts/gepa/results/eval-1-variant-d.json +0 -218
  40. package/scripts/gepa/state.json +0 -49
@@ -1,203 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * GEPA Eval Tracker Hook
4
- *
5
- * Captures agent behavior during sessions for evaluation.
6
- * Install in ~/.claude/settings.json under hooks.
7
- */
8
-
9
- import fs from 'fs';
10
- import path from 'path';
11
-
12
- const GEPA_DIR =
13
- process.env.GEPA_DIR || path.join(process.env.HOME, '.claude', 'gepa');
14
- const RESULTS_DIR = path.join(GEPA_DIR, 'results');
15
- const SESSIONS_DIR = path.join(RESULTS_DIR, 'sessions');
16
-
17
- // Ensure directories exist
18
- [RESULTS_DIR, SESSIONS_DIR].forEach((dir) => {
19
- if (!fs.existsSync(dir)) {
20
- fs.mkdirSync(dir, { recursive: true });
21
- }
22
- });
23
-
24
- /**
25
- * Session tracking state
26
- */
27
- class SessionTracker {
28
- constructor() {
29
- this.sessionId = process.env.CLAUDE_SESSION_ID || `session-${Date.now()}`;
30
- this.variant = process.env.GEPA_VARIANT || 'baseline';
31
- this.generation = parseInt(process.env.GEPA_GENERATION || '0');
32
-
33
- this.data = {
34
- sessionId: this.sessionId,
35
- variant: this.variant,
36
- generation: this.generation,
37
- startTime: new Date().toISOString(),
38
- endTime: null,
39
- toolCalls: [],
40
- errors: [],
41
- userFeedback: [],
42
- tokenUsage: { input: 0, output: 0 },
43
- metrics: {},
44
- };
45
- }
46
-
47
- trackToolCall(tool, input, output, duration) {
48
- this.data.toolCalls.push({
49
- tool,
50
- input: this.sanitize(input),
51
- output: this.summarize(output),
52
- duration,
53
- timestamp: new Date().toISOString(),
54
- success: !output?.error,
55
- });
56
- }
57
-
58
- trackError(error, context) {
59
- this.data.errors.push({
60
- error: error.message || String(error),
61
- context,
62
- timestamp: new Date().toISOString(),
63
- });
64
- }
65
-
66
- trackFeedback(type, value) {
67
- this.data.userFeedback.push({
68
- type, // 'thumbs_up', 'thumbs_down', 'correction', 'retry'
69
- value,
70
- timestamp: new Date().toISOString(),
71
- });
72
- }
73
-
74
- trackTokens(input, output) {
75
- this.data.tokenUsage.input += input;
76
- this.data.tokenUsage.output += output;
77
- }
78
-
79
- sanitize(obj) {
80
- // Remove sensitive data
81
- if (typeof obj !== 'object') return obj;
82
- const sanitized = { ...obj };
83
- const sensitiveKeys = ['apiKey', 'token', 'password', 'secret', 'key'];
84
- for (const key of Object.keys(sanitized)) {
85
- if (sensitiveKeys.some((s) => key.toLowerCase().includes(s))) {
86
- sanitized[key] = '[REDACTED]';
87
- }
88
- }
89
- return sanitized;
90
- }
91
-
92
- summarize(output) {
93
- // Truncate long outputs
94
- const str = typeof output === 'string' ? output : JSON.stringify(output);
95
- return str.length > 1000 ? str.slice(0, 1000) + '...[truncated]' : str;
96
- }
97
-
98
- finalize() {
99
- this.data.endTime = new Date().toISOString();
100
- this.data.duration =
101
- new Date(this.data.endTime) - new Date(this.data.startTime);
102
-
103
- // Calculate basic metrics
104
- this.data.metrics = {
105
- totalToolCalls: this.data.toolCalls.length,
106
- successfulToolCalls: this.data.toolCalls.filter((t) => t.success).length,
107
- errorCount: this.data.errors.length,
108
- avgToolDuration:
109
- this.data.toolCalls.length > 0
110
- ? this.data.toolCalls.reduce((sum, t) => sum + (t.duration || 0), 0) /
111
- this.data.toolCalls.length
112
- : 0,
113
- positiveFeeback: this.data.userFeedback.filter(
114
- (f) => f.type === 'thumbs_up'
115
- ).length,
116
- negativeFeeback: this.data.userFeedback.filter(
117
- (f) => f.type === 'thumbs_down'
118
- ).length,
119
- };
120
-
121
- return this.data;
122
- }
123
-
124
- save() {
125
- const data = this.finalize();
126
- const filename = `${this.generation}-${this.variant}-${this.sessionId}.json`;
127
- const filepath = path.join(SESSIONS_DIR, filename);
128
- fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
129
-
130
- // Also append to scores.jsonl for quick analysis
131
- const scoreLine = {
132
- sessionId: this.sessionId,
133
- variant: this.variant,
134
- generation: this.generation,
135
- metrics: data.metrics,
136
- timestamp: data.endTime,
137
- };
138
- fs.appendFileSync(
139
- path.join(RESULTS_DIR, 'scores.jsonl'),
140
- JSON.stringify(scoreLine) + '\n'
141
- );
142
-
143
- return filepath;
144
- }
145
- }
146
-
147
- // Global tracker instance
148
- let tracker = null;
149
-
150
- /**
151
- * Hook handlers
152
- */
153
- export function onSessionStart(context) {
154
- tracker = new SessionTracker();
155
- console.error(
156
- `[GEPA] Tracking session: ${tracker.sessionId} (gen=${tracker.generation}, variant=${tracker.variant})`
157
- );
158
- }
159
-
160
- export function onToolCall(tool, input) {
161
- if (!tracker) return;
162
- tracker._currentTool = { tool, input, startTime: Date.now() };
163
- }
164
-
165
- export function onToolResult(tool, result) {
166
- if (!tracker || !tracker._currentTool) return;
167
- const duration = Date.now() - tracker._currentTool.startTime;
168
- tracker.trackToolCall(tool, tracker._currentTool.input, result, duration);
169
- tracker._currentTool = null;
170
- }
171
-
172
- export function onError(error, context) {
173
- if (!tracker) return;
174
- tracker.trackError(error, context);
175
- }
176
-
177
- export function onUserFeedback(type, value) {
178
- if (!tracker) return;
179
- tracker.trackFeedback(type, value);
180
- }
181
-
182
- export function onSessionEnd() {
183
- if (!tracker) return;
184
- const filepath = tracker.save();
185
- console.error(`[GEPA] Session data saved to: ${filepath}`);
186
- tracker = null;
187
- }
188
-
189
- // CLI interface for testing
190
- if (process.argv[1] === import.meta.url.replace('file://', '')) {
191
- const command = process.argv[2];
192
-
193
- if (command === 'test') {
194
- onSessionStart({});
195
- onToolCall('Read', { file: '/test/file.ts' });
196
- onToolResult('Read', { content: 'test content...' });
197
- onToolCall('Edit', { file: '/test/file.ts', changes: '...' });
198
- onToolResult('Edit', { success: true });
199
- onUserFeedback('thumbs_up', 'good job');
200
- onSessionEnd();
201
- console.log('Test session recorded');
202
- }
203
- }
@@ -1,350 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * GEPA Reflection Engine
4
- *
5
- * Analyzes evaluation results to generate insights for next mutation cycle.
6
- * This is the key differentiator from random mutations.
7
- */
8
-
9
- import fs from 'fs';
10
- import path from 'path';
11
- import { fileURLToPath } from 'url';
12
- import { spawn } from 'child_process';
13
-
14
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
- const PROJECT_ROOT = path.resolve(__dirname, '../../..');
16
-
17
- // Load .env from project root
18
- const envPath = path.join(PROJECT_ROOT, '.env');
19
- if (fs.existsSync(envPath)) {
20
- for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
21
- const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
22
- if (match && !process.env[match[1]]) {
23
- process.env[match[1]] = match[2].replace(/^["']|["']$/g, '');
24
- }
25
- }
26
- }
27
-
28
- /**
29
- * Call Claude CLI via spawn (stdin pipe, no shell interpolation)
30
- */
31
- function spawnClaude(prompt) {
32
- return new Promise((resolve, reject) => {
33
- const child = spawn('claude', ['--print'], {
34
- stdio: ['pipe', 'pipe', 'pipe'],
35
- env: { ...process.env },
36
- });
37
-
38
- let stdout = '';
39
- let stderr = '';
40
-
41
- child.stdout.on('data', (d) => (stdout += d));
42
- child.stderr.on('data', (d) => (stderr += d));
43
-
44
- child.on('close', (code) => {
45
- if (code !== 0 && !stdout)
46
- return reject(new Error(stderr || `claude exited ${code}`));
47
- resolve(stdout);
48
- });
49
-
50
- child.on('error', reject);
51
-
52
- child.stdin.write(prompt);
53
- child.stdin.end();
54
- });
55
- }
56
-
57
- const GEPA_DIR = path.join(__dirname, '..');
58
- const RESULTS_DIR = path.join(GEPA_DIR, 'results');
59
- const GENERATIONS_DIR = path.join(GEPA_DIR, 'generations');
60
-
61
- /**
62
- * Analyze session data for patterns
63
- */
64
- function analyzeSessionPatterns() {
65
- const sessionsDir = path.join(RESULTS_DIR, 'sessions');
66
- if (!fs.existsSync(sessionsDir)) return { patterns: [], insights: [] };
67
-
68
- const sessions = fs
69
- .readdirSync(sessionsDir)
70
- .filter((f) => f.endsWith('.json'))
71
- .map((f) => JSON.parse(fs.readFileSync(path.join(sessionsDir, f), 'utf8')));
72
-
73
- const patterns = {
74
- // Error patterns
75
- commonErrors: extractCommonErrors(sessions),
76
-
77
- // Tool usage patterns
78
- toolUsage: extractToolPatterns(sessions),
79
-
80
- // Feedback patterns
81
- feedbackPatterns: extractFeedbackPatterns(sessions),
82
-
83
- // Performance patterns
84
- performanceByVariant: extractPerformanceByVariant(sessions),
85
- };
86
-
87
- return patterns;
88
- }
89
-
90
- function extractCommonErrors(sessions) {
91
- const errorCounts = {};
92
-
93
- sessions.forEach((s) => {
94
- s.errors?.forEach((e) => {
95
- const normalized = normalizeError(e.error);
96
- errorCounts[normalized] = (errorCounts[normalized] || 0) + 1;
97
- });
98
- });
99
-
100
- return Object.entries(errorCounts)
101
- .sort((a, b) => b[1] - a[1])
102
- .slice(0, 10)
103
- .map(([error, count]) => ({ error, count }));
104
- }
105
-
106
- function normalizeError(error) {
107
- // Normalize error messages for grouping
108
- return error
109
- .replace(/\d+/g, 'N')
110
- .replace(/['"`][^'"`]+['"`]/g, '"..."')
111
- .replace(/\/[^\s]+/g, '/path')
112
- .slice(0, 100);
113
- }
114
-
115
- function extractToolPatterns(sessions) {
116
- const toolStats = {};
117
-
118
- sessions.forEach((s) => {
119
- s.toolCalls?.forEach((t) => {
120
- if (!toolStats[t.tool]) {
121
- toolStats[t.tool] = { count: 0, success: 0, avgDuration: 0 };
122
- }
123
- toolStats[t.tool].count++;
124
- if (t.success) toolStats[t.tool].success++;
125
- toolStats[t.tool].avgDuration += t.duration || 0;
126
- });
127
- });
128
-
129
- // Calculate averages
130
- Object.values(toolStats).forEach((s) => {
131
- s.avgDuration = s.count > 0 ? s.avgDuration / s.count : 0;
132
- s.successRate = s.count > 0 ? s.success / s.count : 0;
133
- });
134
-
135
- return toolStats;
136
- }
137
-
138
- function extractFeedbackPatterns(sessions) {
139
- const feedback = { positive: 0, negative: 0, corrections: 0, retries: 0 };
140
-
141
- sessions.forEach((s) => {
142
- s.userFeedback?.forEach((f) => {
143
- if (f.type === 'thumbs_up') feedback.positive++;
144
- if (f.type === 'thumbs_down') feedback.negative++;
145
- if (f.type === 'correction') feedback.corrections++;
146
- if (f.type === 'retry') feedback.retries++;
147
- });
148
- });
149
-
150
- return feedback;
151
- }
152
-
153
- function extractPerformanceByVariant(sessions) {
154
- const variants = {};
155
-
156
- sessions.forEach((s) => {
157
- if (!variants[s.variant]) {
158
- variants[s.variant] = {
159
- sessions: 0,
160
- totalErrors: 0,
161
- totalSuccess: 0,
162
- avgDuration: 0,
163
- };
164
- }
165
-
166
- variants[s.variant].sessions++;
167
- variants[s.variant].totalErrors += s.metrics?.errorCount || 0;
168
- variants[s.variant].totalSuccess += s.metrics?.successfulToolCalls || 0;
169
- variants[s.variant].avgDuration += s.duration || 0;
170
- });
171
-
172
- // Normalize
173
- Object.values(variants).forEach((v) => {
174
- v.avgDuration = v.sessions > 0 ? v.avgDuration / v.sessions : 0;
175
- v.errorRate = v.sessions > 0 ? v.totalErrors / v.sessions : 0;
176
- v.successRate = v.totalSuccess / (v.totalSuccess + v.totalErrors) || 0;
177
- });
178
-
179
- return variants;
180
- }
181
-
182
- /**
183
- * Generate reflection insights
184
- */
185
- async function generateReflection() {
186
- const patterns = analyzeSessionPatterns();
187
- const state = JSON.parse(
188
- fs.readFileSync(path.join(GEPA_DIR, 'state.json'), 'utf8')
189
- );
190
-
191
- // Load current best prompt for context
192
- const currentPrompt = fs.readFileSync(
193
- path.join(
194
- GENERATIONS_DIR,
195
- `gen-${String(state.currentGeneration).padStart(3, '0')}`,
196
- `${state.bestVariant}.md`
197
- ),
198
- 'utf8'
199
- );
200
-
201
- const reflectionPrompt = `Analyze these AI agent performance patterns and generate specific improvement recommendations.
202
-
203
- CURRENT SYSTEM PROMPT (excerpt):
204
- \`\`\`markdown
205
- ${currentPrompt.slice(0, 3000)}...
206
- \`\`\`
207
-
208
- PERFORMANCE PATTERNS:
209
-
210
- 1. COMMON ERRORS (${patterns.commonErrors.length} types):
211
- ${patterns.commonErrors.map((e) => ` - "${e.error}" (${e.count}x)`).join('\n')}
212
-
213
- 2. TOOL USAGE:
214
- ${Object.entries(patterns.toolUsage)
215
- .map(
216
- ([tool, s]) =>
217
- ` - ${tool}: ${s.count} calls, ${(s.successRate * 100).toFixed(0)}% success, ${s.avgDuration.toFixed(0)}ms avg`
218
- )
219
- .join('\n')}
220
-
221
- 3. USER FEEDBACK:
222
- - Positive: ${patterns.feedbackPatterns.positive}
223
- - Negative: ${patterns.feedbackPatterns.negative}
224
- - Corrections needed: ${patterns.feedbackPatterns.corrections}
225
- - Retries: ${patterns.feedbackPatterns.retries}
226
-
227
- 4. VARIANT PERFORMANCE:
228
- ${Object.entries(patterns.performanceByVariant)
229
- .map(
230
- ([v, s]) =>
231
- ` - ${v}: ${s.sessions} sessions, ${s.errorRate.toFixed(1)} errors/session, ${(s.successRate * 100).toFixed(0)}% success`
232
- )
233
- .join('\n')}
234
-
235
- Based on this data, provide:
236
-
237
- 1. TOP 3 FAILURE MODES - What's causing the most errors?
238
-
239
- 2. MISSING INSTRUCTIONS - What rules should be added to prevent errors?
240
-
241
- 3. UNCLEAR INSTRUCTIONS - What existing rules are being misinterpreted?
242
-
243
- 4. PRIORITY MUTATIONS - What specific changes would have highest impact?
244
-
245
- Format as JSON:
246
- {
247
- "failureModes": ["...", "...", "..."],
248
- "missingInstructions": ["...", "...", "..."],
249
- "unclearInstructions": ["...", "...", "..."],
250
- "priorityMutations": [
251
- {"type": "add|modify|remove", "section": "...", "change": "...", "rationale": "..."},
252
- ...
253
- ]
254
- }`;
255
-
256
- try {
257
- const result = await spawnClaude(reflectionPrompt);
258
-
259
- // Parse JSON from response
260
- const jsonMatch = result.match(/\{[\s\S]*\}/);
261
- if (jsonMatch) {
262
- const insights = JSON.parse(jsonMatch[0]);
263
-
264
- // Save reflection
265
- const reflectionPath = path.join(
266
- RESULTS_DIR,
267
- `reflection-${Date.now()}.json`
268
- );
269
- fs.writeFileSync(
270
- reflectionPath,
271
- JSON.stringify(
272
- {
273
- timestamp: new Date().toISOString(),
274
- generation: state.currentGeneration,
275
- patterns,
276
- insights,
277
- },
278
- null,
279
- 2
280
- )
281
- );
282
-
283
- return insights;
284
- }
285
- } catch (e) {
286
- console.error('Reflection failed:', e.message);
287
- }
288
-
289
- return null;
290
- }
291
-
292
- /**
293
- * Generate focused mutation prompts based on reflection
294
- */
295
- function generateMutationGuide(insights) {
296
- if (!insights) return null;
297
-
298
- return `
299
- REFLECTION-GUIDED MUTATIONS:
300
-
301
- Based on ${insights.failureModes.length} identified failure modes:
302
- ${insights.failureModes.map((f, i) => `${i + 1}. ${f}`).join('\n')}
303
-
304
- PRIORITY CHANGES:
305
- ${insights.priorityMutations
306
- .map(
307
- (m) =>
308
- `- [${m.type.toUpperCase()}] ${m.section}: ${m.change}\n Rationale: ${m.rationale}`
309
- )
310
- .join('\n\n')}
311
-
312
- MISSING INSTRUCTIONS TO ADD:
313
- ${insights.missingInstructions.map((i) => `- ${i}`).join('\n')}
314
-
315
- UNCLEAR INSTRUCTIONS TO CLARIFY:
316
- ${insights.unclearInstructions.map((i) => `- ${i}`).join('\n')}
317
- `;
318
- }
319
-
320
- // CLI
321
- const command = process.argv[2];
322
-
323
- switch (command) {
324
- case 'analyze':
325
- const patterns = analyzeSessionPatterns();
326
- console.log(JSON.stringify(patterns, null, 2));
327
- break;
328
-
329
- case 'reflect':
330
- generateReflection().then((insights) => {
331
- if (insights) {
332
- console.log('\nReflection Insights:');
333
- console.log(JSON.stringify(insights, null, 2));
334
- console.log('\nMutation Guide:');
335
- console.log(generateMutationGuide(insights));
336
- }
337
- });
338
- break;
339
-
340
- default:
341
- console.log(`
342
- GEPA Reflection Engine
343
-
344
- Usage:
345
- node reflect.js analyze Analyze session patterns
346
- node reflect.js reflect Generate reflection insights
347
- `);
348
- }
349
-
350
- export { analyzeSessionPatterns, generateReflection, generateMutationGuide };