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.
- package/LICENSE +21 -0
- package/dist/ai.d.ts +2 -0
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +28 -45
- package/dist/ai.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +37 -87
- package/dist/analyzer.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +109 -15
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +76 -7
- package/dist/context.js.map +1 -1
- package/dist/providers.d.ts +46 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +287 -0
- package/dist/providers.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/config.d.ts +73 -0
- package/dist/tools/config.d.ts.map +1 -0
- package/dist/tools/config.js +94 -0
- package/dist/tools/config.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +293 -210
- package/dist/tui.js.map +1 -1
- package/package.json +17 -5
- package/dist/tests/analyze.test.d.ts +0 -2
- package/dist/tests/analyze.test.d.ts.map +0 -1
- package/dist/tests/analyze.test.js +0 -120
- package/dist/tests/analyze.test.js.map +0 -1
- package/dist/tests/context.test.d.ts +0 -2
- package/dist/tests/context.test.d.ts.map +0 -1
- package/dist/tests/context.test.js +0 -213
- package/dist/tests/context.test.js.map +0 -1
- package/dist/tests/edge-cases.test.d.ts +0 -2
- package/dist/tests/edge-cases.test.d.ts.map +0 -1
- package/dist/tests/edge-cases.test.js +0 -234
- package/dist/tests/edge-cases.test.js.map +0 -1
- package/dist/tests/journal.test.d.ts +0 -2
- package/dist/tests/journal.test.d.ts.map +0 -1
- package/dist/tests/journal.test.js +0 -184
- package/dist/tests/journal.test.js.map +0 -1
- package/dist/tests/metrics.test.d.ts +0 -2
- package/dist/tests/metrics.test.d.ts.map +0 -1
- package/dist/tests/metrics.test.js +0 -178
- package/dist/tests/metrics.test.js.map +0 -1
- package/dist/tests/phase.test.d.ts +0 -2
- package/dist/tests/phase.test.d.ts.map +0 -1
- package/dist/tests/phase.test.js +0 -110
- package/dist/tests/phase.test.js.map +0 -1
- package/dist/tests/prompts.test.d.ts +0 -2
- package/dist/tests/prompts.test.d.ts.map +0 -1
- package/dist/tests/prompts.test.js +0 -157
- package/dist/tests/prompts.test.js.map +0 -1
- package/dist/tests/search.test.d.ts +0 -2
- package/dist/tests/search.test.d.ts.map +0 -1
- package/dist/tests/search.test.js +0 -228
- package/dist/tests/search.test.js.map +0 -1
- package/dist/tests/security.test.d.ts +0 -2
- package/dist/tests/security.test.d.ts.map +0 -1
- package/dist/tests/security.test.js +0 -105
- package/dist/tests/security.test.js.map +0 -1
- package/dist/tests/server.test.d.ts +0 -2
- package/dist/tests/server.test.d.ts.map +0 -1
- package/dist/tests/server.test.js +0 -93
- package/dist/tests/server.test.js.map +0 -1
- package/dist/tests/techstack.test.d.ts +0 -2
- package/dist/tests/techstack.test.d.ts.map +0 -1
- package/dist/tests/techstack.test.js +0 -187
- package/dist/tests/techstack.test.js.map +0 -1
- package/dist/tests/tools.test.d.ts +0 -2
- package/dist/tests/tools.test.d.ts.map +0 -1
- package/dist/tests/tools.test.js +0 -137
- package/dist/tests/tools.test.js.map +0 -1
- package/dist/tests/tracker.test.d.ts +0 -2
- package/dist/tests/tracker.test.d.ts.map +0 -1
- package/dist/tests/tracker.test.js +0 -197
- 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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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 === '
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
629
|
-
|
|
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
|
-
|
|
633
|
-
tuiState.
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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}
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
render();
|
|
711
|
+
if (key === 'r') {
|
|
712
|
+
await runAnalysis();
|
|
664
713
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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 = `${
|
|
736
|
+
tuiState.message = `${yellow}!${reset} No suggestion to decline.`;
|
|
681
737
|
render();
|
|
682
738
|
}
|
|
683
739
|
}
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
700
|
-
tuiState.message = `${red}!${reset}
|
|
758
|
+
catch (error) {
|
|
759
|
+
tuiState.message = `${red}!${reset} Verification failed.`;
|
|
760
|
+
render();
|
|
701
761
|
}
|
|
702
762
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
-
|
|
843
|
+
catch (error) {
|
|
844
|
+
tuiState.message = `${red}!${reset} Failed to analyze response.`;
|
|
766
845
|
render();
|
|
767
846
|
}
|
|
768
847
|
}
|
|
769
|
-
|
|
770
|
-
|
|
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
|
}
|