@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,456 @@
1
+ /**
2
+ * Boss Claude Redis Initialization
3
+ *
4
+ * Sets up all required Redis data structures according to REDIS-ARCHITECTURE.md
5
+ * Creates default Boss identity and initializes core data structures.
6
+ *
7
+ * Data Structures Initialized:
8
+ * - boss:identity (String/JSON) - Global Boss Claude state
9
+ * - boss:sessions:history (Sorted Set) - Temporal session index
10
+ * - boss:leaderboard:xp (Sorted Set) - Global XP ranking
11
+ * - boss:achievements:{user} (Set) - Achievement tracking
12
+ *
13
+ * This module runs automatically during setup wizard and can be
14
+ * re-run safely to repair/reset the Redis state.
15
+ */
16
+
17
+ import Redis from 'ioredis';
18
+ import chalk from 'chalk';
19
+
20
+ // Default identity configuration
21
+ const DEFAULT_IDENTITY = {
22
+ level: 1,
23
+ xp: 0,
24
+ token_bank: 0,
25
+ total_sessions: 0,
26
+ repos_managed: 0,
27
+ created_at: new Date().toISOString(),
28
+ updated_at: new Date().toISOString()
29
+ };
30
+
31
+ // Available achievements (for reference)
32
+ const ACHIEVEMENTS = [
33
+ 'first_session', // Complete first session
34
+ 'level_5', // Reach level 5
35
+ 'level_10', // Reach level 10
36
+ 'token_saver', // Save 100k tokens
37
+ 'perfect_execution', // 10 sessions with no errors
38
+ 'speed_demon', // Complete task in under 1 min
39
+ 'repo_master', // Manage 10+ repos
40
+ 'consistency_king', // 7 day streak
41
+ ];
42
+
43
+ /**
44
+ * Initialize Redis with all required data structures
45
+ *
46
+ * @param {string} redisUrl - Redis connection string
47
+ * @param {string} username - GitHub username for user-specific data
48
+ * @param {boolean} force - Force re-initialization even if data exists
49
+ * @returns {Promise<Object>} Initialization results
50
+ */
51
+ export async function initializeRedis(redisUrl, username = 'default', force = false) {
52
+ const client = new Redis(redisUrl);
53
+ const results = {
54
+ identity: { created: false, existed: false },
55
+ history: { created: false, existed: false },
56
+ leaderboard: { created: false, existed: false },
57
+ achievements: { created: false, existed: false },
58
+ healthCheck: { passed: false }
59
+ };
60
+
61
+ try {
62
+ // Test connection
63
+ await client.ping();
64
+
65
+ // 1. Initialize Boss Identity
66
+ const identityExists = await client.exists('boss:identity');
67
+
68
+ if (!identityExists || force) {
69
+ await client.set('boss:identity', JSON.stringify(DEFAULT_IDENTITY));
70
+ results.identity.created = true;
71
+
72
+ if (identityExists && force) {
73
+ results.identity.existed = true;
74
+ }
75
+ } else {
76
+ results.identity.existed = true;
77
+ }
78
+
79
+ // 2. Initialize Session History (Sorted Set)
80
+ // This is a sorted set, so we just verify it exists
81
+ const historyExists = await client.exists('boss:sessions:history');
82
+
83
+ if (!historyExists) {
84
+ // Initialize empty sorted set by adding and removing a dummy entry
85
+ await client.zadd('boss:sessions:history', 0, '__init__');
86
+ await client.zrem('boss:sessions:history', '__init__');
87
+ results.history.created = true;
88
+ } else {
89
+ results.history.existed = true;
90
+ }
91
+
92
+ // 3. Initialize Leaderboard (Sorted Set)
93
+ const leaderboardExists = await client.exists('boss:leaderboard:xp');
94
+
95
+ if (!leaderboardExists || force) {
96
+ // Add initial user with 0 XP
97
+ await client.zadd('boss:leaderboard:xp', 0, username);
98
+ results.leaderboard.created = true;
99
+
100
+ if (leaderboardExists && force) {
101
+ results.leaderboard.existed = true;
102
+ }
103
+ } else {
104
+ // Update existing user or add if not present
105
+ const userExists = await client.zscore('boss:leaderboard:xp', username);
106
+ if (!userExists) {
107
+ await client.zadd('boss:leaderboard:xp', 0, username);
108
+ results.leaderboard.created = true;
109
+ } else {
110
+ results.leaderboard.existed = true;
111
+ }
112
+ }
113
+
114
+ // 4. Initialize Achievements (Set)
115
+ const achievementsKey = `boss:achievements:${username}`;
116
+ const achievementsExist = await client.exists(achievementsKey);
117
+
118
+ if (!achievementsExist) {
119
+ // Initialize empty set by adding and removing a dummy entry
120
+ await client.sadd(achievementsKey, '__init__');
121
+ await client.srem(achievementsKey, '__init__');
122
+ results.achievements.created = true;
123
+ } else {
124
+ results.achievements.existed = true;
125
+ }
126
+
127
+ // 5. Health check
128
+ const healthCheckResult = await performHealthCheck(client);
129
+ results.healthCheck = healthCheckResult;
130
+
131
+ return results;
132
+
133
+ } catch (error) {
134
+ throw new Error(`Redis initialization failed: ${error.message}`);
135
+ } finally {
136
+ await client.quit();
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Perform comprehensive health check on Redis data structures
142
+ *
143
+ * @param {Redis} client - Connected Redis client
144
+ * @returns {Promise<Object>} Health check results
145
+ */
146
+ async function performHealthCheck(client) {
147
+ const checks = {
148
+ passed: true,
149
+ details: {}
150
+ };
151
+
152
+ try {
153
+ // Check 1: Identity exists and is valid JSON
154
+ const identity = await client.get('boss:identity');
155
+ if (!identity) {
156
+ checks.passed = false;
157
+ checks.details.identity = 'Missing boss:identity key';
158
+ } else {
159
+ try {
160
+ const parsed = JSON.parse(identity);
161
+ const requiredFields = ['level', 'xp', 'token_bank', 'total_sessions', 'repos_managed'];
162
+ const missingFields = requiredFields.filter(field => !(field in parsed));
163
+
164
+ if (missingFields.length > 0) {
165
+ checks.passed = false;
166
+ checks.details.identity = `Missing fields: ${missingFields.join(', ')}`;
167
+ } else {
168
+ checks.details.identity = 'OK';
169
+ }
170
+ } catch (e) {
171
+ checks.passed = false;
172
+ checks.details.identity = 'Invalid JSON format';
173
+ }
174
+ }
175
+
176
+ // Check 2: Session history is a sorted set
177
+ const historyType = await client.type('boss:sessions:history');
178
+ if (historyType !== 'zset' && historyType !== 'none') {
179
+ checks.passed = false;
180
+ checks.details.history = `Wrong type: ${historyType} (expected: zset)`;
181
+ } else {
182
+ checks.details.history = 'OK';
183
+ }
184
+
185
+ // Check 3: Leaderboard is a sorted set
186
+ const leaderboardType = await client.type('boss:leaderboard:xp');
187
+ if (leaderboardType !== 'zset' && leaderboardType !== 'none') {
188
+ checks.passed = false;
189
+ checks.details.leaderboard = `Wrong type: ${leaderboardType} (expected: zset)`;
190
+ } else {
191
+ checks.details.leaderboard = 'OK';
192
+ }
193
+
194
+ // Check 4: Test read/write operations
195
+ const testKey = 'boss:healthcheck:test';
196
+ const testValue = Date.now().toString();
197
+
198
+ await client.set(testKey, testValue);
199
+ const retrieved = await client.get(testKey);
200
+ await client.del(testKey);
201
+
202
+ if (retrieved !== testValue) {
203
+ checks.passed = false;
204
+ checks.details.readWrite = 'Read/write test failed';
205
+ } else {
206
+ checks.details.readWrite = 'OK';
207
+ }
208
+
209
+ // Check 5: Test sorted set operations
210
+ const testZSetKey = 'boss:healthcheck:zset';
211
+ await client.zadd(testZSetKey, 100, 'test_member');
212
+ const score = await client.zscore(testZSetKey, 'test_member');
213
+ await client.del(testZSetKey);
214
+
215
+ if (score !== '100') {
216
+ checks.passed = false;
217
+ checks.details.sortedSets = 'Sorted set test failed';
218
+ } else {
219
+ checks.details.sortedSets = 'OK';
220
+ }
221
+
222
+ } catch (error) {
223
+ checks.passed = false;
224
+ checks.details.error = error.message;
225
+ }
226
+
227
+ return checks;
228
+ }
229
+
230
+ /**
231
+ * Get current Redis statistics
232
+ *
233
+ * @param {string} redisUrl - Redis connection string
234
+ * @returns {Promise<Object>} Redis statistics
235
+ */
236
+ export async function getRedisStats(redisUrl) {
237
+ const client = new Redis(redisUrl);
238
+
239
+ try {
240
+ await client.ping();
241
+
242
+ const stats = {
243
+ identity: null,
244
+ totalSessions: 0,
245
+ totalRepos: 0,
246
+ activeSessions: 0,
247
+ leaderboardSize: 0,
248
+ achievements: {},
249
+ cacheKeys: 0
250
+ };
251
+
252
+ // Get identity
253
+ const identityData = await client.get('boss:identity');
254
+ if (identityData) {
255
+ stats.identity = JSON.parse(identityData);
256
+ }
257
+
258
+ // Count sessions in history
259
+ stats.totalSessions = await client.zcard('boss:sessions:history');
260
+
261
+ // Count repos
262
+ const repoKeys = await client.keys('boss:repo:*');
263
+ stats.totalRepos = repoKeys.length;
264
+
265
+ // Count active sessions
266
+ const activeSessionKeys = await client.keys('boss:session:*:current');
267
+ stats.activeSessions = activeSessionKeys.length;
268
+
269
+ // Get leaderboard size
270
+ stats.leaderboardSize = await client.zcard('boss:leaderboard:xp');
271
+
272
+ // Count cache keys
273
+ const cacheKeys = await client.keys('boss:cache:*');
274
+ stats.cacheKeys = cacheKeys.length;
275
+
276
+ // Get achievements for all users
277
+ const achievementKeys = await client.keys('boss:achievements:*');
278
+ for (const key of achievementKeys) {
279
+ const username = key.replace('boss:achievements:', '');
280
+ const achievements = await client.smembers(key);
281
+ stats.achievements[username] = achievements.filter(a => a !== '__init__');
282
+ }
283
+
284
+ return stats;
285
+
286
+ } catch (error) {
287
+ throw new Error(`Failed to get Redis stats: ${error.message}`);
288
+ } finally {
289
+ await client.quit();
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Reset Redis to initial state (WARNING: Destructive!)
295
+ *
296
+ * @param {string} redisUrl - Redis connection string
297
+ * @param {string} username - GitHub username
298
+ * @returns {Promise<void>}
299
+ */
300
+ export async function resetRedis(redisUrl, username = 'default') {
301
+ const client = new Redis(redisUrl);
302
+
303
+ try {
304
+ await client.ping();
305
+
306
+ // Delete all Boss Claude keys
307
+ const allKeys = await client.keys('boss:*');
308
+
309
+ if (allKeys.length > 0) {
310
+ await client.del(...allKeys);
311
+ }
312
+
313
+ // Re-initialize
314
+ await client.quit();
315
+ return await initializeRedis(redisUrl, username, true);
316
+
317
+ } catch (error) {
318
+ throw new Error(`Failed to reset Redis: ${error.message}`);
319
+ } finally {
320
+ if (client.status === 'ready') {
321
+ await client.quit();
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Verify Redis connection and structure
328
+ *
329
+ * @param {string} redisUrl - Redis connection string
330
+ * @returns {Promise<Object>} Verification results
331
+ */
332
+ export async function verifyRedis(redisUrl) {
333
+ const client = new Redis(redisUrl);
334
+
335
+ try {
336
+ // Test connection
337
+ await client.ping();
338
+
339
+ // Get server info
340
+ const serverInfo = await client.info('server');
341
+ const redisVersion = serverInfo.match(/redis_version:(.+)/)?.[1]?.trim();
342
+
343
+ // Perform health check
344
+ const healthCheck = await performHealthCheck(client);
345
+
346
+ // Get key counts
347
+ const identityExists = await client.exists('boss:identity');
348
+ const historySize = await client.zcard('boss:sessions:history');
349
+ const leaderboardSize = await client.zcard('boss:leaderboard:xp');
350
+
351
+ return {
352
+ connected: true,
353
+ version: redisVersion,
354
+ healthCheck,
355
+ structures: {
356
+ identity: identityExists === 1,
357
+ sessionHistory: historySize,
358
+ leaderboard: leaderboardSize
359
+ }
360
+ };
361
+
362
+ } catch (error) {
363
+ return {
364
+ connected: false,
365
+ error: error.message
366
+ };
367
+ } finally {
368
+ await client.quit();
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Print formatted initialization results
374
+ *
375
+ * @param {Object} results - Results from initializeRedis
376
+ * @param {string} username - GitHub username
377
+ */
378
+ export function printInitResults(results, username) {
379
+ console.log(chalk.cyan('\nšŸ“¦ Redis Initialization Results\n'));
380
+
381
+ // Identity
382
+ if (results.identity.created) {
383
+ console.log(chalk.green('āœ“ boss:identity') + chalk.dim(' - Created default identity'));
384
+ } else if (results.identity.existed) {
385
+ console.log(chalk.yellow('ā—‹ boss:identity') + chalk.dim(' - Already exists (preserved)'));
386
+ }
387
+
388
+ // Session History
389
+ if (results.history.created) {
390
+ console.log(chalk.green('āœ“ boss:sessions:history') + chalk.dim(' - Initialized sorted set'));
391
+ } else if (results.history.existed) {
392
+ console.log(chalk.yellow('ā—‹ boss:sessions:history') + chalk.dim(' - Already exists'));
393
+ }
394
+
395
+ // Leaderboard
396
+ if (results.leaderboard.created) {
397
+ console.log(chalk.green(`āœ“ boss:leaderboard:xp`) + chalk.dim(` - Added ${username} with 0 XP`));
398
+ } else if (results.leaderboard.existed) {
399
+ console.log(chalk.yellow('ā—‹ boss:leaderboard:xp') + chalk.dim(' - Already exists'));
400
+ }
401
+
402
+ // Achievements
403
+ if (results.achievements.created) {
404
+ console.log(chalk.green(`āœ“ boss:achievements:${username}`) + chalk.dim(' - Initialized empty set'));
405
+ } else if (results.achievements.existed) {
406
+ console.log(chalk.yellow(`ā—‹ boss:achievements:${username}`) + chalk.dim(' - Already exists'));
407
+ }
408
+
409
+ // Health Check
410
+ console.log();
411
+ if (results.healthCheck.passed) {
412
+ console.log(chalk.green('āœ“ Health Check Passed') + chalk.dim(' - All structures validated'));
413
+ } else {
414
+ console.log(chalk.red('āœ— Health Check Failed'));
415
+ Object.entries(results.healthCheck.details).forEach(([key, value]) => {
416
+ if (value !== 'OK') {
417
+ console.log(chalk.red(` āœ— ${key}: `) + chalk.dim(value));
418
+ }
419
+ });
420
+ }
421
+
422
+ console.log();
423
+ }
424
+
425
+ /**
426
+ * Integration point for setup wizard
427
+ * Called automatically after Redis connection is validated
428
+ *
429
+ * @param {string} redisUrl - Redis connection string
430
+ * @param {string} username - GitHub username
431
+ * @returns {Promise<boolean>} Success status
432
+ */
433
+ export async function setupRedisForWizard(redisUrl, username) {
434
+ try {
435
+ console.log(chalk.cyan('\nšŸ”§ Initializing Redis data structures...\n'));
436
+
437
+ const results = await initializeRedis(redisUrl, username, false);
438
+ printInitResults(results, username);
439
+
440
+ if (!results.healthCheck.passed) {
441
+ console.log(chalk.yellow('\nāš ļø Warning: Health check failed. Some features may not work correctly.'));
442
+ console.log(chalk.dim('You can try running: boss-claude redis:reset\n'));
443
+ return false;
444
+ }
445
+
446
+ console.log(chalk.green('āœ“ Redis initialization complete!\n'));
447
+ return true;
448
+
449
+ } catch (error) {
450
+ console.log(chalk.red(`\nāœ— Redis initialization failed: ${error.message}\n`));
451
+ return false;
452
+ }
453
+ }
454
+
455
+ // Export constants for use in other modules
456
+ export { DEFAULT_IDENTITY, ACHIEVEMENTS };