@cpretzinger/boss-claude 1.0.0 → 1.0.2
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 +304 -1
- package/bin/boss-claude.js +1138 -0
- package/bin/commands/mode.js +250 -0
- package/bin/onyx-guard.js +259 -0
- package/bin/onyx-guard.sh +251 -0
- package/bin/prompts.js +284 -0
- package/bin/rollback.js +85 -0
- package/bin/setup-wizard.js +492 -0
- package/config/.env.example +17 -0
- package/lib/README.md +83 -0
- package/lib/agent-logger.js +61 -0
- package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
- package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
- package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
- package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
- package/lib/agents/memory-supervisor.js +526 -0
- package/lib/agents/registry.js +135 -0
- package/lib/auto-monitor.js +131 -0
- package/lib/checkpoint-hook.js +112 -0
- package/lib/checkpoint.js +319 -0
- package/lib/commentator.js +213 -0
- package/lib/context-scribe.js +120 -0
- package/lib/delegation-strategies.js +326 -0
- package/lib/hierarchy-validator.js +643 -0
- package/lib/index.js +15 -0
- package/lib/init-with-mode.js +261 -0
- package/lib/init.js +44 -6
- package/lib/memory-result-aggregator.js +252 -0
- package/lib/memory.js +35 -7
- package/lib/mode-enforcer.js +473 -0
- package/lib/onyx-banner.js +169 -0
- package/lib/onyx-identity.js +214 -0
- package/lib/onyx-monitor.js +381 -0
- package/lib/onyx-reminder.js +188 -0
- package/lib/onyx-tool-interceptor.js +341 -0
- package/lib/onyx-wrapper.js +315 -0
- package/lib/orchestrator-gate.js +334 -0
- package/lib/output-formatter.js +296 -0
- package/lib/postgres.js +1 -1
- package/lib/prompt-injector.js +220 -0
- package/lib/prompts.js +532 -0
- package/lib/session.js +153 -6
- package/lib/setup/README.md +187 -0
- package/lib/setup/env-manager.js +785 -0
- package/lib/setup/error-recovery.js +630 -0
- package/lib/setup/explain-scopes.js +385 -0
- package/lib/setup/github-instructions.js +333 -0
- package/lib/setup/github-repo.js +254 -0
- package/lib/setup/import-credentials.js +498 -0
- package/lib/setup/index.js +62 -0
- package/lib/setup/init-postgres.js +785 -0
- package/lib/setup/init-redis.js +456 -0
- package/lib/setup/integration-test.js +652 -0
- package/lib/setup/progress.js +357 -0
- package/lib/setup/rollback.js +670 -0
- package/lib/setup/rollback.test.js +452 -0
- package/lib/setup/setup-with-rollback.example.js +351 -0
- package/lib/setup/summary.js +400 -0
- package/lib/setup/test-github-setup.js +10 -0
- package/lib/setup/test-postgres-init.js +98 -0
- package/lib/setup/verify-setup.js +102 -0
- package/lib/task-agent-worker.js +235 -0
- package/lib/token-monitor.js +466 -0
- package/lib/tool-wrapper-integration.js +369 -0
- package/lib/tool-wrapper.js +387 -0
- package/lib/validators/README.md +497 -0
- package/lib/validators/config.js +583 -0
- package/lib/validators/config.test.js +175 -0
- package/lib/validators/github.js +310 -0
- package/lib/validators/github.test.js +61 -0
- package/lib/validators/index.js +15 -0
- package/lib/validators/postgres.js +525 -0
- package/package.json +98 -13
- package/scripts/benchmark-memory.js +433 -0
- package/scripts/check-secrets.sh +12 -0
- package/scripts/fetch-todos.mjs +148 -0
- package/scripts/graceful-shutdown.sh +156 -0
- package/scripts/install-onyx-hooks.js +373 -0
- package/scripts/install.js +119 -18
- package/scripts/redis-monitor.js +284 -0
- package/scripts/redis-setup.js +412 -0
- package/scripts/test-memory-retrieval.js +201 -0
- package/scripts/validate-exports.js +68 -0
- package/scripts/validate-package.js +120 -0
- package/scripts/verify-onyx-deployment.js +309 -0
- package/scripts/verify-redis-deployment.js +354 -0
- package/scripts/verify-redis-init.js +219 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Memory Engineer
|
|
3
|
+
*
|
|
4
|
+
* Fast cache layer for Boss Claude memory system.
|
|
5
|
+
* Queries Redis for recent sessions with <100ms response time.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Query cache (boss:memory:query:{hash})
|
|
9
|
+
* - Recent sessions (boss:memory:recent:sessions)
|
|
10
|
+
* - Session summaries for text search
|
|
11
|
+
* - <100ms response time
|
|
12
|
+
*
|
|
13
|
+
* Architecture:
|
|
14
|
+
* - Layer 1: Query cache (instant hits)
|
|
15
|
+
* - Layer 2: Recent sessions (last 10 sessions, Redis sorted set)
|
|
16
|
+
* - Layer 3: Session summaries (full-text search on Redis)
|
|
17
|
+
*
|
|
18
|
+
* @module lib/agents/memory-engineers/redis-memory-engineer
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import Redis from 'ioredis';
|
|
22
|
+
import crypto from 'crypto';
|
|
23
|
+
import dotenv from 'dotenv';
|
|
24
|
+
import { existsSync } from 'fs';
|
|
25
|
+
import { join } from 'path';
|
|
26
|
+
import os from 'os';
|
|
27
|
+
|
|
28
|
+
// Load environment variables
|
|
29
|
+
const envPath = join(os.homedir(), '.boss-claude', '.env');
|
|
30
|
+
if (existsSync(envPath)) {
|
|
31
|
+
dotenv.config({ path: envPath });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let redis = null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get or create Redis client
|
|
38
|
+
*/
|
|
39
|
+
function getRedis() {
|
|
40
|
+
if (!redis) {
|
|
41
|
+
if (!process.env.REDIS_URL) {
|
|
42
|
+
throw new Error('REDIS_URL not found. Please run: boss-claude init');
|
|
43
|
+
}
|
|
44
|
+
redis = new Redis(process.env.REDIS_URL);
|
|
45
|
+
}
|
|
46
|
+
return redis;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate cache key hash for query
|
|
51
|
+
*/
|
|
52
|
+
function getCacheHash(query, filters = {}) {
|
|
53
|
+
const cacheKey = JSON.stringify({ query, filters });
|
|
54
|
+
return crypto.createHash('sha256').update(cacheKey).digest('hex').substring(0, 16);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Redis Memory Engineer Class
|
|
59
|
+
*/
|
|
60
|
+
export class RedisMemoryEngineer {
|
|
61
|
+
constructor() {
|
|
62
|
+
this.client = getRedis();
|
|
63
|
+
this.cachePrefix = 'boss:memory:query';
|
|
64
|
+
this.recentSessionsKey = 'boss:memory:recent:sessions';
|
|
65
|
+
this.sessionPrefix = 'boss:memory:session';
|
|
66
|
+
this.cacheTTL = 3600; // 1 hour cache TTL
|
|
67
|
+
this.maxRecentSessions = 10;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Query memory with caching
|
|
72
|
+
*
|
|
73
|
+
* @param {string} query - Search query
|
|
74
|
+
* @param {Object} filters - Optional filters (repo, tags, dateRange)
|
|
75
|
+
* @param {number} limit - Maximum results to return
|
|
76
|
+
* @returns {Promise<Array>} - Array of matching sessions
|
|
77
|
+
*/
|
|
78
|
+
async query(query, filters = {}, limit = 5) {
|
|
79
|
+
const startTime = Date.now();
|
|
80
|
+
|
|
81
|
+
// Step 1: Check query cache
|
|
82
|
+
const cacheHash = getCacheHash(query, filters);
|
|
83
|
+
const cacheKey = `${this.cachePrefix}:${cacheHash}`;
|
|
84
|
+
|
|
85
|
+
const cached = await this.client.get(cacheKey);
|
|
86
|
+
if (cached) {
|
|
87
|
+
const results = JSON.parse(cached);
|
|
88
|
+
const duration = Date.now() - startTime;
|
|
89
|
+
return {
|
|
90
|
+
results: results.slice(0, limit),
|
|
91
|
+
source: 'cache',
|
|
92
|
+
duration_ms: duration
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Step 2: Search recent sessions
|
|
97
|
+
const results = await this.searchRecentSessions(query, filters, limit);
|
|
98
|
+
|
|
99
|
+
// Step 3: Cache results
|
|
100
|
+
await this.client.setex(cacheKey, this.cacheTTL, JSON.stringify(results));
|
|
101
|
+
|
|
102
|
+
const duration = Date.now() - startTime;
|
|
103
|
+
return {
|
|
104
|
+
results,
|
|
105
|
+
source: 'redis',
|
|
106
|
+
duration_ms: duration
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Search recent sessions in Redis
|
|
112
|
+
*/
|
|
113
|
+
async searchRecentSessions(query, filters = {}, limit = 5) {
|
|
114
|
+
// Get recent session IDs from sorted set
|
|
115
|
+
const sessionIds = await this.client.zrevrange(
|
|
116
|
+
this.recentSessionsKey,
|
|
117
|
+
0,
|
|
118
|
+
this.maxRecentSessions - 1
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (sessionIds.length === 0) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Fetch session data
|
|
126
|
+
const pipeline = this.client.pipeline();
|
|
127
|
+
sessionIds.forEach(id => {
|
|
128
|
+
pipeline.get(`${this.sessionPrefix}:${id}`);
|
|
129
|
+
});
|
|
130
|
+
const sessionData = await pipeline.exec();
|
|
131
|
+
|
|
132
|
+
// Parse and filter sessions
|
|
133
|
+
const sessions = sessionData
|
|
134
|
+
.map(([err, data]) => {
|
|
135
|
+
if (err || !data) return null;
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(data);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
.filter(s => s !== null);
|
|
143
|
+
|
|
144
|
+
// Apply filters and search
|
|
145
|
+
let filtered = sessions;
|
|
146
|
+
|
|
147
|
+
// Filter by repository
|
|
148
|
+
if (filters.repo) {
|
|
149
|
+
filtered = filtered.filter(s =>
|
|
150
|
+
s.repo && s.repo.name === filters.repo
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Filter by tags
|
|
155
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
156
|
+
filtered = filtered.filter(s =>
|
|
157
|
+
s.tags && s.tags.some(tag => filters.tags.includes(tag))
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Filter by date range
|
|
162
|
+
if (filters.dateRange) {
|
|
163
|
+
const { start, end } = filters.dateRange;
|
|
164
|
+
filtered = filtered.filter(s => {
|
|
165
|
+
const sessionDate = new Date(s.started_at);
|
|
166
|
+
return (!start || sessionDate >= new Date(start)) &&
|
|
167
|
+
(!end || sessionDate <= new Date(end));
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Text search in summary and content
|
|
172
|
+
if (query) {
|
|
173
|
+
const queryLower = query.toLowerCase();
|
|
174
|
+
filtered = filtered.filter(s => {
|
|
175
|
+
const summaryMatch = s.summary && s.summary.toLowerCase().includes(queryLower);
|
|
176
|
+
const contentMatch = s.content && JSON.stringify(s.content).toLowerCase().includes(queryLower);
|
|
177
|
+
const tagsMatch = s.tags && s.tags.some(tag => tag.toLowerCase().includes(queryLower));
|
|
178
|
+
return summaryMatch || contentMatch || tagsMatch;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Sort by relevance (date for now, could add scoring)
|
|
183
|
+
filtered.sort((a, b) =>
|
|
184
|
+
new Date(b.started_at) - new Date(a.started_at)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return filtered.slice(0, limit);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Add session to recent sessions cache
|
|
192
|
+
*/
|
|
193
|
+
async addSession(sessionId, sessionData) {
|
|
194
|
+
const timestamp = Date.now();
|
|
195
|
+
|
|
196
|
+
// Add to sorted set (score = timestamp)
|
|
197
|
+
await this.client.zadd(
|
|
198
|
+
this.recentSessionsKey,
|
|
199
|
+
timestamp,
|
|
200
|
+
sessionId
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Store session data
|
|
204
|
+
await this.client.setex(
|
|
205
|
+
`${this.sessionPrefix}:${sessionId}`,
|
|
206
|
+
86400 * 7, // 7 days TTL
|
|
207
|
+
JSON.stringify(sessionData)
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Trim sorted set to max size
|
|
211
|
+
await this.client.zremrangebyrank(
|
|
212
|
+
this.recentSessionsKey,
|
|
213
|
+
0,
|
|
214
|
+
-(this.maxRecentSessions + 1)
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
session_id: sessionId,
|
|
219
|
+
timestamp,
|
|
220
|
+
cached: true
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get recent sessions (no search)
|
|
226
|
+
*/
|
|
227
|
+
async getRecentSessions(limit = 10) {
|
|
228
|
+
const startTime = Date.now();
|
|
229
|
+
|
|
230
|
+
const sessionIds = await this.client.zrevrange(
|
|
231
|
+
this.recentSessionsKey,
|
|
232
|
+
0,
|
|
233
|
+
limit - 1
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (sessionIds.length === 0) {
|
|
237
|
+
return {
|
|
238
|
+
results: [],
|
|
239
|
+
source: 'redis',
|
|
240
|
+
duration_ms: Date.now() - startTime
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const pipeline = this.client.pipeline();
|
|
245
|
+
sessionIds.forEach(id => {
|
|
246
|
+
pipeline.get(`${this.sessionPrefix}:${id}`);
|
|
247
|
+
});
|
|
248
|
+
const sessionData = await pipeline.exec();
|
|
249
|
+
|
|
250
|
+
const sessions = sessionData
|
|
251
|
+
.map(([err, data]) => {
|
|
252
|
+
if (err || !data) return null;
|
|
253
|
+
try {
|
|
254
|
+
return JSON.parse(data);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
.filter(s => s !== null);
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
results: sessions,
|
|
263
|
+
source: 'redis',
|
|
264
|
+
duration_ms: Date.now() - startTime
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Clear query cache
|
|
270
|
+
*/
|
|
271
|
+
async clearCache(pattern = '*') {
|
|
272
|
+
const keys = [];
|
|
273
|
+
const matchPattern = `${this.cachePrefix}:${pattern}`;
|
|
274
|
+
let cursor = '0';
|
|
275
|
+
|
|
276
|
+
// Use SCAN instead of KEYS to avoid blocking Redis
|
|
277
|
+
do {
|
|
278
|
+
const [nextCursor, foundKeys] = await this.client.scan(
|
|
279
|
+
cursor,
|
|
280
|
+
'MATCH',
|
|
281
|
+
matchPattern,
|
|
282
|
+
'COUNT',
|
|
283
|
+
100
|
|
284
|
+
);
|
|
285
|
+
cursor = nextCursor;
|
|
286
|
+
keys.push(...foundKeys);
|
|
287
|
+
} while (cursor !== '0');
|
|
288
|
+
|
|
289
|
+
if (keys.length > 0) {
|
|
290
|
+
await this.client.del(...keys);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
cleared: keys.length,
|
|
294
|
+
pattern: matchPattern
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get cache stats
|
|
300
|
+
*/
|
|
301
|
+
async getStats() {
|
|
302
|
+
const recentCount = await this.client.zcard(this.recentSessionsKey);
|
|
303
|
+
|
|
304
|
+
// Use SCAN instead of KEYS to avoid blocking Redis
|
|
305
|
+
const cacheKeys = [];
|
|
306
|
+
let cursor = '0';
|
|
307
|
+
do {
|
|
308
|
+
const [nextCursor, foundKeys] = await this.client.scan(
|
|
309
|
+
cursor,
|
|
310
|
+
'MATCH',
|
|
311
|
+
`${this.cachePrefix}:*`,
|
|
312
|
+
'COUNT',
|
|
313
|
+
100
|
|
314
|
+
);
|
|
315
|
+
cursor = nextCursor;
|
|
316
|
+
cacheKeys.push(...foundKeys);
|
|
317
|
+
} while (cursor !== '0');
|
|
318
|
+
|
|
319
|
+
const sessionKeys = [];
|
|
320
|
+
cursor = '0';
|
|
321
|
+
do {
|
|
322
|
+
const [nextCursor, foundKeys] = await this.client.scan(
|
|
323
|
+
cursor,
|
|
324
|
+
'MATCH',
|
|
325
|
+
`${this.sessionPrefix}:*`,
|
|
326
|
+
'COUNT',
|
|
327
|
+
100
|
|
328
|
+
);
|
|
329
|
+
cursor = nextCursor;
|
|
330
|
+
sessionKeys.push(...foundKeys);
|
|
331
|
+
} while (cursor !== '0');
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
recent_sessions: recentCount,
|
|
335
|
+
cached_queries: cacheKeys.length,
|
|
336
|
+
stored_sessions: sessionKeys.length,
|
|
337
|
+
cache_prefix: this.cachePrefix,
|
|
338
|
+
session_prefix: this.sessionPrefix
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Close Redis connection
|
|
344
|
+
*/
|
|
345
|
+
async close() {
|
|
346
|
+
if (this.client) {
|
|
347
|
+
await this.client.quit();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Create and export singleton instance
|
|
354
|
+
*/
|
|
355
|
+
export const redisMemoryEngineer = new RedisMemoryEngineer();
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Quick query interface
|
|
359
|
+
*/
|
|
360
|
+
export async function queryMemory(query, filters = {}, limit = 5) {
|
|
361
|
+
return await redisMemoryEngineer.query(query, filters, limit);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get recent sessions
|
|
366
|
+
*/
|
|
367
|
+
export async function getRecentSessions(limit = 10) {
|
|
368
|
+
return await redisMemoryEngineer.getRecentSessions(limit);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Add session to cache
|
|
373
|
+
*/
|
|
374
|
+
export async function cacheSession(sessionId, sessionData) {
|
|
375
|
+
return await redisMemoryEngineer.addSession(sessionId, sessionData);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get cache statistics
|
|
380
|
+
*/
|
|
381
|
+
export async function getCacheStats() {
|
|
382
|
+
return await redisMemoryEngineer.getStats();
|
|
383
|
+
}
|