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.
Files changed (2) hide show
  1. package/knoxis-collab.js +1114 -57
  2. 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 = loadConfig();
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
- try { fs.appendFileSync(logFile, line); } catch (e) {}
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
- if (!verbose) {
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
- if (!verbose) {
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
- // DETECT PROJECT CONTEXT (lightweight)
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)) info.push('Java/Maven project (pom.xml)');
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)) info.push('Java/Gradle project');
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)) info.push('Next.js app');
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
- const result = spawnSync('git', ['branch', '--show-current'], { cwd: WORKSPACE, stdio: 'pipe' });
299
- if (result.status === 0) {
300
- const branch = result.stdout.toString().trim();
301
- if (branch) info.push(`Git branch: ${branch}`);
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
- const proc = spawn(CLAUDE_BIN, args, {
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
- const elapsed = Math.round((Date.now() - startTime) / 1000);
514
- printClaudeStatus(`working... ${outputLines} lines, ${elapsed}s`);
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. Always keep first 2 messages (greeting context)
557
- // and last 18 messages. Truncate long assistant messages.
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
- if (conversationHistory.length <= MAX_MESSAGES) return;
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
- const trimmed = [
565
- ...conversationHistory.slice(0, keep),
566
- { role: 'user', content: '[Earlier conversation trimmed for context. See dispatch summaries in system prompt for work done.]' },
567
- ...conversationHistory.slice(-tail)
568
- ];
569
- conversationHistory.length = 0;
570
- conversationHistory.push(...trimmed);
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
- const result = spawnSync('git', ['diff', '--stat'], { cwd: WORKSPACE, stdio: 'pipe' });
622
- const output = result.stdout.toString().trim();
623
- if (output) {
624
- console.log(`\n ${C.dim}Git changes in workspace:${C.reset}`);
625
- output.split('\n').forEach(line => console.log(` ${C.dim} ${line}${C.reset}`));
626
- } else {
627
- console.log(`\n ${C.dim}No uncommitted changes.${C.reset}`);
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}Not a git repository or git not available.${C.reset}`);
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(response.claudePrompt);
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
- // Track dispatch summary (short version for system prompt context)
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(shortSummary);
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({ action: 'respond', message: review.message })
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(greeting.claudePrompt);
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(review.message.split('\n')[0].slice(0, 150));
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',