@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,214 @@
1
+ /**
2
+ * ONYX IDENTITY MODULE
3
+ *
4
+ * Enforces orchestrator-only behavior by injecting identity into every system prompt.
5
+ * ONYX delegates to worker agents but NEVER uses worker tools directly.
6
+ */
7
+
8
+ export const ONYX_IDENTITY = {
9
+ name: 'ONYX',
10
+ tier: 'Meta-Boss',
11
+ role: 'Orchestrator',
12
+ description: 'Top-level orchestrator that delegates to specialized agents',
13
+
14
+ // Core behavioral constraints
15
+ constraints: {
16
+ canDelegateToWorkers: true,
17
+ canDelegateToBosses: true,
18
+ canUseWorkerTools: false,
19
+ canExecuteDirectly: false,
20
+ mustValidateHierarchy: true
21
+ },
22
+
23
+ // Allowed operations (orchestration only)
24
+ allowedOperations: [
25
+ 'task_analysis',
26
+ 'agent_selection',
27
+ 'delegation',
28
+ 'validation',
29
+ 'hierarchy_enforcement',
30
+ 'result_aggregation',
31
+ 'user_communication'
32
+ ],
33
+
34
+ // Forbidden operations (worker tasks)
35
+ forbiddenOperations: [
36
+ 'direct_code_execution',
37
+ 'database_queries',
38
+ 'file_system_writes',
39
+ 'api_calls',
40
+ 'workflow_creation',
41
+ 'deployment_operations'
42
+ ]
43
+ };
44
+
45
+ /**
46
+ * Generates the ONYX identity prompt that must be injected into every system context
47
+ *
48
+ * @returns {string} The ONYX identity statement
49
+ */
50
+ export function getOnyxIdentityPrompt() {
51
+ return `
52
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
53
+ 🔷 YOU ARE ONYX - META-BOSS ORCHESTRATOR
54
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
55
+
56
+ IDENTITY: ${ONYX_IDENTITY.name}
57
+ TIER: ${ONYX_IDENTITY.tier}
58
+ ROLE: ${ONYX_IDENTITY.role}
59
+
60
+ CORE DIRECTIVE: YOU ARE AN ORCHESTRATOR ONLY
61
+
62
+ ✅ YOU CAN:
63
+ ${ONYX_IDENTITY.allowedOperations.map(op => ` • ${op.replace(/_/g, ' ').toUpperCase()}`).join('\n')}
64
+
65
+ ❌ YOU CANNOT:
66
+ ${ONYX_IDENTITY.forbiddenOperations.map(op => ` • ${op.replace(/_/g, ' ').toUpperCase()}`).join('\n')}
67
+
68
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
69
+ ⚠️ CRITICAL RULES:
70
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
71
+
72
+ 1. NEVER execute worker tasks yourself
73
+ 2. ALWAYS delegate to appropriate specialist agents
74
+ 3. ALWAYS validate hierarchy before accepting worker results
75
+ 4. NEVER use tools meant for worker agents (database, files, APIs)
76
+ 5. Your role is to ANALYZE, DELEGATE, and VALIDATE only
77
+
78
+ When a user asks you to perform a task:
79
+ → Analyze the task
80
+ → Identify the specialist agent
81
+ → Delegate with clear requirements
82
+ → Validate the result through proper hierarchy
83
+ → Report back to user
84
+
85
+ You are the conductor, not the orchestra.
86
+ You are the architect, not the builder.
87
+ You are the orchestrator, not the worker.
88
+
89
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
90
+ `;
91
+ }
92
+
93
+ /**
94
+ * Validates that current operation adheres to ONYX identity constraints
95
+ *
96
+ * @param {string} operation - The operation being attempted
97
+ * @returns {Object} Validation result with allowed status and reason
98
+ */
99
+ export function validateOnyxOperation(operation) {
100
+ const normalizedOp = operation.toLowerCase().replace(/\s+/g, '_');
101
+
102
+ // Check if operation is explicitly allowed
103
+ if (ONYX_IDENTITY.allowedOperations.some(allowed => normalizedOp.includes(allowed))) {
104
+ return {
105
+ allowed: true,
106
+ reason: `Operation "${operation}" is within ONYX orchestrator scope`,
107
+ operation
108
+ };
109
+ }
110
+
111
+ // Check if operation is explicitly forbidden
112
+ if (ONYX_IDENTITY.forbiddenOperations.some(forbidden => normalizedOp.includes(forbidden))) {
113
+ return {
114
+ allowed: false,
115
+ reason: `Operation "${operation}" is a worker task - ONYX must delegate`,
116
+ operation,
117
+ suggestedAction: 'Delegate to appropriate specialist agent',
118
+ violation: true
119
+ };
120
+ }
121
+
122
+ // Unknown operation - err on the side of caution
123
+ return {
124
+ allowed: false,
125
+ reason: `Operation "${operation}" is not in ONYX's allowed operations`,
126
+ operation,
127
+ suggestedAction: 'Verify if this requires delegation',
128
+ requiresReview: true
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Generates context-refresh reminder that ONYX is orchestrator only
134
+ * Used in long conversations or after context resets
135
+ *
136
+ * @returns {string} Compact identity reminder
137
+ */
138
+ export function getOnyxContextReminder() {
139
+ return `
140
+ ⚡ ONYX IDENTITY REMINDER ⚡
141
+ You are ONYX (Meta-Boss Orchestrator). You DELEGATE tasks to specialists.
142
+ You DO NOT execute worker tasks yourself. Analyze → Delegate → Validate.
143
+ `;
144
+ }
145
+
146
+ /**
147
+ * Checks if a tool invocation violates ONYX orchestrator constraints
148
+ *
149
+ * @param {string} toolName - The tool being invoked
150
+ * @param {Object} toolParams - Tool parameters
151
+ * @returns {Object} Violation check result
152
+ */
153
+ export function checkToolViolation(toolName, toolParams = {}) {
154
+ // Define tools that are ONLY for workers
155
+ const workerOnlyTools = [
156
+ 'executeSQL',
157
+ 'writeFile',
158
+ 'createWorkflow',
159
+ 'deployCode',
160
+ 'callAPI',
161
+ 'modifySchema',
162
+ 'commitCode',
163
+ 'runTests',
164
+ 'startServer'
165
+ ];
166
+
167
+ // Define tools that are OK for orchestrators
168
+ const orchestratorTools = [
169
+ 'analyzeTask',
170
+ 'selectAgent',
171
+ 'delegateTask',
172
+ 'validateResult',
173
+ 'hierarchyCheck',
174
+ 'getStatus',
175
+ 'reportToUser'
176
+ ];
177
+
178
+ const isWorkerTool = workerOnlyTools.some(tool =>
179
+ toolName.toLowerCase().includes(tool.toLowerCase())
180
+ );
181
+
182
+ const isOrchestratorTool = orchestratorTools.some(tool =>
183
+ toolName.toLowerCase().includes(tool.toLowerCase())
184
+ );
185
+
186
+ if (isWorkerTool) {
187
+ return {
188
+ violation: true,
189
+ severity: 'critical',
190
+ message: `ONYX VIOLATION: Tool "${toolName}" is for worker agents only`,
191
+ action: 'DELEGATE this task instead of executing directly',
192
+ toolName
193
+ };
194
+ }
195
+
196
+ if (isOrchestratorTool) {
197
+ return {
198
+ violation: false,
199
+ message: `Tool "${toolName}" is approved for orchestrator use`,
200
+ toolName
201
+ };
202
+ }
203
+
204
+ // Unknown tool - flag for review
205
+ return {
206
+ violation: false,
207
+ warning: true,
208
+ message: `Tool "${toolName}" not recognized - verify it's appropriate for orchestrator`,
209
+ action: 'Review if this should be delegated',
210
+ toolName
211
+ };
212
+ }
213
+
214
+ export default ONYX_IDENTITY;
@@ -0,0 +1,381 @@
1
+ import Redis from 'ioredis';
2
+ import dotenv from 'dotenv';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { existsSync } from 'fs';
6
+ import os from 'os';
7
+ import { appendFileSync, mkdirSync } from 'fs';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ // Load environment variables from ~/.boss-claude/.env
13
+ const envPath = join(os.homedir(), '.boss-claude', '.env');
14
+ if (existsSync(envPath)) {
15
+ dotenv.config({ path: envPath });
16
+ }
17
+
18
+ const BOSS_DIR = join(os.homedir(), '.boss-claude');
19
+ const ALERT_LOG = join(BOSS_DIR, 'onyx-alerts.log');
20
+
21
+ // Ensure directory exists
22
+ try {
23
+ mkdirSync(BOSS_DIR, { recursive: true });
24
+ } catch (err) {
25
+ // Directory already exists
26
+ }
27
+
28
+ let redis = null;
29
+
30
+ function getRedis() {
31
+ if (!redis) {
32
+ if (!process.env.REDIS_URL) {
33
+ throw new Error('REDIS_URL not found. Please run: boss-claude init');
34
+ }
35
+ redis = new Redis(process.env.REDIS_URL);
36
+ }
37
+ return redis;
38
+ }
39
+
40
+ // Redis keys
41
+ const DELEGATION_KEY = 'boss:onyx:delegation';
42
+ const DIRECT_ACTION_KEY = 'boss:onyx:direct_actions';
43
+ const ALERT_THRESHOLD_KEY = 'boss:onyx:alert_threshold';
44
+ const LAST_ALERT_KEY = 'boss:onyx:last_alert';
45
+
46
+ // Default alert threshold: 95%
47
+ const DEFAULT_THRESHOLD = 0.95;
48
+
49
+ /**
50
+ * Log an alert to file
51
+ * @param {string} message - Alert message
52
+ * @param {object} data - Additional data to log
53
+ */
54
+ function logAlert(message, data = {}) {
55
+ const timestamp = new Date().toISOString();
56
+ const logLine = `[${timestamp}] ALERT: ${message}\n${JSON.stringify(data, null, 2)}\n\n`;
57
+
58
+ try {
59
+ appendFileSync(ALERT_LOG, logLine, 'utf8');
60
+ } catch (err) {
61
+ console.error(`Failed to write alert log: ${err.message}`);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Track a task delegated to ONYX agent
67
+ * @param {string} agentName - Name of the ONYX agent
68
+ * @param {string} taskDescription - Description of the task
69
+ * @param {object} metadata - Additional metadata
70
+ * @returns {Promise<object>} Updated statistics
71
+ */
72
+ export async function trackDelegation(agentName, taskDescription, metadata = {}) {
73
+ const client = getRedis();
74
+ const timestamp = new Date().toISOString();
75
+
76
+ // Increment delegation counter
77
+ await client.hincrby(DELEGATION_KEY, 'total', 1);
78
+ await client.hincrby(DELEGATION_KEY, `agent:${agentName}`, 1);
79
+
80
+ // Store delegation event
81
+ const event = {
82
+ type: 'delegation',
83
+ agent: agentName,
84
+ task: taskDescription,
85
+ timestamp,
86
+ metadata
87
+ };
88
+
89
+ await client.lpush('boss:onyx:events', JSON.stringify(event));
90
+ await client.ltrim('boss:onyx:events', 0, 999); // Keep last 1000 events
91
+
92
+ // Check delegation ratio and alert if needed
93
+ const stats = await getDelegationStats();
94
+ await checkThresholdAndAlert(stats);
95
+
96
+ return stats;
97
+ }
98
+
99
+ /**
100
+ * Track a direct action taken by Boss Claude (not delegated)
101
+ * @param {string} actionType - Type of direct action
102
+ * @param {string} description - Description of the action
103
+ * @param {object} metadata - Additional metadata
104
+ * @returns {Promise<object>} Updated statistics
105
+ */
106
+ export async function trackDirectAction(actionType, description, metadata = {}) {
107
+ const client = getRedis();
108
+ const timestamp = new Date().toISOString();
109
+
110
+ // Increment direct action counter
111
+ await client.hincrby(DIRECT_ACTION_KEY, 'total', 1);
112
+ await client.hincrby(DIRECT_ACTION_KEY, `type:${actionType}`, 1);
113
+
114
+ // Store direct action event
115
+ const event = {
116
+ type: 'direct_action',
117
+ action_type: actionType,
118
+ description,
119
+ timestamp,
120
+ metadata
121
+ };
122
+
123
+ await client.lpush('boss:onyx:events', JSON.stringify(event));
124
+ await client.ltrim('boss:onyx:events', 0, 999);
125
+
126
+ // Check delegation ratio and alert if needed
127
+ const stats = await getDelegationStats();
128
+ await checkThresholdAndAlert(stats);
129
+
130
+ return stats;
131
+ }
132
+
133
+ /**
134
+ * Get current delegation statistics
135
+ * @returns {Promise<object>} Delegation stats including ratio
136
+ */
137
+ export async function getDelegationStats() {
138
+ const client = getRedis();
139
+
140
+ const delegationData = await client.hgetall(DELEGATION_KEY);
141
+ const directActionData = await client.hgetall(DIRECT_ACTION_KEY);
142
+
143
+ const totalDelegations = parseInt(delegationData.total || 0);
144
+ const totalDirectActions = parseInt(directActionData.total || 0);
145
+ const totalActions = totalDelegations + totalDirectActions;
146
+
147
+ const delegationRatio = totalActions > 0 ? totalDelegations / totalActions : 0;
148
+
149
+ // Get agent breakdown
150
+ const agentStats = {};
151
+ for (const [key, value] of Object.entries(delegationData)) {
152
+ if (key.startsWith('agent:')) {
153
+ const agentName = key.replace('agent:', '');
154
+ agentStats[agentName] = parseInt(value);
155
+ }
156
+ }
157
+
158
+ // Get action type breakdown
159
+ const actionTypeStats = {};
160
+ for (const [key, value] of Object.entries(directActionData)) {
161
+ if (key.startsWith('type:')) {
162
+ const actionType = key.replace('type:', '');
163
+ actionTypeStats[actionType] = parseInt(value);
164
+ }
165
+ }
166
+
167
+ const threshold = parseFloat(await client.get(ALERT_THRESHOLD_KEY) || DEFAULT_THRESHOLD);
168
+
169
+ return {
170
+ total_delegations: totalDelegations,
171
+ total_direct_actions: totalDirectActions,
172
+ total_actions: totalActions,
173
+ delegation_ratio: delegationRatio,
174
+ delegation_percentage: (delegationRatio * 100).toFixed(2),
175
+ threshold: threshold,
176
+ threshold_percentage: (threshold * 100).toFixed(0),
177
+ meets_threshold: delegationRatio >= threshold,
178
+ agent_breakdown: agentStats,
179
+ direct_action_breakdown: actionTypeStats
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Get recent events (delegations and direct actions)
185
+ * @param {number} limit - Number of events to retrieve (default: 50)
186
+ * @returns {Promise<Array>} Recent events
187
+ */
188
+ export async function getRecentEvents(limit = 50) {
189
+ const client = getRedis();
190
+ const events = await client.lrange('boss:onyx:events', 0, limit - 1);
191
+
192
+ return events.map(e => JSON.parse(e));
193
+ }
194
+
195
+ /**
196
+ * Check if delegation ratio meets threshold and alert if not
197
+ * @param {object} stats - Current delegation statistics
198
+ * @returns {Promise<boolean>} True if alert was triggered
199
+ */
200
+ async function checkThresholdAndAlert(stats) {
201
+ const client = getRedis();
202
+
203
+ // Don't alert if we don't have enough data
204
+ if (stats.total_actions < 10) {
205
+ return false;
206
+ }
207
+
208
+ if (!stats.meets_threshold) {
209
+ // Check if we've alerted recently (throttle to once per hour)
210
+ const lastAlert = await client.get(LAST_ALERT_KEY);
211
+ const now = Date.now();
212
+
213
+ if (lastAlert) {
214
+ const lastAlertTime = parseInt(lastAlert);
215
+ const hoursSinceLastAlert = (now - lastAlertTime) / (1000 * 60 * 60);
216
+
217
+ if (hoursSinceLastAlert < 1) {
218
+ return false; // Skip alert, too soon
219
+ }
220
+ }
221
+
222
+ // Trigger alert
223
+ const alertMessage = `ONYX delegation ratio dropped below ${stats.threshold_percentage}%`;
224
+ const alertData = {
225
+ current_ratio: stats.delegation_percentage + '%',
226
+ threshold: stats.threshold_percentage + '%',
227
+ total_actions: stats.total_actions,
228
+ delegations: stats.total_delegations,
229
+ direct_actions: stats.total_direct_actions,
230
+ agent_breakdown: stats.agent_breakdown,
231
+ direct_action_breakdown: stats.direct_action_breakdown
232
+ };
233
+
234
+ logAlert(alertMessage, alertData);
235
+ console.warn(`\n⚠️ ${alertMessage}`);
236
+ console.warn(`Current ratio: ${stats.delegation_percentage}% (${stats.total_delegations}/${stats.total_actions})`);
237
+ console.warn(`View details: boss-claude onyx-status\n`);
238
+
239
+ // Update last alert timestamp
240
+ await client.set(LAST_ALERT_KEY, now.toString());
241
+
242
+ return true;
243
+ }
244
+
245
+ return false;
246
+ }
247
+
248
+ /**
249
+ * Set the alert threshold (0.0 to 1.0)
250
+ * @param {number} threshold - New threshold value (e.g., 0.95 for 95%)
251
+ * @returns {Promise<number>} The new threshold
252
+ */
253
+ export async function setAlertThreshold(threshold) {
254
+ if (threshold < 0 || threshold > 1) {
255
+ throw new Error('Threshold must be between 0 and 1');
256
+ }
257
+
258
+ const client = getRedis();
259
+ await client.set(ALERT_THRESHOLD_KEY, threshold.toString());
260
+
261
+ return threshold;
262
+ }
263
+
264
+ /**
265
+ * Get the current alert threshold
266
+ * @returns {Promise<number>} Current threshold
267
+ */
268
+ export async function getAlertThreshold() {
269
+ const client = getRedis();
270
+ return parseFloat(await client.get(ALERT_THRESHOLD_KEY) || DEFAULT_THRESHOLD);
271
+ }
272
+
273
+ /**
274
+ * Reset all delegation tracking data
275
+ * @returns {Promise<void>}
276
+ */
277
+ export async function resetTracking() {
278
+ const client = getRedis();
279
+
280
+ await client.del(DELEGATION_KEY);
281
+ await client.del(DIRECT_ACTION_KEY);
282
+ await client.del('boss:onyx:events');
283
+ await client.del(LAST_ALERT_KEY);
284
+
285
+ console.log('ONYX delegation tracking has been reset');
286
+ }
287
+
288
+ /**
289
+ * Generate a comprehensive delegation report
290
+ * @param {object} options - Report options
291
+ * @returns {Promise<object>} Detailed report
292
+ */
293
+ export async function generateReport(options = {}) {
294
+ const stats = await getDelegationStats();
295
+ const recentEvents = await getRecentEvents(options.eventLimit || 50);
296
+
297
+ // Calculate time-based metrics
298
+ const now = new Date();
299
+ const oneDayAgo = new Date(now - 24 * 60 * 60 * 1000);
300
+ const oneWeekAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);
301
+
302
+ const last24h = recentEvents.filter(e => new Date(e.timestamp) > oneDayAgo);
303
+ const last7d = recentEvents.filter(e => new Date(e.timestamp) > oneWeekAgo);
304
+
305
+ const delegations24h = last24h.filter(e => e.type === 'delegation').length;
306
+ const directActions24h = last24h.filter(e => e.type === 'direct_action').length;
307
+ const total24h = delegations24h + directActions24h;
308
+ const ratio24h = total24h > 0 ? delegations24h / total24h : 0;
309
+
310
+ const delegations7d = last7d.filter(e => e.type === 'delegation').length;
311
+ const directActions7d = last7d.filter(e => e.type === 'direct_action').length;
312
+ const total7d = delegations7d + directActions7d;
313
+ const ratio7d = total7d > 0 ? delegations7d / total7d : 0;
314
+
315
+ return {
316
+ overall: stats,
317
+ time_periods: {
318
+ last_24h: {
319
+ delegations: delegations24h,
320
+ direct_actions: directActions24h,
321
+ total: total24h,
322
+ ratio: ratio24h,
323
+ percentage: (ratio24h * 100).toFixed(2)
324
+ },
325
+ last_7d: {
326
+ delegations: delegations7d,
327
+ direct_actions: directActions7d,
328
+ total: total7d,
329
+ ratio: ratio7d,
330
+ percentage: (ratio7d * 100).toFixed(2)
331
+ }
332
+ },
333
+ recent_events: recentEvents.slice(0, 10),
334
+ generated_at: now.toISOString()
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Get formatted status for display
340
+ * @returns {Promise<string>} Formatted status string
341
+ */
342
+ export async function getFormattedStatus() {
343
+ const report = await generateReport();
344
+ const stats = report.overall;
345
+
346
+ const statusIcon = stats.meets_threshold ? '✅' : '⚠️';
347
+
348
+ return `
349
+ ${statusIcon} ONYX DELEGATION MONITOR
350
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
351
+
352
+ OVERALL STATISTICS:
353
+ Delegation Ratio: ${stats.delegation_percentage}% (${stats.total_delegations}/${stats.total_actions})
354
+ Alert Threshold: ${stats.threshold_percentage}%
355
+ Status: ${stats.meets_threshold ? 'PASSING ✅' : 'BELOW THRESHOLD ⚠️'}
356
+
357
+ RECENT ACTIVITY (Last 24h):
358
+ Delegations: ${report.time_periods.last_24h.delegations}
359
+ Direct Actions: ${report.time_periods.last_24h.direct_actions}
360
+ Ratio: ${report.time_periods.last_24h.percentage}%
361
+
362
+ AGENT BREAKDOWN:
363
+ ${Object.entries(stats.agent_breakdown)
364
+ .sort((a, b) => b[1] - a[1])
365
+ .map(([agent, count]) => ` ${agent}: ${count} tasks`)
366
+ .join('\n') || ' No delegations yet'}
367
+
368
+ DIRECT ACTION BREAKDOWN:
369
+ ${Object.entries(stats.direct_action_breakdown)
370
+ .sort((a, b) => b[1] - a[1])
371
+ .map(([type, count]) => ` ${type}: ${count} actions`)
372
+ .join('\n') || ' No direct actions yet'}
373
+
374
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
375
+
376
+ Alert Log: ${ALERT_LOG}
377
+ Run 'boss-claude onyx-report' for detailed analysis
378
+ `;
379
+ }
380
+
381
+ export { ALERT_LOG };