midas-mcp 3.0.0 → 3.3.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 (118) 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 +129 -135
  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/docs/DEPLOYMENT.md +190 -0
  18. package/dist/index.js +4 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/monitoring.d.ts +97 -0
  21. package/dist/monitoring.d.ts.map +1 -0
  22. package/dist/monitoring.js +258 -0
  23. package/dist/monitoring.js.map +1 -0
  24. package/dist/prompts/grow.d.ts.map +1 -1
  25. package/dist/prompts/grow.js +155 -47
  26. package/dist/prompts/grow.js.map +1 -1
  27. package/dist/providers.d.ts +46 -0
  28. package/dist/providers.d.ts.map +1 -0
  29. package/dist/providers.js +349 -0
  30. package/dist/providers.js.map +1 -0
  31. package/dist/server.d.ts.map +1 -1
  32. package/dist/server.js +28 -3
  33. package/dist/server.js.map +1 -1
  34. package/dist/state/phase.d.ts +19 -4
  35. package/dist/state/phase.d.ts.map +1 -1
  36. package/dist/state/phase.js +19 -10
  37. package/dist/state/phase.js.map +1 -1
  38. package/dist/tools/analyze.d.ts.map +1 -1
  39. package/dist/tools/analyze.js +21 -9
  40. package/dist/tools/analyze.js.map +1 -1
  41. package/dist/tools/completeness.d.ts +36 -0
  42. package/dist/tools/completeness.d.ts.map +1 -0
  43. package/dist/tools/completeness.js +838 -0
  44. package/dist/tools/completeness.js.map +1 -0
  45. package/dist/tools/config.d.ts +73 -0
  46. package/dist/tools/config.d.ts.map +1 -0
  47. package/dist/tools/config.js +94 -0
  48. package/dist/tools/config.js.map +1 -0
  49. package/dist/tools/grow.d.ts +157 -0
  50. package/dist/tools/grow.d.ts.map +1 -0
  51. package/dist/tools/grow.js +532 -0
  52. package/dist/tools/grow.js.map +1 -0
  53. package/dist/tools/index.d.ts +4 -0
  54. package/dist/tools/index.d.ts.map +1 -1
  55. package/dist/tools/index.js +8 -0
  56. package/dist/tools/index.js.map +1 -1
  57. package/dist/tools/validate.d.ts +60 -0
  58. package/dist/tools/validate.d.ts.map +1 -0
  59. package/dist/tools/validate.js +234 -0
  60. package/dist/tools/validate.js.map +1 -0
  61. package/dist/tools/verify.d.ts +4 -4
  62. package/dist/tui.d.ts.map +1 -1
  63. package/dist/tui.js +302 -180
  64. package/dist/tui.js.map +1 -1
  65. package/docs/DEPLOYMENT.md +190 -0
  66. package/package.json +17 -5
  67. package/dist/tests/analyze.test.d.ts +0 -2
  68. package/dist/tests/analyze.test.d.ts.map +0 -1
  69. package/dist/tests/analyze.test.js +0 -120
  70. package/dist/tests/analyze.test.js.map +0 -1
  71. package/dist/tests/context.test.d.ts +0 -2
  72. package/dist/tests/context.test.d.ts.map +0 -1
  73. package/dist/tests/context.test.js +0 -213
  74. package/dist/tests/context.test.js.map +0 -1
  75. package/dist/tests/edge-cases.test.d.ts +0 -2
  76. package/dist/tests/edge-cases.test.d.ts.map +0 -1
  77. package/dist/tests/edge-cases.test.js +0 -234
  78. package/dist/tests/edge-cases.test.js.map +0 -1
  79. package/dist/tests/journal.test.d.ts +0 -2
  80. package/dist/tests/journal.test.d.ts.map +0 -1
  81. package/dist/tests/journal.test.js +0 -184
  82. package/dist/tests/journal.test.js.map +0 -1
  83. package/dist/tests/metrics.test.d.ts +0 -2
  84. package/dist/tests/metrics.test.d.ts.map +0 -1
  85. package/dist/tests/metrics.test.js +0 -178
  86. package/dist/tests/metrics.test.js.map +0 -1
  87. package/dist/tests/phase.test.d.ts +0 -2
  88. package/dist/tests/phase.test.d.ts.map +0 -1
  89. package/dist/tests/phase.test.js +0 -110
  90. package/dist/tests/phase.test.js.map +0 -1
  91. package/dist/tests/prompts.test.d.ts +0 -2
  92. package/dist/tests/prompts.test.d.ts.map +0 -1
  93. package/dist/tests/prompts.test.js +0 -157
  94. package/dist/tests/prompts.test.js.map +0 -1
  95. package/dist/tests/search.test.d.ts +0 -2
  96. package/dist/tests/search.test.d.ts.map +0 -1
  97. package/dist/tests/search.test.js +0 -228
  98. package/dist/tests/search.test.js.map +0 -1
  99. package/dist/tests/security.test.d.ts +0 -2
  100. package/dist/tests/security.test.d.ts.map +0 -1
  101. package/dist/tests/security.test.js +0 -105
  102. package/dist/tests/security.test.js.map +0 -1
  103. package/dist/tests/server.test.d.ts +0 -2
  104. package/dist/tests/server.test.d.ts.map +0 -1
  105. package/dist/tests/server.test.js +0 -93
  106. package/dist/tests/server.test.js.map +0 -1
  107. package/dist/tests/techstack.test.d.ts +0 -2
  108. package/dist/tests/techstack.test.d.ts.map +0 -1
  109. package/dist/tests/techstack.test.js +0 -187
  110. package/dist/tests/techstack.test.js.map +0 -1
  111. package/dist/tests/tools.test.d.ts +0 -2
  112. package/dist/tests/tools.test.d.ts.map +0 -1
  113. package/dist/tests/tools.test.js +0 -137
  114. package/dist/tests/tools.test.js.map +0 -1
  115. package/dist/tests/tracker.test.d.ts +0 -2
  116. package/dist/tests/tracker.test.d.ts.map +0 -1
  117. package/dist/tests/tracker.test.js +0 -197
  118. package/dist/tests/tracker.test.js.map +0 -1
