@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,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-MONITOR
|
|
3
|
+
*
|
|
4
|
+
* Automatic token monitoring that wraps around Boss Claude operations.
|
|
5
|
+
* Can be used as a drop-in middleware to enforce delegation rules.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import tokenMonitor from './token-monitor.js';
|
|
9
|
+
import hierarchyValidator from './hierarchy-validator.js';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Auto-monitored operation wrapper
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const result = await autoMonitor.execute('Search codebase', async () => {
|
|
17
|
+
* // Your operation here
|
|
18
|
+
* return await searchCodebase();
|
|
19
|
+
* }, { estimatedTokens: 250 });
|
|
20
|
+
*
|
|
21
|
+
* @param {string} description - Operation description
|
|
22
|
+
* @param {Function} operation - Async operation to execute
|
|
23
|
+
* @param {Object} options - Options
|
|
24
|
+
* @param {number} options.estimatedTokens - Estimated token cost
|
|
25
|
+
* @param {boolean} options.forceDelegation - Force delegation check
|
|
26
|
+
* @param {string} options.agent - Agent performing operation (default: 'Boss Claude')
|
|
27
|
+
* @returns {Promise<Object>} Result with tokens used and operation ID
|
|
28
|
+
*/
|
|
29
|
+
export async function execute(description, operation, options = {}) {
|
|
30
|
+
const {
|
|
31
|
+
estimatedTokens = 0,
|
|
32
|
+
forceDelegation = false,
|
|
33
|
+
agent = 'Boss Claude'
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
// Start monitoring
|
|
37
|
+
const opId = tokenMonitor.startOperation(description, estimatedTokens);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Check if delegation is recommended
|
|
41
|
+
if (estimatedTokens > 100 || forceDelegation) {
|
|
42
|
+
const delegation = await hierarchyValidator.checkDelegation(
|
|
43
|
+
description,
|
|
44
|
+
estimatedTokens,
|
|
45
|
+
agent
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (delegation.shouldDelegate && !delegation.override) {
|
|
49
|
+
// User chose to delegate - record it
|
|
50
|
+
tokenMonitor.recordDelegation(opId, delegation.agent);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
delegated: true,
|
|
54
|
+
agent: delegation.agent,
|
|
55
|
+
opId,
|
|
56
|
+
message: `Operation delegated to ${delegation.agent}`
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (delegation.override) {
|
|
61
|
+
// User overrode delegation - will be monitored closely
|
|
62
|
+
console.log(chalk.yellow(`⚠️ Override logged - monitoring token usage`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Execute operation
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
const result = await operation();
|
|
69
|
+
const duration = Date.now() - startTime;
|
|
70
|
+
|
|
71
|
+
// Estimate tokens used (rough heuristic: 1 token ≈ 2ms for I/O operations)
|
|
72
|
+
const estimatedActualTokens = Math.floor(duration / 2);
|
|
73
|
+
|
|
74
|
+
// Update monitor with actual tokens
|
|
75
|
+
tokenMonitor.addTokens(opId, estimatedActualTokens);
|
|
76
|
+
|
|
77
|
+
// Complete operation
|
|
78
|
+
tokenMonitor.completeOperation(opId);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
result,
|
|
83
|
+
opId,
|
|
84
|
+
tokensUsed: estimatedActualTokens,
|
|
85
|
+
duration
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// Log error and complete operation
|
|
89
|
+
console.error(chalk.red(`❌ Operation failed: ${error.message}`));
|
|
90
|
+
tokenMonitor.completeOperation(opId);
|
|
91
|
+
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Quick delegation check without execution
|
|
98
|
+
*/
|
|
99
|
+
export function shouldDelegate(estimatedTokens) {
|
|
100
|
+
return tokenMonitor.shouldDelegate(estimatedTokens);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Display session summary
|
|
105
|
+
*/
|
|
106
|
+
export function showSummary() {
|
|
107
|
+
tokenMonitor.displaySummary();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Reset session
|
|
112
|
+
*/
|
|
113
|
+
export function reset() {
|
|
114
|
+
tokenMonitor.resetSession();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get violation count for current session
|
|
119
|
+
*/
|
|
120
|
+
export function getViolations() {
|
|
121
|
+
const summary = tokenMonitor.getSessionSummary();
|
|
122
|
+
return summary.violations;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default {
|
|
126
|
+
execute,
|
|
127
|
+
shouldDelegate,
|
|
128
|
+
showSummary,
|
|
129
|
+
reset,
|
|
130
|
+
getViolations
|
|
131
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHECKPOINT AUTOMATION HOOK
|
|
3
|
+
* Automatically triggers checkpoint every 5 messages
|
|
4
|
+
* Integrated with Boss Claude session tracking
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { incrementMessage, formatCheckpointPrompt, getCheckpointStatus } from './checkpoint.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pre-message hook - call before ONYX processes each message
|
|
11
|
+
* Returns checkpoint prompt if checkpoint needed
|
|
12
|
+
*/
|
|
13
|
+
export async function preMessageHook() {
|
|
14
|
+
try {
|
|
15
|
+
const checkpoint = await incrementMessage();
|
|
16
|
+
|
|
17
|
+
if (checkpoint) {
|
|
18
|
+
// Checkpoint triggered - return prompt
|
|
19
|
+
return {
|
|
20
|
+
needsCheckpoint: true,
|
|
21
|
+
prompt: formatCheckpointPrompt(checkpoint),
|
|
22
|
+
checkpoint
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
needsCheckpoint: false
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Checkpoint hook error:', error.message);
|
|
31
|
+
return { needsCheckpoint: false };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Post-message hook - track ONYX activity
|
|
37
|
+
* Can be used to auto-detect delegation vs direct execution
|
|
38
|
+
*/
|
|
39
|
+
export async function postMessageHook(messageData) {
|
|
40
|
+
// Future: Auto-detect delegation from message patterns
|
|
41
|
+
// Look for keywords: "delegating to", "assigning to", specialist names
|
|
42
|
+
const specialists = [
|
|
43
|
+
'n8n-workflow-architect',
|
|
44
|
+
'postgres-n8n-specialist',
|
|
45
|
+
'redis-architect',
|
|
46
|
+
'github-expert',
|
|
47
|
+
'automation-architect',
|
|
48
|
+
'openai-streaming-expert',
|
|
49
|
+
'data-science-pipeline'
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const message = messageData.content?.toLowerCase() || '';
|
|
53
|
+
|
|
54
|
+
const delegationDetected = specialists.some(s =>
|
|
55
|
+
message.includes(s.toLowerCase()) ||
|
|
56
|
+
message.includes('delegate') ||
|
|
57
|
+
message.includes('assigning to')
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
delegation_detected: delegationDetected,
|
|
62
|
+
specialist: specialists.find(s => message.includes(s.toLowerCase()))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate checkpoint reminder for ONYX
|
|
68
|
+
*/
|
|
69
|
+
export function generateCheckpointReminder(messagesUntilCheckpoint) {
|
|
70
|
+
if (messagesUntilCheckpoint <= 2) {
|
|
71
|
+
return `\n⚠️ CHECKPOINT in ${messagesUntilCheckpoint} message${messagesUntilCheckpoint === 1 ? '' : 's'}\n`;
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate checkpoint response
|
|
78
|
+
*/
|
|
79
|
+
export function validateCheckpointResponse(response) {
|
|
80
|
+
const required = ['choice', 'tokens', 'justification'];
|
|
81
|
+
const missing = required.filter(field => !(field in response));
|
|
82
|
+
|
|
83
|
+
if (missing.length > 0) {
|
|
84
|
+
return {
|
|
85
|
+
valid: false,
|
|
86
|
+
error: `Missing required fields: ${missing.join(', ')}`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (![1, 2].includes(response.choice)) {
|
|
91
|
+
return {
|
|
92
|
+
valid: false,
|
|
93
|
+
error: 'Choice must be 1 (delegated) or 2 (burned)'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof response.tokens !== 'number' || response.tokens < 0) {
|
|
98
|
+
return {
|
|
99
|
+
valid: false,
|
|
100
|
+
error: 'Tokens must be a positive number'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (response.choice === 1 && !response.specialist) {
|
|
105
|
+
return {
|
|
106
|
+
valid: false,
|
|
107
|
+
error: 'Specialist required when delegation chosen'
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { valid: true };
|
|
112
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONYX CHECKPOINT SYSTEM
|
|
3
|
+
* Pauses every 5 messages to enforce delegation accountability
|
|
4
|
+
* Rule: "Did you delegate or burn tokens?"
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Redis from 'ioredis';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import { exec } from 'child_process';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
|
|
21
|
+
// Load environment variables from ~/.boss-claude/.env
|
|
22
|
+
const envPath = join(os.homedir(), '.boss-claude', '.env');
|
|
23
|
+
if (existsSync(envPath)) {
|
|
24
|
+
dotenv.config({ path: envPath });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let redis = null;
|
|
28
|
+
|
|
29
|
+
function getRedis() {
|
|
30
|
+
if (!redis) {
|
|
31
|
+
if (!process.env.REDIS_URL) {
|
|
32
|
+
throw new Error('REDIS_URL not found. Please run: boss-claude init');
|
|
33
|
+
}
|
|
34
|
+
redis = new Redis(process.env.REDIS_URL);
|
|
35
|
+
}
|
|
36
|
+
return redis;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getCurrentRepo() {
|
|
40
|
+
try {
|
|
41
|
+
const { stdout: repoPath } = await execAsync('git rev-parse --show-toplevel');
|
|
42
|
+
const { stdout: repoUrl } = await execAsync('git config --get remote.origin.url');
|
|
43
|
+
const repoName = repoUrl.trim().split('/').pop().replace('.git', '');
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name: repoName,
|
|
47
|
+
path: repoPath.trim(),
|
|
48
|
+
url: repoUrl.trim()
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize checkpoint tracking for current session
|
|
57
|
+
*/
|
|
58
|
+
export async function initCheckpoint() {
|
|
59
|
+
const repo = await getCurrentRepo();
|
|
60
|
+
if (!repo) {
|
|
61
|
+
throw new Error('Not in a git repository');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const client = getRedis();
|
|
65
|
+
const checkpointKey = `boss:checkpoint:${repo.name}:current`;
|
|
66
|
+
|
|
67
|
+
const checkpoint = {
|
|
68
|
+
repo: repo.name,
|
|
69
|
+
started_at: new Date().toISOString(),
|
|
70
|
+
message_count: 0,
|
|
71
|
+
checkpoints_passed: 0,
|
|
72
|
+
delegation_count: 0,
|
|
73
|
+
token_burn_count: 0,
|
|
74
|
+
last_checkpoint: null,
|
|
75
|
+
total_tokens_saved: 0,
|
|
76
|
+
total_tokens_burned: 0
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
await client.set(checkpointKey, JSON.stringify(checkpoint));
|
|
80
|
+
return checkpoint;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Increment message counter and check if checkpoint needed
|
|
85
|
+
* Returns checkpoint data if checkpoint triggered, null otherwise
|
|
86
|
+
*/
|
|
87
|
+
export async function incrementMessage() {
|
|
88
|
+
const repo = await getCurrentRepo();
|
|
89
|
+
if (!repo) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const client = getRedis();
|
|
94
|
+
const checkpointKey = `boss:checkpoint:${repo.name}:current`;
|
|
95
|
+
|
|
96
|
+
let checkpoint = await client.get(checkpointKey);
|
|
97
|
+
if (!checkpoint) {
|
|
98
|
+
checkpoint = await initCheckpoint();
|
|
99
|
+
} else {
|
|
100
|
+
checkpoint = JSON.parse(checkpoint);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
checkpoint.message_count++;
|
|
104
|
+
|
|
105
|
+
// Check if checkpoint needed (every 5 messages)
|
|
106
|
+
const needsCheckpoint = checkpoint.message_count % 5 === 0;
|
|
107
|
+
|
|
108
|
+
if (needsCheckpoint) {
|
|
109
|
+
checkpoint.last_checkpoint = new Date().toISOString();
|
|
110
|
+
checkpoint.awaiting_response = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await client.set(checkpointKey, JSON.stringify(checkpoint));
|
|
114
|
+
|
|
115
|
+
return needsCheckpoint ? checkpoint : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Record delegation decision at checkpoint
|
|
120
|
+
* @param {boolean} delegated - Did ONYX delegate the task?
|
|
121
|
+
* @param {number} tokensSaved - Estimated tokens saved by delegation (if delegated)
|
|
122
|
+
* @param {number} tokensBurned - Actual tokens used (if not delegated)
|
|
123
|
+
* @param {string} justification - Reason for decision
|
|
124
|
+
* @param {string} specialist - Which specialist was used (if delegated)
|
|
125
|
+
*/
|
|
126
|
+
export async function recordCheckpoint(delegated, tokensSaved = 0, tokensBurned = 0, justification = '', specialist = '') {
|
|
127
|
+
const repo = await getCurrentRepo();
|
|
128
|
+
if (!repo) {
|
|
129
|
+
throw new Error('Not in a git repository');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const client = getRedis();
|
|
133
|
+
const checkpointKey = `boss:checkpoint:${repo.name}:current`;
|
|
134
|
+
|
|
135
|
+
let checkpoint = await client.get(checkpointKey);
|
|
136
|
+
if (!checkpoint) {
|
|
137
|
+
throw new Error('No active checkpoint found');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
checkpoint = JSON.parse(checkpoint);
|
|
141
|
+
|
|
142
|
+
if (!checkpoint.awaiting_response) {
|
|
143
|
+
throw new Error('No checkpoint awaiting response');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Update checkpoint stats
|
|
147
|
+
checkpoint.checkpoints_passed++;
|
|
148
|
+
checkpoint.awaiting_response = false;
|
|
149
|
+
|
|
150
|
+
if (delegated) {
|
|
151
|
+
checkpoint.delegation_count++;
|
|
152
|
+
checkpoint.total_tokens_saved += tokensSaved;
|
|
153
|
+
} else {
|
|
154
|
+
checkpoint.token_burn_count++;
|
|
155
|
+
checkpoint.total_tokens_burned += tokensBurned;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Save checkpoint record to history
|
|
159
|
+
const historyKey = `boss:checkpoint:${repo.name}:history`;
|
|
160
|
+
const record = {
|
|
161
|
+
timestamp: checkpoint.last_checkpoint,
|
|
162
|
+
message_count: checkpoint.message_count,
|
|
163
|
+
delegated,
|
|
164
|
+
tokens_saved: tokensSaved,
|
|
165
|
+
tokens_burned: tokensBurned,
|
|
166
|
+
justification,
|
|
167
|
+
specialist
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
await client.lpush(historyKey, JSON.stringify(record));
|
|
171
|
+
await client.ltrim(historyKey, 0, 99); // Keep last 100 checkpoints
|
|
172
|
+
|
|
173
|
+
// Update current checkpoint
|
|
174
|
+
await client.set(checkpointKey, JSON.stringify(checkpoint));
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
checkpoint,
|
|
178
|
+
record
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get current checkpoint status
|
|
184
|
+
*/
|
|
185
|
+
export async function getCheckpointStatus() {
|
|
186
|
+
const repo = await getCurrentRepo();
|
|
187
|
+
if (!repo) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const client = getRedis();
|
|
192
|
+
const checkpointKey = `boss:checkpoint:${repo.name}:current`;
|
|
193
|
+
|
|
194
|
+
const checkpoint = await client.get(checkpointKey);
|
|
195
|
+
if (!checkpoint) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return JSON.parse(checkpoint);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get checkpoint history
|
|
204
|
+
* @param {number} limit - Number of records to retrieve
|
|
205
|
+
*/
|
|
206
|
+
export async function getCheckpointHistory(limit = 20) {
|
|
207
|
+
const repo = await getCurrentRepo();
|
|
208
|
+
if (!repo) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const client = getRedis();
|
|
213
|
+
const historyKey = `boss:checkpoint:${repo.name}:history`;
|
|
214
|
+
|
|
215
|
+
const records = await client.lrange(historyKey, 0, limit - 1);
|
|
216
|
+
return records.map(r => JSON.parse(r));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Calculate delegation efficiency
|
|
221
|
+
*/
|
|
222
|
+
export async function getDelegationEfficiency() {
|
|
223
|
+
const checkpoint = await getCheckpointStatus();
|
|
224
|
+
if (!checkpoint || checkpoint.checkpoints_passed === 0) {
|
|
225
|
+
return {
|
|
226
|
+
delegation_rate: 0,
|
|
227
|
+
total_checkpoints: 0,
|
|
228
|
+
tokens_saved: 0,
|
|
229
|
+
tokens_burned: 0,
|
|
230
|
+
efficiency_score: 0
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const delegation_rate = (checkpoint.delegation_count / checkpoint.checkpoints_passed) * 100;
|
|
235
|
+
const efficiency_score = checkpoint.total_tokens_saved > 0
|
|
236
|
+
? ((checkpoint.total_tokens_saved - checkpoint.total_tokens_burned) / checkpoint.total_tokens_saved) * 100
|
|
237
|
+
: 0;
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
delegation_rate: delegation_rate.toFixed(1),
|
|
241
|
+
total_checkpoints: checkpoint.checkpoints_passed,
|
|
242
|
+
delegations: checkpoint.delegation_count,
|
|
243
|
+
token_burns: checkpoint.token_burn_count,
|
|
244
|
+
tokens_saved: checkpoint.total_tokens_saved,
|
|
245
|
+
tokens_burned: checkpoint.total_tokens_burned,
|
|
246
|
+
net_savings: checkpoint.total_tokens_saved - checkpoint.total_tokens_burned,
|
|
247
|
+
efficiency_score: efficiency_score.toFixed(1)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Format checkpoint prompt for ONYX
|
|
253
|
+
*/
|
|
254
|
+
export function formatCheckpointPrompt(checkpoint) {
|
|
255
|
+
const efficiency = checkpoint.checkpoints_passed > 0
|
|
256
|
+
? ((checkpoint.delegation_count / checkpoint.checkpoints_passed) * 100).toFixed(1)
|
|
257
|
+
: 0;
|
|
258
|
+
|
|
259
|
+
return `
|
|
260
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
261
|
+
⚠️ CHECKPOINT #${checkpoint.checkpoints_passed + 1}
|
|
262
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
263
|
+
|
|
264
|
+
📊 Session Stats:
|
|
265
|
+
Messages: ${checkpoint.message_count}
|
|
266
|
+
Checkpoints Passed: ${checkpoint.checkpoints_passed}
|
|
267
|
+
Delegation Rate: ${efficiency}%
|
|
268
|
+
Tokens Saved: ${checkpoint.total_tokens_saved.toLocaleString()}
|
|
269
|
+
Tokens Burned: ${checkpoint.total_tokens_burned.toLocaleString()}
|
|
270
|
+
|
|
271
|
+
🔍 ACCOUNTABILITY QUESTION:
|
|
272
|
+
|
|
273
|
+
Did you delegate or burn tokens?
|
|
274
|
+
|
|
275
|
+
Options:
|
|
276
|
+
1. DELEGATED - I assigned work to a specialist
|
|
277
|
+
2. BURNED - I did it myself (justify why)
|
|
278
|
+
|
|
279
|
+
Respond with:
|
|
280
|
+
- Choice: [1 or 2]
|
|
281
|
+
- Tokens: [saved if delegated, burned if not]
|
|
282
|
+
- Specialist: [name if delegated]
|
|
283
|
+
- Justification: [brief reason]
|
|
284
|
+
|
|
285
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Reset checkpoint for new session
|
|
291
|
+
*/
|
|
292
|
+
export async function resetCheckpoint() {
|
|
293
|
+
const repo = await getCurrentRepo();
|
|
294
|
+
if (!repo) {
|
|
295
|
+
throw new Error('Not in a git repository');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const client = getRedis();
|
|
299
|
+
const checkpointKey = `boss:checkpoint:${repo.name}:current`;
|
|
300
|
+
await client.del(checkpointKey);
|
|
301
|
+
|
|
302
|
+
return await initCheckpoint();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Export checkpoint data for analysis
|
|
307
|
+
*/
|
|
308
|
+
export async function exportCheckpointData() {
|
|
309
|
+
const checkpoint = await getCheckpointStatus();
|
|
310
|
+
const history = await getCheckpointHistory(100);
|
|
311
|
+
const efficiency = await getDelegationEfficiency();
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
current: checkpoint,
|
|
315
|
+
history,
|
|
316
|
+
efficiency,
|
|
317
|
+
exported_at: new Date().toISOString()
|
|
318
|
+
};
|
|
319
|
+
}
|