@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.
Files changed (87) hide show
  1. package/README.md +304 -1
  2. package/bin/boss-claude.js +1138 -0
  3. package/bin/commands/mode.js +250 -0
  4. package/bin/onyx-guard.js +259 -0
  5. package/bin/onyx-guard.sh +251 -0
  6. package/bin/prompts.js +284 -0
  7. package/bin/rollback.js +85 -0
  8. package/bin/setup-wizard.js +492 -0
  9. package/config/.env.example +17 -0
  10. package/lib/README.md +83 -0
  11. package/lib/agent-logger.js +61 -0
  12. package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
  13. package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
  14. package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
  15. package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
  16. package/lib/agents/memory-supervisor.js +526 -0
  17. package/lib/agents/registry.js +135 -0
  18. package/lib/auto-monitor.js +131 -0
  19. package/lib/checkpoint-hook.js +112 -0
  20. package/lib/checkpoint.js +319 -0
  21. package/lib/commentator.js +213 -0
  22. package/lib/context-scribe.js +120 -0
  23. package/lib/delegation-strategies.js +326 -0
  24. package/lib/hierarchy-validator.js +643 -0
  25. package/lib/index.js +15 -0
  26. package/lib/init-with-mode.js +261 -0
  27. package/lib/init.js +44 -6
  28. package/lib/memory-result-aggregator.js +252 -0
  29. package/lib/memory.js +35 -7
  30. package/lib/mode-enforcer.js +473 -0
  31. package/lib/onyx-banner.js +169 -0
  32. package/lib/onyx-identity.js +214 -0
  33. package/lib/onyx-monitor.js +381 -0
  34. package/lib/onyx-reminder.js +188 -0
  35. package/lib/onyx-tool-interceptor.js +341 -0
  36. package/lib/onyx-wrapper.js +315 -0
  37. package/lib/orchestrator-gate.js +334 -0
  38. package/lib/output-formatter.js +296 -0
  39. package/lib/postgres.js +1 -1
  40. package/lib/prompt-injector.js +220 -0
  41. package/lib/prompts.js +532 -0
  42. package/lib/session.js +153 -6
  43. package/lib/setup/README.md +187 -0
  44. package/lib/setup/env-manager.js +785 -0
  45. package/lib/setup/error-recovery.js +630 -0
  46. package/lib/setup/explain-scopes.js +385 -0
  47. package/lib/setup/github-instructions.js +333 -0
  48. package/lib/setup/github-repo.js +254 -0
  49. package/lib/setup/import-credentials.js +498 -0
  50. package/lib/setup/index.js +62 -0
  51. package/lib/setup/init-postgres.js +785 -0
  52. package/lib/setup/init-redis.js +456 -0
  53. package/lib/setup/integration-test.js +652 -0
  54. package/lib/setup/progress.js +357 -0
  55. package/lib/setup/rollback.js +670 -0
  56. package/lib/setup/rollback.test.js +452 -0
  57. package/lib/setup/setup-with-rollback.example.js +351 -0
  58. package/lib/setup/summary.js +400 -0
  59. package/lib/setup/test-github-setup.js +10 -0
  60. package/lib/setup/test-postgres-init.js +98 -0
  61. package/lib/setup/verify-setup.js +102 -0
  62. package/lib/task-agent-worker.js +235 -0
  63. package/lib/token-monitor.js +466 -0
  64. package/lib/tool-wrapper-integration.js +369 -0
  65. package/lib/tool-wrapper.js +387 -0
  66. package/lib/validators/README.md +497 -0
  67. package/lib/validators/config.js +583 -0
  68. package/lib/validators/config.test.js +175 -0
  69. package/lib/validators/github.js +310 -0
  70. package/lib/validators/github.test.js +61 -0
  71. package/lib/validators/index.js +15 -0
  72. package/lib/validators/postgres.js +525 -0
  73. package/package.json +98 -13
  74. package/scripts/benchmark-memory.js +433 -0
  75. package/scripts/check-secrets.sh +12 -0
  76. package/scripts/fetch-todos.mjs +148 -0
  77. package/scripts/graceful-shutdown.sh +156 -0
  78. package/scripts/install-onyx-hooks.js +373 -0
  79. package/scripts/install.js +119 -18
  80. package/scripts/redis-monitor.js +284 -0
  81. package/scripts/redis-setup.js +412 -0
  82. package/scripts/test-memory-retrieval.js +201 -0
  83. package/scripts/validate-exports.js +68 -0
  84. package/scripts/validate-package.js +120 -0
  85. package/scripts/verify-onyx-deployment.js +309 -0
  86. package/scripts/verify-redis-deployment.js +354 -0
  87. 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
+ }