package/dist/tui.js CHANGED
@@ -20,12 +20,14 @@ const cyan = `${ESC}[36m`;
20
20
  const magenta = `${ESC}[35m`;
21
21
  const white = `${ESC}[37m`;
22
22
  const red = `${ESC}[31m`;
23
- // Screen control - use alternate buffer for clean refreshes
24
- const enterAltScreen = `${ESC}[?1049h`; // Switch to alternate screen buffer
25
- const exitAltScreen = `${ESC}[?1049l`; // Switch back to main buffer
23
+ // Screen control - NO alternate buffer (allows terminal scrolling)
24
+ const cursorHome = `${ESC}[H`; // Move cursor to top-left
25
+ const clearToEnd = `${ESC}[J`; // Clear from cursor to end of screen
26
26
  const clearScreen = `${ESC}[2J${ESC}[H`; // Clear screen + cursor to home
27
27
  const hideCursor = `${ESC}[?25l`; // Hide cursor during redraw
28
28
  const showCursor = `${ESC}[?25h`; // Show cursor after redraw
29
+ const saveCursor = `${ESC}7`; // Save cursor position
30
+ const restoreCursor = `${ESC}8`; // Restore cursor position
29
31
  const PHASE_COLORS = {
30
32
  EAGLE_SIGHT: yellow,
31
33
  BUILD: blue,
@@ -211,6 +213,35 @@ function drawUI(state, _projectPath) {
211
213
  lines.push(`${cyan}╚${hLine}╝${reset}`);
212
214
  return lines.join('\n');
213
215
  }
216
+ // Help screen
217
+ if (state.showingHelp) {
218
+ lines.push(emptyRow());
219
+ lines.push(row(`${bold}${cyan}HELP - Keyboard Shortcuts${reset}`));
220
+ lines.push(emptyRow());
221
+ lines.push(row(`${dim}Navigation:${reset}`));
222
+ lines.push(row(` ${bold}[r]${reset} Analyze Re-analyze the project`));
223
+ lines.push(row(` ${bold}[c]${reset} Copy Copy suggested prompt to clipboard`));
224
+ lines.push(row(` ${bold}[n]${reset} Next Advance to next phase step`));
225
+ lines.push(row(` ${bold}[p]${reset} Prev Go back to previous step`));
226
+ lines.push(emptyRow());
227
+ lines.push(row(`${dim}Actions:${reset}`));
228
+ lines.push(row(` ${bold}[v]${reset} Paste Paste AI response for analysis`));
229
+ lines.push(row(` ${bold}[j]${reset} Journal Save current session to journal`));
230
+ lines.push(row(` ${bold}[a]${reset} Add Rules Add .cursorrules to project`));
231
+ lines.push(emptyRow());
232
+ lines.push(row(`${dim}Display:${reset}`));
233
+ lines.push(row(` ${bold}[b]${reset} Beginner Toggle simplified display mode`));
234
+ lines.push(row(` ${bold}[?]${reset} Help Show/hide this help screen`));
235
+ lines.push(row(` ${bold}[R]${reset} Reset Hard reset TUI state`));
236
+ lines.push(emptyRow());
237
+ lines.push(row(`${dim}Exit:${reset}`));
238
+ lines.push(row(` ${bold}[q]${reset} Quit Exit Midas TUI`));
239
+ lines.push(emptyRow());
240
+ lines.push(`${cyan}╠${hLine}╣${reset}`);
241
+ lines.push(row(`${dim}Press any key to close help${reset}`));
242
+ lines.push(`${cyan}╚${hLine}╝${reset}`);
243
+ return lines.join('\n');
244
+ }
214
245
  if (state.isAnalyzing) {
215
246
  lines.push(emptyRow());
216
247
  lines.push(row(`${magenta}...${reset} ${bold}Analyzing project${reset}`));
@@ -338,7 +369,7 @@ function drawUI(state, _projectPath) {
338
369
  }
339
370
  lines.push(emptyRow());
340
371
  lines.push(`${cyan}╠${hLine}╣${reset}`);
341
- lines.push(row(`${dim}[c]${reset} Copy ${dim}[i]${reset} Input ${dim}[r]${reset} Analyze ${dim}[v]${reset} Verify ${dim}[q]${reset} Quit`));
372
+ lines.push(row(`${dim}[c]${reset} Copy ${dim}[r]${reset} Analyze ${dim}[v]${reset} Verify ${dim}[?]${reset} Help ${dim}[q]${reset} Quit`));
342
373
  lines.push(`${cyan}╚${hLine}╝${reset}`);
343
374
  return lines.join('\n');
344
375
  }
@@ -346,8 +377,8 @@ export async function runInteractive() {
346
377
  const projectPath = process.cwd();
347
378
  // Check for API key on first run
348
379
  await ensureApiKey();
349
- // Switch to alternate screen buffer for clean TUI (doesn't pollute main terminal)
350
- process.stdout.write(enterAltScreen);
380
+ // Clear screen and save position (no alternate buffer - allows terminal scrolling)
381
+ process.stdout.write(saveCursor + clearScreen);
351
382
  // Set up raw mode
352
383
  if (process.stdin.isTTY) {
353
384
  process.stdin.setRawMode(true);
@@ -356,7 +387,7 @@ export async function runInteractive() {
356
387
  process.stdin.setEncoding('utf8');
357
388
  // Cleanup function to restore terminal state
358
389
  const cleanup = () => {
359
- process.stdout.write(showCursor + exitAltScreen);
390
+ process.stdout.write(showCursor + '\n');
360
391
  if (process.stdin.isTTY) {
361
392
  process.stdin.setRawMode(false);
362
393
  }
@@ -375,6 +406,14 @@ export async function runInteractive() {
375
406
  console.error('Uncaught exception:', err);
376
407
  process.exit(1);
377
408
  });
409
+ process.on('unhandledRejection', (reason) => {
410
+ cleanup();
411
+ console.error('Unhandled rejection:', reason);
412
+ process.exit(1);
413
+ });
414
+ // Debounce tracking for rapid key presses
415
+ let lastKeyTime = 0;
416
+ const DEBOUNCE_MS = 50;
378
417
  // Start a new session for metrics tracking
379
418
  const currentPhase = loadState(projectPath).current;
380
419
  const sessionId = startSession(projectPath, currentPhase);
@@ -389,6 +428,8 @@ export async function runInteractive() {
389
428
  hasApiKey: hasApiKey(),
390
429
  showingSessionStart: true, // Start with session starter prompt
391
430
  showingRejectionInput: false,
431
+ showingHelp: false,
432
+ beginnerMode: false, // Toggle with 'b' key
392
433
  sessionStarterPrompt: getSessionStarterPrompt(projectPath),
393
434
  sessionId,
394
435
  sessionStreak: metrics.currentStreak,
@@ -398,14 +439,30 @@ export async function runInteractive() {
398
439
  suggestionAcceptanceRate: getSuggestionAcceptanceRate(projectPath),
399
440
  };
400
441
  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 = '';
442
+ try {
443
+ // Hide cursor, clear, draw, show cursor - prevents flicker
444
+ process.stdout.write(hideCursor + clearScreen);
445
+ process.stdout.write(drawUI(tuiState, projectPath));
446
+ if (tuiState.message) {
447
+ process.stdout.write(`\n ${tuiState.message}`);
448
+ tuiState.message = '';
449
+ }
450
+ process.stdout.write(showCursor);
451
+ }
452
+ catch (error) {
453
+ // Attempt recovery
454
+ process.stdout.write(clearScreen + showCursor);
455
+ console.error('Render error:', error);
407
456
  }
408
- process.stdout.write(showCursor);
457
+ };
458
+ // Reset state to recover from bad state
459
+ const resetState = () => {
460
+ tuiState.analysis = null;
461
+ tuiState.isAnalyzing = false;
462
+ tuiState.showingSessionStart = false;
463
+ tuiState.showingRejectionInput = false;
464
+ tuiState.message = `${yellow}!${reset} State reset. Press [r] to re-analyze.`;
465
+ tuiState.filesChanged = false;
409
466
  };
410
467
  const runAnalysis = async () => {
411
468
  if (!tuiState.hasApiKey) {
@@ -445,6 +502,7 @@ export async function runInteractive() {
445
502
  render();
446
503
  };
447
504
  const promptForRejectionReason = async () => {
505
+ const TIMEOUT_MS = 60000; // 60 second timeout
448
506
  return new Promise((resolve) => {
449
507
  console.log(`\n ${yellow}Why are you declining this suggestion?${reset}`);
450
508
  console.log(` ${dim}(This helps Midas learn. Press Enter to skip.)${reset}\n`);
@@ -455,7 +513,16 @@ export async function runInteractive() {
455
513
  input: process.stdin,
456
514
  output: process.stdout,
457
515
  });
516
+ // Timeout protection
517
+ const timeout = setTimeout(() => {
518
+ rl.close();
519
+ if (process.stdin.isTTY) {
520
+ process.stdin.setRawMode(true);
521
+ }
522
+ resolve(''); // Timeout = no reason given
523
+ }, TIMEOUT_MS);
458
524
  rl.question(' > ', (answer) => {
525
+ clearTimeout(timeout);
459
526
  rl.close();
460
527
  if (process.stdin.isTTY) {
461
528
  process.stdin.setRawMode(true);
@@ -466,9 +533,10 @@ export async function runInteractive() {
466
533
  };
467
534
  // Multi-line input for pasting AI responses
468
535
  const promptForResponse = async () => {
536
+ const TIMEOUT_MS = 120000; // 2 minute timeout for pasting
469
537
  return new Promise((resolve) => {
470
- // Exit alternate screen temporarily for input
471
- process.stdout.write(exitAltScreen);
538
+ // Move to bottom and show input area
539
+ process.stdout.write('\n');
472
540
  console.log(`\n ${cyan}━━━ Paste AI Response ━━━${reset}`);
473
541
  console.log(` ${dim}Paste the response, then press Enter twice to submit.${reset}\n`);
474
542
  if (process.stdin.isTTY) {
@@ -480,12 +548,24 @@ export async function runInteractive() {
480
548
  });
481
549
  const lines = [];
482
550
  let emptyLineCount = 0;
551
+ let timeoutId = null;
552
+ // Timeout protection - auto-cancel after 2 minutes
553
+ timeoutId = setTimeout(() => {
554
+ rl.close();
555
+ if (process.stdin.isTTY) {
556
+ process.stdin.setRawMode(true);
557
+ }
558
+ process.stdout.write(clearScreen);
559
+ resolve(null);
560
+ }, TIMEOUT_MS);
483
561
  const finishInput = () => {
562
+ if (timeoutId)
563
+ clearTimeout(timeoutId);
484
564
  rl.close();
485
565
  if (process.stdin.isTTY) {
486
566
  process.stdin.setRawMode(true);
487
567
  }
488
- process.stdout.write(enterAltScreen);
568
+ process.stdout.write(clearScreen);
489
569
  // Remove trailing empty lines
490
570
  while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
491
571
  lines.pop();
@@ -542,7 +622,7 @@ export async function runInteractive() {
542
622
  if (process.stdin.isTTY) {
543
623
  process.stdin.setRawMode(true);
544
624
  }
545
- process.stdout.write(enterAltScreen);
625
+ // Don't clear here - finishInput handles it
546
626
  });
547
627
  });
548
628
  };
@@ -588,203 +668,245 @@ export async function runInteractive() {
588
668
  }
589
669
  });
590
670
  process.stdin.on('data', async (key) => {
591
- if (key === 'q' || key === '\u0003') {
592
- // End session and save metrics
593
- const endPhase = tuiState.analysis?.currentPhase || { phase: 'IDLE' };
594
- endSession(projectPath, tuiState.sessionId, endPhase);
595
- clearInterval(activityInterval);
596
- stopWatchingEvents();
597
- cleanup(); // Restore terminal state
598
- console.log(`\n ${cyan}Midas${reset} signing off. Session saved. Happy vibecoding!\n`);
599
- process.exit(0);
600
- }
601
- // Session start screen handling
602
- if (tuiState.showingSessionStart) {
603
- if (key === 'c') {
604
- try {
605
- await copyToClipboard(tuiState.sessionStarterPrompt);
606
- tuiState.message = `${green}OK${reset} Session starter copied! Paste it in your new Cursor chat.`;
607
- }
608
- catch {
609
- tuiState.message = `${yellow}!${reset} Could not copy.`;
610
- }
671
+ // Debounce rapid key presses
672
+ const now = Date.now();
673
+ if (now - lastKeyTime < DEBOUNCE_MS)
674
+ return;
675
+ lastKeyTime = now;
676
+ try {
677
+ // Shift+R for hard reset (recovery from bad state)
678
+ if (key === 'R') {
679
+ resetState();
611
680
  render();
612
681
  return;
613
682
  }
614
- if (key === 'p') {
615
- tuiState.showingSessionStart = false;
616
- render();
617
- if (tuiState.hasApiKey) {
618
- await runAnalysis();
619
- }
620
- return;
683
+ if (key === 'q' || key === '\u0003') {
684
+ // End session and save metrics
685
+ const endPhase = tuiState.analysis?.currentPhase || { phase: 'IDLE' };
686
+ endSession(projectPath, tuiState.sessionId, endPhase);
687
+ clearInterval(activityInterval);
688
+ stopWatchingEvents();
689
+ cleanup(); // Restore terminal state
690
+ console.log(`\n ${cyan}Midas${reset} signing off. Session saved. Happy vibecoding!\n`);
691
+ process.exit(0);
621
692
  }
622
- if (key === 'u') {
623
- try {
624
- await copyUserRules();
625
- tuiState.message = `${green}OK${reset} User Rules copied! Paste in Cursor Settings -> Rules for AI`;
626
- }
627
- catch {
628
- tuiState.message = `${yellow}!${reset} Could not copy.`;
629
- }
693
+ // Help screen - any key closes it
694
+ if (tuiState.showingHelp) {
695
+ tuiState.showingHelp = false;
630
696
  render();
631
697
  return;
632
698
  }
633
- return; // Ignore other keys on session start screen
634
- }
635
- if (key === 'c') {
636
- if (tuiState.analysis?.suggestedPrompt) {
637
- try {
638
- await copyToClipboard(tuiState.analysis.suggestedPrompt);
639
- tuiState.message = `${green}OK${reset} Prompt copied to clipboard!`;
640
- logEvent(projectPath, { type: 'prompt_copied', message: tuiState.analysis.suggestedPrompt.slice(0, 100) });
641
- recordPromptCopied(projectPath, tuiState.sessionId);
699
+ // Session start screen handling
700
+ if (tuiState.showingSessionStart) {
701
+ if (key === 'c') {
702
+ try {
703
+ await copyToClipboard(tuiState.sessionStarterPrompt);
704
+ tuiState.message = `${green}OK${reset} Session starter copied! Paste it in your new Cursor chat.`;
705
+ }
706
+ catch {
707
+ tuiState.message = `${yellow}!${reset} Could not copy.`;
708
+ }
709
+ render();
710
+ return;
642
711
  }
643
- catch {
644
- tuiState.message = `${yellow}!${reset} Could not copy.`;
712
+ if (key === 'p') {
713
+ tuiState.showingSessionStart = false;
714
+ render();
715
+ if (tuiState.hasApiKey) {
716
+ await runAnalysis();
717
+ }
718
+ return;
645
719
  }
720
+ if (key === 'u') {
721
+ try {
722
+ await copyUserRules();
723
+ tuiState.message = `${green}OK${reset} User Rules copied! Paste in Cursor Settings -> Rules for AI`;
724
+ }
725
+ catch {
726
+ tuiState.message = `${yellow}!${reset} Could not copy.`;
727
+ }
728
+ render();
729
+ return;
730
+ }
731
+ return; // Ignore other keys on session start screen
646
732
  }
647
- else {
648
- tuiState.message = `${yellow}!${reset} No prompt to copy.`;
649
- }
650
- render();
651
- }
652
- if (key === 'r') {
653
- await runAnalysis();
654
- }
655
- if (key === 'x') {
656
- // Decline/reject the current suggestion
657
- if (tuiState.analysis?.suggestedPrompt) {
658
- tuiState.showingRejectionInput = true;
659
- const reason = await promptForRejectionReason();
660
- tuiState.showingRejectionInput = false;
661
- recordSuggestionOutcome(projectPath, false, undefined, reason || undefined);
662
- tuiState.suggestionAcceptanceRate = getSuggestionAcceptanceRate(projectPath);
663
- if (reason) {
664
- tuiState.message = `${yellow}OK${reset} Noted: "${truncate(reason, 40)}" - will learn from this.`;
733
+ if (key === 'c') {
734
+ if (tuiState.analysis?.suggestedPrompt) {
735
+ try {
736
+ await copyToClipboard(tuiState.analysis.suggestedPrompt);
737
+ tuiState.message = `${green}OK${reset} Prompt copied to clipboard!`;
738
+ logEvent(projectPath, { type: 'prompt_copied', message: tuiState.analysis.suggestedPrompt.slice(0, 100) });
739
+ recordPromptCopied(projectPath, tuiState.sessionId);
740
+ }
741
+ catch {
742
+ tuiState.message = `${yellow}!${reset} Could not copy.`;
743
+ }
665
744
  }
666
745
  else {
667
- tuiState.message = `${yellow}OK${reset} Suggestion declined.`;
746
+ tuiState.message = `${yellow}!${reset} No prompt to copy.`;
668
747
  }
669
- logEvent(projectPath, {
670
- type: 'prompt_copied',
671
- message: 'Suggestion declined',
672
- data: { reason: reason || 'No reason given' }
673
- });
674
748
  render();
675
749
  }
676
- else {
677
- tuiState.message = `${yellow}!${reset} No suggestion to decline.`;
750
+ if (key === 'r') {
751
+ await runAnalysis();
752
+ }
753
+ if (key === '?') {
754
+ // Toggle help screen
755
+ tuiState.showingHelp = !tuiState.showingHelp;
678
756
  render();
757
+ return;
679
758
  }
680
- }
681
- if (key === 'v') {
682
- // Run verification gates
683
- tuiState.message = `${magenta}...${reset} Running verification gates (build, test, lint)...`;
684
- render();
685
- try {
686
- const { verify } = await import('./tools/verify.js');
687
- const result = verify({ projectPath });
688
- tuiState.gatesStatus = getGatesStatus(projectPath);
689
- if (result.allPass) {
690
- tuiState.message = `${green}OK${reset} All gates pass!${result.autoAdvanced ? ` Auto-advanced to ${result.autoAdvanced.to}` : ''}`;
691
- // Re-analyze to get new suggestions
692
- await runAnalysis();
759
+ if (key === 'b') {
760
+ // Toggle beginner mode (simplified display)
761
+ tuiState.beginnerMode = !tuiState.beginnerMode;
762
+ tuiState.message = tuiState.beginnerMode
763
+ ? `${green}OK${reset} Beginner mode ON - simplified display`
764
+ : `${green}OK${reset} Beginner mode OFF - full display`;
765
+ render();
766
+ return;
767
+ }
768
+ if (key === 'x') {
769
+ // Decline/reject the current suggestion
770
+ if (tuiState.analysis?.suggestedPrompt) {
771
+ tuiState.showingRejectionInput = true;
772
+ const reason = await promptForRejectionReason();
773
+ tuiState.showingRejectionInput = false;
774
+ recordSuggestionOutcome(projectPath, false, undefined, reason || undefined);
775
+ tuiState.suggestionAcceptanceRate = getSuggestionAcceptanceRate(projectPath);
776
+ if (reason) {
777
+ tuiState.message = `${yellow}OK${reset} Noted: "${truncate(reason, 40)}" - will learn from this.`;
778
+ }
779
+ else {
780
+ tuiState.message = `${yellow}OK${reset} Suggestion declined.`;
781
+ }
782
+ logEvent(projectPath, {
783
+ type: 'prompt_copied',
784
+ message: 'Suggestion declined',
785
+ data: { reason: reason || 'No reason given' }
786
+ });
787
+ render();
693
788
  }
694
789
  else {
695
- tuiState.message = `${red}!${reset} Failing: ${result.failing.join(', ')}`;
790
+ tuiState.message = `${yellow}!${reset} No suggestion to decline.`;
696
791
  render();
697
792
  }
698
793
  }
699
- catch (error) {
700
- tuiState.message = `${red}!${reset} Verification failed.`;
794
+ if (key === 'v') {
795
+ // Run verification gates
796
+ tuiState.message = `${magenta}...${reset} Running verification gates (build, test, lint)...`;
701
797
  render();
702
- }
703
- }
704
- if (key === 'a') {
705
- // Add .cursorrules to project
706
- try {
707
- const { writeCursorRules, detectTechStack } = await import('./techstack.js');
708
- const projectName = projectPath.split('/').pop() || 'project';
709
- const result = writeCursorRules(projectPath, projectName);
710
- if (result.success) {
711
- const stack = detectTechStack(projectPath);
712
- tuiState.message = `${green}OK${reset} Created .cursorrules for ${stack.language}${stack.framework ? `/${stack.framework}` : ''}`;
798
+ try {
799
+ const { verify } = await import('./tools/verify.js');
800
+ const result = verify({ projectPath });
801
+ tuiState.gatesStatus = getGatesStatus(projectPath);
802
+ if (result.allPass) {
803
+ tuiState.message = `${green}OK${reset} All gates pass!${result.autoAdvanced ? ` Auto-advanced to ${result.autoAdvanced.to}` : ''}`;
804
+ // Re-analyze to get new suggestions
805
+ await runAnalysis();
806
+ }
807
+ else {
808
+ tuiState.message = `${red}!${reset} Failing: ${result.failing.join(', ')}`;
809
+ render();
810
+ }
713
811
  }
714
- else {
715
- tuiState.message = `${red}!${reset} Failed to create .cursorrules`;
812
+ catch (error) {
813
+ tuiState.message = `${red}!${reset} Verification failed.`;
814
+ render();
716
815
  }
717
816
  }
718
- catch (error) {
719
- tuiState.message = `${red}!${reset} Error creating .cursorrules`;
720
- }
721
- render();
722
- }
723
- if (key === 'i') {
724
- // Input/paste AI response for analysis
725
- const exchange = await promptForResponse();
726
- if (!exchange) {
727
- tuiState.message = `${yellow}!${reset} No response pasted.`;
728
- render();
729
- return;
730
- }
731
- tuiState.message = `${magenta}...${reset} Analyzing response...`;
732
- render();
733
- try {
734
- // Analyze the pasted response
735
- const analysis = await analyzeResponse(projectPath, exchange.userPrompt, exchange.aiResponse);
736
- // Auto-save to journal
737
- const journalTitle = analysis.summary.slice(0, 80);
738
- saveToJournal({
739
- projectPath,
740
- title: journalTitle,
741
- conversation: `USER:\n${exchange.userPrompt}\n\nAI:\n${exchange.aiResponse}`,
742
- tags: analysis.taskComplete ? ['completed'] : ['in-progress'],
743
- });
744
- // Record any errors to error memory
745
- if (analysis.errors.length > 0) {
746
- const { recordError } = await import('./tracker.js');
747
- for (const err of analysis.errors) {
748
- recordError(projectPath, err, undefined, undefined);
817
+ if (key === 'a') {
818
+ // Add .cursorrules to project
819
+ try {
820
+ const { writeCursorRules, detectTechStack } = await import('./techstack.js');
821
+ const projectName = projectPath.split('/').pop() || 'project';
822
+ const result = writeCursorRules(projectPath, projectName);
823
+ if (result.success) {
824
+ const stack = detectTechStack(projectPath);
825
+ tuiState.message = `${green}OK${reset} Created .cursorrules for ${stack.language}${stack.framework ? `/${stack.framework}` : ''}`;
749
826
  }
827
+ else {
828
+ tuiState.message = `${red}!${reset} Failed to create .cursorrules`;
829
+ }
830
+ }
831
+ catch (error) {
832
+ tuiState.message = `${red}!${reset} Error creating .cursorrules`;
750
833
  }
751
- // Log event
752
- logEvent(projectPath, {
753
- type: 'ai_suggestion',
754
- message: `Analyzed: ${analysis.summary}`,
755
- data: {
756
- accomplished: analysis.accomplished.length,
757
- errors: analysis.errors.length,
758
- taskComplete: analysis.taskComplete,
759
- },
760
- });
761
- // Update analysis with new suggested prompt
762
- if (tuiState.analysis) {
763
- tuiState.analysis.suggestedPrompt = analysis.suggestedNextPrompt;
764
- tuiState.analysis.whatsNext = analysis.suggestedNextPrompt;
834
+ render();
835
+ }
836
+ if (key === 'i') {
837
+ // Input/paste AI response for analysis
838
+ const exchange = await promptForResponse();
839
+ if (!exchange) {
840
+ tuiState.message = `${yellow}!${reset} No response pasted.`;
841
+ render();
842
+ return;
765
843
  }
766
- // Show summary
767
- const accomplishedCount = analysis.accomplished.length;
768
- const errorsCount = analysis.errors.length;
769
- let statusMsg = `${green}OK${reset} Analyzed: ${analysis.summary.slice(0, 40)}`;
770
- if (accomplishedCount > 0)
771
- statusMsg += ` | ${green}${accomplishedCount} done${reset}`;
772
- if (errorsCount > 0)
773
- statusMsg += ` | ${red}${errorsCount} errors${reset}`;
774
- tuiState.message = statusMsg;
775
- tuiState.filesChanged = true; // Trigger refresh prompt
776
- // Re-analyze if task complete or errors found
777
- if (analysis.taskComplete || analysis.shouldAdvancePhase) {
778
- await runAnalysis();
844
+ tuiState.message = `${magenta}...${reset} Analyzing response...`;
845
+ render();
846
+ try {
847
+ // Analyze the pasted response
848
+ const analysis = await analyzeResponse(projectPath, exchange.userPrompt, exchange.aiResponse);
849
+ // Auto-save to journal
850
+ const journalTitle = analysis.summary.slice(0, 80);
851
+ saveToJournal({
852
+ projectPath,
853
+ title: journalTitle,
854
+ conversation: `USER:\n${exchange.userPrompt}\n\nAI:\n${exchange.aiResponse}`,
855
+ tags: analysis.taskComplete ? ['completed'] : ['in-progress'],
856
+ });
857
+ // Record any errors to error memory
858
+ if (analysis.errors.length > 0) {
859
+ const { recordError } = await import('./tracker.js');
860
+ for (const err of analysis.errors) {
861
+ recordError(projectPath, err, undefined, undefined);
862
+ }
863
+ }
864
+ // Log event
865
+ logEvent(projectPath, {
866
+ type: 'ai_suggestion',
867
+ message: `Analyzed: ${analysis.summary}`,
868
+ data: {
869
+ accomplished: analysis.accomplished.length,
870
+ errors: analysis.errors.length,
871
+ taskComplete: analysis.taskComplete,
872
+ },
873
+ });
874
+ // Update analysis with new suggested prompt
875
+ if (tuiState.analysis) {
876
+ tuiState.analysis.suggestedPrompt = analysis.suggestedNextPrompt;
877
+ tuiState.analysis.whatsNext = analysis.suggestedNextPrompt;
878
+ }
879
+ // Show summary
880
+ const accomplishedCount = analysis.accomplished.length;
881
+ const errorsCount = analysis.errors.length;
882
+ let statusMsg = `${green}OK${reset} Analyzed: ${analysis.summary.slice(0, 40)}`;
883
+ if (accomplishedCount > 0)
884
+ statusMsg += ` | ${green}${accomplishedCount} done${reset}`;
885
+ if (errorsCount > 0)
886
+ statusMsg += ` | ${red}${errorsCount} errors${reset}`;
887
+ tuiState.message = statusMsg;
888
+ tuiState.filesChanged = true; // Trigger refresh prompt
889
+ // Re-analyze if task complete or errors found
890
+ if (analysis.taskComplete || analysis.shouldAdvancePhase) {
891
+ await runAnalysis();
892
+ }
893
+ else {
894
+ render();
895
+ }
779
896
  }
780
- else {
897
+ catch (error) {
898
+ tuiState.message = `${red}!${reset} Failed to analyze response.`;
781
899
  render();
782
900
  }
783
901
  }
784
- catch (error) {
785
- tuiState.message = `${red}!${reset} Failed to analyze response.`;
902
+ }
903
+ catch (error) {
904
+ // Global error handler for key processing
905
+ tuiState.message = `${red}!${reset} Error processing key. Press Shift+R to reset.`;
906
+ try {
786
907
  render();
787
908
  }
909
+ catch { /* ignore render errors */ }
788
910
  }
789
911
  });
790
912
  }