knoxis-collab 1.1.2 → 1.4.0
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/knoxis-collab.js +1114 -57
- package/package.json +2 -2
package/knoxis-collab.js
CHANGED
|
@@ -38,8 +38,22 @@
|
|
|
38
38
|
const { spawn, spawnSync } = require('child_process');
|
|
39
39
|
const crypto = require('crypto');
|
|
40
40
|
const https = require('https');
|
|
41
|
+
|
|
42
|
+
// Create HTTP agents with connection keep-alive
|
|
43
|
+
const httpAgent = new (require('http').Agent)({
|
|
44
|
+
keepAlive: true,
|
|
45
|
+
keepAliveMsecs: 1000,
|
|
46
|
+
maxSockets: 10
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const httpsAgent = new https.Agent({
|
|
50
|
+
keepAlive: true,
|
|
51
|
+
keepAliveMsecs: 1000,
|
|
52
|
+
maxSockets: 10
|
|
53
|
+
});
|
|
41
54
|
const readline = require('readline');
|
|
42
55
|
const fs = require('fs');
|
|
56
|
+
const fsPromises = require('fs').promises;
|
|
43
57
|
const path = require('path');
|
|
44
58
|
const os = require('os');
|
|
45
59
|
|
|
@@ -71,7 +85,20 @@ const cliArgs = parseArgs();
|
|
|
71
85
|
const WORKSPACE = cliArgs.workspace || process.env.KNOXIS_WORKSPACE || process.cwd();
|
|
72
86
|
|
|
73
87
|
// Load config
|
|
74
|
-
function loadConfig() {
|
|
88
|
+
async function loadConfig() {
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
91
|
+
const data = await fsPromises.readFile(CONFIG_PATH, 'utf8');
|
|
92
|
+
return JSON.parse(data);
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error(`Failed to load config: ${e.message}`);
|
|
96
|
+
}
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Synchronous config load for initialization
|
|
101
|
+
function loadConfigSync() {
|
|
75
102
|
try {
|
|
76
103
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
77
104
|
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
@@ -80,7 +107,7 @@ function loadConfig() {
|
|
|
80
107
|
return {};
|
|
81
108
|
}
|
|
82
109
|
|
|
83
|
-
const config =
|
|
110
|
+
const config = loadConfigSync();
|
|
84
111
|
const GROQ_API_KEY = process.env.GROQ_API_KEY || config.groqApiKey || '';
|
|
85
112
|
const BACKEND_URL = process.env.KNOXIS_BACKEND_URL || config.backendUrl || '';
|
|
86
113
|
const USER_ID = process.env.KNOXIS_USER_ID || config.userId || '';
|
|
@@ -153,12 +180,44 @@ let isClaudeRunning = false;
|
|
|
153
180
|
let activeClaudeProc = null;
|
|
154
181
|
const conversationHistory = []; // Groq message history
|
|
155
182
|
const dispatchSummaries = []; // Short summaries of what Claude did each dispatch
|
|
183
|
+
const MAX_DISPATCH_SUMMARIES = 20; // Limit dispatch summaries for memory
|
|
184
|
+
const aiInsights = { // AI decision-making layer state
|
|
185
|
+
projectType: null,
|
|
186
|
+
complexity: 'medium',
|
|
187
|
+
patterns: [],
|
|
188
|
+
risks: [],
|
|
189
|
+
suggestions: [],
|
|
190
|
+
contextCache: new Map(),
|
|
191
|
+
decisionHistory: [],
|
|
192
|
+
feedbackHistory: [], // User feedback on AI suggestions
|
|
193
|
+
patternWeights: new Map(), // Learned weights for patterns
|
|
194
|
+
suggestionScores: new Map() // Performance scores for suggestions
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Feedback state for current session
|
|
198
|
+
const feedbackState = {
|
|
199
|
+
pendingFeedback: null, // Current suggestion awaiting feedback
|
|
200
|
+
lastSuggestions: [], // Last set of suggestions shown
|
|
201
|
+
feedbackPromptShown: false, // Whether we've prompted for feedback
|
|
202
|
+
awaitingRating: false // Whether we're waiting for a rating
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Code review and commenting state
|
|
206
|
+
const codeReview = {
|
|
207
|
+
pendingReviews: [], // Code blocks awaiting review
|
|
208
|
+
reviewHistory: [], // Past code reviews
|
|
209
|
+
inlineComments: new Map(), // File -> line -> comment mapping
|
|
210
|
+
activeReviewSession: null, // Current review session ID
|
|
211
|
+
autoReviewEnabled: false, // Auto-review on code changes
|
|
212
|
+
lastReviewedCode: null // Cache last reviewed code
|
|
213
|
+
};
|
|
156
214
|
|
|
157
215
|
// ═══════════════════════════════════════════════════════════════
|
|
158
|
-
// SESSION LOG
|
|
216
|
+
// SESSION LOG & PERSISTENCE
|
|
159
217
|
// ═══════════════════════════════════════════════════════════════
|
|
160
218
|
|
|
161
219
|
const sessionDir = path.join(WORKSPACE, '.knoxis', 'sessions');
|
|
220
|
+
const feedbackFile = path.join(WORKSPACE, '.knoxis', 'ai-feedback.json');
|
|
162
221
|
try { fs.mkdirSync(sessionDir, { recursive: true }); } catch (e) {}
|
|
163
222
|
|
|
164
223
|
const logFile = path.join(
|
|
@@ -166,9 +225,213 @@ const logFile = path.join(
|
|
|
166
225
|
`${new Date().toISOString().replace(/[:.]/g, '-')}-collab-${SESSION_ID.slice(0, 8)}.log`
|
|
167
226
|
);
|
|
168
227
|
|
|
228
|
+
// Async logging with queue for better performance
|
|
229
|
+
const logQueue = [];
|
|
230
|
+
let logTimer = null;
|
|
231
|
+
|
|
169
232
|
function log(entry) {
|
|
170
233
|
const line = `[${new Date().toISOString()}] ${entry}\n`;
|
|
171
|
-
|
|
234
|
+
logQueue.push(line);
|
|
235
|
+
|
|
236
|
+
// Batch write logs every 100ms
|
|
237
|
+
if (!logTimer) {
|
|
238
|
+
logTimer = setTimeout(async () => {
|
|
239
|
+
if (logQueue.length > 0) {
|
|
240
|
+
const batch = logQueue.splice(0, logQueue.length).join('');
|
|
241
|
+
try {
|
|
242
|
+
await fsPromises.appendFile(logFile, batch);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
console.error(`Log write failed: ${e.message}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
logTimer = null;
|
|
248
|
+
}, 100);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Load persisted feedback data
|
|
253
|
+
async function loadFeedbackData() {
|
|
254
|
+
try {
|
|
255
|
+
if (fs.existsSync(feedbackFile)) {
|
|
256
|
+
const content = await fsPromises.readFile(feedbackFile, 'utf8');
|
|
257
|
+
const data = JSON.parse(content);
|
|
258
|
+
|
|
259
|
+
// Restore pattern weights
|
|
260
|
+
if (data.patternWeights) {
|
|
261
|
+
Object.entries(data.patternWeights).forEach(([pattern, weight]) => {
|
|
262
|
+
aiInsights.patternWeights.set(pattern, weight);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Restore suggestion scores
|
|
267
|
+
if (data.suggestionScores) {
|
|
268
|
+
Object.entries(data.suggestionScores).forEach(([suggestion, score]) => {
|
|
269
|
+
aiInsights.suggestionScores.set(suggestion, score);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Load recent feedback history (last 50 items)
|
|
274
|
+
if (data.feedbackHistory) {
|
|
275
|
+
aiInsights.feedbackHistory = data.feedbackHistory.slice(-50);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
log(`Loaded feedback data: ${aiInsights.patternWeights.size} patterns, ${aiInsights.suggestionScores.size} suggestions`);
|
|
279
|
+
}
|
|
280
|
+
} catch (e) {
|
|
281
|
+
log(`Failed to load feedback data: ${e.message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Save feedback data for persistence
|
|
286
|
+
async function saveFeedbackData() {
|
|
287
|
+
try {
|
|
288
|
+
const data = {
|
|
289
|
+
version: '1.0',
|
|
290
|
+
lastUpdated: new Date().toISOString(),
|
|
291
|
+
patternWeights: Object.fromEntries(aiInsights.patternWeights),
|
|
292
|
+
suggestionScores: Object.fromEntries(aiInsights.suggestionScores),
|
|
293
|
+
feedbackHistory: aiInsights.feedbackHistory.slice(-50) // Keep last 50
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
await fsPromises.writeFile(feedbackFile, JSON.stringify(data, null, 2));
|
|
297
|
+
log(`Saved feedback data: ${aiInsights.patternWeights.size} patterns, ${aiInsights.suggestionScores.size} suggestions`);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
log(`Failed to save feedback data: ${e.message}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ═══════════════════════════════════════════════════════════════
|
|
304
|
+
// CODE REVIEW AND COMMENTING FUNCTIONS
|
|
305
|
+
// ═══════════════════════════════════════════════════════════════
|
|
306
|
+
|
|
307
|
+
// Review code with inline comments
|
|
308
|
+
async function reviewCode(code, filePath, startLine = 1) {
|
|
309
|
+
const reviewId = crypto.randomUUID().substring(0, 8);
|
|
310
|
+
codeReview.activeReviewSession = reviewId;
|
|
311
|
+
|
|
312
|
+
const review = {
|
|
313
|
+
id: reviewId,
|
|
314
|
+
timestamp: Date.now(),
|
|
315
|
+
filePath: filePath,
|
|
316
|
+
code: code,
|
|
317
|
+
startLine: startLine,
|
|
318
|
+
comments: [],
|
|
319
|
+
summary: null
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Call Groq for code review
|
|
323
|
+
const userPrompt = `You are a senior code reviewer. Review the following code and provide:
|
|
324
|
+
1. Inline comments for specific lines (format: "Line X: comment")
|
|
325
|
+
2. General feedback and suggestions
|
|
326
|
+
3. Identify potential issues, bugs, or improvements
|
|
327
|
+
Be constructive and specific. Focus on code quality, performance, security, and maintainability.
|
|
328
|
+
|
|
329
|
+
Review this code from ${filePath} starting at line ${startLine}:
|
|
330
|
+
|
|
331
|
+
${code}`;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const reviewResponse = await callGroq(
|
|
335
|
+
[{ role: 'user', content: userPrompt }],
|
|
336
|
+
projectContext
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Extract text from Groq response object
|
|
340
|
+
const reviewText = reviewResponse.message || JSON.stringify(reviewResponse);
|
|
341
|
+
|
|
342
|
+
// Parse inline comments from response
|
|
343
|
+
const lines = reviewText.split('\n');
|
|
344
|
+
const inlineCommentPattern = /^Line\s+(\d+):\s*(.+)/i;
|
|
345
|
+
|
|
346
|
+
lines.forEach(line => {
|
|
347
|
+
const match = line.match(inlineCommentPattern);
|
|
348
|
+
if (match) {
|
|
349
|
+
const lineNum = parseInt(match[1]) + startLine - 1;
|
|
350
|
+
const comment = match[2].trim();
|
|
351
|
+
|
|
352
|
+
review.comments.push({
|
|
353
|
+
line: lineNum,
|
|
354
|
+
comment: comment,
|
|
355
|
+
severity: detectSeverity(comment)
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Store in inline comments map
|
|
359
|
+
if (!codeReview.inlineComments.has(filePath)) {
|
|
360
|
+
codeReview.inlineComments.set(filePath, new Map());
|
|
361
|
+
}
|
|
362
|
+
codeReview.inlineComments.get(filePath).set(lineNum, comment);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
review.summary = reviewText;
|
|
367
|
+
codeReview.reviewHistory.push(review);
|
|
368
|
+
|
|
369
|
+
// Keep review history bounded
|
|
370
|
+
if (codeReview.reviewHistory.length > 10) {
|
|
371
|
+
codeReview.reviewHistory.shift();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return review;
|
|
375
|
+
} catch (e) {
|
|
376
|
+
log(`Code review failed: ${e.message}`);
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Detect severity of comment (error, warning, info)
|
|
382
|
+
function detectSeverity(comment) {
|
|
383
|
+
const lowerComment = comment.toLowerCase();
|
|
384
|
+
if (lowerComment.includes('error') || lowerComment.includes('bug') ||
|
|
385
|
+
lowerComment.includes('critical') || lowerComment.includes('security')) {
|
|
386
|
+
return 'error';
|
|
387
|
+
} else if (lowerComment.includes('warning') || lowerComment.includes('potential') ||
|
|
388
|
+
lowerComment.includes('consider') || lowerComment.includes('performance')) {
|
|
389
|
+
return 'warning';
|
|
390
|
+
}
|
|
391
|
+
return 'info';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Format and display code review results
|
|
395
|
+
function displayCodeReview(review) {
|
|
396
|
+
if (!review) return;
|
|
397
|
+
|
|
398
|
+
console.log(`\n ${C.cyan}━━━ Code Review (${review.id}) ━━━${C.reset}`);
|
|
399
|
+
console.log(` ${C.dim}File: ${review.filePath}${C.reset}`);
|
|
400
|
+
|
|
401
|
+
// Display inline comments
|
|
402
|
+
if (review.comments.length > 0) {
|
|
403
|
+
console.log(`\n ${C.bold}Inline Comments:${C.reset}`);
|
|
404
|
+
review.comments.forEach(comment => {
|
|
405
|
+
const icon = comment.severity === 'error' ? '❌' :
|
|
406
|
+
comment.severity === 'warning' ? '⚠️' : '💡';
|
|
407
|
+
console.log(` ${icon} ${C.dim}Line ${comment.line}:${C.reset} ${comment.comment}`);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Display summary
|
|
412
|
+
console.log(`\n ${C.bold}Review Summary:${C.reset}`);
|
|
413
|
+
const summaryLines = review.summary.split('\n').slice(0, 10);
|
|
414
|
+
summaryLines.forEach(line => {
|
|
415
|
+
if (line.trim()) console.log(` ${line}`);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
console.log(`\n ${C.dim}Review saved. Use /reviews to see history.${C.reset}\n`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Extract code from Claude's output for review
|
|
422
|
+
function extractCodeFromOutput(output) {
|
|
423
|
+
const codeBlocks = [];
|
|
424
|
+
const codeBlockPattern = /```(\w+)?\n([\s\S]*?)```/g;
|
|
425
|
+
let match;
|
|
426
|
+
|
|
427
|
+
while ((match = codeBlockPattern.exec(output)) !== null) {
|
|
428
|
+
codeBlocks.push({
|
|
429
|
+
language: match[1] || 'unknown',
|
|
430
|
+
code: match[2].trim()
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return codeBlocks;
|
|
172
435
|
}
|
|
173
436
|
|
|
174
437
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -188,6 +451,66 @@ const C = {
|
|
|
188
451
|
white: '\x1b[37m',
|
|
189
452
|
};
|
|
190
453
|
|
|
454
|
+
// ─── LOADING SPINNER ───
|
|
455
|
+
class LoadingSpinner {
|
|
456
|
+
constructor() {
|
|
457
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
458
|
+
this.interval = null;
|
|
459
|
+
this.frameIndex = 0;
|
|
460
|
+
this.message = '';
|
|
461
|
+
this.active = false;
|
|
462
|
+
this.startTime = null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
start(message = 'Processing') {
|
|
466
|
+
if (this.active) return;
|
|
467
|
+
|
|
468
|
+
this.active = true;
|
|
469
|
+
this.message = message;
|
|
470
|
+
this.frameIndex = 0;
|
|
471
|
+
this.startTime = Date.now();
|
|
472
|
+
|
|
473
|
+
// Clear line first
|
|
474
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
475
|
+
|
|
476
|
+
// Only update spinner if output is visible (not in verbose mode)
|
|
477
|
+
this.interval = setInterval(() => {
|
|
478
|
+
if (!this.active || verbose) {
|
|
479
|
+
// Stop updating if verbose mode is on
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const elapsed = Math.round((Date.now() - this.startTime) / 1000);
|
|
484
|
+
const frame = this.frames[this.frameIndex];
|
|
485
|
+
const timeStr = elapsed > 0 ? ` (${elapsed}s)` : '';
|
|
486
|
+
|
|
487
|
+
process.stdout.write(`\r ${C.blue}${frame} ${this.message}...${timeStr}${C.reset}${''.padEnd(30)}`);
|
|
488
|
+
|
|
489
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
490
|
+
}, 80);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
update(message) {
|
|
494
|
+
this.message = message;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
stop(clearLine = true) {
|
|
498
|
+
if (!this.active) return;
|
|
499
|
+
|
|
500
|
+
this.active = false;
|
|
501
|
+
if (this.interval) {
|
|
502
|
+
clearInterval(this.interval);
|
|
503
|
+
this.interval = null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (clearLine) {
|
|
507
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const spinner = new LoadingSpinner();
|
|
513
|
+
|
|
191
514
|
function printHeader() {
|
|
192
515
|
console.log('');
|
|
193
516
|
console.log(`${C.cyan}╔══════════════════════════════════════════════════════════════╗${C.reset}`);
|
|
@@ -202,7 +525,9 @@ function printHeader() {
|
|
|
202
525
|
console.log(` ${C.dim}Log:${C.reset} ${path.basename(logFile)}`);
|
|
203
526
|
console.log('');
|
|
204
527
|
console.log(` ${C.dim}Talk to Knoxis naturally. He dispatches to Claude when needed.${C.reset}`);
|
|
205
|
-
console.log(` ${C.dim}Commands: /status /verbose /diff /log /exit${C.reset}`);
|
|
528
|
+
console.log(` ${C.dim}Commands: /status /verbose /diff /log /ai /feedback /review /exit${C.reset}`);
|
|
529
|
+
console.log(` ${C.dim}Quick feedback: 👍 or +1 (good), 👎 or -1 (needs improvement)${C.reset}`);
|
|
530
|
+
console.log(` ${C.dim}Code review: /review [file] or /autoreview to toggle${C.reset}`);
|
|
206
531
|
console.log('');
|
|
207
532
|
}
|
|
208
533
|
|
|
@@ -228,13 +553,15 @@ function printClaudeLine(text) {
|
|
|
228
553
|
}
|
|
229
554
|
|
|
230
555
|
function printClaudeStatus(status) {
|
|
231
|
-
|
|
556
|
+
// Deprecated - using spinner instead
|
|
557
|
+
if (!verbose && !spinner.active) {
|
|
232
558
|
process.stdout.write(`\r ${C.blue} ⟳ Claude: ${status}${C.reset}${''.padEnd(20)}`);
|
|
233
559
|
}
|
|
234
560
|
}
|
|
235
561
|
|
|
236
562
|
function clearClaudeStatus() {
|
|
237
|
-
|
|
563
|
+
// Deprecated - using spinner instead
|
|
564
|
+
if (!verbose && !spinner.active) {
|
|
238
565
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
239
566
|
}
|
|
240
567
|
}
|
|
@@ -262,11 +589,326 @@ function loadInitialTask() {
|
|
|
262
589
|
}
|
|
263
590
|
|
|
264
591
|
// ═══════════════════════════════════════════════════════════════
|
|
265
|
-
//
|
|
592
|
+
// AI DECISION-MAKING LAYER
|
|
593
|
+
// ═══════════════════════════════════════════════════════════════
|
|
594
|
+
|
|
595
|
+
class AIDecisionEngine {
|
|
596
|
+
constructor() {
|
|
597
|
+
this.contextWindow = 10; // Number of recent interactions to consider
|
|
598
|
+
this.patternLibrary = {
|
|
599
|
+
'refactoring': ['refactor', 'clean', 'optimize', 'improve', 'restructure'],
|
|
600
|
+
'bugfix': ['fix', 'bug', 'error', 'issue', 'problem', 'broken'],
|
|
601
|
+
'feature': ['add', 'implement', 'create', 'build', 'new feature'],
|
|
602
|
+
'testing': ['test', 'spec', 'coverage', 'unit test', 'integration'],
|
|
603
|
+
'documentation': ['document', 'readme', 'docs', 'comment', 'explain'],
|
|
604
|
+
'performance': ['slow', 'performance', 'optimize', 'speed', 'latency'],
|
|
605
|
+
'security': ['security', 'vulnerability', 'auth', 'encryption', 'secure']
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
analyzeIntent(userInput) {
|
|
610
|
+
const input = userInput.toLowerCase();
|
|
611
|
+
const detectedPatterns = [];
|
|
612
|
+
|
|
613
|
+
for (const [pattern, keywords] of Object.entries(this.patternLibrary)) {
|
|
614
|
+
if (keywords.some(keyword => input.includes(keyword))) {
|
|
615
|
+
detectedPatterns.push(pattern);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const baseConfidence = detectedPatterns.length > 0 ? 0.8 + (detectedPatterns.length * 0.05) : 0.3;
|
|
620
|
+
const adjustedConfidence = this.getAdjustedConfidence(baseConfidence, detectedPatterns);
|
|
621
|
+
|
|
622
|
+
return {
|
|
623
|
+
patterns: detectedPatterns,
|
|
624
|
+
priority: this.calculatePriority(detectedPatterns),
|
|
625
|
+
confidence: adjustedConfidence,
|
|
626
|
+
baseConfidence: baseConfidence
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
calculatePriority(patterns) {
|
|
631
|
+
const priorityMap = {
|
|
632
|
+
'security': 'critical',
|
|
633
|
+
'bugfix': 'high',
|
|
634
|
+
'performance': 'high',
|
|
635
|
+
'feature': 'medium',
|
|
636
|
+
'refactoring': 'medium',
|
|
637
|
+
'testing': 'low',
|
|
638
|
+
'documentation': 'low'
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
for (const pattern of ['security', 'bugfix', 'performance', 'feature', 'refactoring', 'testing', 'documentation']) {
|
|
642
|
+
if (patterns.includes(pattern)) return priorityMap[pattern];
|
|
643
|
+
}
|
|
644
|
+
return 'medium';
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
analyzeComplexity(task, projectContext) {
|
|
648
|
+
let complexity = 1; // Base complexity
|
|
649
|
+
|
|
650
|
+
// Factor in task characteristics
|
|
651
|
+
if (task.length > 200) complexity += 0.5;
|
|
652
|
+
if (task.includes('multiple') || task.includes('several')) complexity += 1;
|
|
653
|
+
if (task.includes('architecture') || task.includes('design')) complexity += 1.5;
|
|
654
|
+
if (task.includes('simple') || task.includes('quick')) complexity -= 0.5;
|
|
655
|
+
|
|
656
|
+
// Factor in project size (if detectable)
|
|
657
|
+
if (projectContext.includes('large') || projectContext.includes('enterprise')) complexity += 1;
|
|
658
|
+
|
|
659
|
+
// Factor in detected patterns
|
|
660
|
+
const patterns = this.analyzeIntent(task).patterns;
|
|
661
|
+
if (patterns.includes('refactoring')) complexity += 1;
|
|
662
|
+
if (patterns.includes('security')) complexity += 1.5;
|
|
663
|
+
if (patterns.length > 2) complexity += 0.5 * patterns.length;
|
|
664
|
+
|
|
665
|
+
// Normalize to categories
|
|
666
|
+
if (complexity < 1.5) return 'simple';
|
|
667
|
+
if (complexity < 3) return 'medium';
|
|
668
|
+
if (complexity < 5) return 'complex';
|
|
669
|
+
return 'very_complex';
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
generateContextualInsights(userInput, conversationHistory, projectContext) {
|
|
673
|
+
const intent = this.analyzeIntent(userInput);
|
|
674
|
+
const complexity = this.analyzeComplexity(userInput, projectContext);
|
|
675
|
+
|
|
676
|
+
const insights = {
|
|
677
|
+
intent: intent,
|
|
678
|
+
complexity: complexity,
|
|
679
|
+
recommendations: [],
|
|
680
|
+
warnings: [],
|
|
681
|
+
contextEnhancements: []
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// Generate recommendations based on patterns
|
|
685
|
+
if (intent.patterns.includes('bugfix')) {
|
|
686
|
+
insights.recommendations.push('Consider adding tests to prevent regression');
|
|
687
|
+
insights.contextEnhancements.push('Include error logs and stack traces if available');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (intent.patterns.includes('feature')) {
|
|
691
|
+
insights.recommendations.push('Break down into smaller, testable components');
|
|
692
|
+
insights.contextEnhancements.push('Consider edge cases and error handling');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (intent.patterns.includes('performance')) {
|
|
696
|
+
insights.recommendations.push('Profile before optimizing');
|
|
697
|
+
insights.contextEnhancements.push('Include current performance metrics');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (intent.patterns.includes('security')) {
|
|
701
|
+
insights.warnings.push('Security changes require careful review');
|
|
702
|
+
insights.recommendations.push('Follow OWASP guidelines');
|
|
703
|
+
insights.contextEnhancements.push('Consider threat modeling');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Add complexity-based insights
|
|
707
|
+
if (complexity === 'very_complex') {
|
|
708
|
+
insights.warnings.push('This task appears complex - consider breaking it down');
|
|
709
|
+
insights.recommendations.push('Start with a design document or architecture diagram');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (complexity === 'simple') {
|
|
713
|
+
insights.recommendations.push('This looks straightforward - should be quick to implement');
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Historical pattern analysis
|
|
717
|
+
if (conversationHistory.length > 5) {
|
|
718
|
+
const recentTopics = this.analyzeRecentTopics(conversationHistory);
|
|
719
|
+
if (recentTopics.length > 0) {
|
|
720
|
+
insights.contextEnhancements.push(`Recent focus areas: ${recentTopics.join(', ')}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return insights;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
analyzeRecentTopics(history) {
|
|
728
|
+
const recentMessages = history.slice(-5);
|
|
729
|
+
const topics = new Set();
|
|
730
|
+
|
|
731
|
+
recentMessages.forEach(msg => {
|
|
732
|
+
if (msg.role === 'user') {
|
|
733
|
+
const intent = this.analyzeIntent(msg.content);
|
|
734
|
+
intent.patterns.forEach(p => topics.add(p));
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
return Array.from(topics);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
enrichClaudePrompt(originalPrompt, insights, projectContext) {
|
|
742
|
+
let enrichedPrompt = originalPrompt;
|
|
743
|
+
|
|
744
|
+
// Add context header
|
|
745
|
+
const contextHeader = `[AI CONTEXT ANALYSIS]
|
|
746
|
+
Intent: ${insights.intent.patterns.join(', ') || 'general'}
|
|
747
|
+
Priority: ${insights.intent.priority}
|
|
748
|
+
Complexity: ${insights.complexity}
|
|
749
|
+
Confidence: ${(insights.intent.confidence * 100).toFixed(0)}%\n\n`;
|
|
750
|
+
|
|
751
|
+
// Add recommendations if any
|
|
752
|
+
if (insights.recommendations.length > 0) {
|
|
753
|
+
enrichedPrompt = contextHeader +
|
|
754
|
+
`[RECOMMENDATIONS]\n${insights.recommendations.map(r => `- ${r}`).join('\n')}\n\n` +
|
|
755
|
+
originalPrompt;
|
|
756
|
+
} else {
|
|
757
|
+
enrichedPrompt = contextHeader + originalPrompt;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Add warnings if any
|
|
761
|
+
if (insights.warnings.length > 0) {
|
|
762
|
+
enrichedPrompt += `\n\n[IMPORTANT WARNINGS]\n${insights.warnings.map(w => `⚠️ ${w}`).join('\n')}`;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Add context enhancements
|
|
766
|
+
if (insights.contextEnhancements.length > 0) {
|
|
767
|
+
enrichedPrompt += `\n\n[ADDITIONAL CONTEXT]\n${insights.contextEnhancements.map(c => `- ${c}`).join('\n')}`;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return enrichedPrompt;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
recordDecision(userInput, decision, outcome) {
|
|
774
|
+
const MAX_DECISION_HISTORY = 20;
|
|
775
|
+
|
|
776
|
+
aiInsights.decisionHistory.push({
|
|
777
|
+
timestamp: new Date().toISOString(),
|
|
778
|
+
input: userInput.slice(0, 100),
|
|
779
|
+
decision: decision,
|
|
780
|
+
outcome: outcome
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// Keep only last N decisions for memory management
|
|
784
|
+
if (aiInsights.decisionHistory.length > MAX_DECISION_HISTORY) {
|
|
785
|
+
aiInsights.decisionHistory.shift();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
suggestNextSteps(claudeOutput, originalTask) {
|
|
790
|
+
const suggestions = [];
|
|
791
|
+
const outputLower = claudeOutput.toLowerCase();
|
|
792
|
+
|
|
793
|
+
// Analyze what was done
|
|
794
|
+
if (outputLower.includes('created') || outputLower.includes('added')) {
|
|
795
|
+
suggestions.push('Test the new functionality');
|
|
796
|
+
suggestions.push('Review the generated code for edge cases');
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (outputLower.includes('fixed') || outputLower.includes('resolved')) {
|
|
800
|
+
suggestions.push('Verify the fix works as expected');
|
|
801
|
+
suggestions.push('Add a test to prevent regression');
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (outputLower.includes('refactored') || outputLower.includes('improved')) {
|
|
805
|
+
suggestions.push('Run existing tests to ensure nothing broke');
|
|
806
|
+
suggestions.push('Check performance impact');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (outputLower.includes('error') || outputLower.includes('failed')) {
|
|
810
|
+
suggestions.push('Review error messages for root cause');
|
|
811
|
+
suggestions.push('Check logs for additional context');
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Apply learned weights from feedback
|
|
815
|
+
const weightedSuggestions = this.applyFeedbackWeights(
|
|
816
|
+
suggestions.length > 0 ? suggestions : ['Review the changes', 'Test the implementation']
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
return weightedSuggestions;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Apply learned weights from user feedback
|
|
823
|
+
applyFeedbackWeights(suggestions) {
|
|
824
|
+
return suggestions.map(suggestion => {
|
|
825
|
+
const score = aiInsights.suggestionScores.get(suggestion) || 0;
|
|
826
|
+
return { text: suggestion, score: score };
|
|
827
|
+
}).sort((a, b) => b.score - a.score).map(s => s.text);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Process user feedback and update learning
|
|
831
|
+
processFeedback(feedback) {
|
|
832
|
+
const { type, rating, text, context, suggestions } = feedback;
|
|
833
|
+
|
|
834
|
+
// Record feedback
|
|
835
|
+
aiInsights.feedbackHistory.push({
|
|
836
|
+
timestamp: new Date().toISOString(),
|
|
837
|
+
type: type,
|
|
838
|
+
rating: rating,
|
|
839
|
+
text: text || '',
|
|
840
|
+
context: context,
|
|
841
|
+
suggestions: suggestions
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// Update pattern weights based on feedback
|
|
845
|
+
if (context && context.patterns) {
|
|
846
|
+
context.patterns.forEach(pattern => {
|
|
847
|
+
const currentWeight = aiInsights.patternWeights.get(pattern) || 1.0;
|
|
848
|
+
const adjustment = rating === 'up' ? 0.1 : rating === 'down' ? -0.1 : 0;
|
|
849
|
+
aiInsights.patternWeights.set(pattern, Math.max(0.1, Math.min(2.0, currentWeight + adjustment)));
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Update suggestion scores
|
|
854
|
+
if (suggestions) {
|
|
855
|
+
suggestions.forEach(suggestion => {
|
|
856
|
+
const currentScore = aiInsights.suggestionScores.get(suggestion) || 0;
|
|
857
|
+
const scoreAdjustment = rating === 'up' ? 1 : rating === 'down' ? -1 : 0;
|
|
858
|
+
aiInsights.suggestionScores.set(suggestion, currentScore + scoreAdjustment);
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Keep feedback history bounded for memory management
|
|
863
|
+
const MAX_FEEDBACK_HISTORY = 100;
|
|
864
|
+
if (aiInsights.feedbackHistory.length > MAX_FEEDBACK_HISTORY) {
|
|
865
|
+
// Remove oldest entries
|
|
866
|
+
aiInsights.feedbackHistory.splice(0, aiInsights.feedbackHistory.length - MAX_FEEDBACK_HISTORY);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Save feedback data after processing
|
|
870
|
+
saveFeedbackData(); // Fire and forget for performance
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
processed: true,
|
|
874
|
+
message: this.generateFeedbackResponse(rating, text)
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Generate response based on feedback
|
|
879
|
+
generateFeedbackResponse(rating, text) {
|
|
880
|
+
if (rating === 'up') {
|
|
881
|
+
return text ? `Thanks for the positive feedback! Noted: "${text}"` : 'Thanks for the positive feedback!';
|
|
882
|
+
} else if (rating === 'down') {
|
|
883
|
+
return text ? `I appreciate the feedback. I'll improve based on: "${text}"` : 'Thanks for the feedback. I\'ll work on improving.';
|
|
884
|
+
} else if (text) {
|
|
885
|
+
return `Feedback noted: "${text}"`;
|
|
886
|
+
}
|
|
887
|
+
return 'Feedback recorded. Thank you!';
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Get feedback-adjusted confidence
|
|
891
|
+
getAdjustedConfidence(baseConfidence, patterns) {
|
|
892
|
+
let adjustedConfidence = baseConfidence;
|
|
893
|
+
|
|
894
|
+
patterns.forEach(pattern => {
|
|
895
|
+
const weight = aiInsights.patternWeights.get(pattern) || 1.0;
|
|
896
|
+
adjustedConfidence *= weight;
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
return Math.max(0.1, Math.min(1.0, adjustedConfidence));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const aiEngine = new AIDecisionEngine();
|
|
904
|
+
|
|
905
|
+
// ═══════════════════════════════════════════════════════════════
|
|
906
|
+
// DETECT PROJECT CONTEXT (enhanced with AI analysis)
|
|
266
907
|
// ═══════════════════════════════════════════════════════════════
|
|
267
908
|
|
|
268
909
|
function detectProject() {
|
|
269
910
|
const info = [];
|
|
911
|
+
let techStack = [];
|
|
270
912
|
|
|
271
913
|
try {
|
|
272
914
|
const pkgPath = path.join(WORKSPACE, 'package.json');
|
|
@@ -274,34 +916,72 @@ function detectProject() {
|
|
|
274
916
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
275
917
|
info.push(`Node.js project: ${pkg.name || 'unknown'}`);
|
|
276
918
|
if (pkg.description) info.push(pkg.description);
|
|
919
|
+
|
|
920
|
+
// Analyze dependencies for AI insights
|
|
921
|
+
if (pkg.dependencies) {
|
|
922
|
+
if (pkg.dependencies['react']) techStack.push('React');
|
|
923
|
+
if (pkg.dependencies['vue']) techStack.push('Vue');
|
|
924
|
+
if (pkg.dependencies['express']) techStack.push('Express');
|
|
925
|
+
if (pkg.dependencies['next']) techStack.push('Next.js');
|
|
926
|
+
aiInsights.projectType = 'nodejs';
|
|
927
|
+
}
|
|
277
928
|
}
|
|
278
929
|
} catch (e) {}
|
|
279
930
|
|
|
280
931
|
try {
|
|
281
932
|
const pomPath = path.join(WORKSPACE, 'pom.xml');
|
|
282
|
-
if (fs.existsSync(pomPath))
|
|
933
|
+
if (fs.existsSync(pomPath)) {
|
|
934
|
+
info.push('Java/Maven project (pom.xml)');
|
|
935
|
+
techStack.push('Java', 'Maven');
|
|
936
|
+
aiInsights.projectType = 'java-maven';
|
|
937
|
+
}
|
|
283
938
|
} catch (e) {}
|
|
284
939
|
|
|
285
940
|
try {
|
|
286
941
|
const gradlePath = path.join(WORKSPACE, 'build.gradle');
|
|
287
|
-
if (fs.existsSync(gradlePath))
|
|
942
|
+
if (fs.existsSync(gradlePath)) {
|
|
943
|
+
info.push('Java/Gradle project');
|
|
944
|
+
techStack.push('Java', 'Gradle');
|
|
945
|
+
aiInsights.projectType = 'java-gradle';
|
|
946
|
+
}
|
|
288
947
|
} catch (e) {}
|
|
289
948
|
|
|
290
949
|
try {
|
|
291
950
|
const nextPath = path.join(WORKSPACE, 'next.config.js');
|
|
292
951
|
const nextPath2 = path.join(WORKSPACE, 'next.config.mjs');
|
|
293
|
-
if (fs.existsSync(nextPath) || fs.existsSync(nextPath2))
|
|
952
|
+
if (fs.existsSync(nextPath) || fs.existsSync(nextPath2)) {
|
|
953
|
+
info.push('Next.js app');
|
|
954
|
+
techStack.push('Next.js');
|
|
955
|
+
}
|
|
294
956
|
} catch (e) {}
|
|
295
957
|
|
|
296
|
-
// Git branch
|
|
958
|
+
// Git branch - use cached value or async check
|
|
297
959
|
try {
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
const
|
|
301
|
-
|
|
960
|
+
// For initial detection, still use sync but add caching
|
|
961
|
+
if (!aiInsights.contextCache.has('gitBranch')) {
|
|
962
|
+
const result = spawnSync('git', ['branch', '--show-current'], {
|
|
963
|
+
cwd: WORKSPACE,
|
|
964
|
+
stdio: 'pipe',
|
|
965
|
+
shell: false // Prevent shell injection
|
|
966
|
+
});
|
|
967
|
+
if (result.status === 0) {
|
|
968
|
+
const branch = result.stdout.toString().trim();
|
|
969
|
+
if (branch) {
|
|
970
|
+
aiInsights.contextCache.set('gitBranch', branch);
|
|
971
|
+
info.push(`Git branch: ${branch}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
const cachedBranch = aiInsights.contextCache.get('gitBranch');
|
|
976
|
+
info.push(`Git branch: ${cachedBranch}`);
|
|
302
977
|
}
|
|
303
978
|
} catch (e) {}
|
|
304
979
|
|
|
980
|
+
// Store detected patterns in AI insights
|
|
981
|
+
if (techStack.length > 0) {
|
|
982
|
+
aiInsights.patterns = techStack;
|
|
983
|
+
}
|
|
984
|
+
|
|
305
985
|
return info.join(' · ');
|
|
306
986
|
}
|
|
307
987
|
|
|
@@ -400,8 +1080,10 @@ function callGroq(messages, projectContext) {
|
|
|
400
1080
|
method: 'POST',
|
|
401
1081
|
headers: {
|
|
402
1082
|
'Content-Type': 'application/json',
|
|
403
|
-
'Content-Length': Buffer.byteLength(payload)
|
|
404
|
-
|
|
1083
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
1084
|
+
'Connection': 'keep-alive'
|
|
1085
|
+
},
|
|
1086
|
+
agent: url.protocol === 'https:' ? httpsAgent : httpAgent // Use keep-alive agent
|
|
405
1087
|
};
|
|
406
1088
|
} else {
|
|
407
1089
|
// Direct Groq API call (legacy / developer override)
|
|
@@ -412,8 +1094,10 @@ function callGroq(messages, projectContext) {
|
|
|
412
1094
|
headers: {
|
|
413
1095
|
'Content-Type': 'application/json',
|
|
414
1096
|
'Authorization': `Bearer ${GROQ_API_KEY}`,
|
|
415
|
-
'Content-Length': Buffer.byteLength(payload)
|
|
416
|
-
|
|
1097
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
1098
|
+
'Connection': 'keep-alive'
|
|
1099
|
+
},
|
|
1100
|
+
agent: httpsAgent // Use keep-alive agent
|
|
417
1101
|
};
|
|
418
1102
|
}
|
|
419
1103
|
|
|
@@ -491,7 +1175,11 @@ function dispatchToClaude(prompt) {
|
|
|
491
1175
|
|
|
492
1176
|
log(`DISPATCH #${claudeDispatches}:\n${prompt}`);
|
|
493
1177
|
|
|
494
|
-
|
|
1178
|
+
// On Windows, spawn with shell requires a single command string to avoid DEP0190
|
|
1179
|
+
const spawnCmd = IS_WIN ? `"${CLAUDE_BIN}" ${args.join(' ')}` : CLAUDE_BIN;
|
|
1180
|
+
const spawnArgs = IS_WIN ? [] : args;
|
|
1181
|
+
|
|
1182
|
+
const proc = spawn(spawnCmd, spawnArgs, {
|
|
495
1183
|
cwd: WORKSPACE,
|
|
496
1184
|
env: { ...process.env },
|
|
497
1185
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -504,14 +1192,19 @@ function dispatchToClaude(prompt) {
|
|
|
504
1192
|
let outputLines = 0;
|
|
505
1193
|
const startTime = Date.now();
|
|
506
1194
|
|
|
1195
|
+
// Start spinner when Claude begins processing
|
|
1196
|
+
if (!verbose) {
|
|
1197
|
+
spinner.start('Claude is processing');
|
|
1198
|
+
}
|
|
1199
|
+
|
|
507
1200
|
proc.stdout.on('data', (chunk) => {
|
|
508
1201
|
const text = chunk.toString();
|
|
509
1202
|
stdout += text;
|
|
510
1203
|
outputLines += text.split('\n').filter(l => l.trim()).length;
|
|
511
1204
|
printClaudeLine(text);
|
|
512
1205
|
if (!verbose) {
|
|
513
|
-
|
|
514
|
-
|
|
1206
|
+
// Update spinner with progress info
|
|
1207
|
+
spinner.update(`Claude is processing (${outputLines} lines)`);
|
|
515
1208
|
}
|
|
516
1209
|
});
|
|
517
1210
|
|
|
@@ -525,6 +1218,11 @@ function dispatchToClaude(prompt) {
|
|
|
525
1218
|
proc.on('close', (code) => {
|
|
526
1219
|
isClaudeRunning = false;
|
|
527
1220
|
activeClaudeProc = null;
|
|
1221
|
+
|
|
1222
|
+
// Stop spinner
|
|
1223
|
+
if (!verbose) {
|
|
1224
|
+
spinner.stop();
|
|
1225
|
+
}
|
|
528
1226
|
clearClaudeStatus();
|
|
529
1227
|
|
|
530
1228
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
@@ -540,6 +1238,11 @@ function dispatchToClaude(prompt) {
|
|
|
540
1238
|
proc.on('error', (err) => {
|
|
541
1239
|
isClaudeRunning = false;
|
|
542
1240
|
activeClaudeProc = null;
|
|
1241
|
+
|
|
1242
|
+
// Stop spinner on error
|
|
1243
|
+
if (!verbose) {
|
|
1244
|
+
spinner.stop();
|
|
1245
|
+
}
|
|
543
1246
|
clearClaudeStatus();
|
|
544
1247
|
reject(err);
|
|
545
1248
|
});
|
|
@@ -553,21 +1256,35 @@ function dispatchToClaude(prompt) {
|
|
|
553
1256
|
// CONVERSATION MANAGEMENT
|
|
554
1257
|
// ═══════════════════════════════════════════════════════════════
|
|
555
1258
|
|
|
556
|
-
// Keep conversation history bounded
|
|
557
|
-
//
|
|
1259
|
+
// Keep conversation history bounded with efficient memory management
|
|
1260
|
+
// Always keep first 2 messages (greeting context) and last 18 messages
|
|
558
1261
|
function trimHistory() {
|
|
559
1262
|
const MAX_MESSAGES = 24;
|
|
560
|
-
|
|
1263
|
+
const MAX_MESSAGE_LENGTH = 5000; // Truncate individual messages too
|
|
1264
|
+
|
|
1265
|
+
if (conversationHistory.length <= MAX_MESSAGES) {
|
|
1266
|
+
// Still truncate long messages even if under message limit
|
|
1267
|
+
conversationHistory.forEach(msg => {
|
|
1268
|
+
if (msg.content && msg.content.length > MAX_MESSAGE_LENGTH) {
|
|
1269
|
+
msg.content = msg.content.substring(0, MAX_MESSAGE_LENGTH) + '... [truncated]';
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
561
1274
|
|
|
562
1275
|
const keep = 2; // greeting exchange
|
|
563
|
-
const tail = MAX_MESSAGES - keep;
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
conversationHistory.
|
|
1276
|
+
const tail = MAX_MESSAGES - keep - 1; // -1 for the trim notice
|
|
1277
|
+
|
|
1278
|
+
// Use splice for efficient in-place modification
|
|
1279
|
+
const trimNotice = { role: 'user', content: '[Earlier conversation trimmed for context. See dispatch summaries in system prompt for work done.]' };
|
|
1280
|
+
conversationHistory.splice(keep, conversationHistory.length - keep - tail, trimNotice);
|
|
1281
|
+
|
|
1282
|
+
// Truncate long messages
|
|
1283
|
+
conversationHistory.forEach(msg => {
|
|
1284
|
+
if (msg.content && msg.content.length > MAX_MESSAGE_LENGTH) {
|
|
1285
|
+
msg.content = msg.content.substring(0, MAX_MESSAGE_LENGTH) + '... [truncated]';
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
571
1288
|
}
|
|
572
1289
|
|
|
573
1290
|
// Truncate Claude output for Groq review (Groq doesn't need the full thing)
|
|
@@ -584,10 +1301,15 @@ function truncateForReview(output, maxChars) {
|
|
|
584
1301
|
// COMMAND HANDLERS
|
|
585
1302
|
// ═══════════════════════════════════════════════════════════════
|
|
586
1303
|
|
|
587
|
-
function handleCommand(input) {
|
|
1304
|
+
async function handleCommand(input) {
|
|
588
1305
|
const cmd = input.toLowerCase().trim();
|
|
589
1306
|
|
|
590
1307
|
if (cmd === '/exit' || cmd === '/quit' || cmd === '/q') {
|
|
1308
|
+
// Save feedback data before exit
|
|
1309
|
+
if (aiInsights.feedbackHistory.length > 0) {
|
|
1310
|
+
saveFeedbackData(); // Fire and forget on exit
|
|
1311
|
+
console.log(` ${C.green}✓ AI learning saved${C.reset}`);
|
|
1312
|
+
}
|
|
591
1313
|
log('SESSION END (user exit)');
|
|
592
1314
|
console.log(`\n ${C.dim}Session ended. ${claudeDispatches} dispatches to Claude.${C.reset}`);
|
|
593
1315
|
console.log(` ${C.dim}Log: ${logFile}${C.reset}\n`);
|
|
@@ -618,18 +1340,37 @@ function handleCommand(input) {
|
|
|
618
1340
|
|
|
619
1341
|
if (cmd === '/diff') {
|
|
620
1342
|
try {
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1343
|
+
// Use spawn instead of spawnSync for better performance
|
|
1344
|
+
const { spawn } = require('child_process');
|
|
1345
|
+
const gitDiff = spawn('git', ['diff', '--stat'], {
|
|
1346
|
+
cwd: WORKSPACE,
|
|
1347
|
+
stdio: 'pipe',
|
|
1348
|
+
shell: false // Prevent shell injection
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
let output = '';
|
|
1352
|
+
gitDiff.stdout.on('data', (data) => {
|
|
1353
|
+
output += data.toString();
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
gitDiff.on('close', (code) => {
|
|
1357
|
+
if (code === 0 && output.trim()) {
|
|
1358
|
+
console.log(`\n ${C.dim}Git changes in workspace:${C.reset}`);
|
|
1359
|
+
output.trim().split('\n').forEach(line => console.log(` ${C.dim} ${line}${C.reset}`));
|
|
1360
|
+
} else {
|
|
1361
|
+
console.log(`\n ${C.dim}No uncommitted changes.${C.reset}`);
|
|
1362
|
+
}
|
|
1363
|
+
console.log('');
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
gitDiff.on('error', () => {
|
|
1367
|
+
console.log(`\n ${C.dim}Not a git repository or git not available.${C.reset}`);
|
|
1368
|
+
console.log('');
|
|
1369
|
+
});
|
|
629
1370
|
} catch (e) {
|
|
630
|
-
console.log(`\n ${C.dim}
|
|
1371
|
+
console.log(`\n ${C.dim}Error running git diff.${C.reset}`);
|
|
1372
|
+
console.log('');
|
|
631
1373
|
}
|
|
632
|
-
console.log('');
|
|
633
1374
|
return true;
|
|
634
1375
|
}
|
|
635
1376
|
|
|
@@ -638,6 +1379,147 @@ function handleCommand(input) {
|
|
|
638
1379
|
return true;
|
|
639
1380
|
}
|
|
640
1381
|
|
|
1382
|
+
if (cmd === '/ai') {
|
|
1383
|
+
console.log(`\n ${C.cyan}${C.bold}AI Decision Layer Status${C.reset}`);
|
|
1384
|
+
console.log(` ${C.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`);
|
|
1385
|
+
console.log(` ${C.dim}Project Type:${C.reset} ${aiInsights.projectType || 'Not detected'}`);
|
|
1386
|
+
console.log(` ${C.dim}Tech Stack:${C.reset} ${aiInsights.patterns.length > 0 ? aiInsights.patterns.join(', ') : 'Not detected'}`);
|
|
1387
|
+
console.log(` ${C.dim}Complexity:${C.reset} ${aiInsights.complexity}`);
|
|
1388
|
+
console.log(` ${C.dim}Context Cache:${C.reset} ${aiInsights.contextCache.size} items`);
|
|
1389
|
+
console.log(` ${C.dim}Decision History:${C.reset} ${aiInsights.decisionHistory.length} decisions`);
|
|
1390
|
+
console.log(` ${C.dim}Feedback Count:${C.reset} ${aiInsights.feedbackHistory.length} items`);
|
|
1391
|
+
|
|
1392
|
+
// Show pattern weights if any have been adjusted
|
|
1393
|
+
const adjustedPatterns = Array.from(aiInsights.patternWeights.entries())
|
|
1394
|
+
.filter(([_, weight]) => weight !== 1.0);
|
|
1395
|
+
if (adjustedPatterns.length > 0) {
|
|
1396
|
+
console.log(`\n ${C.dim}Learned Pattern Weights:${C.reset}`);
|
|
1397
|
+
adjustedPatterns.forEach(([pattern, weight]) => {
|
|
1398
|
+
const indicator = weight > 1.0 ? '↑' : '↓';
|
|
1399
|
+
console.log(` ${pattern}: ${weight.toFixed(2)} ${indicator}`);
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (aiInsights.decisionHistory.length > 0) {
|
|
1404
|
+
console.log(`\n ${C.dim}Recent Decisions:${C.reset}`);
|
|
1405
|
+
aiInsights.decisionHistory.slice(-3).forEach((decision, i) => {
|
|
1406
|
+
const time = new Date(decision.timestamp).toLocaleTimeString();
|
|
1407
|
+
console.log(` ${i + 1}. ${time} - ${decision.decision} (${decision.input.slice(0, 30)}...)`);
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
if (conversationHistory.length > 2) {
|
|
1412
|
+
const recentInput = conversationHistory.filter(m => m.role === 'user').slice(-1)[0];
|
|
1413
|
+
if (recentInput) {
|
|
1414
|
+
const quickInsights = aiEngine.analyzeIntent(recentInput.content);
|
|
1415
|
+
if (quickInsights.patterns.length > 0) {
|
|
1416
|
+
console.log(`\n ${C.dim}Last Intent Analysis:${C.reset}`);
|
|
1417
|
+
console.log(` Patterns: ${quickInsights.patterns.join(', ')}`);
|
|
1418
|
+
console.log(` Priority: ${quickInsights.priority}`);
|
|
1419
|
+
console.log(` Confidence: ${(quickInsights.confidence * 100).toFixed(0)}%`);
|
|
1420
|
+
if (quickInsights.baseConfidence !== quickInsights.confidence) {
|
|
1421
|
+
console.log(` Adjusted from: ${(quickInsights.baseConfidence * 100).toFixed(0)}% (feedback-based)`);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
console.log('');
|
|
1427
|
+
return true;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (cmd === '/feedback' || cmd === '/fb') {
|
|
1431
|
+
console.log(`\n ${C.cyan}${C.bold}Feedback System${C.reset}`);
|
|
1432
|
+
console.log(` ${C.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`);
|
|
1433
|
+
|
|
1434
|
+
if (feedbackState.lastSuggestions.length > 0) {
|
|
1435
|
+
console.log(` ${C.dim}Last suggestions:${C.reset}`);
|
|
1436
|
+
feedbackState.lastSuggestions.forEach((sugg, i) => {
|
|
1437
|
+
console.log(` ${i + 1}. ${sugg}`);
|
|
1438
|
+
});
|
|
1439
|
+
console.log(`\n ${C.green}Rate with: +1 (good) or -1 (needs improvement)${C.reset}`);
|
|
1440
|
+
console.log(` ${C.green}Or provide detailed feedback: /feedback "your text here"${C.reset}`);
|
|
1441
|
+
feedbackState.awaitingRating = true;
|
|
1442
|
+
} else {
|
|
1443
|
+
console.log(` ${C.dim}No recent suggestions to rate.${C.reset}`);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (aiInsights.feedbackHistory.length > 0) {
|
|
1447
|
+
console.log(`\n ${C.dim}Recent feedback:${C.reset}`);
|
|
1448
|
+
aiInsights.feedbackHistory.slice(-3).forEach((fb, i) => {
|
|
1449
|
+
const time = new Date(fb.timestamp).toLocaleTimeString();
|
|
1450
|
+
const icon = fb.rating === 'up' ? '👍' : fb.rating === 'down' ? '👎' : '💬';
|
|
1451
|
+
console.log(` ${time} ${icon} ${fb.text ? fb.text.slice(0, 40) + '...' : ''}`);
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
console.log('');
|
|
1456
|
+
return true;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Code review commands
|
|
1460
|
+
if (cmd === '/review') {
|
|
1461
|
+
const args = input.split(' ').slice(1);
|
|
1462
|
+
|
|
1463
|
+
if (args.length === 0) {
|
|
1464
|
+
// Review last Claude output if available
|
|
1465
|
+
if (dispatchSummaries.length > 0) {
|
|
1466
|
+
const lastOutput = dispatchSummaries[dispatchSummaries.length - 1];
|
|
1467
|
+
const codeBlocks = extractCodeFromOutput(lastOutput);
|
|
1468
|
+
|
|
1469
|
+
if (codeBlocks.length > 0) {
|
|
1470
|
+
console.log(`\n ${C.cyan}Reviewing code from last Claude output...${C.reset}\n`);
|
|
1471
|
+
for (const block of codeBlocks) {
|
|
1472
|
+
const review = await reviewCode(block.code, `<inline-${block.language}>`, 1);
|
|
1473
|
+
displayCodeReview(review);
|
|
1474
|
+
}
|
|
1475
|
+
} else {
|
|
1476
|
+
console.log(` ${C.yellow}No code found in last output. Use: /review [file]${C.reset}\n`);
|
|
1477
|
+
}
|
|
1478
|
+
} else {
|
|
1479
|
+
console.log(` ${C.yellow}No recent output to review. Use: /review [file]${C.reset}\n`);
|
|
1480
|
+
}
|
|
1481
|
+
} else {
|
|
1482
|
+
// Review specific file
|
|
1483
|
+
const filePath = path.resolve(WORKSPACE, args[0]);
|
|
1484
|
+
try {
|
|
1485
|
+
const fileContent = await fsPromises.readFile(filePath, 'utf8');
|
|
1486
|
+
console.log(`\n ${C.cyan}Reviewing ${path.basename(filePath)}...${C.reset}\n`);
|
|
1487
|
+
const review = await reviewCode(fileContent, filePath, 1);
|
|
1488
|
+
displayCodeReview(review);
|
|
1489
|
+
} catch (e) {
|
|
1490
|
+
console.log(` ${C.red}Error reading file: ${e.message}${C.reset}\n`);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (cmd === '/autoreview') {
|
|
1497
|
+
codeReview.autoReviewEnabled = !codeReview.autoReviewEnabled;
|
|
1498
|
+
const status = codeReview.autoReviewEnabled ? 'ENABLED' : 'DISABLED';
|
|
1499
|
+
console.log(`\n ${C.green}Auto-review ${status}${C.reset}`);
|
|
1500
|
+
console.log(` ${C.dim}Code will ${codeReview.autoReviewEnabled ? '' : 'not '}be automatically reviewed after Claude outputs.${C.reset}\n`);
|
|
1501
|
+
return true;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
if (cmd === '/reviews') {
|
|
1505
|
+
if (codeReview.reviewHistory.length === 0) {
|
|
1506
|
+
console.log(`\n ${C.dim}No code reviews yet.${C.reset}\n`);
|
|
1507
|
+
} else {
|
|
1508
|
+
console.log(`\n ${C.cyan}━━━ Code Review History ━━━${C.reset}`);
|
|
1509
|
+
codeReview.reviewHistory.slice(-5).forEach(review => {
|
|
1510
|
+
const time = new Date(review.timestamp).toLocaleTimeString();
|
|
1511
|
+
const commentCount = review.comments.length;
|
|
1512
|
+
const errorCount = review.comments.filter(c => c.severity === 'error').length;
|
|
1513
|
+
const warningCount = review.comments.filter(c => c.severity === 'warning').length;
|
|
1514
|
+
|
|
1515
|
+
console.log(` ${C.dim}${time}${C.reset} [${review.id}] ${review.filePath}`);
|
|
1516
|
+
console.log(` ${errorCount} errors, ${warningCount} warnings, ${commentCount} total comments`);
|
|
1517
|
+
});
|
|
1518
|
+
console.log('');
|
|
1519
|
+
}
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
641
1523
|
return false;
|
|
642
1524
|
}
|
|
643
1525
|
|
|
@@ -646,14 +1528,77 @@ function handleCommand(input) {
|
|
|
646
1528
|
// ═══════════════════════════════════════════════════════════════
|
|
647
1529
|
|
|
648
1530
|
let projectContext = '';
|
|
1531
|
+
let inputDebounceTimer = null;
|
|
1532
|
+
const INPUT_DEBOUNCE_MS = 100;
|
|
1533
|
+
|
|
1534
|
+
// Debounced input handler
|
|
1535
|
+
function debouncedHandleUserInput(input, rl) {
|
|
1536
|
+
if (inputDebounceTimer) {
|
|
1537
|
+
clearTimeout(inputDebounceTimer);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
inputDebounceTimer = setTimeout(() => {
|
|
1541
|
+
inputDebounceTimer = null;
|
|
1542
|
+
handleUserInput(input, rl);
|
|
1543
|
+
}, INPUT_DEBOUNCE_MS);
|
|
1544
|
+
}
|
|
649
1545
|
|
|
650
1546
|
async function handleUserInput(input, rl) {
|
|
651
1547
|
const trimmed = input.trim();
|
|
652
1548
|
if (!trimmed) return;
|
|
653
1549
|
|
|
1550
|
+
// Handle quick feedback (thumbs up/down, +1/-1)
|
|
1551
|
+
if (trimmed === '👍' || trimmed === '+1' || trimmed === '++' ||
|
|
1552
|
+
trimmed === '👎' || trimmed === '-1' || trimmed === '--') {
|
|
1553
|
+
|
|
1554
|
+
const rating = (trimmed === '👍' || trimmed === '+1' || trimmed === '++') ? 'up' : 'down';
|
|
1555
|
+
|
|
1556
|
+
if (feedbackState.lastSuggestions.length > 0) {
|
|
1557
|
+
const feedback = {
|
|
1558
|
+
type: 'quick',
|
|
1559
|
+
rating: rating,
|
|
1560
|
+
text: null,
|
|
1561
|
+
context: feedbackState.pendingFeedback,
|
|
1562
|
+
suggestions: feedbackState.lastSuggestions
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
const result = aiEngine.processFeedback(feedback);
|
|
1566
|
+
console.log(`\n ${C.green}${result.message}${C.reset}\n`);
|
|
1567
|
+
log(`FEEDBACK: ${rating} for suggestions`);
|
|
1568
|
+
|
|
1569
|
+
// Clear feedback state
|
|
1570
|
+
feedbackState.awaitingRating = false;
|
|
1571
|
+
feedbackState.feedbackPromptShown = false;
|
|
1572
|
+
return;
|
|
1573
|
+
} else {
|
|
1574
|
+
console.log(`\n ${C.yellow}No recent suggestions to rate. Use /feedback to see feedback options.${C.reset}\n`);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// Handle detailed feedback with text
|
|
1580
|
+
if (trimmed.startsWith('/feedback "') || trimmed.startsWith('/fb "')) {
|
|
1581
|
+
const match = trimmed.match(/^\/(?:feedback|fb)\s+"([^"]+)"/);
|
|
1582
|
+
if (match) {
|
|
1583
|
+
const feedbackText = match[1];
|
|
1584
|
+
const feedback = {
|
|
1585
|
+
type: 'detailed',
|
|
1586
|
+
rating: null,
|
|
1587
|
+
text: feedbackText,
|
|
1588
|
+
context: feedbackState.pendingFeedback,
|
|
1589
|
+
suggestions: feedbackState.lastSuggestions
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
const result = aiEngine.processFeedback(feedback);
|
|
1593
|
+
console.log(`\n ${C.green}${result.message}${C.reset}\n`);
|
|
1594
|
+
log(`FEEDBACK: "${feedbackText}"`);
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
654
1599
|
// Handle slash commands
|
|
655
1600
|
if (trimmed.startsWith('/')) {
|
|
656
|
-
if (handleCommand(trimmed)) return;
|
|
1601
|
+
if (await handleCommand(trimmed)) return;
|
|
657
1602
|
// Unknown command — pass through to Knoxis
|
|
658
1603
|
}
|
|
659
1604
|
|
|
@@ -670,27 +1615,71 @@ async function handleUserInput(input, rl) {
|
|
|
670
1615
|
try {
|
|
671
1616
|
// Send to Knoxis (Groq)
|
|
672
1617
|
trimHistory();
|
|
1618
|
+
|
|
1619
|
+
// Show spinner while Knoxis is thinking
|
|
1620
|
+
spinner.start('Knoxis is thinking');
|
|
673
1621
|
const response = await callGroq(conversationHistory, projectContext);
|
|
1622
|
+
spinner.stop();
|
|
674
1623
|
|
|
675
1624
|
if (response.action === 'dispatch' && response.claudePrompt) {
|
|
676
1625
|
// ─── DISPATCH TO CLAUDE ───
|
|
677
1626
|
printKnoxis(response.message);
|
|
678
1627
|
log(`KNOXIS (dispatch): ${response.message}`);
|
|
679
1628
|
|
|
1629
|
+
// ─── AI DECISION LAYER: Analyze and Enrich ───
|
|
1630
|
+
const insights = aiEngine.generateContextualInsights(trimmed, conversationHistory, projectContext);
|
|
1631
|
+
|
|
1632
|
+
// Show AI insights if verbose mode
|
|
1633
|
+
if (verbose && (insights.recommendations.length > 0 || insights.warnings.length > 0)) {
|
|
1634
|
+
console.log(`\n ${C.cyan}AI Insights:${C.reset}`);
|
|
1635
|
+
if (insights.intent.patterns.length > 0) {
|
|
1636
|
+
console.log(` ${C.dim} Intent: ${insights.intent.patterns.join(', ')} (${insights.intent.priority} priority)${C.reset}`);
|
|
1637
|
+
}
|
|
1638
|
+
console.log(` ${C.dim} Complexity: ${insights.complexity}${C.reset}`);
|
|
1639
|
+
if (insights.recommendations.length > 0) {
|
|
1640
|
+
console.log(` ${C.dim} Recommendations:${C.reset}`);
|
|
1641
|
+
insights.recommendations.forEach(r => console.log(` ${C.dim} • ${r}${C.reset}`));
|
|
1642
|
+
}
|
|
1643
|
+
if (insights.warnings.length > 0) {
|
|
1644
|
+
console.log(` ${C.yellow} Warnings:${C.reset}`);
|
|
1645
|
+
insights.warnings.forEach(w => console.log(` ${C.yellow} ⚠ ${w}${C.reset}`));
|
|
1646
|
+
}
|
|
1647
|
+
console.log('');
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// Enrich Claude prompt with AI insights
|
|
1651
|
+
const enrichedPrompt = aiEngine.enrichClaudePrompt(response.claudePrompt, insights, projectContext);
|
|
1652
|
+
|
|
1653
|
+
// Record AI decision
|
|
1654
|
+
aiEngine.recordDecision(trimmed, 'dispatch_enriched', insights);
|
|
1655
|
+
|
|
680
1656
|
// Record the assistant dispatch decision
|
|
681
1657
|
conversationHistory.push({
|
|
682
1658
|
role: 'assistant',
|
|
683
|
-
content: JSON.stringify({ action: 'dispatch', message: response.message })
|
|
1659
|
+
content: JSON.stringify({ action: 'dispatch', message: response.message, aiInsights: insights })
|
|
684
1660
|
});
|
|
685
1661
|
|
|
686
1662
|
printDivider('Dispatching to Claude Code', C.blue);
|
|
687
|
-
if (!verbose) printClaudeStatus('starting...');
|
|
688
1663
|
|
|
689
1664
|
try {
|
|
690
|
-
const result = await dispatchToClaude(
|
|
1665
|
+
const result = await dispatchToClaude(enrichedPrompt);
|
|
691
1666
|
|
|
692
1667
|
printDivider(`Claude finished (${result.elapsed}s, ${result.lines} lines)`, C.green);
|
|
693
1668
|
|
|
1669
|
+
// Auto-review code if enabled
|
|
1670
|
+
if (codeReview.autoReviewEnabled) {
|
|
1671
|
+
const codeBlocks = extractCodeFromOutput(result.output);
|
|
1672
|
+
if (codeBlocks.length > 0) {
|
|
1673
|
+
console.log(`\n ${C.cyan}Auto-reviewing code...${C.reset}`);
|
|
1674
|
+
for (const block of codeBlocks) {
|
|
1675
|
+
const review = await reviewCode(block.code, `<${block.language}>`, 1);
|
|
1676
|
+
if (review && review.comments.length > 0) {
|
|
1677
|
+
displayCodeReview(review);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
694
1683
|
// Send output to Knoxis for review
|
|
695
1684
|
const reviewMessages = [
|
|
696
1685
|
...conversationHistory,
|
|
@@ -700,18 +1689,45 @@ async function handleUserInput(input, rl) {
|
|
|
700
1689
|
}
|
|
701
1690
|
];
|
|
702
1691
|
|
|
1692
|
+
spinner.start('Knoxis is reviewing Claude\'s work');
|
|
703
1693
|
const review = await callGroq(reviewMessages, projectContext);
|
|
1694
|
+
spinner.stop();
|
|
704
1695
|
printKnoxis(review.message);
|
|
705
1696
|
log(`KNOXIS (review): ${review.message}`);
|
|
706
1697
|
|
|
707
|
-
//
|
|
1698
|
+
// ─── AI DECISION LAYER: Suggest Next Steps ───
|
|
1699
|
+
const nextSteps = aiEngine.suggestNextSteps(result.output, trimmed);
|
|
1700
|
+
if (nextSteps.length > 0) {
|
|
1701
|
+
console.log(`\n ${C.cyan}AI Suggested Next Steps:${C.reset}`);
|
|
1702
|
+
nextSteps.forEach((step, i) => {
|
|
1703
|
+
console.log(` ${C.dim} ${i + 1}. ${step}${C.reset}`);
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// Store suggestions for feedback
|
|
1707
|
+
feedbackState.lastSuggestions = nextSteps;
|
|
1708
|
+
feedbackState.pendingFeedback = insights;
|
|
1709
|
+
|
|
1710
|
+
// Prompt for feedback (but don't be annoying)
|
|
1711
|
+
if (!feedbackState.feedbackPromptShown) {
|
|
1712
|
+
console.log(`\n ${C.dim}💡 Rate suggestions with +1 (helpful) or -1 (not helpful)${C.reset}`);
|
|
1713
|
+
feedbackState.feedbackPromptShown = true;
|
|
1714
|
+
}
|
|
1715
|
+
console.log('');
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// Track dispatch summary and full output for code review
|
|
708
1719
|
const shortSummary = review.message.split('\n')[0].slice(0, 150);
|
|
709
|
-
dispatchSummaries.push(
|
|
1720
|
+
dispatchSummaries.push(result.output); // Store full output for review command
|
|
1721
|
+
codeReview.lastReviewedCode = result.output; // Cache for quick access
|
|
710
1722
|
|
|
711
|
-
// Add review to history
|
|
1723
|
+
// Add review to history with AI suggestions
|
|
712
1724
|
conversationHistory.push({
|
|
713
1725
|
role: 'assistant',
|
|
714
|
-
content: JSON.stringify({
|
|
1726
|
+
content: JSON.stringify({
|
|
1727
|
+
action: 'respond',
|
|
1728
|
+
message: review.message,
|
|
1729
|
+
aiNextSteps: nextSteps
|
|
1730
|
+
})
|
|
715
1731
|
});
|
|
716
1732
|
|
|
717
1733
|
} catch (claudeErr) {
|
|
@@ -776,12 +1792,20 @@ async function main() {
|
|
|
776
1792
|
// Detect project
|
|
777
1793
|
projectContext = detectProject();
|
|
778
1794
|
|
|
1795
|
+
// Load feedback data from previous sessions
|
|
1796
|
+
await loadFeedbackData();
|
|
1797
|
+
|
|
779
1798
|
// Print header
|
|
780
1799
|
printHeader();
|
|
781
1800
|
if (projectContext) {
|
|
782
1801
|
console.log(` ${C.dim}${projectContext}${C.reset}\n`);
|
|
783
1802
|
}
|
|
784
1803
|
|
|
1804
|
+
// Show if we have learned patterns
|
|
1805
|
+
if (aiInsights.patternWeights.size > 0 || aiInsights.suggestionScores.size > 0) {
|
|
1806
|
+
console.log(` ${C.green}✓ Loaded AI learning from ${aiInsights.feedbackHistory.length} previous feedback items${C.reset}\n`);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
785
1809
|
log(`SESSION START: ${SESSION_ID}`);
|
|
786
1810
|
log(`WORKSPACE: ${WORKSPACE}`);
|
|
787
1811
|
log(`PROJECT: ${projectContext}`);
|
|
@@ -795,6 +1819,9 @@ async function main() {
|
|
|
795
1819
|
|
|
796
1820
|
// Handle Ctrl+C — kill Claude if running, otherwise exit
|
|
797
1821
|
process.on('SIGINT', () => {
|
|
1822
|
+
// Stop any active spinner
|
|
1823
|
+
spinner.stop();
|
|
1824
|
+
|
|
798
1825
|
if (isClaudeRunning && activeClaudeProc) {
|
|
799
1826
|
console.log(`\n\n ${C.yellow}Cancelling Claude...${C.reset}\n`);
|
|
800
1827
|
try { activeClaudeProc.kill('SIGTERM'); } catch (e) {}
|
|
@@ -802,6 +1829,10 @@ async function main() {
|
|
|
802
1829
|
try { if (activeClaudeProc) activeClaudeProc.kill('SIGKILL'); } catch (e) {}
|
|
803
1830
|
}, 3000);
|
|
804
1831
|
} else {
|
|
1832
|
+
// Save feedback data before exit
|
|
1833
|
+
if (aiInsights.feedbackHistory.length > 0) {
|
|
1834
|
+
saveFeedbackData(); // Fire and forget on exit
|
|
1835
|
+
}
|
|
805
1836
|
log('SESSION END (Ctrl+C)');
|
|
806
1837
|
console.log(`\n\n ${C.dim}Session ended. ${claudeDispatches} dispatches to Claude.${C.reset}`);
|
|
807
1838
|
console.log(` ${C.dim}Log: ${logFile}${C.reset}\n`);
|
|
@@ -819,21 +1850,42 @@ async function main() {
|
|
|
819
1850
|
conversationHistory.push({ role: 'user', content: greetingContent });
|
|
820
1851
|
log(`INITIAL TASK: ${initialTask}`);
|
|
821
1852
|
|
|
1853
|
+
spinner.start('Initializing session');
|
|
822
1854
|
const greeting = await callGroq(conversationHistory, projectContext);
|
|
1855
|
+
spinner.stop();
|
|
823
1856
|
|
|
824
1857
|
if (greeting.action === 'dispatch' && greeting.claudePrompt) {
|
|
825
1858
|
printKnoxis(greeting.message);
|
|
1859
|
+
|
|
1860
|
+
// AI insights for initial task
|
|
1861
|
+
const insights = aiEngine.generateContextualInsights(initialTask, conversationHistory, projectContext);
|
|
1862
|
+
const enrichedPrompt = aiEngine.enrichClaudePrompt(greeting.claudePrompt, insights, projectContext);
|
|
1863
|
+
aiEngine.recordDecision(initialTask, 'initial_dispatch', insights);
|
|
1864
|
+
|
|
826
1865
|
conversationHistory.push({
|
|
827
1866
|
role: 'assistant',
|
|
828
|
-
content: JSON.stringify({ action: 'dispatch', message: greeting.message })
|
|
1867
|
+
content: JSON.stringify({ action: 'dispatch', message: greeting.message, aiInsights: insights })
|
|
829
1868
|
});
|
|
830
1869
|
|
|
831
1870
|
printDivider('Dispatching to Claude Code', C.blue);
|
|
832
|
-
if (!verbose) printClaudeStatus('starting...');
|
|
833
1871
|
|
|
834
|
-
const result = await dispatchToClaude(
|
|
1872
|
+
const result = await dispatchToClaude(enrichedPrompt);
|
|
835
1873
|
printDivider(`Claude finished (${result.elapsed}s, ${result.lines} lines)`, C.green);
|
|
836
1874
|
|
|
1875
|
+
// Auto-review code if enabled
|
|
1876
|
+
if (codeReview.autoReviewEnabled) {
|
|
1877
|
+
const codeBlocks = extractCodeFromOutput(result.output);
|
|
1878
|
+
if (codeBlocks.length > 0) {
|
|
1879
|
+
console.log(`\n ${C.cyan}Auto-reviewing code...${C.reset}`);
|
|
1880
|
+
for (const block of codeBlocks) {
|
|
1881
|
+
const review = await reviewCode(block.code, `<${block.language}>`, 1);
|
|
1882
|
+
if (review && review.comments.length > 0) {
|
|
1883
|
+
displayCodeReview(review);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
837
1889
|
const reviewMessages = [
|
|
838
1890
|
...conversationHistory,
|
|
839
1891
|
{
|
|
@@ -842,9 +1894,12 @@ async function main() {
|
|
|
842
1894
|
}
|
|
843
1895
|
];
|
|
844
1896
|
|
|
1897
|
+
spinner.start('Knoxis is reviewing Claude\'s work');
|
|
845
1898
|
const review = await callGroq(reviewMessages, projectContext);
|
|
1899
|
+
spinner.stop();
|
|
846
1900
|
printKnoxis(review.message);
|
|
847
|
-
dispatchSummaries.push(
|
|
1901
|
+
dispatchSummaries.push(result.output); // Store full output for review command
|
|
1902
|
+
codeReview.lastReviewedCode = result.output; // Cache for quick access
|
|
848
1903
|
conversationHistory.push({
|
|
849
1904
|
role: 'assistant',
|
|
850
1905
|
content: JSON.stringify({ action: 'respond', message: review.message })
|
|
@@ -864,7 +1919,9 @@ async function main() {
|
|
|
864
1919
|
content: `[SYSTEM: Collaborative session started. Workspace: ${WORKSPACE}. Project: ${projectContext || 'unknown'}. Greet the user briefly (1-2 sentences) and ask what they want to work on.]`
|
|
865
1920
|
});
|
|
866
1921
|
|
|
1922
|
+
spinner.start('Initializing session');
|
|
867
1923
|
const greeting = await callGroq(conversationHistory, projectContext);
|
|
1924
|
+
spinner.stop();
|
|
868
1925
|
printKnoxis(greeting.message);
|
|
869
1926
|
conversationHistory.push({
|
|
870
1927
|
role: 'assistant',
|