@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,213 @@
1
+ import { watch } from 'fs';
2
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
3
+ import { join } from 'path';
4
+ import os from 'os';
5
+ import { logAgent } from './agent-logger.js';
6
+ import { getEfficiencyStats } from './session.js';
7
+
8
+ const TASK_DIR = '/private/tmp/claude/-Users-craigpretzinger-projects-BOSS-claude/tasks';
9
+ const WATCHED_FILES = new Map();
10
+ const MAX_CONTEXT = 200000; // Claude's context window size
11
+
12
+ function parseActivityLine(line) {
13
+ try {
14
+ const json = JSON.parse(line);
15
+
16
+ if (json.type === 'user' && json.message?.role === 'user') {
17
+ const content = json.message.content;
18
+ if (typeof content === 'string' && content.length > 0) {
19
+ // NO word wrap here - wrapping happens at display time in watch command
20
+ // Storing multi-line text in log breaks the log format
21
+ return `Task: ${content}`;
22
+ }
23
+ }
24
+
25
+ if (json.type === 'assistant' && json.message?.role === 'assistant') {
26
+ const content = json.message.content;
27
+ if (Array.isArray(content)) {
28
+ for (const item of content) {
29
+ // Handle text responses (agent output)
30
+ if (item.type === 'text' && item.text) {
31
+ // NO word wrap here - wrapping happens at display time in watch command
32
+ // Storing multi-line text in log breaks the log format
33
+ return `Response: ${item.text}`;
34
+ }
35
+ if (item.type === 'tool_use') {
36
+ const toolName = item.name;
37
+ const input = item.input;
38
+ if (toolName === 'Read' && input?.file_path) {
39
+ return `Reading: ${input.file_path.split('/').pop()}`;
40
+ }
41
+ if (toolName === 'Bash' && input?.description) {
42
+ return `Exec: ${input.description}`;
43
+ }
44
+ if (toolName === 'Write' && input?.file_path) {
45
+ return `Writing: ${input.file_path.split('/').pop()}`;
46
+ }
47
+ if (toolName === 'Edit' && input?.file_path) {
48
+ return `Editing: ${input.file_path.split('/').pop()}`;
49
+ }
50
+ return `Using ${toolName}`;
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return null;
56
+ } catch (err) {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function checkFileForActivity(filePath) {
62
+ try {
63
+ if (!existsSync(filePath)) return null;
64
+
65
+ const currentSize = readFileSync(filePath).length;
66
+ const lastSize = WATCHED_FILES.get(filePath) || 0;
67
+
68
+ if (currentSize <= lastSize) {
69
+ WATCHED_FILES.set(filePath, currentSize);
70
+ return null;
71
+ }
72
+
73
+ const content = readFileSync(filePath, 'utf8');
74
+ const lines = content.split('\n').filter(l => l.trim());
75
+
76
+ for (let i = lines.length - 1; i >= 0; i--) {
77
+ const activity = parseActivityLine(lines[i]);
78
+ if (activity) {
79
+ WATCHED_FILES.set(filePath, currentSize);
80
+ return activity;
81
+ }
82
+ }
83
+
84
+ WATCHED_FILES.set(filePath, currentSize);
85
+ return null;
86
+ } catch (err) {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ function getTokenContextStats() {
92
+ try {
93
+ const files = require('fs').readdirSync(TASK_DIR);
94
+ const outputFiles = files.filter(f => f.endsWith('.output'));
95
+
96
+ if (outputFiles.length === 0) return null;
97
+
98
+ // Get the most recent output file
99
+ const latestFile = outputFiles
100
+ .map(f => ({ name: f, path: join(TASK_DIR, f) }))
101
+ .sort((a, b) => {
102
+ try {
103
+ const statA = require('fs').statSync(a.path);
104
+ const statB = require('fs').statSync(b.path);
105
+ return statB.mtimeMs - statA.mtimeMs;
106
+ } catch {
107
+ return 0;
108
+ }
109
+ })[0];
110
+
111
+ if (!latestFile) return null;
112
+
113
+ const content = readFileSync(latestFile.path, 'utf8');
114
+ const lines = content.split('\n').filter(l => l.trim());
115
+
116
+ // Look for token budget in system warnings
117
+ for (let i = lines.length - 1; i >= 0; i--) {
118
+ try {
119
+ const json = JSON.parse(lines[i]);
120
+ if (json.type === 'system_warning' && json.message) {
121
+ const match = json.message.match(/Token usage: (\d+)\/(\d+)/);
122
+ if (match) {
123
+ const used = parseInt(match[1]);
124
+ const total = parseInt(match[2]);
125
+ const percent = ((used / total) * 100).toFixed(1);
126
+ return { used, total, percent };
127
+ }
128
+ }
129
+ } catch {
130
+ continue;
131
+ }
132
+ }
133
+
134
+ return null;
135
+ } catch (err) {
136
+ return null;
137
+ }
138
+ }
139
+
140
+ export function startCommentator() {
141
+ console.log('🎙️ Commentator active - watching agents...\n');
142
+
143
+ if (!existsSync(TASK_DIR)) {
144
+ console.error(`Task directory not found: ${TASK_DIR}`);
145
+ return;
146
+ }
147
+
148
+ logAgent('Commentator', 'ACTIVE', 'Monitoring agent output files');
149
+
150
+ const watcher = watch(TASK_DIR, { recursive: false }, (eventType, filename) => {
151
+ if (filename && filename.endsWith('.output')) {
152
+ const filePath = join(TASK_DIR, filename);
153
+ const activity = checkFileForActivity(filePath);
154
+ if (activity) {
155
+ logAgent('Commentator', 'OBSERVE', activity);
156
+ console.log(`📢 ${activity}`);
157
+ }
158
+ }
159
+ });
160
+
161
+ // Poll efficiency stats every 30 seconds
162
+ const pollInterval = setInterval(async () => {
163
+ // Get context stats from task files
164
+ const contextStats = getTokenContextStats();
165
+
166
+ // Get efficiency stats from session
167
+ let effStats = null;
168
+ try {
169
+ effStats = await getEfficiencyStats();
170
+ } catch (e) {
171
+ // Redis might not be available
172
+ }
173
+
174
+ console.log('\n' + '─'.repeat(60));
175
+ console.log('📊 CONDUCTOR DASHBOARD');
176
+ console.log('─'.repeat(60));
177
+
178
+ if (contextStats) {
179
+ const pct = parseFloat(contextStats.percent);
180
+ const color = pct < 50 ? '\x1b[32m' : pct < 80 ? '\x1b[33m' : '\x1b[31m';
181
+ console.log(` Context: ${color}${contextStats.used.toLocaleString()}/${contextStats.total.toLocaleString()} (${contextStats.percent}%)\x1b[0m`);
182
+ }
183
+
184
+ if (effStats) {
185
+ const totalTokens = effStats.total_tokens || 0;
186
+ const onyxPct = totalTokens > 0 ? ((effStats.onyx_tokens / totalTokens) * 100).toFixed(1) : '0.0';
187
+ const agentPct = totalTokens > 0 ? ((effStats.agent_tokens / totalTokens) * 100).toFixed(1) : '0.0';
188
+
189
+ console.log(` 🎺 ONYX: ${effStats.onyx_tokens.toLocaleString()} tokens (${onyxPct}%)`);
190
+ console.log(` 🎻 Agents: ${effStats.agent_tokens.toLocaleString()} tokens (${agentPct}%)`);
191
+ console.log(` 📈 Efficiency: ${effStats.efficiency_ratio}`);
192
+ console.log(` 🎯 Delegations: ${effStats.delegations}`);
193
+ console.log(` 💎 Projected XP: +${effStats.projected_bonus_xp + effStats.delegation_bonus_xp}`);
194
+ } else {
195
+ console.log(' (Run boss-claude status to initialize tracking)');
196
+ }
197
+
198
+ console.log('─'.repeat(60) + '\n');
199
+
200
+ // Log to agent log
201
+ if (effStats) {
202
+ logAgent('Commentator', 'STATS', `Efficiency: ${effStats.efficiency_ratio}, Delegations: ${effStats.delegations}`);
203
+ }
204
+ }, 30000); // 30 seconds
205
+
206
+ process.on('SIGINT', () => {
207
+ clearInterval(pollInterval);
208
+ watcher.close();
209
+ logAgent('Commentator', 'SHUTDOWN', 'Stopped');
210
+ console.log('\n👋 Commentator stopped');
211
+ process.exit(0);
212
+ });
213
+ }
@@ -0,0 +1,120 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import os from 'os';
4
+
5
+ const BOSS_DIR = join(os.homedir(), '.boss-claude');
6
+ const SCRIBE_FILE = join(BOSS_DIR, 'current-session.json');
7
+
8
+ // Ensure directory exists
9
+ try { mkdirSync(BOSS_DIR, { recursive: true }); } catch (e) {}
10
+
11
+ let scribe = null;
12
+
13
+ export function initScribe() {
14
+ scribe = {
15
+ startTime: Date.now(),
16
+ decisions: [],
17
+ filesChanged: [],
18
+ lessons: [],
19
+ issuesResolved: [],
20
+ bugsFound: []
21
+ };
22
+ saveScribe();
23
+ return scribe;
24
+ }
25
+
26
+ export function getScribe() {
27
+ if (!scribe && existsSync(SCRIBE_FILE)) {
28
+ scribe = JSON.parse(readFileSync(SCRIBE_FILE, 'utf8'));
29
+ }
30
+ return scribe || initScribe();
31
+ }
32
+
33
+ function saveScribe() {
34
+ if (scribe) {
35
+ writeFileSync(SCRIBE_FILE, JSON.stringify(scribe, null, 2));
36
+ }
37
+ }
38
+
39
+ export function logDecision(decision, reasoning = '') {
40
+ getScribe();
41
+ scribe.decisions.push({ decision, reasoning, timestamp: Date.now() });
42
+ saveScribe();
43
+ }
44
+
45
+ export function logFileChange(filePath, changeType, description = '') {
46
+ getScribe();
47
+ scribe.filesChanged.push({ filePath, changeType, description, timestamp: Date.now() });
48
+ saveScribe();
49
+ }
50
+
51
+ export function logLesson(lesson, category = 'general') {
52
+ getScribe();
53
+ scribe.lessons.push({ lesson, category, timestamp: Date.now() });
54
+ saveScribe();
55
+ }
56
+
57
+ export function logIssueResolved(issue, solution) {
58
+ getScribe();
59
+ scribe.issuesResolved.push({ issue, solution, timestamp: Date.now() });
60
+ saveScribe();
61
+ }
62
+
63
+ export function logBugFound(bug, location, impact = 'medium') {
64
+ getScribe();
65
+ scribe.bugsFound.push({ bug, location, impact, timestamp: Date.now() });
66
+ saveScribe();
67
+ }
68
+
69
+ export function generateSessionSummary() {
70
+ const s = getScribe();
71
+ const duration = Math.round((Date.now() - s.startTime) / 60000);
72
+
73
+ let summary = `## Session Overview\nDuration: ${duration} minutes\n\n`;
74
+
75
+ if (s.decisions.length) {
76
+ summary += `### Decisions (${s.decisions.length})\n`;
77
+ s.decisions.forEach(d => summary += `- ${d.decision}${d.reasoning ? ` (${d.reasoning})` : ''}\n`);
78
+ summary += '\n';
79
+ }
80
+
81
+ if (s.filesChanged.length) {
82
+ summary += `### Files Changed (${s.filesChanged.length})\n`;
83
+ s.filesChanged.forEach(f => summary += `- [${f.changeType}] ${f.filePath}${f.description ? `: ${f.description}` : ''}\n`);
84
+ summary += '\n';
85
+ }
86
+
87
+ if (s.issuesResolved.length) {
88
+ summary += `### Issues Resolved (${s.issuesResolved.length})\n`;
89
+ s.issuesResolved.forEach(i => summary += `- ${i.issue} -> ${i.solution}\n`);
90
+ summary += '\n';
91
+ }
92
+
93
+ if (s.lessons.length) {
94
+ summary += `### Lessons Learned (${s.lessons.length})\n`;
95
+ s.lessons.forEach(l => summary += `- [${l.category}] ${l.lesson}\n`);
96
+ summary += '\n';
97
+ }
98
+
99
+ return {
100
+ summary,
101
+ stats: {
102
+ duration,
103
+ decisions: s.decisions.length,
104
+ filesChanged: s.filesChanged.length,
105
+ issuesResolved: s.issuesResolved.length,
106
+ lessons: s.lessons.length
107
+ }
108
+ };
109
+ }
110
+
111
+ export function clearScribe() {
112
+ scribe = null;
113
+ if (existsSync(SCRIBE_FILE)) {
114
+ writeFileSync(SCRIBE_FILE, '{}');
115
+ }
116
+ }
117
+
118
+ export function exportScribe() {
119
+ return JSON.stringify(getScribe(), null, 2);
120
+ }
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DELEGATION STRATEGIES
4
+ *
5
+ * Defines auto-delegation strategies for different tool types.
6
+ * Determines when to delegate vs. when to allow direct execution.
7
+ */
8
+
9
+ /**
10
+ * Tool categories for delegation
11
+ */
12
+ export const TOOL_CATEGORIES = {
13
+ // Always delegate to Task agent
14
+ FILE_OPS: ['Read', 'Write', 'Edit', 'NotebookEdit'],
15
+ SEARCH_OPS: ['Grep', 'Glob'],
16
+ EXEC_OPS: ['Bash'],
17
+
18
+ // Sometimes delegate based on complexity
19
+ CONDITIONAL: ['WebFetch', 'WebSearch'],
20
+
21
+ // Never delegate - ONYX can use directly
22
+ ALLOWED: ['Task', 'TodoWrite', 'Skill']
23
+ };
24
+
25
+ /**
26
+ * Complexity thresholds for conditional delegation
27
+ */
28
+ const COMPLEXITY_THRESHOLDS = {
29
+ // File operations
30
+ file_read_lines: 500, // Auto-delegate reads >500 lines
31
+ file_write_lines: 100, // Auto-delegate writes >100 lines
32
+
33
+ // Search operations
34
+ grep_results: 50, // Auto-delegate if expected >50 results
35
+ glob_patterns: 10, // Auto-delegate complex glob patterns
36
+
37
+ // Bash operations
38
+ bash_command_length: 100, // Auto-delegate long commands
39
+ bash_piped_commands: 2 // Auto-delegate if >2 pipes
40
+ };
41
+
42
+ /**
43
+ * Determine if tool should be delegated
44
+ *
45
+ * @param {string} toolName - Name of tool
46
+ * @param {Object} toolParams - Tool parameters
47
+ * @param {Object} context - Execution context
48
+ * @returns {Object} { shouldDelegate: boolean, reason: string, strategy: string }
49
+ */
50
+ export function shouldDelegateTool(toolName, toolParams = {}, context = {}) {
51
+ const agentName = context.agentName || 'Unknown';
52
+
53
+ // ONYX agents ALWAYS delegate file/search/exec operations
54
+ const isOnyxAgent = agentName.toUpperCase().includes('ONYX') ||
55
+ agentName === 'BOSS' ||
56
+ agentName === 'META-BOSS';
57
+
58
+ // Check if tool is in always-delegate categories
59
+ const isFileOp = TOOL_CATEGORIES.FILE_OPS.includes(toolName);
60
+ const isSearchOp = TOOL_CATEGORIES.SEARCH_OPS.includes(toolName);
61
+ const isExecOp = TOOL_CATEGORIES.EXEC_OPS.includes(toolName);
62
+ const isAllowed = TOOL_CATEGORIES.ALLOWED.includes(toolName);
63
+
64
+ // Allowed tools - never delegate
65
+ if (isAllowed) {
66
+ return {
67
+ shouldDelegate: false,
68
+ reason: `${toolName} is allowed for ${agentName}`,
69
+ strategy: 'ALLOWED'
70
+ };
71
+ }
72
+
73
+ // ONYX agents - auto-delegate file/search/exec
74
+ if (isOnyxAgent) {
75
+ if (isFileOp || isSearchOp || isExecOp) {
76
+ return {
77
+ shouldDelegate: true,
78
+ reason: `${agentName} must delegate ${toolName} to Task agent`,
79
+ strategy: 'ONYX_AUTO_DELEGATE',
80
+ delegateTo: 'Task'
81
+ };
82
+ }
83
+ }
84
+
85
+ // Non-ONYX agents - check complexity thresholds
86
+ if (isFileOp) {
87
+ const complexity = analyzeFileOpComplexity(toolName, toolParams);
88
+ if (complexity.shouldDelegate) {
89
+ return {
90
+ shouldDelegate: true,
91
+ reason: `File operation too complex: ${complexity.reason}`,
92
+ strategy: 'COMPLEXITY_THRESHOLD',
93
+ delegateTo: 'Task',
94
+ complexity
95
+ };
96
+ }
97
+ }
98
+
99
+ if (isSearchOp) {
100
+ const complexity = analyzeSearchOpComplexity(toolName, toolParams);
101
+ if (complexity.shouldDelegate) {
102
+ return {
103
+ shouldDelegate: true,
104
+ reason: `Search operation too complex: ${complexity.reason}`,
105
+ strategy: 'COMPLEXITY_THRESHOLD',
106
+ delegateTo: 'Task',
107
+ complexity
108
+ };
109
+ }
110
+ }
111
+
112
+ if (isExecOp) {
113
+ const complexity = analyzeExecOpComplexity(toolName, toolParams);
114
+ if (complexity.shouldDelegate) {
115
+ return {
116
+ shouldDelegate: true,
117
+ reason: `Bash operation too complex: ${complexity.reason}`,
118
+ strategy: 'COMPLEXITY_THRESHOLD',
119
+ delegateTo: 'Task',
120
+ complexity
121
+ };
122
+ }
123
+ }
124
+
125
+ // Default: allow execution
126
+ return {
127
+ shouldDelegate: false,
128
+ reason: 'Below complexity threshold',
129
+ strategy: 'ALLOW_DIRECT'
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Analyze file operation complexity
135
+ */
136
+ function analyzeFileOpComplexity(toolName, params) {
137
+ const result = {
138
+ shouldDelegate: false,
139
+ reason: '',
140
+ score: 0
141
+ };
142
+
143
+ if (toolName === 'Read') {
144
+ // Check if file is large (via limit param)
145
+ if (params.limit && params.limit > COMPLEXITY_THRESHOLDS.file_read_lines) {
146
+ result.shouldDelegate = true;
147
+ result.reason = `Reading ${params.limit} lines (threshold: ${COMPLEXITY_THRESHOLDS.file_read_lines})`;
148
+ result.score = 2;
149
+ }
150
+ }
151
+
152
+ if (toolName === 'Write') {
153
+ // Check content length (both line count and character count)
154
+ const content = params.content || '';
155
+ const lineCount = content.split('\n').length;
156
+ const charCount = content.length;
157
+
158
+ if (lineCount > COMPLEXITY_THRESHOLDS.file_write_lines) {
159
+ result.shouldDelegate = true;
160
+ result.reason = `Writing ${lineCount} lines (threshold: ${COMPLEXITY_THRESHOLDS.file_write_lines})`;
161
+ result.score = 2;
162
+ } else if (charCount > 5000) {
163
+ // Large single-line content
164
+ result.shouldDelegate = true;
165
+ result.reason = `Writing large content (${charCount} chars)`;
166
+ result.score = 2;
167
+ }
168
+ }
169
+
170
+ if (toolName === 'Edit') {
171
+ // Check if large replacement
172
+ const oldLength = (params.old_string || '').length;
173
+ const newLength = (params.new_string || '').length;
174
+ if (oldLength > 500 || newLength > 500) {
175
+ result.shouldDelegate = true;
176
+ result.reason = `Large edit operation (${Math.max(oldLength, newLength)} chars)`;
177
+ result.score = 2;
178
+ }
179
+ }
180
+
181
+ return result;
182
+ }
183
+
184
+ /**
185
+ * Analyze search operation complexity
186
+ */
187
+ function analyzeSearchOpComplexity(toolName, params) {
188
+ const result = {
189
+ shouldDelegate: false,
190
+ reason: '',
191
+ score: 0
192
+ };
193
+
194
+ if (toolName === 'Grep') {
195
+ // Check if complex regex pattern
196
+ const pattern = params.pattern || '';
197
+ const hasAdvancedRegex = /[\[\]\{\}\(\)\|\\\^\$\*\+\?]/.test(pattern);
198
+ const isMultiline = params.multiline === true;
199
+
200
+ if (hasAdvancedRegex && isMultiline) {
201
+ result.shouldDelegate = true;
202
+ result.reason = 'Complex regex with multiline mode';
203
+ result.score = 2;
204
+ }
205
+ }
206
+
207
+ if (toolName === 'Glob') {
208
+ // Check pattern complexity
209
+ const pattern = params.pattern || '';
210
+ const hasMultipleWildcards = (pattern.match(/\*\*/g) || []).length > 2;
211
+ const hasComplexBraces = /\{[^}]+,[^}]+\}/.test(pattern);
212
+
213
+ if (hasMultipleWildcards || hasComplexBraces) {
214
+ result.shouldDelegate = true;
215
+ result.reason = 'Complex glob pattern with multiple wildcards';
216
+ result.score = 2;
217
+ }
218
+ }
219
+
220
+ return result;
221
+ }
222
+
223
+ /**
224
+ * Analyze execution operation complexity
225
+ */
226
+ function analyzeExecOpComplexity(toolName, params) {
227
+ const result = {
228
+ shouldDelegate: false,
229
+ reason: '',
230
+ score: 0
231
+ };
232
+
233
+ if (toolName === 'Bash') {
234
+ const command = params.command || '';
235
+
236
+ // Check command length
237
+ if (command.length > COMPLEXITY_THRESHOLDS.bash_command_length) {
238
+ result.shouldDelegate = true;
239
+ result.reason = `Long command (${command.length} chars)`;
240
+ result.score = 2;
241
+ }
242
+
243
+ // Check for pipes
244
+ const pipeCount = (command.match(/\|/g) || []).length;
245
+ if (pipeCount > COMPLEXITY_THRESHOLDS.bash_piped_commands) {
246
+ result.shouldDelegate = true;
247
+ result.reason = `Complex piped command (${pipeCount} pipes)`;
248
+ result.score = 2;
249
+ }
250
+
251
+ // Check for dangerous operations
252
+ const hasDangerousOps = /rm -rf|dd |mkfs|format|:(){:|fork/.test(command);
253
+ if (hasDangerousOps) {
254
+ result.shouldDelegate = true;
255
+ result.reason = 'Potentially dangerous operation detected';
256
+ result.score = 3;
257
+ }
258
+ }
259
+
260
+ return result;
261
+ }
262
+
263
+ /**
264
+ * Generate delegation message for Task tool
265
+ */
266
+ export function generateDelegationMessage(toolName, toolParams, decision) {
267
+ const messages = {
268
+ Read: `Read file: ${toolParams.file_path || '[file_path]'}${toolParams.limit ? ` (${toolParams.limit} lines)` : ''}`,
269
+ Write: `Write to file: ${toolParams.file_path || '[file_path]'}`,
270
+ Edit: `Edit file: ${toolParams.file_path || '[file_path]'}`,
271
+ Bash: `Execute command: ${toolParams.command || ''}`,
272
+ Grep: `Search for pattern "${toolParams.pattern || '[pattern]'}" ${toolParams.path ? `in ${toolParams.path}` : 'in codebase'}`,
273
+ Glob: `Find files matching "${toolParams.pattern || '[pattern]'}"`
274
+ };
275
+
276
+ const baseMessage = messages[toolName] || `Execute ${toolName} operation`;
277
+ const reason = decision.reason || 'Auto-delegation by Tool Wrapper';
278
+
279
+ return {
280
+ description: `AUTO-DELEGATED: ${baseMessage}`,
281
+ details: reason,
282
+ originalTool: toolName,
283
+ originalParams: toolParams,
284
+ strategy: decision.strategy
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Get delegation statistics
290
+ */
291
+ export function getDelegationStats(delegationLog = []) {
292
+ const stats = {
293
+ total: delegationLog.length,
294
+ delegated: 0,
295
+ direct: 0,
296
+ byStrategy: {},
297
+ byTool: {},
298
+ delegationRate: 0
299
+ };
300
+
301
+ delegationLog.forEach(entry => {
302
+ if (entry.delegated) {
303
+ stats.delegated++;
304
+ const strategy = entry.strategy || 'UNKNOWN';
305
+ stats.byStrategy[strategy] = (stats.byStrategy[strategy] || 0) + 1;
306
+ } else {
307
+ stats.direct++;
308
+ }
309
+
310
+ const tool = entry.tool || 'UNKNOWN';
311
+ stats.byTool[tool] = (stats.byTool[tool] || 0) + 1;
312
+ });
313
+
314
+ stats.delegationRate = stats.total > 0
315
+ ? (stats.delegated / stats.total * 100).toFixed(2)
316
+ : 0;
317
+
318
+ return stats;
319
+ }
320
+
321
+ export default {
322
+ TOOL_CATEGORIES,
323
+ shouldDelegateTool,
324
+ generateDelegationMessage,
325
+ getDelegationStats
326
+ };