midas-mcp 2.9.0 → 3.2.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ai.d.ts +2 -0
  3. package/dist/ai.d.ts.map +1 -1
  4. package/dist/ai.js +28 -45
  5. package/dist/ai.js.map +1 -1
  6. package/dist/analyzer.d.ts.map +1 -1
  7. package/dist/analyzer.js +37 -87
  8. package/dist/analyzer.js.map +1 -1
  9. package/dist/config.d.ts +16 -0
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +109 -15
  12. package/dist/config.js.map +1 -1
  13. package/dist/context.d.ts +8 -1
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/context.js +76 -7
  16. package/dist/context.js.map +1 -1
  17. package/dist/providers.d.ts +46 -0
  18. package/dist/providers.d.ts.map +1 -0
  19. package/dist/providers.js +287 -0
  20. package/dist/providers.js.map +1 -0
  21. package/dist/server.d.ts.map +1 -1
  22. package/dist/server.js +9 -2
  23. package/dist/server.js.map +1 -1
  24. package/dist/tools/config.d.ts +73 -0
  25. package/dist/tools/config.d.ts.map +1 -0
  26. package/dist/tools/config.js +94 -0
  27. package/dist/tools/config.js.map +1 -0
  28. package/dist/tools/index.d.ts +1 -0
  29. package/dist/tools/index.d.ts.map +1 -1
  30. package/dist/tools/index.js +2 -0
  31. package/dist/tools/index.js.map +1 -1
  32. package/dist/tui.d.ts.map +1 -1
  33. package/dist/tui.js +293 -210
  34. package/dist/tui.js.map +1 -1
  35. package/package.json +17 -5
  36. package/dist/tests/analyze.test.d.ts +0 -2
  37. package/dist/tests/analyze.test.d.ts.map +0 -1
  38. package/dist/tests/analyze.test.js +0 -120
  39. package/dist/tests/analyze.test.js.map +0 -1
  40. package/dist/tests/context.test.d.ts +0 -2
  41. package/dist/tests/context.test.d.ts.map +0 -1
  42. package/dist/tests/context.test.js +0 -213
  43. package/dist/tests/context.test.js.map +0 -1
  44. package/dist/tests/edge-cases.test.d.ts +0 -2
  45. package/dist/tests/edge-cases.test.d.ts.map +0 -1
  46. package/dist/tests/edge-cases.test.js +0 -234
  47. package/dist/tests/edge-cases.test.js.map +0 -1
  48. package/dist/tests/journal.test.d.ts +0 -2
  49. package/dist/tests/journal.test.d.ts.map +0 -1
  50. package/dist/tests/journal.test.js +0 -184
  51. package/dist/tests/journal.test.js.map +0 -1
  52. package/dist/tests/metrics.test.d.ts +0 -2
  53. package/dist/tests/metrics.test.d.ts.map +0 -1
  54. package/dist/tests/metrics.test.js +0 -178
  55. package/dist/tests/metrics.test.js.map +0 -1
  56. package/dist/tests/phase.test.d.ts +0 -2
  57. package/dist/tests/phase.test.d.ts.map +0 -1
  58. package/dist/tests/phase.test.js +0 -110
  59. package/dist/tests/phase.test.js.map +0 -1
  60. package/dist/tests/prompts.test.d.ts +0 -2
  61. package/dist/tests/prompts.test.d.ts.map +0 -1
  62. package/dist/tests/prompts.test.js +0 -157
  63. package/dist/tests/prompts.test.js.map +0 -1
  64. package/dist/tests/search.test.d.ts +0 -2
  65. package/dist/tests/search.test.d.ts.map +0 -1
  66. package/dist/tests/search.test.js +0 -228
  67. package/dist/tests/search.test.js.map +0 -1
  68. package/dist/tests/security.test.d.ts +0 -2
  69. package/dist/tests/security.test.d.ts.map +0 -1
  70. package/dist/tests/security.test.js +0 -105
  71. package/dist/tests/security.test.js.map +0 -1
  72. package/dist/tests/server.test.d.ts +0 -2
  73. package/dist/tests/server.test.d.ts.map +0 -1
  74. package/dist/tests/server.test.js +0 -93
  75. package/dist/tests/server.test.js.map +0 -1
  76. package/dist/tests/techstack.test.d.ts +0 -2
  77. package/dist/tests/techstack.test.d.ts.map +0 -1
  78. package/dist/tests/techstack.test.js +0 -187
  79. package/dist/tests/techstack.test.js.map +0 -1
  80. package/dist/tests/tools.test.d.ts +0 -2
  81. package/dist/tests/tools.test.d.ts.map +0 -1
  82. package/dist/tests/tools.test.js +0 -137
  83. package/dist/tests/tools.test.js.map +0 -1
  84. package/dist/tests/tracker.test.d.ts +0 -2
  85. package/dist/tests/tracker.test.d.ts.map +0 -1
  86. package/dist/tests/tracker.test.js +0 -197
  87. package/dist/tests/tracker.test.js.map +0 -1
