vibecodingmachine-cli 2026.2.20-438 → 2026.2.26-1739
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/bin/auth/auth-compliance.js +126 -0
- package/bin/cli-program.js +104 -0
- package/bin/cli-setup.js +52 -0
- package/bin/commands/agent-commands.js +310 -0
- package/bin/commands/auto-commands.js +70 -0
- package/bin/commands/command-aliases.js +118 -0
- package/bin/commands/repo-commands.js +39 -0
- package/bin/commands/rui-commands.js +152 -0
- package/bin/config/cli-config.js +394 -0
- package/bin/init/environment-setup.js +84 -0
- package/bin/update/update-checker.js +126 -0
- package/bin/vibecodingmachine-new.js +50 -0
- package/bin/vibecodingmachine.js +29 -663
- package/package.json +8 -2
- package/src/commands/agents/add.js +277 -0
- package/src/commands/agents/check.js +380 -0
- package/src/commands/agents/list.js +471 -0
- package/src/commands/agents/remove.js +351 -0
- package/src/commands/analyze-file-sizes.js +428 -0
- package/src/commands/auto-direct/code-processor.js +282 -0
- package/src/commands/auto-direct/file-scanner.js +266 -0
- package/src/commands/auto-direct/provider-config.js +178 -0
- package/src/commands/auto-direct/provider-manager.js +219 -0
- package/src/commands/auto-direct/requirement-manager.js +172 -0
- package/src/commands/auto-direct/status-display.js +91 -0
- package/src/commands/auto-direct/utils.js +106 -0
- package/src/commands/auto-direct.js +875 -488
- package/src/commands/auto-execution.js +342 -0
- package/src/commands/auto-provider-management.js +102 -0
- package/src/commands/auto-requirement-management.js +161 -0
- package/src/commands/auto-status-helpers.js +141 -0
- package/src/commands/auto.js +105 -5155
- package/src/commands/check-compliance.js +536 -0
- package/src/commands/continuous-scan.js +119 -0
- package/src/commands/ide.js +16 -4
- package/src/commands/refactor-file.js +486 -0
- package/src/commands/requirements.js +301 -2
- package/src/commands/timeout.js +290 -0
- package/src/trui/TruiInterface.js +108 -0
- package/src/trui/agents/AgentInterface.js +580 -0
- package/src/utils/antigravity-installer.js +60 -6
- package/src/utils/clarification-actions.js +290 -0
- package/src/utils/config.js +123 -2
- package/src/utils/first-run.js +5 -5
- package/src/utils/ide-handlers.js +212 -0
- package/src/utils/interactive/clarification-actions.js +348 -0
- package/src/utils/interactive/core-ui.js +265 -0
- package/src/utils/interactive/file-backup.js +237 -0
- package/src/utils/interactive/file-import-export.js +305 -0
- package/src/utils/interactive/file-operations.js +49 -0
- package/src/utils/interactive/file-validation.js +276 -0
- package/src/utils/interactive/interactive-prompts.js +480 -0
- package/src/utils/interactive/requirement-actions.js +127 -0
- package/src/utils/interactive/requirement-crud.js +356 -0
- package/src/utils/interactive/requirements-navigation.js +286 -0
- package/src/utils/interactive.js +390 -3459
- package/src/utils/provider-checker/agent-checker.js +250 -0
- package/src/utils/provider-checker/agent-runner.js +450 -0
- package/src/utils/provider-checker/cli-installer.js +123 -0
- package/src/utils/provider-checker/cli-utils.js +15 -0
- package/src/utils/provider-checker/format-utils.js +32 -0
- package/src/utils/provider-checker/ide-manager.js +72 -0
- package/src/utils/provider-checker/ide-utils.js +71 -0
- package/src/utils/provider-checker/node-detector.js +56 -0
- package/src/utils/provider-checker/node-utils.js +61 -0
- package/src/utils/provider-checker/process-spawn.js +22 -0
- package/src/utils/provider-checker/process-utils.js +37 -0
- package/src/utils/provider-checker/provider-validator.js +160 -0
- package/src/utils/provider-checker/quota-checker.js +54 -0
- package/src/utils/provider-checker/quota-detector.js +44 -0
- package/src/utils/provider-checker/requirements-manager.js +94 -0
- package/src/utils/provider-checker/test-requirements.js +95 -0
- package/src/utils/provider-checker/time-formatter.js +18 -0
- package/src/utils/provider-checker-new.js +14 -0
- package/src/utils/provider-checker.js +12 -407
- package/src/utils/provider-checkers/ide-manager.js +128 -0
- package/src/utils/provider-checkers/node-executable-finder.js +51 -0
- package/src/utils/provider-checkers/provider-checker-core.js +172 -0
- package/src/utils/provider-checkers/provider-checker-main.js +107 -0
- package/src/utils/provider-manager.js +60 -4
- package/src/utils/provider-registry.js +26 -3
- package/src/utils/provider-utils.js +173 -0
- package/src/utils/quota-detectors.js +212 -0
- package/src/utils/requirement-action-handlers.js +288 -0
- package/src/utils/requirement-actions/clarification-actions.js +229 -0
- package/src/utils/requirement-actions/confirmation-prompts.js +93 -0
- package/src/utils/requirement-actions/file-operations.js +92 -0
- package/src/utils/requirement-actions/helpers.js +40 -0
- package/src/utils/requirement-actions/requirement-operations.js +335 -0
- package/src/utils/requirement-actions.js +46 -856
- package/src/utils/requirement-file-operations.js +259 -0
- package/src/utils/requirement-helpers.js +128 -0
- package/src/utils/requirement-management.js +279 -0
- package/src/utils/requirement-navigation.js +146 -0
- package/src/utils/requirement-organization.js +271 -0
- package/src/utils/simple-trui.js +75 -1
- package/src/utils/trui-navigation.js +28 -2
- package/src/utils/trui-req-tree.js +196 -11
- package/src/utils/trui-specifications.js +31 -1
- package/src/utils/interactive-backup.js +0 -5664
- package/src/utils/trui-provider-manager.js +0 -182
|
@@ -5,7 +5,25 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
-
const
|
|
8
|
+
const vibecodingmachineCore = require('vibecodingmachine-core');
|
|
9
|
+
const {
|
|
10
|
+
DirectLLMManager,
|
|
11
|
+
AppleScriptManager,
|
|
12
|
+
QuotaDetector,
|
|
13
|
+
IDEHealthTracker,
|
|
14
|
+
TimeoutCalculator,
|
|
15
|
+
t,
|
|
16
|
+
detectLocale,
|
|
17
|
+
setLocale
|
|
18
|
+
} = vibecodingmachineCore;
|
|
19
|
+
|
|
20
|
+
// Extract requirement management classes
|
|
21
|
+
const {
|
|
22
|
+
RequirementFileParser,
|
|
23
|
+
RequirementSequencer,
|
|
24
|
+
DefaultRequirementManager,
|
|
25
|
+
JSONStorage
|
|
26
|
+
} = vibecodingmachineCore;
|
|
9
27
|
|
|
10
28
|
// Initialize locale detection for auto mode
|
|
11
29
|
const detectedLocale = detectLocale();
|
|
@@ -26,6 +44,9 @@ const { checkKiroRateLimit, handleKiroRateLimit } = require('../utils/kiro-js-ha
|
|
|
26
44
|
const { checkClineRateLimit, handleClineRateLimit } = require('../utils/cline-js-handler');
|
|
27
45
|
const { startAutoMode, stopAutoMode, updateAutoModeStatus } = require('../utils/auto-mode');
|
|
28
46
|
|
|
47
|
+
// Global keyboard handler reference for cleanup
|
|
48
|
+
let keyboardHandler = null;
|
|
49
|
+
|
|
29
50
|
// Status management will use in-process tracking instead of external file
|
|
30
51
|
const CLI_ENTRY_POINT = path.join(__dirname, '../../bin/vibecodingmachine.js');
|
|
31
52
|
|
|
@@ -37,7 +58,7 @@ const CLI_ENTRY_POINT = path.join(__dirname, '../../bin/vibecodingmachine.js');
|
|
|
37
58
|
async function ensureClineInstalled(forceInstall = false) {
|
|
38
59
|
const { DirectLLMManager } = require('vibecodingmachine-core');
|
|
39
60
|
const llm = new DirectLLMManager();
|
|
40
|
-
|
|
61
|
+
|
|
41
62
|
// Check if already available
|
|
42
63
|
if (!forceInstall && await llm.isClineAvailable()) {
|
|
43
64
|
return true;
|
|
@@ -45,16 +66,16 @@ async function ensureClineInstalled(forceInstall = false) {
|
|
|
45
66
|
|
|
46
67
|
const ora = require('ora');
|
|
47
68
|
const { execSync } = require('child_process');
|
|
48
|
-
|
|
69
|
+
|
|
49
70
|
const spinner = ora('Installing Cline CLI...').start();
|
|
50
|
-
|
|
71
|
+
|
|
51
72
|
try {
|
|
52
73
|
// Install Cline CLI globally
|
|
53
74
|
execSync('npm install -g cline', { stdio: 'pipe', encoding: 'utf8' });
|
|
54
|
-
|
|
75
|
+
|
|
55
76
|
// Verify installation
|
|
56
77
|
const isAvailable = await llm.isClineAvailable();
|
|
57
|
-
|
|
78
|
+
|
|
58
79
|
if (isAvailable) {
|
|
59
80
|
spinner.succeed('Cline CLI installed successfully');
|
|
60
81
|
console.log(chalk.green('✓ Cline CLI is now ready to use'));
|
|
@@ -459,317 +480,317 @@ async function moveRequirementToVerify(repoPath, requirementText) {
|
|
|
459
480
|
|
|
460
481
|
const content = await fs.readFile(reqPath, 'utf8');
|
|
461
482
|
const lines = content.split('\n');
|
|
462
|
-
// Find the requirement by its title (in ### header format)
|
|
463
|
-
// Only look in TODO section
|
|
464
|
-
const normalizedRequirement = requirementText.trim();
|
|
465
|
-
const snippet = normalizedRequirement.substring(0, 80);
|
|
466
|
-
let requirementStartIndex = -1;
|
|
467
|
-
let requirementEndIndex = -1;
|
|
468
|
-
let inTodoSection = false;
|
|
469
|
-
|
|
470
|
-
for (let i = 0; i < lines.length; i++) {
|
|
471
|
-
const line = lines[i];
|
|
472
|
-
const trimmed = line.trim();
|
|
473
|
-
|
|
474
|
-
// Check if we're entering TODO section
|
|
475
|
-
if (trimmed.startsWith('##') && trimmed.includes('Requirements not yet completed')) {
|
|
476
|
-
inTodoSection = true;
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Check if we're leaving TODO section
|
|
481
|
-
if (inTodoSection && trimmed.startsWith('##') && !trimmed.startsWith('###') && !trimmed.includes('Requirements not yet completed')) {
|
|
482
|
-
inTodoSection = false;
|
|
483
|
-
}
|
|
483
|
+
// Find the requirement by its title (in ### header format)
|
|
484
|
+
// Only look in TODO section
|
|
485
|
+
const normalizedRequirement = requirementText.trim();
|
|
486
|
+
const snippet = normalizedRequirement.substring(0, 80);
|
|
487
|
+
let requirementStartIndex = -1;
|
|
488
|
+
let requirementEndIndex = -1;
|
|
489
|
+
let inTodoSection = false;
|
|
484
490
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (title) {
|
|
489
|
-
// Try multiple matching strategies
|
|
490
|
-
const normalizedTitle = title.trim();
|
|
491
|
+
for (let i = 0; i < lines.length; i++) {
|
|
492
|
+
const line = lines[i];
|
|
493
|
+
const trimmed = line.trim();
|
|
491
494
|
|
|
492
|
-
//
|
|
493
|
-
if (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// Check if either contains the other (for partial matches)
|
|
497
|
-
else if (normalizedTitle.includes(normalizedRequirement) || normalizedRequirement.includes(normalizedTitle)) {
|
|
498
|
-
requirementStartIndex = i;
|
|
495
|
+
// Check if we're entering TODO section
|
|
496
|
+
if (trimmed.startsWith('##') && trimmed.includes('Requirements not yet completed')) {
|
|
497
|
+
inTodoSection = true;
|
|
498
|
+
continue;
|
|
499
499
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
|
|
501
|
+
// Check if we're leaving TODO section
|
|
502
|
+
if (inTodoSection && trimmed.startsWith('##') && !trimmed.startsWith('###') && !trimmed.includes('Requirements not yet completed')) {
|
|
503
|
+
inTodoSection = false;
|
|
503
504
|
}
|
|
504
505
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
506
|
+
// Only look for requirements in TODO section
|
|
507
|
+
if (inTodoSection && trimmed.startsWith('###')) {
|
|
508
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
509
|
+
if (title) {
|
|
510
|
+
// Try multiple matching strategies
|
|
511
|
+
const normalizedTitle = title.trim();
|
|
512
|
+
|
|
513
|
+
// Exact match
|
|
514
|
+
if (normalizedTitle === normalizedRequirement) {
|
|
515
|
+
requirementStartIndex = i;
|
|
516
|
+
}
|
|
517
|
+
// Check if either contains the other (for partial matches)
|
|
518
|
+
else if (normalizedTitle.includes(normalizedRequirement) || normalizedRequirement.includes(normalizedTitle)) {
|
|
519
|
+
requirementStartIndex = i;
|
|
520
|
+
}
|
|
521
|
+
// Check snippet matches
|
|
522
|
+
else if (normalizedTitle.includes(snippet) || snippet.includes(normalizedTitle.substring(0, 80))) {
|
|
523
|
+
requirementStartIndex = i;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (requirementStartIndex !== -1) {
|
|
527
|
+
// Find the end of this requirement (next ### or ## header)
|
|
528
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
529
|
+
const nextLine = lines[j].trim();
|
|
530
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
531
|
+
requirementEndIndex = j;
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (requirementEndIndex === -1) {
|
|
536
|
+
requirementEndIndex = lines.length;
|
|
537
|
+
}
|
|
511
538
|
break;
|
|
512
539
|
}
|
|
513
540
|
}
|
|
514
|
-
if (requirementEndIndex === -1) {
|
|
515
|
-
requirementEndIndex = lines.length;
|
|
516
|
-
}
|
|
517
|
-
break;
|
|
518
541
|
}
|
|
519
542
|
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
543
|
|
|
523
|
-
if (requirementStartIndex === -1) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
544
|
+
if (requirementStartIndex === -1) {
|
|
545
|
+
console.log(chalk.yellow(`⚠️ ${t('auto.direct.requirement.not.found.todo', { requirement: requirementText.substring(0, 60) + '...' })}`));
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
527
548
|
|
|
528
|
-
// Extract the entire requirement block
|
|
529
|
-
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
549
|
+
// Extract the entire requirement block
|
|
550
|
+
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
530
551
|
|
|
531
|
-
// Remove the requirement from its current location
|
|
532
|
-
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
552
|
+
// Remove the requirement from its current location
|
|
553
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
533
554
|
|
|
534
|
-
// Check if there are any more requirements in TODO section after removal
|
|
535
|
-
let hasMoreTodoRequirements = false;
|
|
536
|
-
inTodoSection = false; // Reset the existing variable
|
|
537
|
-
for (let i = 0; i < lines.length; i++) {
|
|
538
|
-
|
|
555
|
+
// Check if there are any more requirements in TODO section after removal
|
|
556
|
+
let hasMoreTodoRequirements = false;
|
|
557
|
+
inTodoSection = false; // Reset the existing variable
|
|
558
|
+
for (let i = 0; i < lines.length; i++) {
|
|
559
|
+
const line = lines[i].trim();
|
|
539
560
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
561
|
+
// Check if we're entering TODO section
|
|
562
|
+
if (line.startsWith('##') && line.includes('Requirements not yet completed')) {
|
|
563
|
+
inTodoSection = true;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
545
566
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
567
|
+
// Check if we're leaving TODO section
|
|
568
|
+
if (inTodoSection && line.startsWith('##') && !line.startsWith('###') && !line.includes('Requirements not yet completed')) {
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
550
571
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
572
|
+
// Check if we found a requirement in TODO section
|
|
573
|
+
if (inTodoSection && line.startsWith('###')) {
|
|
574
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
575
|
+
if (title) {
|
|
576
|
+
hasMoreTodoRequirements = true;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
557
580
|
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
581
|
|
|
561
|
-
// If no more TODO requirements, log message
|
|
562
|
-
if (!hasMoreTodoRequirements) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
582
|
+
// If no more TODO requirements, log message
|
|
583
|
+
if (!hasMoreTodoRequirements) {
|
|
584
|
+
console.log(chalk.green(`🎉 ${t('auto.direct.requirement.no.more.todo')}`));
|
|
585
|
+
// Add a new requirement to the TODO section
|
|
586
|
+
const newRequirement = '### R14: TESTREQ1 with promo code FRIENDSANDFAMILYROCK';
|
|
587
|
+
lines.push(newRequirement);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Find or create TO VERIFY BY HUMAN section with conflict resolution
|
|
591
|
+
// IMPORTANT: Do NOT match VERIFIED sections - only match TO VERIFY sections
|
|
592
|
+
const verifySectionVariants = [
|
|
593
|
+
'## 🔍 TO VERIFY BY HUMAN',
|
|
594
|
+
'## 🔍 TO VERIFY',
|
|
595
|
+
'## TO VERIFY',
|
|
596
|
+
'## ✅ TO VERIFY',
|
|
597
|
+
'## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
|
|
598
|
+
'## 📊 CROSS-COMPUTER REQUIREMENT ASSIGNMENT',
|
|
599
|
+
'## 🖥️ COMPUTER FOCUS AREA MANAGEMENT',
|
|
600
|
+
'## 🔍 spec-kit: TO VERIFY BY HUMAN'
|
|
601
|
+
];
|
|
602
|
+
|
|
603
|
+
let verifyIndex = -1;
|
|
604
|
+
for (let i = 0; i < lines.length; i++) {
|
|
605
|
+
const line = lines[i];
|
|
606
|
+
const trimmed = line.trim();
|
|
607
|
+
|
|
608
|
+
// Check each variant more carefully
|
|
609
|
+
for (const variant of verifySectionVariants) {
|
|
610
|
+
const variantTrimmed = variant.trim();
|
|
611
|
+
// Exact match or line starts with variant
|
|
612
|
+
if (trimmed === variantTrimmed || trimmed.startsWith(variantTrimmed)) {
|
|
613
|
+
// Double-check: make sure it's NOT a VERIFIED section (without TO VERIFY)
|
|
614
|
+
if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) &&
|
|
615
|
+
(trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot') || trimmed.includes('CROSS-COMPUTER REQUIREMENT ASSIGNMENT') || trimmed.includes('COMPUTER FOCUS AREA MANAGEMENT') || trimmed.includes('spec-kit'))) {
|
|
616
|
+
verifyIndex = i;
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (verifyIndex !== -1) break;
|
|
622
|
+
}
|
|
568
623
|
|
|
569
|
-
|
|
570
|
-
//
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
624
|
+
if (verifyIndex === -1) {
|
|
625
|
+
// Create TO VERIFY section - place it BEFORE VERIFIED section if one exists, otherwise before CHANGELOG
|
|
626
|
+
const verifiedIndex = lines.findIndex(line => {
|
|
627
|
+
const trimmed = line.trim();
|
|
628
|
+
return trimmed === '## 📝 VERIFIED' || trimmed.startsWith('## 📝 VERIFIED') ||
|
|
629
|
+
(trimmed.startsWith('##') && trimmed.includes('VERIFIED') && !trimmed.includes('TO VERIFY'));
|
|
630
|
+
});
|
|
631
|
+
const changelogIndex = lines.findIndex(line => line.includes('## CHANGELOG'));
|
|
632
|
+
const manualFeedbackIndex = lines.findIndex(line => line.trim().startsWith('## ❓'));
|
|
633
|
+
|
|
634
|
+
// Prefer: before VERIFIED > before CHANGELOG > before manual feedback > at end
|
|
635
|
+
let insertionIndex = lines.length;
|
|
636
|
+
if (verifiedIndex > 0) {
|
|
637
|
+
insertionIndex = verifiedIndex;
|
|
638
|
+
} else if (changelogIndex > 0) {
|
|
639
|
+
insertionIndex = changelogIndex;
|
|
640
|
+
} else if (manualFeedbackIndex > 0) {
|
|
641
|
+
insertionIndex = manualFeedbackIndex;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const block = [];
|
|
645
|
+
if (insertionIndex > 0 && lines[insertionIndex - 1].trim() !== '') {
|
|
646
|
+
block.push('');
|
|
647
|
+
}
|
|
648
|
+
block.push('## 🔍 spec-kit: TO VERIFY BY HUMAN', '### Automatic Registration and Tracking', '### User Registration', '');
|
|
649
|
+
block.push('## 📊 CROSS-COMPUTER REQUIREMENT ASSIGNMENT');
|
|
650
|
+
block.push('## 🖥️ COMPUTER FOCUS AREA MANAGEMENT');
|
|
651
|
+
lines.splice(insertionIndex, 0, ...block);
|
|
652
|
+
verifyIndex = lines.findIndex(line => {
|
|
653
|
+
const trimmed = line.trim();
|
|
654
|
+
return trimmed === '## 🔍 spec-kit: TO VERIFY BY HUMAN' || trimmed.startsWith('## 🔍 spec-kit: TO VERIFY BY HUMAN');
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Safety check: verifyIndex should be valid
|
|
658
|
+
if (verifyIndex === -1) {
|
|
659
|
+
console.error(t('auto.direct.verify.section.create.failed'));
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Safety check: verify we're not inserting into a VERIFIED section
|
|
665
|
+
const verifyLine = lines[verifyIndex] || '';
|
|
666
|
+
if (verifyLine.includes('## 📝 VERIFIED') || (verifyLine.trim().startsWith('##') && verifyLine.includes('VERIFIED') && !verifyLine.includes('TO VERIFY'))) {
|
|
667
|
+
console.error('ERROR: Attempted to insert into VERIFIED section instead of TO VERIFY');
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Remove any existing duplicate of this requirement in TO VERIFY section
|
|
672
|
+
// Find the next section header after TO VERIFY
|
|
673
|
+
let nextSectionIndex = lines.length;
|
|
674
|
+
for (let i = verifyIndex + 1; i < lines.length; i++) {
|
|
675
|
+
const trimmed = lines[i].trim();
|
|
676
|
+
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
677
|
+
nextSectionIndex = i;
|
|
596
678
|
break;
|
|
597
679
|
}
|
|
598
680
|
}
|
|
599
|
-
}
|
|
600
|
-
if (verifyIndex !== -1) break;
|
|
601
|
-
}
|
|
602
681
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
(trimmed.startsWith('##') && trimmed.includes('VERIFIED') && !trimmed.includes('TO VERIFY'));
|
|
609
|
-
});
|
|
610
|
-
const changelogIndex = lines.findIndex(line => line.includes('## CHANGELOG'));
|
|
611
|
-
const manualFeedbackIndex = lines.findIndex(line => line.trim().startsWith('## ❓'));
|
|
612
|
-
|
|
613
|
-
// Prefer: before VERIFIED > before CHANGELOG > before manual feedback > at end
|
|
614
|
-
let insertionIndex = lines.length;
|
|
615
|
-
if (verifiedIndex > 0) {
|
|
616
|
-
insertionIndex = verifiedIndex;
|
|
617
|
-
} else if (changelogIndex > 0) {
|
|
618
|
-
insertionIndex = changelogIndex;
|
|
619
|
-
} else if (manualFeedbackIndex > 0) {
|
|
620
|
-
insertionIndex = manualFeedbackIndex;
|
|
621
|
-
}
|
|
682
|
+
// Search for duplicate requirement in TO VERIFY section
|
|
683
|
+
const requirementTitle = requirementBlock[0].trim().replace(/^###\s*/, '').trim();
|
|
684
|
+
for (let i = verifyIndex + 1; i < nextSectionIndex; i++) {
|
|
685
|
+
const line = lines[i];
|
|
686
|
+
const trimmed = line.trim();
|
|
622
687
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
block.push('');
|
|
626
|
-
}
|
|
627
|
-
block.push('## 🔍 spec-kit: TO VERIFY BY HUMAN', '### Automatic Registration and Tracking', '### User Registration', '');
|
|
628
|
-
block.push('## 📊 CROSS-COMPUTER REQUIREMENT ASSIGNMENT');
|
|
629
|
-
block.push('## 🖥️ COMPUTER FOCUS AREA MANAGEMENT');
|
|
630
|
-
lines.splice(insertionIndex, 0, ...block);
|
|
631
|
-
verifyIndex = lines.findIndex(line => {
|
|
632
|
-
const trimmed = line.trim();
|
|
633
|
-
return trimmed === '## 🔍 spec-kit: TO VERIFY BY HUMAN' || trimmed.startsWith('## 🔍 spec-kit: TO VERIFY BY HUMAN');
|
|
634
|
-
});
|
|
688
|
+
if (trimmed.startsWith('###')) {
|
|
689
|
+
const existingTitle = trimmed.replace(/^###\s*/, '').trim();
|
|
635
690
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
}
|
|
691
|
+
// Check if this is a duplicate (exact match or contains/contained by)
|
|
692
|
+
if (existingTitle === requirementTitle ||
|
|
693
|
+
existingTitle.includes(requirementTitle) ||
|
|
694
|
+
requirementTitle.includes(existingTitle)) {
|
|
642
695
|
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
696
|
+
// Find the end of this existing requirement
|
|
697
|
+
let existingEndIndex = nextSectionIndex;
|
|
698
|
+
for (let j = i + 1; j < nextSectionIndex; j++) {
|
|
699
|
+
const nextLine = lines[j].trim();
|
|
700
|
+
if (nextLine.startsWith('###') || nextLine.startsWith('##')) {
|
|
701
|
+
existingEndIndex = j;
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
649
705
|
|
|
650
|
-
// Remove
|
|
651
|
-
|
|
652
|
-
let nextSectionIndex = lines.length;
|
|
653
|
-
for (let i = verifyIndex + 1; i < lines.length; i++) {
|
|
654
|
-
const trimmed = lines[i].trim();
|
|
655
|
-
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
656
|
-
nextSectionIndex = i;
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
706
|
+
// Remove the duplicate
|
|
707
|
+
lines.splice(i, existingEndIndex - i);
|
|
660
708
|
|
|
661
|
-
//
|
|
662
|
-
|
|
663
|
-
for (let i = verifyIndex + 1; i < nextSectionIndex; i++) {
|
|
664
|
-
const line = lines[i];
|
|
665
|
-
const trimmed = line.trim();
|
|
666
|
-
|
|
667
|
-
if (trimmed.startsWith('###')) {
|
|
668
|
-
const existingTitle = trimmed.replace(/^###\s*/, '').trim();
|
|
669
|
-
|
|
670
|
-
// Check if this is a duplicate (exact match or contains/contained by)
|
|
671
|
-
if (existingTitle === requirementTitle ||
|
|
672
|
-
existingTitle.includes(requirementTitle) ||
|
|
673
|
-
requirementTitle.includes(existingTitle)) {
|
|
674
|
-
|
|
675
|
-
// Find the end of this existing requirement
|
|
676
|
-
let existingEndIndex = nextSectionIndex;
|
|
677
|
-
for (let j = i + 1; j < nextSectionIndex; j++) {
|
|
678
|
-
const nextLine = lines[j].trim();
|
|
679
|
-
if (nextLine.startsWith('###') || nextLine.startsWith('##')) {
|
|
680
|
-
existingEndIndex = j;
|
|
709
|
+
// Adjust nextSectionIndex if needed
|
|
710
|
+
nextSectionIndex -= (existingEndIndex - i);
|
|
681
711
|
break;
|
|
682
712
|
}
|
|
683
713
|
}
|
|
684
|
-
|
|
685
|
-
// Remove the duplicate
|
|
686
|
-
lines.splice(i, existingEndIndex - i);
|
|
687
|
-
|
|
688
|
-
// Adjust nextSectionIndex if needed
|
|
689
|
-
nextSectionIndex -= (existingEndIndex - i);
|
|
690
|
-
break;
|
|
691
714
|
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
715
|
|
|
695
|
-
// Insert requirement block at TOP of TO VERIFY section (right after section header)
|
|
696
|
-
let insertIndex = verifyIndex + 1;
|
|
716
|
+
// Insert requirement block at TOP of TO VERIFY section (right after section header)
|
|
717
|
+
let insertIndex = verifyIndex + 1;
|
|
697
718
|
|
|
698
|
-
// Ensure there's a blank line after the section header
|
|
699
|
-
if (lines[insertIndex]?.trim() !== '') {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
}
|
|
719
|
+
// Ensure there's a blank line after the section header
|
|
720
|
+
if (lines[insertIndex]?.trim() !== '') {
|
|
721
|
+
lines.splice(insertIndex, 0, '');
|
|
722
|
+
insertIndex++;
|
|
723
|
+
}
|
|
703
724
|
|
|
704
|
-
// If a Conflict Resolution header already exists immediately after the section header, reuse it
|
|
705
|
-
const conflictHeader = '### Conflict Resolution:';
|
|
706
|
-
if (lines[insertIndex]?.trim().startsWith(conflictHeader)) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
} else {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
}
|
|
725
|
+
// If a Conflict Resolution header already exists immediately after the section header, reuse it
|
|
726
|
+
const conflictHeader = '### Conflict Resolution:';
|
|
727
|
+
if (lines[insertIndex]?.trim().startsWith(conflictHeader)) {
|
|
728
|
+
// Move insertion point to after the existing header
|
|
729
|
+
insertIndex++;
|
|
730
|
+
// Ensure there's a blank line after the header before inserting the requirement
|
|
731
|
+
if (lines[insertIndex]?.trim() !== '') {
|
|
732
|
+
lines.splice(insertIndex, 0, '');
|
|
733
|
+
insertIndex++;
|
|
734
|
+
}
|
|
735
|
+
} else {
|
|
736
|
+
// Insert the conflict header
|
|
737
|
+
lines.splice(insertIndex, 0, conflictHeader);
|
|
738
|
+
insertIndex++;
|
|
739
|
+
// Ensure a blank line after the header
|
|
740
|
+
if (lines[insertIndex]?.trim() !== '') {
|
|
741
|
+
lines.splice(insertIndex, 0, '');
|
|
742
|
+
insertIndex++;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
724
745
|
|
|
725
|
-
// Insert the requirement block
|
|
726
|
-
lines.splice(insertIndex, 0, ...requirementBlock);
|
|
746
|
+
// Insert the requirement block
|
|
747
|
+
lines.splice(insertIndex, 0, ...requirementBlock);
|
|
727
748
|
|
|
728
|
-
// Ensure there's a blank line after the requirement block
|
|
729
|
-
const afterIndex = insertIndex + requirementBlock.length;
|
|
730
|
-
if (afterIndex < lines.length && lines[afterIndex]?.trim() !== '') {
|
|
731
|
-
|
|
732
|
-
}
|
|
749
|
+
// Ensure there's a blank line after the requirement block
|
|
750
|
+
const afterIndex = insertIndex + requirementBlock.length;
|
|
751
|
+
if (afterIndex < lines.length && lines[afterIndex]?.trim() !== '') {
|
|
752
|
+
lines.splice(afterIndex, 0, '');
|
|
753
|
+
}
|
|
733
754
|
|
|
734
|
-
// Move the requirement to the VERIFIED section
|
|
735
|
-
const verifiedSectionVariants = [
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
];
|
|
739
|
-
let verifiedIndex = -1;
|
|
740
|
-
for (let i = 0; i < lines.length; i++) {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
755
|
+
// Move the requirement to the VERIFIED section
|
|
756
|
+
const verifiedSectionVariants = [
|
|
757
|
+
'## 📝 VERIFIED',
|
|
758
|
+
'## VERIFIED'
|
|
759
|
+
];
|
|
760
|
+
let verifiedIndex = -1;
|
|
761
|
+
for (let i = 0; i < lines.length; i++) {
|
|
762
|
+
const line = lines[i];
|
|
763
|
+
const trimmed = line.trim();
|
|
764
|
+
|
|
765
|
+
// Check each variant more carefully
|
|
766
|
+
for (const variant of verifiedSectionVariants) {
|
|
767
|
+
const variantTrimmed = variant.trim();
|
|
768
|
+
// Exact match or line starts with variant
|
|
769
|
+
if (trimmed === variantTrimmed || trimmed.startsWith(variantTrimmed)) {
|
|
770
|
+
verifiedIndex = i;
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (verifiedIndex !== -1) break;
|
|
751
775
|
}
|
|
752
|
-
}
|
|
753
|
-
if (verifiedIndex !== -1) break;
|
|
754
|
-
}
|
|
755
776
|
|
|
756
|
-
if (verifiedIndex === -1) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
777
|
+
if (verifiedIndex === -1) {
|
|
778
|
+
// Create VERIFIED section - place it after TO VERIFY section
|
|
779
|
+
const block = [];
|
|
780
|
+
block.push('## 📝 VERIFIED');
|
|
781
|
+
lines.splice(verifyIndex + 1, 0, ...block);
|
|
782
|
+
verifiedIndex = lines.findIndex(line => {
|
|
783
|
+
const trimmed = line.trim();
|
|
784
|
+
return trimmed === '## 📝 VERIFIED' || trimmed.startsWith('## 📝 VERIFIED');
|
|
785
|
+
});
|
|
786
|
+
}
|
|
766
787
|
|
|
767
|
-
// Insert the requirement block at the end of the VERIFIED section
|
|
768
|
-
let insertIndexVerified = verifiedIndex + 1;
|
|
769
|
-
while (insertIndexVerified < lines.length && lines[insertIndexVerified].trim().startsWith('###')) {
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
lines.splice(insertIndexVerified, 0, ...requirementBlock);
|
|
788
|
+
// Insert the requirement block at the end of the VERIFIED section
|
|
789
|
+
let insertIndexVerified = verifiedIndex + 1;
|
|
790
|
+
while (insertIndexVerified < lines.length && lines[insertIndexVerified].trim().startsWith('###')) {
|
|
791
|
+
insertIndexVerified++;
|
|
792
|
+
}
|
|
793
|
+
lines.splice(insertIndexVerified, 0, ...requirementBlock);
|
|
773
794
|
|
|
774
795
|
|
|
775
796
|
|
|
@@ -862,6 +883,7 @@ async function getAllAvailableProviders() {
|
|
|
862
883
|
const awsSecretKey = process.env.AWS_SECRET_ACCESS_KEY || config.awsSecretAccessKey;
|
|
863
884
|
const claudeCodeAvailable = await llm.isClaudeCodeAvailable();
|
|
864
885
|
const clineAvailable = await llm.isClineAvailable();
|
|
886
|
+
const openCodeAvailable = await llm.isOpenCodeAvailable();
|
|
865
887
|
const ollamaAvailable = await llm.isOllamaAvailable();
|
|
866
888
|
let ollamaModels = [];
|
|
867
889
|
if (ollamaAvailable) {
|
|
@@ -944,7 +966,7 @@ async function getAllAvailableProviders() {
|
|
|
944
966
|
console.log(chalk.yellow(`\n🔧 Cline CLI not found, auto-installing...`));
|
|
945
967
|
clineAvailable = await ensureClineInstalled();
|
|
946
968
|
}
|
|
947
|
-
|
|
969
|
+
|
|
948
970
|
if (!clineAvailable) {
|
|
949
971
|
skipped.push({ provider: providerId, displayName: def.name, enabled, reason: 'Cline CLI not installed — run: npm install -g cline' });
|
|
950
972
|
continue;
|
|
@@ -955,6 +977,17 @@ async function getAllAvailableProviders() {
|
|
|
955
977
|
});
|
|
956
978
|
break;
|
|
957
979
|
}
|
|
980
|
+
case 'opencode': {
|
|
981
|
+
if (!openCodeAvailable) {
|
|
982
|
+
skipped.push({ provider: providerId, displayName: def.name, enabled, reason: 'OpenCode CLI not installed — visit https://opencode.ai to install' });
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
providers.push({
|
|
986
|
+
...base,
|
|
987
|
+
model: def.defaultModel
|
|
988
|
+
});
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
958
991
|
case 'cursor':
|
|
959
992
|
case 'windsurf':
|
|
960
993
|
case 'vscode':
|
|
@@ -1030,15 +1063,15 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1030
1063
|
const now = Date.now();
|
|
1031
1064
|
const timeSinceMarked = now - (rateLimitInfo.markedAt || 0);
|
|
1032
1065
|
const minutesSinceMarked = timeSinceMarked / (1000 * 60);
|
|
1033
|
-
|
|
1066
|
+
|
|
1034
1067
|
// Clear rate limit if:
|
|
1035
1068
|
// 1. It was marked due to platform issues, OR
|
|
1036
1069
|
// 2. It was marked within the last 5 minutes (likely a recent platform issue)
|
|
1037
|
-
const isPlatformError = rateLimitInfo.reason &&
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1070
|
+
const isPlatformError = rateLimitInfo.reason &&
|
|
1071
|
+
(rateLimitInfo.reason.includes('xdg-open') ||
|
|
1072
|
+
rateLimitInfo.reason.includes('command not found') ||
|
|
1073
|
+
rateLimitInfo.reason.includes('Unable to find application'));
|
|
1074
|
+
|
|
1042
1075
|
if (isPlatformError || minutesSinceMarked < 5) {
|
|
1043
1076
|
console.log(chalk.yellow(`⚠️ Clearing incorrect rate limit for Replit (marked ${minutesSinceMarked.toFixed(1)} minutes ago: ${isPlatformError ? 'platform error' : 'recent error'})`));
|
|
1044
1077
|
delete providerManager.rateLimits['replit:replit'];
|
|
@@ -1056,7 +1089,7 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1056
1089
|
}
|
|
1057
1090
|
}
|
|
1058
1091
|
const savedAgent = firstEnabledAgent || config.agent || config.ide;
|
|
1059
|
-
|
|
1092
|
+
|
|
1060
1093
|
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1061
1094
|
console.log(chalk.gray(`[DEBUG] firstEnabledAgent: ${firstEnabledAgent}`));
|
|
1062
1095
|
console.log(chalk.gray(`[DEBUG] config.agent: ${config.agent}`));
|
|
@@ -1076,42 +1109,33 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1076
1109
|
}
|
|
1077
1110
|
|
|
1078
1111
|
const availableProviders = enabledProviders.filter(p => {
|
|
1079
|
-
// Exclude
|
|
1112
|
+
// Exclude specified provider and rate-limited providers
|
|
1080
1113
|
if (excludeProvider && p.provider === excludeProvider) {
|
|
1081
1114
|
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1082
1115
|
console.log(chalk.gray(`[DEBUG] Excluding ${p.provider} due to excludeProvider=${excludeProvider}`));
|
|
1083
1116
|
}
|
|
1084
1117
|
return false;
|
|
1085
1118
|
}
|
|
1086
|
-
|
|
1119
|
+
|
|
1087
1120
|
// For IDE providers, only consider them rate limited if they've actually been used
|
|
1088
|
-
// IDE providers that haven't been used yet should always be available for launch/installation
|
|
1089
1121
|
if (p.type === 'ide') {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
// Debug: Log IDE provider status
|
|
1095
|
-
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1096
|
-
console.log(chalk.gray(`[DEBUG] IDE Provider ${p.provider}: hasBeenUsed=${hasBeenUsed}, isRateLimited=${providerManager.isRateLimited(p.provider, p.model)}`));
|
|
1097
|
-
console.log(chalk.gray(`[DEBUG] Rate limits for ${p.provider}:`, JSON.stringify(providerManager.rateLimits, null, 2)));
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
// If it has been used, check if it's currently rate limited
|
|
1122
|
+
const hasBeenUsed = providerManager.rateLimits[`${p.provider}:`] ||
|
|
1123
|
+
Object.keys(providerManager.rateLimits).some(key => key.startsWith(`${p.provider}:`));
|
|
1124
|
+
|
|
1101
1125
|
if (hasBeenUsed) {
|
|
1102
1126
|
const isRateLimited = providerManager.isRateLimited(p.provider, p.model);
|
|
1103
1127
|
if (process.env.DEBUG_PROVIDER_SELECTION && isRateLimited) {
|
|
1104
|
-
console.log(chalk.gray(`[DEBUG] ${p.provider} is rate limited, excluding`));
|
|
1128
|
+
console.log(chalk.gray(`[DEBUG] IDE Provider ${p.provider} is rate limited, excluding`));
|
|
1105
1129
|
}
|
|
1106
1130
|
return !isRateLimited;
|
|
1107
1131
|
}
|
|
1108
1132
|
// If it hasn't been used yet, it's always available
|
|
1109
1133
|
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1110
|
-
console.log(chalk.gray(`[DEBUG] ${p.provider} hasn't been used yet, including`));
|
|
1134
|
+
console.log(chalk.gray(`[DEBUG] IDE Provider ${p.provider} hasn't been used yet, including`));
|
|
1111
1135
|
}
|
|
1112
1136
|
return true;
|
|
1113
1137
|
}
|
|
1114
|
-
|
|
1138
|
+
|
|
1115
1139
|
// For non-IDE providers, check rate limits normally
|
|
1116
1140
|
const isRateLimited = providerManager.isRateLimited(p.provider, p.model);
|
|
1117
1141
|
if (process.env.DEBUG_PROVIDER_SELECTION && isRateLimited) {
|
|
@@ -1120,53 +1144,7 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1120
1144
|
return !isRateLimited;
|
|
1121
1145
|
});
|
|
1122
1146
|
|
|
1123
|
-
|
|
1124
|
-
if (availableProviders.length === 0) {
|
|
1125
|
-
const { providers: allProviders } = await getAllAvailableProviders();
|
|
1126
|
-
const unusedIdeProviders = allProviders.filter(p =>
|
|
1127
|
-
p.type === 'ide' &&
|
|
1128
|
-
!enabledProviders.some(ep => ep.provider === p.provider) &&
|
|
1129
|
-
!providerManager.rateLimits[`${p.provider}:`] &&
|
|
1130
|
-
!Object.keys(providerManager.rateLimits).some(key => key.startsWith(`${p.provider}:`))
|
|
1131
|
-
);
|
|
1132
|
-
|
|
1133
|
-
if (unusedIdeProviders.length > 0) {
|
|
1134
|
-
console.log(chalk.yellow(`⚠️ No enabled providers available. Auto-enabling unused IDE providers...`));
|
|
1135
|
-
const prefs = await getProviderPreferences();
|
|
1136
|
-
|
|
1137
|
-
// Enable the first few unused IDE providers
|
|
1138
|
-
const providersToEnable = unusedIdeProviders.slice(0, 3);
|
|
1139
|
-
for (const provider of providersToEnable) {
|
|
1140
|
-
prefs.enabled[provider.provider] = true;
|
|
1141
|
-
console.log(chalk.gray(` ✓ Enabled ${provider.displayName}`));
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
await saveProviderPreferences(prefs.order, prefs.enabled);
|
|
1145
|
-
|
|
1146
|
-
// Recalculate available providers with the newly enabled ones
|
|
1147
|
-
const newlyEnabledProviders = allProviders.filter(p =>
|
|
1148
|
-
prefs.enabled[p.provider] !== false
|
|
1149
|
-
);
|
|
1150
|
-
|
|
1151
|
-
const newlyAvailableProviders = newlyEnabledProviders.filter(p => {
|
|
1152
|
-
// Exclude the specified provider
|
|
1153
|
-
if (excludeProvider && p.provider === excludeProvider) {
|
|
1154
|
-
return false;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// For newly enabled IDE providers, they haven't been used yet, so they're always available
|
|
1158
|
-
if (p.type === 'ide') {
|
|
1159
|
-
return true;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// For non-IDE providers, check rate limits normally
|
|
1163
|
-
return !providerManager.isRateLimited(p.provider, p.model);
|
|
1164
|
-
});
|
|
1165
|
-
|
|
1166
|
-
// Add the newly available providers to the list
|
|
1167
|
-
availableProviders.push(...newlyAvailableProviders);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1147
|
+
|
|
1170
1148
|
|
|
1171
1149
|
// Debug: Log provider selection details
|
|
1172
1150
|
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
@@ -1187,25 +1165,25 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1187
1165
|
console.log(chalk.gray(`[DEBUG] Found selection: ${selection ? selection.provider : 'null'}`));
|
|
1188
1166
|
}
|
|
1189
1167
|
}
|
|
1190
|
-
|
|
1168
|
+
|
|
1191
1169
|
// If no selection or the selected provider is rate limited, try to find an unused IDE provider
|
|
1192
1170
|
if (!selection || (selection.type === 'ide' && providerManager.isRateLimited(selection.provider, selection.model))) {
|
|
1193
1171
|
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1194
1172
|
console.log(chalk.gray(`[DEBUG] No valid selection, trying unused IDE providers`));
|
|
1195
1173
|
console.log(chalk.gray(`[DEBUG] Reason: ${!selection ? 'no selection' : 'selection is rate limited'}`));
|
|
1196
1174
|
}
|
|
1197
|
-
|
|
1175
|
+
|
|
1198
1176
|
// Prioritize newly enabled IDE providers that haven't been used
|
|
1199
|
-
const unusedIdeProviders = availableProviders.filter(p =>
|
|
1200
|
-
p.type === 'ide' &&
|
|
1201
|
-
!providerManager.rateLimits[`${p.provider}:`] &&
|
|
1177
|
+
const unusedIdeProviders = availableProviders.filter(p =>
|
|
1178
|
+
p.type === 'ide' &&
|
|
1179
|
+
!providerManager.rateLimits[`${p.provider}:`] &&
|
|
1202
1180
|
!Object.keys(providerManager.rateLimits).some(key => key.startsWith(`${p.provider}:`))
|
|
1203
1181
|
);
|
|
1204
|
-
|
|
1182
|
+
|
|
1205
1183
|
if (process.env.DEBUG_PROVIDER_SELECTION) {
|
|
1206
1184
|
console.log(chalk.gray(`[DEBUG] Unused IDE providers: ${unusedIdeProviders.map(p => p.provider).join(', ')}`));
|
|
1207
1185
|
}
|
|
1208
|
-
|
|
1186
|
+
|
|
1209
1187
|
if (unusedIdeProviders.length > 0) {
|
|
1210
1188
|
selection = unusedIdeProviders[0];
|
|
1211
1189
|
console.log(chalk.green(`✓ Selected unused IDE provider: ${selection.displayName}`));
|
|
@@ -1229,16 +1207,30 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1229
1207
|
return { status: 'ok', provider: selection, disabledProviders };
|
|
1230
1208
|
}
|
|
1231
1209
|
|
|
1232
|
-
const waits =
|
|
1210
|
+
const waits = availableProviders
|
|
1233
1211
|
.map(p => providerManager.getTimeUntilReset(p.provider, p.model))
|
|
1234
1212
|
.filter(Boolean);
|
|
1235
1213
|
const nextResetMs = waits.length ? Math.min(...waits) : null;
|
|
1236
1214
|
|
|
1215
|
+
// Only return all_rate_limited if there are available providers and ALL of them are rate limited
|
|
1216
|
+
const allAvailableRateLimited = availableProviders.length > 0 &&
|
|
1217
|
+
availableProviders.every(p => providerManager.isRateLimited(p.provider, p.model));
|
|
1218
|
+
|
|
1219
|
+
if (allAvailableRateLimited) {
|
|
1220
|
+
return {
|
|
1221
|
+
status: 'all_rate_limited',
|
|
1222
|
+
enabledProviders,
|
|
1223
|
+
disabledProviders,
|
|
1224
|
+
nextResetMs
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// If we reach here, no providers are available (filtered out by excludeProvider or other conditions)
|
|
1237
1229
|
return {
|
|
1238
|
-
status: '
|
|
1230
|
+
status: 'no_available',
|
|
1239
1231
|
enabledProviders,
|
|
1240
1232
|
disabledProviders,
|
|
1241
|
-
|
|
1233
|
+
skipped
|
|
1242
1234
|
};
|
|
1243
1235
|
}
|
|
1244
1236
|
|
|
@@ -1249,7 +1241,7 @@ async function acquireProviderConfig(excludeProvider = null, excludeModel = null
|
|
|
1249
1241
|
if (forcedProvider === 'cline') {
|
|
1250
1242
|
const { DirectLLMManager } = require('vibecodingmachine-core');
|
|
1251
1243
|
const llm = new DirectLLMManager();
|
|
1252
|
-
|
|
1244
|
+
|
|
1253
1245
|
if (!await llm.isClineAvailable()) {
|
|
1254
1246
|
console.log(chalk.yellow(`\n🔧 Cline CLI not found, auto-installing...`));
|
|
1255
1247
|
const installed = await ensureClineInstalled();
|
|
@@ -1259,7 +1251,7 @@ async function acquireProviderConfig(excludeProvider = null, excludeModel = null
|
|
|
1259
1251
|
}
|
|
1260
1252
|
}
|
|
1261
1253
|
}
|
|
1262
|
-
|
|
1254
|
+
|
|
1263
1255
|
const { providers } = await getAllAvailableProviders();
|
|
1264
1256
|
const match = providers.find(p => p.provider === forcedProvider);
|
|
1265
1257
|
if (match) return match;
|
|
@@ -1270,6 +1262,13 @@ async function acquireProviderConfig(excludeProvider = null, excludeModel = null
|
|
|
1270
1262
|
|
|
1271
1263
|
while (true) {
|
|
1272
1264
|
const selection = await getProviderConfig(excludeProvider);
|
|
1265
|
+
|
|
1266
|
+
// Handle case where getProviderConfig returns undefined/null
|
|
1267
|
+
if (!selection) {
|
|
1268
|
+
console.log(chalk.red(`\n✗ ${t('auto.direct.provider.none.available')}\n`));
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1273
1272
|
if (selection.status === 'ok') {
|
|
1274
1273
|
// If we have a specific model to exclude (for same-IDE failover), skip it
|
|
1275
1274
|
if (excludeModel && selection.provider.model === excludeModel) {
|
|
@@ -1317,6 +1316,14 @@ async function acquireProviderConfig(excludeProvider = null, excludeModel = null
|
|
|
1317
1316
|
continue;
|
|
1318
1317
|
}
|
|
1319
1318
|
|
|
1319
|
+
if (selection.status === 'no_available') {
|
|
1320
|
+
console.log(chalk.red(`\n✗ No available providers after filtering\n`));
|
|
1321
|
+
if (selection.disabledProviders && selection.disabledProviders.length > 0) {
|
|
1322
|
+
console.log(chalk.gray(` Enable more providers in Settings to continue\n`));
|
|
1323
|
+
}
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1320
1327
|
return null;
|
|
1321
1328
|
}
|
|
1322
1329
|
}
|
|
@@ -1963,7 +1970,7 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1963
1970
|
if (antigravityQuotaResult && antigravityQuotaResult.isRateLimited) {
|
|
1964
1971
|
quotaHandled = true;
|
|
1965
1972
|
const quotaMessage = antigravityQuotaResult.message || 'Antigravity quota limit detected via AppleScript';
|
|
1966
|
-
|
|
1973
|
+
|
|
1967
1974
|
// Mark the provider as rate limited
|
|
1968
1975
|
try {
|
|
1969
1976
|
if (sharedProviderManager && typeof sharedProviderManager.markRateLimited === 'function') {
|
|
@@ -1978,20 +1985,20 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
1978
1985
|
resolve({ success: false, rateLimited: true, antigravityRateLimited: true, providerRateLimited: ideType, matchedLine: quotaMessage });
|
|
1979
1986
|
return;
|
|
1980
1987
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1988
|
+
} catch (appleScriptError) {
|
|
1989
|
+
console.log(chalk.red(`❌ AppleScript quota detection failed for Antigravity: ${appleScriptError.message}`));
|
|
1990
|
+
}
|
|
1984
1991
|
}
|
|
1985
1992
|
// For Windsurf: AppleScript quota detection
|
|
1986
1993
|
else if (ideType === 'windsurf') {
|
|
1987
1994
|
try {
|
|
1988
1995
|
const appleScriptManager = new AppleScriptManager();
|
|
1989
1996
|
const windsurfQuotaResult = await appleScriptManager.checkWindsurfQuotaLimit();
|
|
1990
|
-
|
|
1997
|
+
|
|
1991
1998
|
if (windsurfQuotaResult && windsurfQuotaResult.hasQuotaWarning) {
|
|
1992
1999
|
quotaHandled = true;
|
|
1993
2000
|
const quotaMessage = windsurfQuotaResult.matchedText || 'Windsurf quota limit detected';
|
|
1994
|
-
|
|
2001
|
+
|
|
1995
2002
|
// Mark the provider as rate limited
|
|
1996
2003
|
try {
|
|
1997
2004
|
if (sharedProviderManager && typeof sharedProviderManager.markRateLimited === 'function') {
|
|
@@ -2000,7 +2007,7 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2000
2007
|
} catch (e) {
|
|
2001
2008
|
// Ignore errors from marking rate-limited
|
|
2002
2009
|
}
|
|
2003
|
-
|
|
2010
|
+
|
|
2004
2011
|
watcher.close();
|
|
2005
2012
|
console.log(chalk.yellow(`\n⚠️ Windsurf quota warning detected: ${quotaMessage.substring(0, 100)}...\n`));
|
|
2006
2013
|
resolve({ success: false, rateLimited: true, windsurfRateLimited: true, providerRateLimited: ideType, matchedLine: quotaMessage });
|
|
@@ -2019,7 +2026,7 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2019
2026
|
if (cursorQuotaResult && cursorQuotaResult.isRateLimited) {
|
|
2020
2027
|
quotaHandled = true;
|
|
2021
2028
|
const quotaMessage = cursorQuotaResult.message || 'Cursor quota limit detected via AppleScript';
|
|
2022
|
-
|
|
2029
|
+
|
|
2023
2030
|
// Mark the provider as rate limited
|
|
2024
2031
|
try {
|
|
2025
2032
|
if (sharedProviderManager && typeof sharedProviderManager.markRateLimited === 'function') {
|
|
@@ -2042,7 +2049,7 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2042
2049
|
else if (ideType === 'vscode' || ideType === 'github-copilot' || ideType === 'amazon-q') {
|
|
2043
2050
|
const quotaDetector = new QuotaDetector();
|
|
2044
2051
|
const ideToCheck = ideType === 'github-copilot' || ideType === 'amazon-q' ? 'vscode' : ideType;
|
|
2045
|
-
|
|
2052
|
+
|
|
2046
2053
|
try {
|
|
2047
2054
|
const quotaResult = await quotaDetector.detectQuotaWarning(ideToCheck);
|
|
2048
2055
|
|
|
@@ -2051,9 +2058,9 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2051
2058
|
const quotaMessage = quotaResult.matchedText || 'Quota limit detected in IDE UI';
|
|
2052
2059
|
|
|
2053
2060
|
// Check if this might be Kiro quota warning even when ideType is amazon-q
|
|
2054
|
-
const isKiroPattern = quotaMessage.toLowerCase().includes('out of credits') ||
|
|
2055
|
-
|
|
2056
|
-
|
|
2061
|
+
const isKiroPattern = quotaMessage.toLowerCase().includes('out of credits') ||
|
|
2062
|
+
quotaMessage.toLowerCase().includes('upgrade plan') ||
|
|
2063
|
+
quotaMessage.toLowerCase().includes('monthly usage limit');
|
|
2057
2064
|
|
|
2058
2065
|
// Mark the provider as rate limited
|
|
2059
2066
|
try {
|
|
@@ -2066,20 +2073,20 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2066
2073
|
|
|
2067
2074
|
watcher.close();
|
|
2068
2075
|
console.log(chalk.yellow(`\n⚠️ Quota warning detected via CDP in ${ideToCheck} UI: ${quotaMessage.substring(0, 100)}...\n`));
|
|
2069
|
-
|
|
2076
|
+
|
|
2070
2077
|
// If this looks like Kiro quota, set kiroRateLimited flag too
|
|
2071
|
-
const resolveObj = {
|
|
2072
|
-
success: false,
|
|
2073
|
-
rateLimited: true,
|
|
2074
|
-
providerRateLimited: ideType,
|
|
2075
|
-
matchedLine: quotaMessage
|
|
2078
|
+
const resolveObj = {
|
|
2079
|
+
success: false,
|
|
2080
|
+
rateLimited: true,
|
|
2081
|
+
providerRateLimited: ideType,
|
|
2082
|
+
matchedLine: quotaMessage
|
|
2076
2083
|
};
|
|
2077
|
-
|
|
2084
|
+
|
|
2078
2085
|
if (ideType === 'amazon-q' && isKiroPattern) {
|
|
2079
2086
|
resolveObj.kiroRateLimited = true;
|
|
2080
2087
|
console.log(chalk.magenta(`💡 Detected potential Kiro quota warning despite amazon-q ideType`));
|
|
2081
2088
|
}
|
|
2082
|
-
|
|
2089
|
+
|
|
2083
2090
|
resolve(resolveObj);
|
|
2084
2091
|
return;
|
|
2085
2092
|
}
|
|
@@ -2093,7 +2100,76 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2093
2100
|
}
|
|
2094
2101
|
}
|
|
2095
2102
|
|
|
2096
|
-
// Check 5:
|
|
2103
|
+
// Check 5: Continuation detection (check every 2 seconds)
|
|
2104
|
+
if (elapsed > 30000 && elapsed % 2000 < 2000) { // Start checking after 30 seconds, every 2 seconds
|
|
2105
|
+
try {
|
|
2106
|
+
let continuationDetected = false;
|
|
2107
|
+
let continuationClicked = false;
|
|
2108
|
+
|
|
2109
|
+
// Use CDP for web-based IDEs
|
|
2110
|
+
if (ideType === 'vscode' || ideType === 'cursor' || ideType === 'windsurf') {
|
|
2111
|
+
const { CDPManager } = require('vibecodingmachine-core');
|
|
2112
|
+
const cdpManager = new CDPManager();
|
|
2113
|
+
|
|
2114
|
+
try {
|
|
2115
|
+
const continuationResult = await cdpManager.checkForContinuation(ideType);
|
|
2116
|
+
if (continuationResult.continuationDetected) {
|
|
2117
|
+
console.log(chalk.yellow(`🔄 Continuation prompt detected in ${ideType}, clicking button...`));
|
|
2118
|
+
const clickResult = await cdpManager.clickContinuationButton(ideType);
|
|
2119
|
+
if (clickResult.success && clickResult.clicked) {
|
|
2120
|
+
continuationDetected = true;
|
|
2121
|
+
continuationClicked = true;
|
|
2122
|
+
console.log(chalk.green(`✅ Continuation button clicked successfully`));
|
|
2123
|
+
} else {
|
|
2124
|
+
console.log(chalk.yellow(`⚠️ Failed to click continuation button: ${clickResult.message || 'Unknown error'}`));
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
} catch (cdpError) {
|
|
2128
|
+
console.log(chalk.gray(` Continuation detection via CDP failed: ${cdpError.message}`));
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
// Use AppleScript for desktop IDEs
|
|
2132
|
+
else if (ideType === 'cline' || ideType === 'claude-code') {
|
|
2133
|
+
const { AppleScriptManager } = require('vibecodingmachine-core');
|
|
2134
|
+
const appleScriptManager = new AppleScriptManager();
|
|
2135
|
+
|
|
2136
|
+
try {
|
|
2137
|
+
const continuationResult = await appleScriptManager.checkForContinuation(ideType);
|
|
2138
|
+
if (continuationResult.continuationDetected) {
|
|
2139
|
+
console.log(chalk.yellow(`🔄 Continuation prompt detected in ${ideType}, clicking button...`));
|
|
2140
|
+
const clickResult = await appleScriptManager.clickContinuationButton(ideType);
|
|
2141
|
+
if (clickResult.success && clickResult.clicked) {
|
|
2142
|
+
continuationDetected = true;
|
|
2143
|
+
continuationClicked = true;
|
|
2144
|
+
console.log(chalk.green(`✅ Continuation button clicked successfully`));
|
|
2145
|
+
} else {
|
|
2146
|
+
console.log(chalk.yellow(`⚠️ Failed to click continuation button: ${clickResult.error || 'Unknown error'}`));
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
} catch (appleScriptError) {
|
|
2150
|
+
console.log(chalk.gray(` Continuation detection via AppleScript failed: ${appleScriptError.message}`));
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
// Record continuation detection in health tracker
|
|
2155
|
+
if (continuationDetected) {
|
|
2156
|
+
try {
|
|
2157
|
+
// Update the interaction record to include continuation detection
|
|
2158
|
+
const currentMetrics = sharedHealthTracker.getHealthMetrics(ideType);
|
|
2159
|
+
if (currentMetrics && currentMetrics.interactions && currentMetrics.interactions.length > 0) {
|
|
2160
|
+
const lastInteraction = currentMetrics.interactions[currentMetrics.interactions.length - 1];
|
|
2161
|
+
lastInteraction.continuationPromptsDetected = (lastInteraction.continuationPromptsDetected || 0) + 1;
|
|
2162
|
+
}
|
|
2163
|
+
} catch (trackerError) {
|
|
2164
|
+
console.log(chalk.gray(` Failed to record continuation detection: ${trackerError.message}`));
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
} catch (error) {
|
|
2168
|
+
console.log(chalk.gray(` Continuation detection error: ${error.message}`));
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// Check 6: Timeout
|
|
2097
2173
|
const elapsed = Date.now() - startTime;
|
|
2098
2174
|
if (elapsed >= timeoutMs) {
|
|
2099
2175
|
watcher.close();
|
|
@@ -2102,6 +2178,45 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2102
2178
|
return;
|
|
2103
2179
|
}
|
|
2104
2180
|
|
|
2181
|
+
// Check 7: Text pattern matching fallback for continuation prompts
|
|
2182
|
+
const fileContent = await fs.readFile(reqPath, 'utf-8');
|
|
2183
|
+
const continuationPatterns = [
|
|
2184
|
+
/continue.*generation/i,
|
|
2185
|
+
/keep.*going/i,
|
|
2186
|
+
/continue.*writing/i,
|
|
2187
|
+
/continue.*coding/i,
|
|
2188
|
+
/proceed/i,
|
|
2189
|
+
/next.*step/i,
|
|
2190
|
+
/continue/i
|
|
2191
|
+
];
|
|
2192
|
+
|
|
2193
|
+
const hasContinuationPrompt = continuationPatterns.some(pattern => pattern.test(fileContent));
|
|
2194
|
+
if (hasContinuationPrompt && elapsed > 30000) { // Only check for text patterns after 30 seconds
|
|
2195
|
+
console.log(chalk.yellow(`🔄 Continuation prompt detected in text, attempting to continue...`));
|
|
2196
|
+
// This is a fallback - we can't click buttons via text patterns, but we can log it
|
|
2197
|
+
try {
|
|
2198
|
+
const currentMetrics = sharedHealthTracker.getHealthMetrics(ideType);
|
|
2199
|
+
if (currentMetrics && currentMetrics.interactions && currentMetrics.interactions.length > 0) {
|
|
2200
|
+
const lastInteraction = currentMetrics.interactions[currentMetrics.interactions.length - 1];
|
|
2201
|
+
lastInteraction.continuationPromptsDetected = (lastInteraction.continuationPromptsDetected || 0) + 1;
|
|
2202
|
+
}
|
|
2203
|
+
} catch (trackerError) {
|
|
2204
|
+
console.log(chalk.gray(` Failed to record text continuation detection: ${trackerError.message}`));
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
// Check 8: Memory monitoring
|
|
2208
|
+
if (elapsed > 60000 && elapsed % 30000 < 30000) { // Check memory every 30 seconds after 1 minute
|
|
2209
|
+
const memoryUsage = process.memoryUsage();
|
|
2210
|
+
const heapUsedMB = Math.round(memoryUsage.heapUsed / 1024 / 1024);
|
|
2211
|
+
const heapTotalMB = Math.round(memoryUsage.heapTotal / 1024 / 1024);
|
|
2212
|
+
const heapUsedPercent = Math.round((memoryUsage.heapUsed / memoryUsage.heapTotal) * 100);
|
|
2213
|
+
|
|
2214
|
+
if (heapUsedMB > 500) { // Warn at 500MB
|
|
2215
|
+
console.log(chalk.yellow(`⚠️ High memory usage detected: ${heapUsedMB}MB heap (${heapUsedPercent}% of ${heapTotalMB}MB)`));
|
|
2216
|
+
console.log(chalk.gray(` RSS: ${Math.round(memoryUsage.rss / 1024 / 1024)}MB, External: ${Math.round(memoryUsage.external / 1024 / 1024)}MB`));
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2105
2220
|
// Log progress every 30 seconds
|
|
2106
2221
|
if (Date.now() - lastCheckTime >= 30000) {
|
|
2107
2222
|
const elapsedMin = Math.floor(elapsed / 60000);
|
|
@@ -2156,13 +2271,13 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
|
|
|
2156
2271
|
}
|
|
2157
2272
|
return { success: false, error: 'Antigravity rate limit reached, retrying with next provider.', shouldRetry: true };
|
|
2158
2273
|
}
|
|
2159
|
-
|
|
2274
|
+
|
|
2160
2275
|
// CRITICAL: Mark provider as unavailable for ANY error so acquireProviderConfig() will skip it
|
|
2161
2276
|
// EXCEPT for web-based IDEs where the error is platform/browser related
|
|
2162
2277
|
const error = ideResult.output || ideResult.error || 'IDE provider failed';
|
|
2163
2278
|
const isWebBasedIDE = providerConfig.provider === 'replit';
|
|
2164
2279
|
const isPlatformError = error.includes('xdg-open') || error.includes('command not found') || error.includes('Unable to find application');
|
|
2165
|
-
|
|
2280
|
+
|
|
2166
2281
|
if (!isWebBasedIDE || !isPlatformError) {
|
|
2167
2282
|
providerManager.markRateLimited(providerConfig.provider, providerConfig.model, error);
|
|
2168
2283
|
} else {
|
|
@@ -2170,19 +2285,37 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
|
|
|
2170
2285
|
// Just log the error and let the system try the next provider
|
|
2171
2286
|
console.log(chalk.yellow(`⚠️ Web-based IDE ${providerConfig.provider} failed due to platform issue: ${error}`));
|
|
2172
2287
|
}
|
|
2173
|
-
|
|
2288
|
+
|
|
2174
2289
|
return { success: false, error: ideResult.error || 'IDE provider failed' };
|
|
2175
2290
|
}
|
|
2176
2291
|
|
|
2177
2292
|
console.log(chalk.green(`✓ ${t('auto.direct.ide.prompt.sent')}`));
|
|
2178
2293
|
|
|
2294
|
+
// Calculate adaptive timeout based on IDE's historical performance
|
|
2295
|
+
const ideType = providerConfig.provider || providerConfig.ide;
|
|
2296
|
+
const healthMetrics = sharedHealthTracker.getHealthMetrics(ideType);
|
|
2297
|
+
let adaptiveTimeoutMs = 30 * 60 * 1000; // Default 30 minutes
|
|
2298
|
+
|
|
2299
|
+
if (healthMetrics && healthMetrics.responseTimes.length > 0) {
|
|
2300
|
+
// Use adaptive timeout calculation if we have historical data
|
|
2301
|
+
const stats = TimeoutCalculator.calculateTimeout(healthMetrics.responseTimes, {
|
|
2302
|
+
defaultTimeout: 30 * 60 * 1000,
|
|
2303
|
+
minTimeout: 5 * 60 * 1000, // 5 minutes minimum
|
|
2304
|
+
maxTimeout: 60 * 60 * 1000, // 60 minutes maximum
|
|
2305
|
+
bufferPercentage: 0.5 // 50% buffer
|
|
2306
|
+
});
|
|
2307
|
+
adaptiveTimeoutMs = stats.timeout;
|
|
2308
|
+
console.log(chalk.gray(` Using adaptive timeout: ${Math.floor(adaptiveTimeoutMs / 60000)} minutes (based on ${healthMetrics.responseTimes.length} historical response times)`));
|
|
2309
|
+
} else {
|
|
2310
|
+
console.log(chalk.gray(` Using default timeout: ${Math.floor(adaptiveTimeoutMs / 60000)} minutes (no historical data for ${ideType})`));
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2179
2313
|
// Wait for IDE agent to complete the work (IDE will update status to DONE itself)
|
|
2180
2314
|
const ideCompletionStartTime = Date.now();
|
|
2181
|
-
const completionResult = await waitForIdeCompletion(repoPath, requirement.text,
|
|
2315
|
+
const completionResult = await waitForIdeCompletion(repoPath, requirement.text, ideType, adaptiveTimeoutMs);
|
|
2182
2316
|
const ideResponseTime = Date.now() - ideCompletionStartTime;
|
|
2183
2317
|
|
|
2184
2318
|
// Track IDE health metrics based on completion result
|
|
2185
|
-
const ideType = providerConfig.provider || providerConfig.ide;
|
|
2186
2319
|
if (completionResult.success) {
|
|
2187
2320
|
// Record success with response time
|
|
2188
2321
|
await sharedHealthTracker.recordSuccess(ideType, ideResponseTime, {
|
|
@@ -2197,7 +2330,7 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
|
|
|
2197
2330
|
} else if (completionResult.reason === 'timeout') {
|
|
2198
2331
|
// Record failure due to timeout
|
|
2199
2332
|
await sharedHealthTracker.recordFailure(ideType, 'Timeout exceeded', {
|
|
2200
|
-
timeoutUsed:
|
|
2333
|
+
timeoutUsed: adaptiveTimeoutMs, // Use the adaptive timeout that was actually used
|
|
2201
2334
|
requirementId: requirement.id || requirement.text.substring(0, 50),
|
|
2202
2335
|
});
|
|
2203
2336
|
} else {
|
|
@@ -2293,6 +2426,13 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
|
|
|
2293
2426
|
* Sends the full spec instruction to the IDE and polls tasks.md for
|
|
2294
2427
|
* progress (any new checkbox ticked), instead of spawning vcm auto:start.
|
|
2295
2428
|
*
|
|
2429
|
+
* COORDINATION STRATEGY:
|
|
2430
|
+
* - Wait for agent completion signals instead of just detecting any progress
|
|
2431
|
+
* - Implement 2-minute cooldown after progress detection to prevent rapid spawning
|
|
2432
|
+
* - Only consider iteration complete when all tasks are done AND agent signals completion
|
|
2433
|
+
* - Use STATUS:WAITING and PLEASE RESPOND signals from agent to coordinate handoff
|
|
2434
|
+
* - Prevent multiple agents from working on the same spec simultaneously
|
|
2435
|
+
*
|
|
2296
2436
|
* @param {Object} spec - Spec object with .path, .directory, .hasTasks, .hasPlan, .hasPlanPrompt
|
|
2297
2437
|
* @param {string} taskText - Text of the NEXT unchecked task (for display)
|
|
2298
2438
|
* @param {string} taskLine - Full line from tasks.md (used if this is a continuation)
|
|
@@ -2304,107 +2444,40 @@ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig) {
|
|
|
2304
2444
|
const { done: doneBefore, total: totalBefore } = countSpecCheckboxes(spec.path);
|
|
2305
2445
|
const pctBefore = totalBefore > 0 ? Math.round((doneBefore / totalBefore) * 100) : 0;
|
|
2306
2446
|
|
|
2447
|
+
// Get configurable timeouts from auto config
|
|
2448
|
+
const autoConfig = await getAutoConfig();
|
|
2449
|
+
const PROGRESS_TIMEOUT_MS = (autoConfig.specProgressTimeoutMinutes || 15) * 60 * 1000; // Configurable, default 15 minutes
|
|
2450
|
+
const MAX_IDE_ATTEMPTS = autoConfig.maxIdeAttempts || 3; // Configurable, default 3 attempts
|
|
2451
|
+
|
|
2307
2452
|
// Build the full spec instruction (mirrors buildSpecInstruction from Electron app)
|
|
2308
2453
|
// This lets the agent work through multiple tasks autonomously rather than one at a time.
|
|
2309
2454
|
let instruction;
|
|
2310
2455
|
if (spec.hasTasks) {
|
|
2311
|
-
instruction = `Implement all remaining tasks in ${spec.path}/tasks.md. Current progress: ${doneBefore}/${totalBefore} tasks (${pctBefore}%) complete. Check off each task (change "[ ]" to "[x]") as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. When ALL tasks are checked off, report "COMPLETION: 100%".`;
|
|
2456
|
+
instruction = `Read AGENTS.md and INSTRUCTIONS.md in the repo root for workflow guidelines. Implement all remaining tasks in ${spec.path}/tasks.md. Current progress: ${doneBefore}/${totalBefore} tasks (${pctBefore}%) complete. Check off each task (change "[ ]" to "[x]") as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. Whenever you stop and wait for user response, say "STATUS:WAITING" followed by "PLEASE RESPOND". When ALL tasks are checked off, report "COMPLETION: 100%", then say "STATUS:WAITING" and "PLEASE RESPOND".`;
|
|
2312
2457
|
} else if (spec.hasPlan) {
|
|
2313
|
-
instruction = `Read ${spec.path}/plan.md, generate tasks.md for this spec, then implement all tasks. Check off each task as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. When ALL tasks are done, report "COMPLETION: 100%".`;
|
|
2458
|
+
instruction = `Read AGENTS.md and INSTRUCTIONS.md in the repo root for workflow guidelines. Read ${spec.path}/plan.md, generate tasks.md for this spec, then implement all tasks. Check off each task as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. Whenever you stop and wait for user response, say "STATUS:WAITING" followed by "PLEASE RESPOND". When ALL tasks are done, report "COMPLETION: 100%", then say "STATUS:WAITING" and "PLEASE RESPOND".`;
|
|
2314
2459
|
} else {
|
|
2315
2460
|
const planPromptNote = spec.hasPlanPrompt
|
|
2316
2461
|
? `Use plan prompt from ${spec.path}/plan-prompt.md to guide planning. ` : '';
|
|
2317
|
-
instruction = `Read ${spec.path}/spec.md. ${planPromptNote}Run speckit workflow: (1) read spec.md, (2) generate plan.md, (3) generate tasks.md, (4) implement all tasks. Check off each task as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. When ALL tasks are done, report "COMPLETION: 100%".`;
|
|
2462
|
+
instruction = `Read AGENTS.md and INSTRUCTIONS.md in the repo root for workflow guidelines. Read ${spec.path}/spec.md. ${planPromptNote}Run speckit workflow: (1) read spec.md, (2) generate plan.md, (3) generate tasks.md, (4) implement all tasks. Check off each task as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. Whenever you stop and wait for user response, say "STATUS:WAITING" followed by "PLEASE RESPOND". When ALL tasks are done, report "COMPLETION: 100%", then say "STATUS:WAITING" and "PLEASE RESPOND".`;
|
|
2318
2463
|
}
|
|
2319
2464
|
|
|
2320
2465
|
// Send the spec instruction to the IDE via AppleScript.
|
|
2321
|
-
//
|
|
2322
|
-
// opens a fresh Cascade conversation, pastes the instruction, and submits — all in one
|
|
2323
|
-
// atomic execSync call. This eliminates the timing race where a detached AppleScript
|
|
2324
|
-
// fires 3 seconds later after VS Code has regained focus.
|
|
2466
|
+
// Uses the shared AppleScriptManager with detached execution for proper timing.
|
|
2325
2467
|
console.log(chalk.cyan(`📤 Sending spec instruction to ${providerConfig.displayName}...\n`));
|
|
2326
2468
|
|
|
2327
2469
|
if (ideType === 'windsurf') {
|
|
2328
2470
|
try {
|
|
2329
|
-
const
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
writeFileSync(tmpTextFile, instruction, 'utf8');
|
|
2340
|
-
|
|
2341
|
-
// Single combined script: activate Windsurf → verify it is frontmost →
|
|
2342
|
-
// focus Cascade → paste → submit.
|
|
2343
|
-
// Both Windsurf AND VS Code report process name "Electron" to System Events,
|
|
2344
|
-
// so we MUST use bundle identifier "com.exafunction.windsurf" to distinguish them.
|
|
2345
|
-
const combinedScript = `
|
|
2346
|
-
set instructionText to (do shell script "cat " & quoted form of "${tmpTextFile}")
|
|
2347
|
-
|
|
2348
|
-
-- Step 1: bring Windsurf to front
|
|
2349
|
-
tell application "Windsurf"
|
|
2350
|
-
activate
|
|
2351
|
-
delay 1.5
|
|
2352
|
-
end tell
|
|
2353
|
-
|
|
2354
|
-
-- Step 2: verify Windsurf is the frontmost app by BUNDLE ID (not name — both
|
|
2355
|
-
-- Windsurf and VS Code report process name "Electron" to System Events).
|
|
2356
|
-
set maxTries to 3
|
|
2357
|
-
set tries to 0
|
|
2358
|
-
repeat
|
|
2359
|
-
set tries to tries + 1
|
|
2360
|
-
tell application "System Events"
|
|
2361
|
-
set frontBundleID to bundle identifier of first process whose frontmost is true
|
|
2362
|
-
end tell
|
|
2363
|
-
if frontBundleID is "com.exafunction.windsurf" then exit repeat
|
|
2364
|
-
if tries >= maxTries then
|
|
2365
|
-
error "Windsurf did not become frontmost (frontmost bundle ID: " & frontBundleID & ")"
|
|
2366
|
-
end if
|
|
2367
|
-
tell application "Windsurf"
|
|
2368
|
-
activate
|
|
2369
|
-
end tell
|
|
2370
|
-
delay 1.0
|
|
2371
|
-
end repeat
|
|
2372
|
-
|
|
2373
|
-
-- Step 3: send keystrokes to Windsurf using bundle ID (required to distinguish
|
|
2374
|
-
-- Windsurf from VS Code since both run as "Electron" processes)
|
|
2375
|
-
tell application "System Events"
|
|
2376
|
-
set windsurfProc to first process whose bundle identifier is "com.exafunction.windsurf"
|
|
2377
|
-
tell windsurfProc
|
|
2378
|
-
set frontmost to true
|
|
2379
|
-
delay 0.5
|
|
2380
|
-
-- ESC: defocus any editor/terminal input that might intercept keystrokes
|
|
2381
|
-
key code 53
|
|
2382
|
-
delay 0.5
|
|
2383
|
-
-- Cmd+Shift+L: focus Cascade chat input (proven approach from sendText())
|
|
2384
|
-
keystroke "l" using {command down, shift down}
|
|
2385
|
-
delay 2.0
|
|
2386
|
-
-- Clear any auto-inserted context text (e.g. @terminal:zsh)
|
|
2387
|
-
keystroke "a" using {command down}
|
|
2388
|
-
delay 0.3
|
|
2389
|
-
key code 51
|
|
2390
|
-
delay 0.3
|
|
2391
|
-
-- Paste the spec instruction
|
|
2392
|
-
set the clipboard to instructionText
|
|
2393
|
-
keystroke "v" using {command down}
|
|
2394
|
-
delay 0.5
|
|
2395
|
-
-- Submit
|
|
2396
|
-
key code 36
|
|
2397
|
-
delay 1.0
|
|
2398
|
-
end tell
|
|
2399
|
-
end tell
|
|
2400
|
-
`;
|
|
2401
|
-
|
|
2402
|
-
writeFileSync(tmpScpt, combinedScript, 'utf8');
|
|
2403
|
-
try {
|
|
2404
|
-
execSync(`osascript "${tmpScpt}"`, { stdio: 'pipe', timeout: 30000 });
|
|
2405
|
-
} finally {
|
|
2406
|
-
try { _unlinkSync(tmpScpt); } catch (_) {}
|
|
2407
|
-
try { _unlinkSync(tmpTextFile); } catch (_) {}
|
|
2471
|
+
const appleScriptManager = new AppleScriptManager();
|
|
2472
|
+
const sendResult = await appleScriptManager.sendText(instruction, 'windsurf');
|
|
2473
|
+
|
|
2474
|
+
if (sendResult && sendResult.success) {
|
|
2475
|
+
console.log(chalk.green(`✓ Spec instruction sent to ${providerConfig.displayName}`));
|
|
2476
|
+
console.log(chalk.gray(`⏳ Polling tasks.md every 30s for checkbox progress...\n`));
|
|
2477
|
+
console.log(chalk.gray(`🔄 Coordination: Will wait for agent completion signals before sending new tasks\n`));
|
|
2478
|
+
} else {
|
|
2479
|
+
console.log(chalk.red(`✗ Failed to send instruction to Windsurf: ${sendResult?.error || 'Unknown error'}`));
|
|
2480
|
+
return { success: false, error: sendResult?.error || 'Unknown error' };
|
|
2408
2481
|
}
|
|
2409
2482
|
} catch (err) {
|
|
2410
2483
|
console.log(chalk.red(`✗ AppleScript error: ${err.message}`));
|
|
@@ -2434,13 +2507,19 @@ end tell
|
|
|
2434
2507
|
|
|
2435
2508
|
console.log(chalk.green(`✓ Spec instruction sent to ${providerConfig.displayName}`));
|
|
2436
2509
|
console.log(chalk.gray(`⏳ Polling tasks.md every 30s for checkbox progress...\n`));
|
|
2510
|
+
console.log(chalk.gray(`🔄 Coordination: Will wait for agent completion signals before sending new tasks\n`));
|
|
2437
2511
|
|
|
2438
2512
|
const POLL_INTERVAL_MS = 30 * 1000; // 30 seconds
|
|
2439
2513
|
const CONTINUE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
2440
|
-
const TIMEOUT_MS =
|
|
2514
|
+
const TIMEOUT_MS = PROGRESS_TIMEOUT_MS; // Use configurable progress timeout
|
|
2515
|
+
const PROGRESS_COOLDOWN_MS = 2 * 60 * 1000; // 2 minutes cooldown after progress
|
|
2441
2516
|
|
|
2442
2517
|
const startTime = Date.now();
|
|
2443
2518
|
let lastContinueSent = Date.now();
|
|
2519
|
+
let lastProgressTime = Date.now(); // Track when we last saw progress
|
|
2520
|
+
let lastProgressDetectionTime = 0; // Track when we last detected progress to implement cooldown
|
|
2521
|
+
let totalAttempts = (spec.totalIdeAttempts || 0) + 1; // Track attempts across IDEs
|
|
2522
|
+
let agentSignaledCompletion = false; // Track if agent signaled completion
|
|
2444
2523
|
|
|
2445
2524
|
return new Promise((resolve) => {
|
|
2446
2525
|
let interval = null;
|
|
@@ -2464,27 +2543,91 @@ end tell
|
|
|
2464
2543
|
const elapsed = Date.now() - startTime;
|
|
2465
2544
|
|
|
2466
2545
|
if (elapsed >= TIMEOUT_MS) {
|
|
2467
|
-
|
|
2468
|
-
|
|
2546
|
+
console.log(chalk.yellow(`⏰ Overall timeout reached - but continuing to monitor existing Windsurf instance\n`));
|
|
2547
|
+
// Don't create new instance - just reset timer and continue monitoring
|
|
2548
|
+
startTime = Date.now(); // Reset the overall timer
|
|
2469
2549
|
return;
|
|
2470
2550
|
}
|
|
2471
2551
|
|
|
2472
|
-
// Detect
|
|
2552
|
+
// Detect progress AND check if IDE agent is done working
|
|
2473
2553
|
const { done: doneNow, total: totalNow } = countSpecCheckboxes(spec.path);
|
|
2554
|
+
const currentTime = Date.now();
|
|
2555
|
+
|
|
2474
2556
|
if (doneNow > doneBefore) {
|
|
2475
2557
|
const pctNow = totalNow > 0 ? Math.round((doneNow / totalNow) * 100) : 0;
|
|
2476
2558
|
console.log(chalk.green(`✓ Progress detected: ${doneNow}/${totalNow} tasks (${pctNow}%) complete\n`));
|
|
2559
|
+
|
|
2560
|
+
// Update progress tracking
|
|
2561
|
+
lastProgressTime = currentTime;
|
|
2562
|
+
lastProgressDetectionTime = currentTime;
|
|
2563
|
+
|
|
2564
|
+
// Check if all tasks are complete - if so, wait for agent completion signal
|
|
2565
|
+
if (doneNow >= totalNow && totalNow > 0) {
|
|
2566
|
+
console.log(chalk.green(`🎯 All tasks checked off! Waiting for agent completion signal...\n`));
|
|
2567
|
+
// Don't immediately return - wait for STATUS:WAITING signal or timeout
|
|
2568
|
+
agentSignaledCompletion = true; // Flag that we're waiting for completion signal
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// If progress detected but not all tasks done, continue waiting for agent to finish current work
|
|
2572
|
+
return; // Continue polling, don't send new task yet
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
// If agent signaled completion (all tasks done) and we've waited a reasonable time, consider it complete
|
|
2576
|
+
if (agentSignaledCompletion && (currentTime - lastProgressDetectionTime) >= 30 * 1000) {
|
|
2577
|
+
console.log(chalk.green(`✅ Agent completed all tasks! Finishing iteration.\n`));
|
|
2477
2578
|
cleanup({ success: true, changes: [] });
|
|
2478
2579
|
return;
|
|
2479
2580
|
}
|
|
2480
2581
|
|
|
2481
|
-
//
|
|
2582
|
+
// Implement cooldown period after progress detection to prevent rapid spawning
|
|
2583
|
+
if (lastProgressDetectionTime > 0 && (currentTime - lastProgressDetectionTime) < PROGRESS_COOLDOWN_MS) {
|
|
2584
|
+
const cooldownRemaining = Math.round((PROGRESS_COOLDOWN_MS - (currentTime - lastProgressDetectionTime)) / 1000);
|
|
2585
|
+
console.log(chalk.gray(`⏱️ In cooldown period (${cooldownRemaining}s remaining) - allowing agent time to complete work\n`));
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// Check for no progress timeout (configurable, default 15 minutes)
|
|
2590
|
+
const timeSinceLastProgress = Date.now() - lastProgressTime;
|
|
2591
|
+
if (timeSinceLastProgress >= PROGRESS_TIMEOUT_MS) {
|
|
2592
|
+
console.log(chalk.yellow(`⚠️ No progress detected for ${Math.round(timeSinceLastProgress / 60000)} minutes on ${providerConfig.displayName}\n`));
|
|
2593
|
+
|
|
2594
|
+
// Check if we've exceeded max IDE attempts
|
|
2595
|
+
if (totalAttempts >= MAX_IDE_ATTEMPTS) {
|
|
2596
|
+
console.log(chalk.red(`✗ Maximum IDE attempts (${MAX_IDE_ATTEMPTS}) reached. Stopping auto mode.\n`));
|
|
2597
|
+
// Stop auto mode - call async function properly
|
|
2598
|
+
stopAutoMode().then(() => {
|
|
2599
|
+
cleanup({ success: false, error: 'max_ide_attempts_exceeded', shouldRetry: false });
|
|
2600
|
+
}).catch((err) => {
|
|
2601
|
+
console.error('Error stopping auto mode:', err);
|
|
2602
|
+
cleanup({ success: false, error: 'max_ide_attempts_exceeded', shouldRetry: false });
|
|
2603
|
+
});
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
// Don't create new instance - just continue monitoring and send continuation messages
|
|
2608
|
+
// Reset the progress timer to allow more time for the existing instance
|
|
2609
|
+
lastProgressTime = Date.now();
|
|
2610
|
+
totalAttempts++;
|
|
2611
|
+
console.log(chalk.yellow(`⚠️ Allowing more time for existing Windsurf instance (attempt ${totalAttempts}/${MAX_IDE_ATTEMPTS})\n`));
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
// Send continuation prompt every 5 minutes if no progress OR if waiting for completion signal
|
|
2482
2616
|
const sinceLastContinue = Date.now() - lastContinueSent;
|
|
2483
2617
|
if (sinceLastContinue >= CONTINUE_INTERVAL_MS) {
|
|
2484
2618
|
lastContinueSent = Date.now();
|
|
2485
2619
|
const pctNow = totalNow > 0 ? Math.round((doneNow / totalNow) * 100) : 0;
|
|
2486
2620
|
const mins = Math.round(elapsed / 60000);
|
|
2487
|
-
|
|
2621
|
+
|
|
2622
|
+
let continueMsg;
|
|
2623
|
+
if (agentSignaledCompletion) {
|
|
2624
|
+
// All tasks done, waiting for completion signal
|
|
2625
|
+
continueMsg = `All tasks appear to be completed (${doneNow}/${totalNow} tasks). If you are truly finished, please report "COMPLETION: 100%" followed by "STATUS:WAITING" and "PLEASE RESPOND". If you need more time, continue working and report progress. (${mins}min elapsed) PLEASE RESPOND`;
|
|
2626
|
+
} else {
|
|
2627
|
+
// Normal continuation prompt
|
|
2628
|
+
continueMsg = `Continue implementing spec "${spec.directory}". Current progress: ${doneNow}/${totalNow} tasks (${pctNow}%) complete. Keep working until ALL tasks in tasks.md are checked off. Next task: "${taskText}". (${mins}min elapsed) PLEASE RESPOND`;
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2488
2631
|
try {
|
|
2489
2632
|
const appleScriptManager = new AppleScriptManager();
|
|
2490
2633
|
// Use plain sendText for continuations (no need to open new thread)
|
|
@@ -2956,10 +3099,88 @@ async function loadEnabledIncompleteSpecs(repoPath) {
|
|
|
2956
3099
|
|
|
2957
3100
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2958
3101
|
|
|
3102
|
+
/**
|
|
3103
|
+
* Process default requirement until completion
|
|
3104
|
+
* @param {RequirementSequencer} sequencer - Requirement sequencer
|
|
3105
|
+
* @param {DefaultRequirementManager} defaultManager - Default requirement manager
|
|
3106
|
+
* @param {Object} providerConfig - Provider configuration
|
|
3107
|
+
* @param {string} repoPath - Repository path
|
|
3108
|
+
*/
|
|
3109
|
+
async function processDefaultRequirement(sequencer, defaultManager, providerConfig, repoPath) {
|
|
3110
|
+
const requirementsPath = await getRequirementsPath(repoPath);
|
|
3111
|
+
let iterationCount = 0;
|
|
3112
|
+
|
|
3113
|
+
while (true) {
|
|
3114
|
+
// Check if default requirement should stop
|
|
3115
|
+
const shouldStop = await defaultManager.shouldStop();
|
|
3116
|
+
if (shouldStop) {
|
|
3117
|
+
const status = await defaultManager.getStatus();
|
|
3118
|
+
console.log(chalk.bold.green(`\n✅ Default Requirement Complete`));
|
|
3119
|
+
console.log(chalk.gray(`Status: ${status.completionReason || 'Completed'}\n`));
|
|
3120
|
+
break;
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
// Check if regular requirements were added (should pause default)
|
|
3124
|
+
const currentState = await sequencer.getCurrentState(requirementsPath);
|
|
3125
|
+
if (currentState.hasRegularRequirements) {
|
|
3126
|
+
console.log(chalk.bold.yellow('\n⏸️ Default Requirement Paused'));
|
|
3127
|
+
console.log(chalk.gray('New regular requirements detected, pausing default requirement...\n'));
|
|
3128
|
+
await defaultManager.pause();
|
|
3129
|
+
break;
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// Get current default requirement status
|
|
3133
|
+
const defaultStatus = await defaultManager.getStatus();
|
|
3134
|
+
iterationCount = defaultStatus.iterationCount + 1;
|
|
3135
|
+
|
|
3136
|
+
console.log(chalk.bold.cyan(`\n${'━'.repeat(80)}`));
|
|
3137
|
+
console.log(chalk.bold.cyan(` ⭐ DEFAULT REQUIREMENT - Iteration ${iterationCount}/${defaultStatus.maxIterations}`));
|
|
3138
|
+
console.log(chalk.bold.cyan(` ${defaultStatus.title}`));
|
|
3139
|
+
console.log(chalk.bold.cyan(`${'━'.repeat(80)}\n`));
|
|
3140
|
+
|
|
3141
|
+
// Create a synthetic requirement object for the default requirement
|
|
3142
|
+
const defaultRequirement = {
|
|
3143
|
+
text: defaultStatus.description,
|
|
3144
|
+
number: iterationCount,
|
|
3145
|
+
isDefault: true
|
|
3146
|
+
};
|
|
3147
|
+
|
|
3148
|
+
// Run iteration with default requirement
|
|
3149
|
+
const result = await runIteration(defaultRequirement, providerConfig, repoPath);
|
|
3150
|
+
|
|
3151
|
+
if (result.success) {
|
|
3152
|
+
// Increment iteration count
|
|
3153
|
+
await defaultManager.incrementIteration();
|
|
3154
|
+
console.log(chalk.bold.green(`✅ Default Requirement Iteration ${iterationCount}/${defaultStatus.maxIterations} COMPLETE`));
|
|
3155
|
+
|
|
3156
|
+
// Small delay between iterations
|
|
3157
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
3158
|
+
} else {
|
|
3159
|
+
// Record failure
|
|
3160
|
+
await defaultManager.recordFailure();
|
|
3161
|
+
console.log(chalk.bold.red(`❌ Default Requirement Iteration ${iterationCount}/${defaultStatus.maxIterations} FAILED`));
|
|
3162
|
+
console.log(chalk.red(`Error: ${result.error}\n`));
|
|
3163
|
+
|
|
3164
|
+
// Check consecutive failures
|
|
3165
|
+
const updatedStatus = await defaultManager.getStatus();
|
|
3166
|
+
if (updatedStatus.consecutiveFailures >= 5) {
|
|
3167
|
+
console.log(chalk.bold.yellow('\n⚠️ Maximum consecutive failures reached'));
|
|
3168
|
+
await defaultManager.markDone('Stopped due to consecutive failures');
|
|
3169
|
+
break;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// Delay longer after failure
|
|
3173
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
|
|
2959
3178
|
/**
|
|
2960
3179
|
* Main auto mode command handler
|
|
2961
3180
|
*/
|
|
2962
3181
|
async function handleAutoStart(options) {
|
|
3182
|
+
let stopMonitoring = null; // Declare outside try block so it's accessible in catch
|
|
3183
|
+
|
|
2963
3184
|
try {
|
|
2964
3185
|
// STRICT AUTH CHECK
|
|
2965
3186
|
const auth = require('../utils/auth');
|
|
@@ -2986,20 +3207,68 @@ async function handleAutoStart(options) {
|
|
|
2986
3207
|
return;
|
|
2987
3208
|
}
|
|
2988
3209
|
|
|
3210
|
+
// Create keyboard handler for 'x' key exit
|
|
3211
|
+
keyboardHandler = createKeyboardHandler({
|
|
3212
|
+
onExit: () => {
|
|
3213
|
+
// Update status to stopped
|
|
3214
|
+
updateAutoModeStatus(repoPath, { running: false });
|
|
3215
|
+
console.log(chalk.gray('Auto mode stopped'));
|
|
3216
|
+
}
|
|
3217
|
+
});
|
|
3218
|
+
|
|
3219
|
+
// Start keyboard handler
|
|
3220
|
+
keyboardHandler.start();
|
|
3221
|
+
|
|
3222
|
+
// Ensure keyboard handler is cleaned up on exit
|
|
3223
|
+
const cleanup = () => {
|
|
3224
|
+
if (keyboardHandler) {
|
|
3225
|
+
keyboardHandler.stop();
|
|
3226
|
+
}
|
|
3227
|
+
};
|
|
3228
|
+
|
|
3229
|
+
// Handle process termination
|
|
3230
|
+
process.on('exit', cleanup);
|
|
3231
|
+
process.on('SIGINT', () => {
|
|
3232
|
+
cleanup();
|
|
3233
|
+
process.exit(0);
|
|
3234
|
+
});
|
|
3235
|
+
process.on('SIGTERM', () => {
|
|
3236
|
+
cleanup();
|
|
3237
|
+
process.exit(0);
|
|
3238
|
+
});
|
|
3239
|
+
|
|
2989
3240
|
// Start Auto Mode status tracking
|
|
2990
3241
|
const config = await getAutoConfig();
|
|
2991
|
-
|
|
3242
|
+
|
|
2992
3243
|
// Save extension to config if provided
|
|
2993
3244
|
if (options.extension) {
|
|
2994
3245
|
config.extension = options.extension;
|
|
2995
3246
|
await setAutoConfig(config);
|
|
2996
3247
|
}
|
|
2997
|
-
|
|
3248
|
+
|
|
2998
3249
|
await startAutoMode(repoPath, { ide: options.ide || config.ide });
|
|
2999
3250
|
|
|
3000
3251
|
// Also load configured stages here since we already have the config
|
|
3001
3252
|
configuredStages = await getStages();
|
|
3002
3253
|
|
|
3254
|
+
// Initialize requirement monitoring for default requirement management
|
|
3255
|
+
const requirementsPath = await getRequirementsPath(repoPath);
|
|
3256
|
+
const storage = new JSONStorage();
|
|
3257
|
+
const parser = new RequirementFileParser();
|
|
3258
|
+
const defaultManager = new DefaultRequirementManager(storage);
|
|
3259
|
+
const sequencer = new RequirementSequencer(parser, defaultManager);
|
|
3260
|
+
|
|
3261
|
+
// Start monitoring requirements file for changes
|
|
3262
|
+
stopMonitoring = sequencer.monitorRequirements(requirementsPath, async (changeType, change) => {
|
|
3263
|
+
if (changeType === 'regular-requirements-added') {
|
|
3264
|
+
console.log(chalk.bold.yellow('\n📝 New regular requirements detected'));
|
|
3265
|
+
console.log(chalk.gray('Default requirement will be paused to process regular requirements first\n'));
|
|
3266
|
+
} else if (changeType === 'regular-requirements-completed') {
|
|
3267
|
+
console.log(chalk.bold.green('\n✅ All regular requirements completed'));
|
|
3268
|
+
console.log(chalk.gray('Default requirement will be resumed\n'));
|
|
3269
|
+
}
|
|
3270
|
+
});
|
|
3271
|
+
|
|
3003
3272
|
console.log(chalk.white(t('auto.repository')), chalk.cyan(repoPath));
|
|
3004
3273
|
|
|
3005
3274
|
// Use the agent that was already determined by provider preferences in interactive.js
|
|
@@ -3038,6 +3307,9 @@ async function handleAutoStart(options) {
|
|
|
3038
3307
|
console.log(chalk.bold.cyan(`\n📋 Processing ${incompleteSpecs.length} incomplete spec(s) before requirements...\n`));
|
|
3039
3308
|
|
|
3040
3309
|
for (const spec of incompleteSpecs) {
|
|
3310
|
+
// Initialize IDE attempts tracking for this spec
|
|
3311
|
+
spec.totalIdeAttempts = 0;
|
|
3312
|
+
|
|
3041
3313
|
const { done: doneStart, total: totalStart } = countSpecCheckboxes(spec.path);
|
|
3042
3314
|
console.log(chalk.bold.magenta(`\n${'━'.repeat(80)}`));
|
|
3043
3315
|
console.log(chalk.bold.magenta(` 📋 SPEC: ${spec.directory} (${spec.title || spec.directory})`));
|
|
@@ -3126,8 +3398,8 @@ end tell
|
|
|
3126
3398
|
_execSync2(`osascript "${_tmpScpt2}"`, { stdio: 'pipe', timeout: 30000 });
|
|
3127
3399
|
return { success: true };
|
|
3128
3400
|
} finally {
|
|
3129
|
-
try { _unlinkSync2(_tmpScpt2); } catch (_) {}
|
|
3130
|
-
try { _unlinkSync2(_tmpText2); } catch (_) {}
|
|
3401
|
+
try { _unlinkSync2(_tmpScpt2); } catch (_) { }
|
|
3402
|
+
try { _unlinkSync2(_tmpText2); } catch (_) { }
|
|
3131
3403
|
}
|
|
3132
3404
|
} else {
|
|
3133
3405
|
const appleScriptManager = new AppleScriptManager();
|
|
@@ -3186,6 +3458,58 @@ end tell
|
|
|
3186
3458
|
}
|
|
3187
3459
|
}
|
|
3188
3460
|
|
|
3461
|
+
// Verify tasks.md has checkbox-format tasks
|
|
3462
|
+
const { done: doneCheck, total: totalCheck } = countSpecCheckboxes(spec.path);
|
|
3463
|
+
if (totalCheck === 0 && spec.hasTasks) {
|
|
3464
|
+
console.log(chalk.yellow(`\n⚠️ tasks.md exists but has no checkbox tasks (narrative format detected)`));
|
|
3465
|
+
console.log(chalk.yellow(` Regenerating tasks.md with /speckit.tasks...\n`));
|
|
3466
|
+
|
|
3467
|
+
// Send task regeneration instruction to IDE
|
|
3468
|
+
const tasksRegenerationText = `Run /speckit.tasks to regenerate tasks.md for spec "${spec.directory}" in checkbox format (- [ ] task).`;
|
|
3469
|
+
|
|
3470
|
+
let regenResult;
|
|
3471
|
+
if (providerConfig.type === 'ide') {
|
|
3472
|
+
console.log(chalk.cyan(`📤 Sending tasks regeneration request to ${providerConfig.displayName}...\n`));
|
|
3473
|
+
const ideType = providerConfig.provider || providerConfig.ide;
|
|
3474
|
+
const appleScriptManager = new AppleScriptManager();
|
|
3475
|
+
try {
|
|
3476
|
+
const sendResult = typeof appleScriptManager.sendTextWithThreadClosure === 'function'
|
|
3477
|
+
? await appleScriptManager.sendTextWithThreadClosure(tasksRegenerationText, ideType)
|
|
3478
|
+
: await appleScriptManager.sendText(tasksRegenerationText, ideType);
|
|
3479
|
+
|
|
3480
|
+
if (!sendResult || !sendResult.success) {
|
|
3481
|
+
console.log(chalk.red(`✗ Failed to send to ${providerConfig.displayName}`));
|
|
3482
|
+
regenResult = { success: false };
|
|
3483
|
+
} else {
|
|
3484
|
+
console.log(chalk.green(`✓ Regeneration request sent. Waiting for tasks.md to be updated...`));
|
|
3485
|
+
// Give IDE time to regenerate (30 seconds)
|
|
3486
|
+
await new Promise(resolve => setTimeout(resolve, 30000));
|
|
3487
|
+
|
|
3488
|
+
// Check if tasks now have checkboxes
|
|
3489
|
+
const { total: totalAfterRegen } = countSpecCheckboxes(spec.path);
|
|
3490
|
+
if (totalAfterRegen > 0) {
|
|
3491
|
+
console.log(chalk.green(`✓ tasks.md regenerated with ${totalAfterRegen} checkbox tasks!\n`));
|
|
3492
|
+
regenResult = { success: true };
|
|
3493
|
+
} else {
|
|
3494
|
+
console.log(chalk.yellow(`⚠️ tasks.md still has no checkbox tasks after regeneration\n`));
|
|
3495
|
+
regenResult = { success: false };
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
} catch (err) {
|
|
3499
|
+
regenResult = { success: false, error: err.message };
|
|
3500
|
+
}
|
|
3501
|
+
} else {
|
|
3502
|
+
const regenRequirement = { text: tasksRegenerationText, package: null, disabled: false };
|
|
3503
|
+
regenResult = await runIteration(regenRequirement, providerConfig, repoPath);
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
if (!regenResult.success) {
|
|
3507
|
+
console.log(chalk.red('✗ Failed to regenerate tasks.md — skipping spec\n'));
|
|
3508
|
+
failedCount++;
|
|
3509
|
+
continue; // Skip to next spec
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3189
3513
|
// Loop until spec is done or stalled
|
|
3190
3514
|
while (true) {
|
|
3191
3515
|
const task = getNextSpecTask(spec.path);
|
|
@@ -3237,27 +3561,53 @@ end tell
|
|
|
3237
3561
|
|
|
3238
3562
|
if (result.success) {
|
|
3239
3563
|
specProviderAttempts = 0;
|
|
3564
|
+
spec.totalIdeAttempts = 0; // Reset IDE attempts on success
|
|
3240
3565
|
completedCount++;
|
|
3241
3566
|
const { done, total } = countSpecCheckboxes(spec.path);
|
|
3242
3567
|
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
3243
3568
|
console.log(chalk.bold.green(`📊 Spec progress: ${done}/${total} tasks (${pct}%) complete`));
|
|
3244
3569
|
} else {
|
|
3245
3570
|
const isRateLimitError = isRateLimitMessage(result.error);
|
|
3246
|
-
const
|
|
3571
|
+
const isMaxAttemptsExceeded = result.error === 'max_ide_attempts_exceeded';
|
|
3572
|
+
const isNoProgress = result.error === 'no_progress';
|
|
3573
|
+
|
|
3574
|
+
let errorType = 'Error';
|
|
3575
|
+
if (isRateLimitError) errorType = 'Rate limit';
|
|
3576
|
+
else if (isMaxAttemptsExceeded) errorType = 'Max IDE attempts exceeded';
|
|
3577
|
+
else if (isNoProgress) errorType = 'No progress';
|
|
3578
|
+
|
|
3579
|
+
// Update total IDE attempts tracking
|
|
3580
|
+
if (result.totalAttempts) {
|
|
3581
|
+
spec.totalIdeAttempts = result.totalAttempts;
|
|
3582
|
+
}
|
|
3247
3583
|
|
|
3248
3584
|
specProviderAttempts++;
|
|
3249
3585
|
failedCount++;
|
|
3250
3586
|
|
|
3587
|
+
if (isMaxAttemptsExceeded) {
|
|
3588
|
+
// Auto mode was stopped, exit completely
|
|
3589
|
+
console.log(chalk.red(`\n✗ ${errorType} — auto mode stopped\n`));
|
|
3590
|
+
return { completedCount, failedCount };
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3251
3593
|
if (specProviderAttempts > MAX_SPEC_TASK_ATTEMPTS) {
|
|
3252
3594
|
console.log(chalk.red(`\n✗ Max provider attempts reached — moving to next spec\n`));
|
|
3253
3595
|
break;
|
|
3254
3596
|
}
|
|
3255
3597
|
|
|
3256
|
-
|
|
3598
|
+
if (isNoProgress) {
|
|
3599
|
+
console.log(chalk.yellow(`⚠️ ${errorType} on ${providerConfig.displayName}, switching IDE...`));
|
|
3600
|
+
} else {
|
|
3601
|
+
console.log(chalk.yellow(`⚠️ ${errorType} on spec task, switching provider...`));
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3257
3604
|
const newProviderConfig = await acquireProviderConfig(providerConfig.provider, providerConfig.model);
|
|
3258
3605
|
if (newProviderConfig) {
|
|
3259
3606
|
providerConfig = newProviderConfig;
|
|
3260
3607
|
console.log(chalk.green(`✓ Switched to: ${providerConfig.displayName}\n`));
|
|
3608
|
+
} else {
|
|
3609
|
+
console.log(chalk.red(`✗ No alternative providers available\n`));
|
|
3610
|
+
break;
|
|
3261
3611
|
}
|
|
3262
3612
|
}
|
|
3263
3613
|
}
|
|
@@ -3276,12 +3626,29 @@ end tell
|
|
|
3276
3626
|
// Get current requirement first to check if there are any TODO items
|
|
3277
3627
|
const requirement = await getCurrentRequirement(repoPath);
|
|
3278
3628
|
if (!requirement) {
|
|
3279
|
-
|
|
3280
|
-
|
|
3629
|
+
// Check if we should process default requirement
|
|
3630
|
+
const requirementsPath = await getRequirementsPath(repoPath);
|
|
3631
|
+
const storage = new JSONStorage();
|
|
3632
|
+
const parser = new RequirementFileParser();
|
|
3633
|
+
const defaultManager = new DefaultRequirementManager(storage);
|
|
3634
|
+
const sequencer = new RequirementSequencer(parser, defaultManager);
|
|
3635
|
+
|
|
3636
|
+
const shouldProcessDefault = await sequencer.shouldProcessDefault(requirementsPath);
|
|
3637
|
+
|
|
3638
|
+
if (shouldProcessDefault) {
|
|
3639
|
+
console.log(chalk.bold.cyan('\n⭐ Default Requirement Mode'));
|
|
3640
|
+
console.log(chalk.gray('All regular requirements completed, processing default requirement...\n'));
|
|
3641
|
+
|
|
3642
|
+
// Process default requirement until completion or max iterations
|
|
3643
|
+
await processDefaultRequirement(sequencer, defaultManager, providerConfig, repoPath);
|
|
3281
3644
|
} else {
|
|
3282
|
-
|
|
3645
|
+
if (completedCount > 0 || failedCount > 0) {
|
|
3646
|
+
console.log(chalk.bold.yellow('\n🎉 All requirements completed!'));
|
|
3647
|
+
} else {
|
|
3648
|
+
console.log(chalk.bold.yellow('\n🎉 No requirements to process.'));
|
|
3649
|
+
}
|
|
3650
|
+
console.log(chalk.gray(`${t('auto.direct.no.more.todo.items')}\n`));
|
|
3283
3651
|
}
|
|
3284
|
-
console.log(chalk.gray(`${t('auto.direct.no.more.todo.items')}\n`));
|
|
3285
3652
|
break;
|
|
3286
3653
|
}
|
|
3287
3654
|
|
|
@@ -3426,12 +3793,32 @@ end tell
|
|
|
3426
3793
|
// Stop Auto Mode status tracking
|
|
3427
3794
|
await stopAutoMode('completed');
|
|
3428
3795
|
|
|
3796
|
+
// Clean up keyboard handler
|
|
3797
|
+
if (keyboardHandler) {
|
|
3798
|
+
keyboardHandler.stop();
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
// Stop requirement monitoring
|
|
3802
|
+
if (stopMonitoring) {
|
|
3803
|
+
stopMonitoring();
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3429
3806
|
} catch (error) {
|
|
3430
3807
|
console.error(chalk.red('\n' + t('auto.fatal.error')), error.message);
|
|
3431
3808
|
if (error.stack) {
|
|
3432
3809
|
console.log(chalk.gray(error.stack));
|
|
3433
3810
|
}
|
|
3434
3811
|
|
|
3812
|
+
// Clean up keyboard handler
|
|
3813
|
+
if (keyboardHandler) {
|
|
3814
|
+
keyboardHandler.stop();
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
// Stop requirement monitoring
|
|
3818
|
+
if (stopMonitoring) {
|
|
3819
|
+
stopMonitoring();
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3435
3822
|
// Stop Auto Mode status tracking on fatal error
|
|
3436
3823
|
await stopAutoMode('error');
|
|
3437
3824
|
process.exit(1);
|