package/dist/tui.js CHANGED
@@ -375,6 +375,14 @@ export async function runInteractive() {
375
375
  console.error('Uncaught exception:', err);
376
376
  process.exit(1);
377
377
  });
378
+ process.on('unhandledRejection', (reason) => {
379
+ cleanup();
380
+ console.error('Unhandled rejection:', reason);
381
+ process.exit(1);
382
+ });
383
+ // Debounce tracking for rapid key presses
384
+ let lastKeyTime = 0;
385
+ const DEBOUNCE_MS = 50;
378
386
  // Start a new session for metrics tracking
379
387
  const currentPhase = loadState(projectPath).current;
380
388
  const sessionId = startSession(projectPath, currentPhase);
@@ -398,14 +406,30 @@ export async function runInteractive() {
398
406
  suggestionAcceptanceRate: getSuggestionAcceptanceRate(projectPath),
399
407
  };
400
408
  const render = () => {
401
- // Hide cursor, clear, draw, show cursor - prevents flicker
402
- process.stdout.write(hideCursor + clearScreen);
403
- process.stdout.write(drawUI(tuiState, projectPath));
404
- if (tuiState.message) {
405
- process.stdout.write(`\n ${tuiState.message}`);
406
- tuiState.message = '';
409
+ try {
410
+ // Hide cursor, clear, draw, show cursor - prevents flicker
411
+ process.stdout.write(hideCursor + clearScreen);
412
+ process.stdout.write(drawUI(tuiState, projectPath));
413
+ if (tuiState.message) {
414
+ process.stdout.write(`\n ${tuiState.message}`);
415
+ tuiState.message = '';
416
+ }
417
+ process.stdout.write(showCursor);
418
+ }
419
+ catch (error) {
420
+ // Attempt recovery
421
+ process.stdout.write(clearScreen + showCursor);
422
+ console.error('Render error:', error);
407
423
  }
408
- process.stdout.write(showCursor);
424
+ };
425
+ // Reset state to recover from bad state
426
+ const resetState = () => {
427
+ tuiState.analysis = null;
428
+ tuiState.isAnalyzing = false;
429
+ tuiState.showingSessionStart = false;
430
+ tuiState.showingRejectionInput = false;
431
+ tuiState.message = `${yellow}!${reset} State reset. Press [r] to re-analyze.`;
432
+ tuiState.filesChanged = false;
409
433
  };
410
434
  const runAnalysis = async () => {
411
435
  if (!tuiState.hasApiKey) {
@@ -445,6 +469,7 @@ export async function runInteractive() {
445
469
  render();
446
470
  };
447
471
  const promptForRejectionReason = async () => {
472
+ const TIMEOUT_MS = 60000; // 60 second timeout
448
473
  return new Promise((resolve) => {
449
474
  console.log(`\n ${yellow}Why are you declining this suggestion?${reset}`);
450
475
  console.log(` ${dim}(This helps Midas learn. Press Enter to skip.)${reset}\n`);
@@ -455,7 +480,16 @@ export async function runInteractive() {
455
480
  input: process.stdin,
456
481
  output: process.stdout,
457
482
  });
483
+ // Timeout protection
484
+ const timeout = setTimeout(() => {
485
+ rl.close();
486
+ if (process.stdin.isTTY) {
487
+ process.stdin.setRawMode(true);
488
+ }
489
+ resolve(''); // Timeout = no reason given
490
+ }, TIMEOUT_MS);
458
491
  rl.question(' > ', (answer) => {
492
+ clearTimeout(timeout);
459
493
  rl.close();
460
494
  if (process.stdin.isTTY) {
461
495
  process.stdin.setRawMode(true);
@@ -466,12 +500,12 @@ export async function runInteractive() {
466
500
  };
467
501
  // Multi-line input for pasting AI responses
468
502
  const promptForResponse = async () => {
503
+ const TIMEOUT_MS = 120000; // 2 minute timeout for pasting
469
504
  return new Promise((resolve) => {
470
505
  // Exit alternate screen temporarily for input
471
506
  process.stdout.write(exitAltScreen);
472
507
  console.log(`\n ${cyan}━━━ Paste AI Response ━━━${reset}`);
473
- console.log(` ${dim}Paste the exchange below. When done, type ${bold}END${reset}${dim} on a new line and press Enter.${reset}\n`);
474
- console.log(` ${dim}Format: First paste your prompt, then the AI response.${reset}\n`);
508
+ console.log(` ${dim}Paste the response, then press Enter twice to submit.${reset}\n`);
475
509
  if (process.stdin.isTTY) {
476
510
  process.stdin.setRawMode(false);
477
511
  }
@@ -480,48 +514,76 @@ export async function runInteractive() {
480
514
  output: process.stdout,
481
515
  });
482
516
  const lines = [];
483
- console.log(` ${dim}--- Start pasting (type END when done) ---${reset}\n`);
484
- rl.on('line', (line) => {
485
- if (line.trim().toUpperCase() === 'END') {
486
- rl.close();
487
- if (process.stdin.isTTY) {
488
- process.stdin.setRawMode(true);
517
+ let emptyLineCount = 0;
518
+ let timeoutId = null;
519
+ // Timeout protection - auto-cancel after 2 minutes
520
+ timeoutId = setTimeout(() => {
521
+ rl.close();
522
+ if (process.stdin.isTTY) {
523
+ process.stdin.setRawMode(true);
524
+ }
525
+ process.stdout.write(enterAltScreen);
526
+ resolve(null);
527
+ }, TIMEOUT_MS);
528
+ const finishInput = () => {
529
+ if (timeoutId)
530
+ clearTimeout(timeoutId);
531
+ rl.close();
532
+ if (process.stdin.isTTY) {
533
+ process.stdin.setRawMode(true);
534
+ }
535
+ process.stdout.write(enterAltScreen);
536
+ // Remove trailing empty lines
537
+ while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
538
+ lines.pop();
539
+ }
540
+ const fullText = lines.join('\n');
541
+ if (fullText.trim().length < 10) {
542
+ resolve(null);
543
+ return;
544
+ }
545
+ // Try to split into user prompt and AI response
546
+ const splitPatterns = [
547
+ /\n(?:assistant|ai|claude|cursor):\s*/i,
548
+ /\n(?:response|answer):\s*/i,
549
+ /\n---+\n/,
550
+ ];
551
+ let userPrompt = '';
552
+ let aiResponse = fullText;
553
+ for (const pattern of splitPatterns) {
554
+ const match = fullText.match(pattern);
555
+ if (match && match.index) {
556
+ userPrompt = fullText.slice(0, match.index).trim();
557
+ aiResponse = fullText.slice(match.index + match[0].length).trim();
558
+ break;
489
559
  }
490
- // Re-enter alternate screen
491
- process.stdout.write(enterAltScreen);
492
- const fullText = lines.join('\n');
493
- if (fullText.trim().length < 10) {
494
- resolve(null);
560
+ }
561
+ // If no split found, treat first 20% as prompt
562
+ if (!userPrompt && fullText.length > 100) {
563
+ const splitPoint = Math.min(500, Math.floor(fullText.length * 0.2));
564
+ userPrompt = fullText.slice(0, splitPoint).trim();
565
+ aiResponse = fullText.slice(splitPoint).trim();
566
+ }
567
+ resolve({ userPrompt, aiResponse });
568
+ };
569
+ rl.on('line', (line) => {
570
+ // Check for double Enter (two empty lines in a row)
571
+ if (line.trim() === '') {
572
+ emptyLineCount++;
573
+ if (emptyLineCount >= 2 && lines.length > 0) {
574
+ finishInput();
495
575
  return;
496
576
  }
497
- // Try to split into user prompt and AI response
498
- // Look for common patterns
499
- const splitPatterns = [
500
- /\n(?:assistant|ai|claude|cursor):\s*/i,
501
- /\n(?:response|answer):\s*/i,
502
- /\n---+\n/,
503
- ];
504
- let userPrompt = '';
505
- let aiResponse = fullText;
506
- for (const pattern of splitPatterns) {
507
- const match = fullText.match(pattern);
508
- if (match && match.index) {
509
- userPrompt = fullText.slice(0, match.index).trim();
510
- aiResponse = fullText.slice(match.index + match[0].length).trim();
511
- break;
512
- }
513
- }
514
- // If no split found, treat first 20% as prompt
515
- if (!userPrompt && fullText.length > 100) {
516
- const splitPoint = Math.min(500, Math.floor(fullText.length * 0.2));
517
- userPrompt = fullText.slice(0, splitPoint).trim();
518
- aiResponse = fullText.slice(splitPoint).trim();
519
- }
520
- resolve({ userPrompt, aiResponse });
521
577
  }
522
578
  else {
523
- lines.push(line);
579
+ emptyLineCount = 0;
580
+ }
581
+ // Also support "END" for those who prefer explicit termination
582
+ if (line.trim().toUpperCase() === 'END' && lines.length > 0) {
583
+ finishInput();
584
+ return;
524
585
  }
586
+ lines.push(line);
525
587
  });
526
588
  rl.on('close', () => {
527
589
  if (process.stdin.isTTY) {
@@ -573,203 +635,224 @@ export async function runInteractive() {
573
635
  }
574
636
  });
575
637
  process.stdin.on('data', async (key) => {
576
- if (key === 'q' || key === '\u0003') {
577
- // End session and save metrics
578
- const endPhase = tuiState.analysis?.currentPhase || { phase: 'IDLE' };
579
- endSession(projectPath, tuiState.sessionId, endPhase);
580
- clearInterval(activityInterval);
581
- stopWatchingEvents();
582
- cleanup(); // Restore terminal state
583
- console.log(`\n ${cyan}Midas${reset} signing off. Session saved. Happy vibecoding!\n`);
584
- process.exit(0);
585
- }
586
- // Session start screen handling
587
- if (tuiState.showingSessionStart) {
588
- if (key === 'c') {
589
- try {
590
- await copyToClipboard(tuiState.sessionStarterPrompt);
591
- tuiState.message = `${green}OK${reset} Session starter copied! Paste it in your new Cursor chat.`;
592
- }
593
- catch {
594
- tuiState.message = `${yellow}!${reset} Could not copy.`;
595
- }
638
+ // Debounce rapid key presses
639
+ const now = Date.now();
640
+ if (now - lastKeyTime < DEBOUNCE_MS)
641
+ return;
642
+ lastKeyTime = now;
643
+ try {
644
+ // Shift+R for hard reset (recovery from bad state)
645
+ if (key === 'R') {
646
+ resetState();
596
647
  render();
597
648
  return;
598
649
  }
599
- if (key === 'p') {
600
- tuiState.showingSessionStart = false;
601
- render();
602
- if (tuiState.hasApiKey) {
603
- await runAnalysis();
604
- }
605
- return;
650
+ if (key === 'q' || key === '\u0003') {
651
+ // End session and save metrics
652
+ const endPhase = tuiState.analysis?.currentPhase || { phase: 'IDLE' };
653
+ endSession(projectPath, tuiState.sessionId, endPhase);
654
+ clearInterval(activityInterval);
655
+ stopWatchingEvents();
656
+ cleanup(); // Restore terminal state
657
+ console.log(`\n ${cyan}Midas${reset} signing off. Session saved. Happy vibecoding!\n`);
658
+ process.exit(0);
606
659
  }
607
- if (key === 'u') {
608
- try {
609
- await copyUserRules();
610
- tuiState.message = `${green}OK${reset} User Rules copied! Paste in Cursor Settings -> Rules for AI`;
611
- }
612
- catch {
613
- tuiState.message = `${yellow}!${reset} Could not copy.`;
660
+ // Session start screen handling
661
+ if (tuiState.showingSessionStart) {
662
+ if (key === 'c') {
663
+ try {
664
+ await copyToClipboard(tuiState.sessionStarterPrompt);
665
+ tuiState.message = `${green}OK${reset} Session starter copied! Paste it in your new Cursor chat.`;
666
+ }
667
+ catch {
668
+ tuiState.message = `${yellow}!${reset} Could not copy.`;
669
+ }
670
+ render();
671
+ return;
614
672
  }
615
- render();
616
- return;
617
- }
618
- return; // Ignore other keys on session start screen
619
- }
620
- if (key === 'c') {
621
- if (tuiState.analysis?.suggestedPrompt) {
622
- try {
623
- await copyToClipboard(tuiState.analysis.suggestedPrompt);
624
- tuiState.message = `${green}OK${reset} Prompt copied to clipboard!`;
625
- logEvent(projectPath, { type: 'prompt_copied', message: tuiState.analysis.suggestedPrompt.slice(0, 100) });
626
- recordPromptCopied(projectPath, tuiState.sessionId);
673
+ if (key === 'p') {
674
+ tuiState.showingSessionStart = false;
675
+ render();
676
+ if (tuiState.hasApiKey) {
677
+ await runAnalysis();
678
+ }
679
+ return;
627
680
  }
628
- catch {
629
- tuiState.message = `${yellow}!${reset} Could not copy.`;
681
+ if (key === 'u') {
682
+ try {
683
+ await copyUserRules();
684
+ tuiState.message = `${green}OK${reset} User Rules copied! Paste in Cursor Settings -> Rules for AI`;
685
+ }
686
+ catch {
687
+ tuiState.message = `${yellow}!${reset} Could not copy.`;
688
+ }
689
+ render();
690
+ return;
630
691
  }
692
+ return; // Ignore other keys on session start screen
631
693
  }
632
- else {
633
- tuiState.message = `${yellow}!${reset} No prompt to copy.`;
634
- }
635
- render();
636
- }
637
- if (key === 'r') {
638
- await runAnalysis();
639
- }
640
- if (key === 'x') {
641
- // Decline/reject the current suggestion
642
- if (tuiState.analysis?.suggestedPrompt) {
643
- tuiState.showingRejectionInput = true;
644
- const reason = await promptForRejectionReason();
645
- tuiState.showingRejectionInput = false;
646
- recordSuggestionOutcome(projectPath, false, undefined, reason || undefined);
647
- tuiState.suggestionAcceptanceRate = getSuggestionAcceptanceRate(projectPath);
648
- if (reason) {
649
- tuiState.message = `${yellow}OK${reset} Noted: "${truncate(reason, 40)}" - will learn from this.`;
694
+ if (key === 'c') {
695
+ if (tuiState.analysis?.suggestedPrompt) {
696
+ try {
697
+ await copyToClipboard(tuiState.analysis.suggestedPrompt);
698
+ tuiState.message = `${green}OK${reset} Prompt copied to clipboard!`;
699
+ logEvent(projectPath, { type: 'prompt_copied', message: tuiState.analysis.suggestedPrompt.slice(0, 100) });
700
+ recordPromptCopied(projectPath, tuiState.sessionId);
701
+ }
702
+ catch {
703
+ tuiState.message = `${yellow}!${reset} Could not copy.`;
704
+ }
650
705
  }
651
706
  else {
652
- tuiState.message = `${yellow}OK${reset} Suggestion declined.`;
707
+ tuiState.message = `${yellow}!${reset} No prompt to copy.`;
653
708
  }
654
- logEvent(projectPath, {
655
- type: 'prompt_copied',
656
- message: 'Suggestion declined',
657
- data: { reason: reason || 'No reason given' }
658
- });
659
709
  render();
660
710
  }
661
- else {
662
- tuiState.message = `${yellow}!${reset} No suggestion to decline.`;
663
- render();
711
+ if (key === 'r') {
712
+ await runAnalysis();
664
713
  }
665
- }
666
- if (key === 'v') {
667
- // Run verification gates
668
- tuiState.message = `${magenta}...${reset} Running verification gates (build, test, lint)...`;
669
- render();
670
- try {
671
- const { verify } = await import('./tools/verify.js');
672
- const result = verify({ projectPath });
673
- tuiState.gatesStatus = getGatesStatus(projectPath);
674
- if (result.allPass) {
675
- tuiState.message = `${green}OK${reset} All gates pass!${result.autoAdvanced ? ` Auto-advanced to ${result.autoAdvanced.to}` : ''}`;
676
- // Re-analyze to get new suggestions
677
- await runAnalysis();
714
+ if (key === 'x') {
715
+ // Decline/reject the current suggestion
716
+ if (tuiState.analysis?.suggestedPrompt) {
717
+ tuiState.showingRejectionInput = true;
718
+ const reason = await promptForRejectionReason();
719
+ tuiState.showingRejectionInput = false;
720
+ recordSuggestionOutcome(projectPath, false, undefined, reason || undefined);
721
+ tuiState.suggestionAcceptanceRate = getSuggestionAcceptanceRate(projectPath);
722
+ if (reason) {
723
+ tuiState.message = `${yellow}OK${reset} Noted: "${truncate(reason, 40)}" - will learn from this.`;
724
+ }
725
+ else {
726
+ tuiState.message = `${yellow}OK${reset} Suggestion declined.`;
727
+ }
728
+ logEvent(projectPath, {
729
+ type: 'prompt_copied',
730
+ message: 'Suggestion declined',
731
+ data: { reason: reason || 'No reason given' }
732
+ });
733
+ render();
678
734
  }
679
735
  else {
680
- tuiState.message = `${red}!${reset} Failing: ${result.failing.join(', ')}`;
736
+ tuiState.message = `${yellow}!${reset} No suggestion to decline.`;
681
737
  render();
682
738
  }
683
739
  }
684
- catch (error) {
685
- tuiState.message = `${red}!${reset} Verification failed.`;
740
+ if (key === 'v') {
741
+ // Run verification gates
742
+ tuiState.message = `${magenta}...${reset} Running verification gates (build, test, lint)...`;
686
743
  render();
687
- }
688
- }
689
- if (key === 'a') {
690
- // Add .cursorrules to project
691
- try {
692
- const { writeCursorRules, detectTechStack } = await import('./techstack.js');
693
- const projectName = projectPath.split('/').pop() || 'project';
694
- const result = writeCursorRules(projectPath, projectName);
695
- if (result.success) {
696
- const stack = detectTechStack(projectPath);
697
- tuiState.message = `${green}OK${reset} Created .cursorrules for ${stack.language}${stack.framework ? `/${stack.framework}` : ''}`;
744
+ try {
745
+ const { verify } = await import('./tools/verify.js');
746
+ const result = verify({ projectPath });
747
+ tuiState.gatesStatus = getGatesStatus(projectPath);
748
+ if (result.allPass) {
749
+ tuiState.message = `${green}OK${reset} All gates pass!${result.autoAdvanced ? ` Auto-advanced to ${result.autoAdvanced.to}` : ''}`;
750
+ // Re-analyze to get new suggestions
751
+ await runAnalysis();
752
+ }
753
+ else {
754
+ tuiState.message = `${red}!${reset} Failing: ${result.failing.join(', ')}`;
755
+ render();
756
+ }
698
757
  }
699
- else {
700
- tuiState.message = `${red}!${reset} Failed to create .cursorrules`;
758
+ catch (error) {
759
+ tuiState.message = `${red}!${reset} Verification failed.`;
760
+ render();
701
761
  }
702
762
  }
703
- catch (error) {
704
- tuiState.message = `${red}!${reset} Error creating .cursorrules`;
705
- }
706
- render();
707
- }
708
- if (key === 'i') {
709
- // Input/paste AI response for analysis
710
- const exchange = await promptForResponse();
711
- if (!exchange) {
712
- tuiState.message = `${yellow}!${reset} No response pasted.`;
713
- render();
714
- return;
715
- }
716
- tuiState.message = `${magenta}...${reset} Analyzing response...`;
717
- render();
718
- try {
719
- // Analyze the pasted response
720
- const analysis = await analyzeResponse(projectPath, exchange.userPrompt, exchange.aiResponse);
721
- // Auto-save to journal
722
- const journalTitle = analysis.summary.slice(0, 80);
723
- saveToJournal({
724
- projectPath,
725
- title: journalTitle,
726
- conversation: `USER:\n${exchange.userPrompt}\n\nAI:\n${exchange.aiResponse}`,
727
- tags: analysis.taskComplete ? ['completed'] : ['in-progress'],
728
- });
729
- // Record any errors to error memory
730
- if (analysis.errors.length > 0) {
731
- const { recordError } = await import('./tracker.js');
732
- for (const err of analysis.errors) {
733
- recordError(projectPath, err, undefined, undefined);
763
+ if (key === 'a') {
764
+ // Add .cursorrules to project
765
+ try {
766
+ const { writeCursorRules, detectTechStack } = await import('./techstack.js');
767
+ const projectName = projectPath.split('/').pop() || 'project';
768
+ const result = writeCursorRules(projectPath, projectName);
769
+ if (result.success) {
770
+ const stack = detectTechStack(projectPath);
771
+ tuiState.message = `${green}OK${reset} Created .cursorrules for ${stack.language}${stack.framework ? `/${stack.framework}` : ''}`;
734
772
  }
773
+ else {
774
+ tuiState.message = `${red}!${reset} Failed to create .cursorrules`;
775
+ }
776
+ }
777
+ catch (error) {
778
+ tuiState.message = `${red}!${reset} Error creating .cursorrules`;
735
779
  }
736
- // Log event
737
- logEvent(projectPath, {
738
- type: 'ai_suggestion',
739
- message: `Analyzed: ${analysis.summary}`,
740
- data: {
741
- accomplished: analysis.accomplished.length,
742
- errors: analysis.errors.length,
743
- taskComplete: analysis.taskComplete,
744
- },
745
- });
746
- // Update analysis with new suggested prompt
747
- if (tuiState.analysis) {
748
- tuiState.analysis.suggestedPrompt = analysis.suggestedNextPrompt;
749
- tuiState.analysis.whatsNext = analysis.suggestedNextPrompt;
780
+ render();
781
+ }
782
+ if (key === 'i') {
783
+ // Input/paste AI response for analysis
784
+ const exchange = await promptForResponse();
785
+ if (!exchange) {
786
+ tuiState.message = `${yellow}!${reset} No response pasted.`;
787
+ render();
788
+ return;
750
789
  }
751
- // Show summary
752
- const accomplishedCount = analysis.accomplished.length;
753
- const errorsCount = analysis.errors.length;
754
- let statusMsg = `${green}OK${reset} Analyzed: ${analysis.summary.slice(0, 40)}`;
755
- if (accomplishedCount > 0)
756
- statusMsg += ` | ${green}${accomplishedCount} done${reset}`;
757
- if (errorsCount > 0)
758
- statusMsg += ` | ${red}${errorsCount} errors${reset}`;
759
- tuiState.message = statusMsg;
760
- tuiState.filesChanged = true; // Trigger refresh prompt
761
- // Re-analyze if task complete or errors found
762
- if (analysis.taskComplete || analysis.shouldAdvancePhase) {
763
- await runAnalysis();
790
+ tuiState.message = `${magenta}...${reset} Analyzing response...`;
791
+ render();
792
+ try {
793
+ // Analyze the pasted response
794
+ const analysis = await analyzeResponse(projectPath, exchange.userPrompt, exchange.aiResponse);
795
+ // Auto-save to journal
796
+ const journalTitle = analysis.summary.slice(0, 80);
797
+ saveToJournal({
798
+ projectPath,
799
+ title: journalTitle,
800
+ conversation: `USER:\n${exchange.userPrompt}\n\nAI:\n${exchange.aiResponse}`,
801
+ tags: analysis.taskComplete ? ['completed'] : ['in-progress'],
802
+ });
803
+ // Record any errors to error memory
804
+ if (analysis.errors.length > 0) {
805
+ const { recordError } = await import('./tracker.js');
806
+ for (const err of analysis.errors) {
807
+ recordError(projectPath, err, undefined, undefined);
808
+ }
809
+ }
810
+ // Log event
811
+ logEvent(projectPath, {
812
+ type: 'ai_suggestion',
813
+ message: `Analyzed: ${analysis.summary}`,
814
+ data: {
815
+ accomplished: analysis.accomplished.length,
816
+ errors: analysis.errors.length,
817
+ taskComplete: analysis.taskComplete,
818
+ },
819
+ });
820
+ // Update analysis with new suggested prompt
821
+ if (tuiState.analysis) {
822
+ tuiState.analysis.suggestedPrompt = analysis.suggestedNextPrompt;
823
+ tuiState.analysis.whatsNext = analysis.suggestedNextPrompt;
824
+ }
825
+ // Show summary
826
+ const accomplishedCount = analysis.accomplished.length;
827
+ const errorsCount = analysis.errors.length;
828
+ let statusMsg = `${green}OK${reset} Analyzed: ${analysis.summary.slice(0, 40)}`;
829
+ if (accomplishedCount > 0)
830
+ statusMsg += ` | ${green}${accomplishedCount} done${reset}`;
831
+ if (errorsCount > 0)
832
+ statusMsg += ` | ${red}${errorsCount} errors${reset}`;
833
+ tuiState.message = statusMsg;
834
+ tuiState.filesChanged = true; // Trigger refresh prompt
835
+ // Re-analyze if task complete or errors found
836
+ if (analysis.taskComplete || analysis.shouldAdvancePhase) {
837
+ await runAnalysis();
838
+ }
839
+ else {
840
+ render();
841
+ }
764
842
  }
765
- else {
843
+ catch (error) {
844
+ tuiState.message = `${red}!${reset} Failed to analyze response.`;
766
845
  render();
767
846
  }
768
847
  }
769
- catch (error) {
770
- tuiState.message = `${red}!${reset} Failed to analyze response.`;
848
+ }
849
+ catch (error) {
850
+ // Global error handler for key processing
851
+ tuiState.message = `${red}!${reset} Error processing key. Press Shift+R to reset.`;
852
+ try {
771
853
  render();
772
854
  }
855
+ catch { /* ignore render errors */ }
773
856
  }
774
857
  });
775
858
  }