vibecodingmachine-cli 2026.2.26-1752 → 2026.3.9-1621
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 +7 -1
- package/bin/commands/agent-commands.js +150 -228
- package/bin/commands/command-aliases.js +68 -0
- package/bin/vibecodingmachine.js +1 -2
- package/package.json +2 -2
- package/src/commands/agents/list.js +71 -115
- package/src/commands/agents-check.js +16 -4
- package/src/commands/analyze-file-sizes.js +1 -1
- package/src/commands/auto-direct/auto-provider-manager.js +290 -0
- package/src/commands/auto-direct/auto-status-display.js +331 -0
- package/src/commands/auto-direct/auto-utils.js +439 -0
- package/src/commands/auto-direct/file-operations.js +110 -0
- package/src/commands/auto-direct/provider-config.js +1 -1
- package/src/commands/auto-direct/provider-manager.js +1 -1
- package/src/commands/auto-direct/status-display.js +1 -1
- package/src/commands/auto-direct/utils.js +24 -18
- package/src/commands/auto-direct-refactored.js +413 -0
- package/src/commands/auto-direct.js +594 -188
- package/src/commands/requirements/commands.js +353 -0
- package/src/commands/requirements/default-handlers.js +272 -0
- package/src/commands/requirements/disable.js +97 -0
- package/src/commands/requirements/enable.js +97 -0
- package/src/commands/requirements/utils.js +194 -0
- package/src/commands/requirements-refactored.js +60 -0
- package/src/commands/requirements.js +38 -771
- package/src/commands/specs/disable.js +96 -0
- package/src/commands/specs/enable.js +96 -0
- package/src/trui/TruiInterface.js +5 -11
- package/src/trui/agents/AgentInterface.js +24 -396
- package/src/trui/agents/handlers/CommandHandler.js +93 -0
- package/src/trui/agents/handlers/ContextManager.js +117 -0
- package/src/trui/agents/handlers/DisplayHandler.js +243 -0
- package/src/trui/agents/handlers/HelpHandler.js +51 -0
- package/src/utils/auth.js +13 -111
- package/src/utils/config.js +4 -0
- package/src/utils/interactive/requirements-navigation.js +17 -15
- package/src/utils/interactive-broken.js +2 -2
- package/src/utils/provider-checker/agent-runner.js +15 -1
- package/src/utils/provider-checker/cli-installer.js +149 -7
- package/src/utils/provider-checker/opencode-checker.js +588 -0
- package/src/utils/provider-checker/provider-validator.js +88 -3
- package/src/utils/provider-checker/time-formatter.js +3 -2
- package/src/utils/provider-manager.js +28 -20
- package/src/utils/provider-registry.js +35 -3
- package/src/utils/requirements-navigator/index.js +94 -0
- package/src/utils/requirements-navigator/input-handler.js +217 -0
- package/src/utils/requirements-navigator/section-loader.js +188 -0
- package/src/utils/requirements-navigator/tree-builder.js +105 -0
- package/src/utils/requirements-navigator/tree-renderer.js +50 -0
- package/src/utils/requirements-navigator.js +2 -583
- package/src/utils/trui-clarifications.js +188 -0
- package/src/utils/trui-feedback.js +54 -1
- package/src/utils/trui-kiro-integration.js +398 -0
- package/src/utils/trui-main-handlers.js +194 -0
- package/src/utils/trui-main-menu.js +235 -0
- package/src/utils/trui-nav-agents.js +178 -25
- package/src/utils/trui-nav-requirements.js +203 -27
- package/src/utils/trui-nav-settings.js +114 -1
- package/src/utils/trui-nav-specifications.js +44 -3
- package/src/utils/trui-navigation-backup.js +603 -0
- package/src/utils/trui-navigation.js +70 -228
- package/src/utils/trui-provider-health.js +274 -0
- package/src/utils/trui-provider-manager.js +376 -0
- package/src/utils/trui-quick-menu.js +25 -1
- package/src/utils/trui-req-actions-backup.js +507 -0
- package/src/utils/trui-req-actions.js +148 -216
- package/src/utils/trui-req-editor.js +170 -0
- package/src/utils/trui-req-file-ops.js +278 -0
- package/src/utils/trui-req-tree-old.js +719 -0
- package/src/utils/trui-req-tree.js +348 -627
- package/src/utils/trui-specifications.js +25 -7
- package/src/utils/trui-windsurf.js +231 -10
- package/src/utils/welcome-screen-extracted.js +2 -2
- package/src/utils/welcome-screen.js +2 -2
|
@@ -201,6 +201,25 @@ function sleep(ms) {
|
|
|
201
201
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Safely log to console with EPIPE protection
|
|
206
|
+
* @param {...any} args - Arguments to pass to console.log
|
|
207
|
+
*/
|
|
208
|
+
function safeLog(...args) {
|
|
209
|
+
try {
|
|
210
|
+
// Check if stdout is still writable before attempting to log
|
|
211
|
+
if (process.stdout && !process.stdout.destroyed) {
|
|
212
|
+
console.log(...args);
|
|
213
|
+
}
|
|
214
|
+
} catch (logError) {
|
|
215
|
+
// Ignore EPIPE and other stdout errors - process may be terminating
|
|
216
|
+
if (logError.code !== 'EPIPE') {
|
|
217
|
+
// Re-throw non-EPIPE errors
|
|
218
|
+
throw logError;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
204
223
|
/**
|
|
205
224
|
* Print purple status card with progress indicators
|
|
206
225
|
*/
|
|
@@ -277,14 +296,20 @@ let statusBoxInitialized = false;
|
|
|
277
296
|
let statusBoxLines = 5; // Number of lines the status box takes
|
|
278
297
|
let storedStatusTitle = '';
|
|
279
298
|
let storedStatus = '';
|
|
299
|
+
let currentStatusMode = 'active'; // Track current mode: 'active', 'waiting', 'stopped'
|
|
280
300
|
|
|
281
301
|
/**
|
|
282
|
-
* Print
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
302
|
+
* Print status card with color-coded states based on mode
|
|
303
|
+
* - Green: actively working on tasks
|
|
304
|
+
* - Yellow: waiting for rate limit to end
|
|
305
|
+
* - Red: stopped or error state
|
|
306
|
+
* @param {string} currentTitle - Current requirement title
|
|
307
|
+
* @param {string} currentStatus - Current workflow status
|
|
308
|
+
* @param {string} mode - Status mode: 'active', 'waiting', 'stopped'
|
|
286
309
|
*/
|
|
287
|
-
function printStatusCard(currentTitle, currentStatus) {
|
|
310
|
+
function printStatusCard(currentTitle, currentStatus, mode = 'active') {
|
|
311
|
+
currentStatusMode = mode; // Update global mode tracking
|
|
312
|
+
|
|
288
313
|
const stages = configuredStages;
|
|
289
314
|
const stageMap = {};
|
|
290
315
|
stages.forEach((s, i) => stageMap[s] = i);
|
|
@@ -298,8 +323,11 @@ function printStatusCard(currentTitle, currentStatus) {
|
|
|
298
323
|
// Completed stages - grey with checkmark
|
|
299
324
|
return chalk.grey(`✅ ${translatedStage}`);
|
|
300
325
|
} else if (idx === currentIndex) {
|
|
301
|
-
// CURRENT stage - BRIGHT WHITE with hammer
|
|
302
|
-
|
|
326
|
+
// CURRENT stage - BRIGHT WHITE with hammer (or different icon for waiting/stopped)
|
|
327
|
+
let icon = '🔨';
|
|
328
|
+
if (mode === 'waiting') icon = '⏳';
|
|
329
|
+
else if (mode === 'stopped') icon = '⏹️';
|
|
330
|
+
return chalk.bold.white(`${icon} ${translatedStage}`);
|
|
303
331
|
} else {
|
|
304
332
|
// Future stages - grey with hourglass
|
|
305
333
|
return chalk.grey(`⏳ ${translatedStage}`);
|
|
@@ -318,13 +346,23 @@ function printStatusCard(currentTitle, currentStatus) {
|
|
|
318
346
|
const titleShort = currentTitle?.substring(0, maxTitleWidth) + (currentTitle?.length > maxTitleWidth ? '...' : '');
|
|
319
347
|
const titleLine = chalk.cyan(workingOnLabel) + chalk.white(titleShort);
|
|
320
348
|
|
|
321
|
-
//
|
|
349
|
+
// Choose color based on mode
|
|
350
|
+
let boxColor;
|
|
351
|
+
if (mode === 'waiting') {
|
|
352
|
+
boxColor = chalk.yellow; // Yellow for waiting mode
|
|
353
|
+
} else if (mode === 'stopped') {
|
|
354
|
+
boxColor = chalk.red; // Red for stopped mode
|
|
355
|
+
} else {
|
|
356
|
+
boxColor = chalk.green; // Green for active mode (changed from magenta)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Build the status box content with dynamic color
|
|
322
360
|
const statusBoxContent =
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
361
|
+
boxColor('╭' + '─'.repeat(boxWidth) + '╮') + '\n' +
|
|
362
|
+
boxColor('│') + padToVisualWidth(' ' + workflowLine, boxWidth) + boxColor('│') + '\n' +
|
|
363
|
+
boxColor('│') + ' '.repeat(boxWidth) + boxColor('│') + '\n' +
|
|
364
|
+
boxColor('│') + padToVisualWidth(' ' + titleLine, boxWidth) + boxColor('│') + '\n' +
|
|
365
|
+
boxColor('╰' + '─'.repeat(boxWidth) + '╯');
|
|
328
366
|
|
|
329
367
|
// Store current state (using stored* names to avoid shadowing with parameters)
|
|
330
368
|
storedStatusTitle = currentTitle;
|
|
@@ -332,6 +370,23 @@ function printStatusCard(currentTitle, currentStatus) {
|
|
|
332
370
|
|
|
333
371
|
// Just print the card normally - no complex cursor manipulation
|
|
334
372
|
console.log(statusBoxContent);
|
|
373
|
+
|
|
374
|
+
// Notify Electron UI about mode changes
|
|
375
|
+
try {
|
|
376
|
+
if (process.versions && process.versions.electron) {
|
|
377
|
+
const { ipcRenderer } = require('electron');
|
|
378
|
+
if (ipcRenderer) {
|
|
379
|
+
ipcRenderer.send('requirements-progress', {
|
|
380
|
+
stage: currentStatus,
|
|
381
|
+
requirement: currentTitle,
|
|
382
|
+
mode: mode,
|
|
383
|
+
timestamp: new Date().toISOString()
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} catch (e) {
|
|
388
|
+
// Ignore if not in Electron context
|
|
389
|
+
}
|
|
335
390
|
}
|
|
336
391
|
|
|
337
392
|
/**
|
|
@@ -479,113 +534,117 @@ async function moveRequirementToVerify(repoPath, requirementText) {
|
|
|
479
534
|
}
|
|
480
535
|
|
|
481
536
|
const content = await fs.readFile(reqPath, 'utf8');
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
537
|
+
// Find the requirement by its title (in ### header format)
|
|
538
|
+
// Only look in TODO section
|
|
539
|
+
const normalizedRequirement = requirementText.trim();
|
|
540
|
+
const snippet = normalizedRequirement.substring(0, 80);
|
|
541
|
+
let requirementStartIndex = -1;
|
|
542
|
+
let requirementEndIndex = -1;
|
|
543
|
+
let inTodoSection = false;
|
|
544
|
+
|
|
545
|
+
for (let i = 0; i < lines.length; i++) {
|
|
546
|
+
const line = lines[i];
|
|
547
|
+
const trimmed = line.trim();
|
|
548
|
+
|
|
549
|
+
// Check if we're entering TODO section
|
|
550
|
+
if (trimmed.startsWith('##') && trimmed.includes('Requirements not yet completed')) {
|
|
551
|
+
inTodoSection = true;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
490
554
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
555
|
+
// Check if we're leaving TODO section
|
|
556
|
+
if (inTodoSection && trimmed.startsWith('##') && !trimmed.startsWith('###') && !trimmed.includes('Requirements not yet completed')) {
|
|
557
|
+
inTodoSection = false;
|
|
558
|
+
}
|
|
494
559
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
560
|
+
// Only look for requirements in TODO section
|
|
561
|
+
if (inTodoSection && trimmed.startsWith('###')) {
|
|
562
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
563
|
+
if (title) {
|
|
564
|
+
// Try multiple matching strategies
|
|
565
|
+
const normalizedTitle = title.trim();
|
|
500
566
|
|
|
501
|
-
//
|
|
502
|
-
if (
|
|
503
|
-
|
|
567
|
+
// Exact match
|
|
568
|
+
if (normalizedTitle === normalizedRequirement) {
|
|
569
|
+
requirementStartIndex = i;
|
|
570
|
+
}
|
|
571
|
+
// Check if either contains the other (for partial matches)
|
|
572
|
+
else if (normalizedTitle.includes(normalizedRequirement) || normalizedRequirement.includes(normalizedTitle)) {
|
|
573
|
+
requirementStartIndex = i;
|
|
574
|
+
}
|
|
575
|
+
// Check snippet matches
|
|
576
|
+
else if (normalizedTitle.includes(snippet) || snippet.includes(normalizedTitle.substring(0, 80))) {
|
|
577
|
+
requirementStartIndex = i;
|
|
504
578
|
}
|
|
505
579
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
}
|
|
580
|
+
if (requirementStartIndex !== -1) {
|
|
581
|
+
// Find the end of this requirement (next ### or ## header)
|
|
582
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
583
|
+
const nextLine = lines[j].trim();
|
|
584
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
585
|
+
requirementEndIndex = j;
|
|
538
586
|
break;
|
|
539
587
|
}
|
|
540
588
|
}
|
|
589
|
+
if (requirementEndIndex === -1) {
|
|
590
|
+
requirementEndIndex = lines.length;
|
|
591
|
+
}
|
|
592
|
+
break;
|
|
541
593
|
}
|
|
542
594
|
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
543
597
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
598
|
+
if (requirementStartIndex === -1) {
|
|
599
|
+
console.log(chalk.yellow(`⚠️ ${t('auto.direct.requirement.not.found.todo', { requirement: requirementText.substring(0, 60) + '...' })}`));
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
548
602
|
|
|
549
|
-
|
|
550
|
-
|
|
603
|
+
// Extract the entire requirement block
|
|
604
|
+
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
551
605
|
|
|
552
|
-
|
|
553
|
-
|
|
606
|
+
// Remove the requirement from its current location
|
|
607
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
554
608
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
609
|
+
// Check if there are any more requirements in TODO section after removal
|
|
610
|
+
let hasMoreTodoRequirements = false;
|
|
611
|
+
inTodoSection = false; // Reset the existing variable
|
|
612
|
+
for (let i = 0; i < lines.length; i++) {
|
|
613
|
+
const line = lines[i].trim();
|
|
560
614
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
615
|
+
// Check if we're entering TODO section
|
|
616
|
+
if (line.startsWith('##') && line.includes('Requirements not yet completed')) {
|
|
617
|
+
inTodoSection = true;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
566
620
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
621
|
+
// Check if we're leaving TODO section
|
|
622
|
+
if (inTodoSection && line.startsWith('##') && !line.startsWith('###') && !line.includes('Requirements not yet completed')) {
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
571
625
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
}
|
|
626
|
+
// Check if we found a requirement in TODO section
|
|
627
|
+
if (inTodoSection && line.startsWith('###')) {
|
|
628
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
629
|
+
if (title) {
|
|
630
|
+
hasMoreTodoRequirements = true;
|
|
631
|
+
break;
|
|
580
632
|
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// If no more TODO requirements, log message
|
|
637
|
+
if (!hasMoreTodoRequirements) {
|
|
638
|
+
console.log(chalk.green(`🎉 ${t('auto.direct.requirement.no.more.todo')}`));
|
|
639
|
+
// Add a new requirement to the TODO section
|
|
640
|
+
const newRequirement = '### R14: TESTREQ1 with promo code FRIENDSANDFAMILYROCK';
|
|
641
|
+
lines.push(newRequirement);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Run /speckit.tasks to regenerate tasks.md for spec "008-enable-disable-requirements" in checkbox format (- [ ] task)
|
|
645
|
+
const childProcess = require('child_process');
|
|
646
|
+
childProcess.execSync('/speckit.tasks --spec 008-enable-disable-requirements --format checkbox');
|
|
581
647
|
|
|
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
648
|
|
|
590
649
|
// Find or create TO VERIFY BY HUMAN section with conflict resolution
|
|
591
650
|
// IMPORTANT: Do NOT match VERIFIED sections - only match TO VERIFY sections
|
|
@@ -884,6 +943,8 @@ async function getAllAvailableProviders() {
|
|
|
884
943
|
const claudeCodeAvailable = await llm.isClaudeCodeAvailable();
|
|
885
944
|
const clineAvailable = await llm.isClineAvailable();
|
|
886
945
|
const openCodeAvailable = await llm.isOpenCodeAvailable();
|
|
946
|
+
const vscodeCopilotCLIResult = await llm.isVSCodeCopilotCLIAvailable();
|
|
947
|
+
const vscodeCopilotCLIAvailable = vscodeCopilotCLIResult.available;
|
|
887
948
|
const ollamaAvailable = await llm.isOllamaAvailable();
|
|
888
949
|
let ollamaModels = [];
|
|
889
950
|
if (ollamaAvailable) {
|
|
@@ -988,6 +1049,45 @@ async function getAllAvailableProviders() {
|
|
|
988
1049
|
});
|
|
989
1050
|
break;
|
|
990
1051
|
}
|
|
1052
|
+
case 'vscode-copilot-cli': {
|
|
1053
|
+
console.log(`[AUTO-DIRECT] Checking VS Code Copilot CLI availability...`);
|
|
1054
|
+
console.log(`[AUTO-DIRECT] vscodeCopilotCLIAvailable: ${vscodeCopilotCLIAvailable}`);
|
|
1055
|
+
console.log(`[AUTO-DIRECT] vscodeCopilotCLIResult:`, vscodeCopilotCLIResult);
|
|
1056
|
+
|
|
1057
|
+
if (!vscodeCopilotCLIAvailable) {
|
|
1058
|
+
let reason = 'VS Code Copilot CLI not installed — run: brew install --cask copilot-cli';
|
|
1059
|
+
|
|
1060
|
+
if (vscodeCopilotCLIResult.needsAuth) {
|
|
1061
|
+
if (vscodeCopilotCLIResult.authMethod === 'manual') {
|
|
1062
|
+
reason = 'VS Code Copilot CLI requires authentication — run: copilot login OR set COPILOT_GITHUB_TOKEN environment variable';
|
|
1063
|
+
} else {
|
|
1064
|
+
reason = `VS Code Copilot CLI authentication failed — try: copilot login OR set COPILOT_GITHUB_TOKEN environment variable`;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
console.log(`[AUTO-DIRECT] Skipping VS Code Copilot CLI: ${reason}`);
|
|
1069
|
+
skipped.push({
|
|
1070
|
+
provider: providerId,
|
|
1071
|
+
displayName: def.name,
|
|
1072
|
+
enabled,
|
|
1073
|
+
reason
|
|
1074
|
+
});
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
let authMethod = 'authenticated';
|
|
1079
|
+
if (vscodeCopilotCLIResult.authMethod && vscodeCopilotCLIResult.authMethod !== 'existing') {
|
|
1080
|
+
authMethod = `auto-authenticated via ${vscodeCopilotCLIResult.authMethod}`;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
console.log(`[AUTO-DIRECT] ✓ VS Code Copilot CLI available (${authMethod})`);
|
|
1084
|
+
|
|
1085
|
+
providers.push({
|
|
1086
|
+
...base,
|
|
1087
|
+
model: def.defaultModel
|
|
1088
|
+
});
|
|
1089
|
+
break;
|
|
1090
|
+
}
|
|
991
1091
|
case 'cursor':
|
|
992
1092
|
case 'windsurf':
|
|
993
1093
|
case 'vscode':
|
|
@@ -1108,6 +1208,48 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1108
1208
|
return { status: 'no_enabled', disabledProviders, skipped };
|
|
1109
1209
|
}
|
|
1110
1210
|
|
|
1211
|
+
// Check if all enabled providers are rate limited (regardless of availability)
|
|
1212
|
+
const allEnabledRateLimited = enabledProviders.length > 0 &&
|
|
1213
|
+
enabledProviders.every(p => providerManager.isRateLimited(p.provider, p.model));
|
|
1214
|
+
|
|
1215
|
+
if (allEnabledRateLimited) {
|
|
1216
|
+
// Calculate wait times for all enabled providers
|
|
1217
|
+
const waits = enabledProviders
|
|
1218
|
+
.map(p => providerManager.getTimeUntilReset(p.provider, p.model))
|
|
1219
|
+
.filter(Boolean);
|
|
1220
|
+
let nextResetMs = waits.length ? Math.min(...waits) : null;
|
|
1221
|
+
|
|
1222
|
+
// Find which provider will be available next
|
|
1223
|
+
let nextProvider = null;
|
|
1224
|
+
let nextResetTime = null;
|
|
1225
|
+
|
|
1226
|
+
for (const provider of enabledProviders) {
|
|
1227
|
+
const resetMs = providerManager.getTimeUntilReset(provider.provider, provider.model);
|
|
1228
|
+
if (resetMs && (!nextResetMs || resetMs < nextResetMs)) {
|
|
1229
|
+
nextResetMs = resetMs;
|
|
1230
|
+
nextProvider = provider;
|
|
1231
|
+
const rateLimitInfo = providerManager.getRateLimitInfo(provider.provider, provider.model);
|
|
1232
|
+
nextResetTime = rateLimitInfo ? rateLimitInfo.resetTime : null;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
return {
|
|
1237
|
+
status: 'all_rate_limited',
|
|
1238
|
+
enabledProviders,
|
|
1239
|
+
disabledProviders,
|
|
1240
|
+
nextResetMs,
|
|
1241
|
+
nextResetTime,
|
|
1242
|
+
providers: enabledProviders.map(p => {
|
|
1243
|
+
const rateLimitInfo = providerManager.getRateLimitInfo(p.provider, p.model);
|
|
1244
|
+
return {
|
|
1245
|
+
...p,
|
|
1246
|
+
rateLimitResetTime: rateLimitInfo ? rateLimitInfo.resetTime : null
|
|
1247
|
+
};
|
|
1248
|
+
})
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Filter out rate-limited providers to get available ones
|
|
1111
1253
|
const availableProviders = enabledProviders.filter(p => {
|
|
1112
1254
|
// Exclude specified provider and rate-limited providers
|
|
1113
1255
|
if (excludeProvider && p.provider === excludeProvider) {
|
|
@@ -1207,24 +1349,6 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1207
1349
|
return { status: 'ok', provider: selection, disabledProviders };
|
|
1208
1350
|
}
|
|
1209
1351
|
|
|
1210
|
-
const waits = availableProviders
|
|
1211
|
-
.map(p => providerManager.getTimeUntilReset(p.provider, p.model))
|
|
1212
|
-
.filter(Boolean);
|
|
1213
|
-
const nextResetMs = waits.length ? Math.min(...waits) : null;
|
|
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
1352
|
// If we reach here, no providers are available (filtered out by excludeProvider or other conditions)
|
|
1229
1353
|
return {
|
|
1230
1354
|
status: 'no_available',
|
|
@@ -1234,6 +1358,132 @@ async function getProviderConfig(excludeProvider = null) {
|
|
|
1234
1358
|
};
|
|
1235
1359
|
}
|
|
1236
1360
|
|
|
1361
|
+
/**
|
|
1362
|
+
* Enhanced rate limit waiting with immediate agent switching
|
|
1363
|
+
* @param {Object} selection - Provider selection object with rate limit info
|
|
1364
|
+
* @param {string} currentTitle - Current requirement title
|
|
1365
|
+
* @param {string} currentStatus - Current workflow status
|
|
1366
|
+
* @returns {Promise<Object>} - Next provider or wait decision
|
|
1367
|
+
*/
|
|
1368
|
+
async function handleRateLimitWithAgentSwitching(selection, currentTitle, currentStatus) {
|
|
1369
|
+
console.log(chalk.yellow(`\n⚠️ ${t('auto.direct.provider.all.rate.limited')}`));
|
|
1370
|
+
|
|
1371
|
+
// Notify GUI about waiting mode if available
|
|
1372
|
+
try {
|
|
1373
|
+
// Try to send status to GUI via IPC if running in Electron context
|
|
1374
|
+
if (process.versions && process.versions.electron) {
|
|
1375
|
+
const { ipcRenderer } = require('electron');
|
|
1376
|
+
if (ipcRenderer) {
|
|
1377
|
+
ipcRenderer.send('requirements-progress', {
|
|
1378
|
+
stage: currentStatus,
|
|
1379
|
+
requirement: currentTitle,
|
|
1380
|
+
mode: 'waiting',
|
|
1381
|
+
timestamp: new Date().toISOString()
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
} catch (e) {
|
|
1386
|
+
// Ignore if not in Electron context
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// Check if we have any providers that will be available soon
|
|
1390
|
+
if (selection.nextResetTime && selection.nextResetMs) {
|
|
1391
|
+
const waitMinutes = Math.max(1, Math.ceil(selection.nextResetMs / 60000));
|
|
1392
|
+
|
|
1393
|
+
console.log(chalk.yellow(`🔄 All enabled providers are currently rate limited.`));
|
|
1394
|
+
|
|
1395
|
+
if (selection.nextProvider && selection.nextResetTime) {
|
|
1396
|
+
const resetTime = new Date(selection.nextResetTime).toLocaleTimeString();
|
|
1397
|
+
const resetDate = new Date(selection.nextResetTime).toLocaleDateString();
|
|
1398
|
+
console.log(chalk.yellow(` Waiting for the next available agent (${selection.nextProvider.displayName}) to reset rate limit at ${resetTime} MST on ${resetDate}`));
|
|
1399
|
+
} else {
|
|
1400
|
+
console.log(chalk.yellow(` Waiting for rate limits to reset...`));
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Update status card to waiting mode
|
|
1404
|
+
printStatusCard(currentTitle, currentStatus, 'waiting');
|
|
1405
|
+
|
|
1406
|
+
// Note: CLI cannot directly send IPC events to Electron app
|
|
1407
|
+
// The Electron app's emitAutoModeProgress function will handle rate limit detection
|
|
1408
|
+
|
|
1409
|
+
// Check for any provider becoming available within the next minute
|
|
1410
|
+
const nearFutureProviders = selection.providers?.filter(p => {
|
|
1411
|
+
if (!p.rateLimitResetTime) return false;
|
|
1412
|
+
const resetMs = new Date(p.rateLimitResetTime).getTime() - Date.now();
|
|
1413
|
+
return resetMs > 0 && resetMs <= 60000; // Within 1 minute
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
if (nearFutureProviders && nearFutureProviders.length > 0) {
|
|
1417
|
+
const nextProvider = nearFutureProviders[0];
|
|
1418
|
+
const nextResetMs = new Date(nextProvider.rateLimitResetTime).getTime() - Date.now();
|
|
1419
|
+
const nextWaitMinutes = Math.max(1, Math.ceil(nextResetMs / 60000));
|
|
1420
|
+
const resetTime = new Date(nextProvider.rateLimitResetTime).toLocaleTimeString();
|
|
1421
|
+
const resetDate = new Date(nextProvider.rateLimitResetTime).toLocaleDateString();
|
|
1422
|
+
|
|
1423
|
+
console.log(chalk.cyan(`⏱️ ${nextProvider.displayName} will be available at ${resetTime} on ${resetDate}`));
|
|
1424
|
+
console.log(chalk.gray(` Waiting for ${nextProvider.displayName} to become available...\n`));
|
|
1425
|
+
|
|
1426
|
+
// Wait for the next available provider (max 1 minute chunks)
|
|
1427
|
+
const waitChunks = Math.ceil(Math.min(nextResetMs, 60000) / 10000);
|
|
1428
|
+
for (let i = 0; i < waitChunks; i++) {
|
|
1429
|
+
await sleep(10000);
|
|
1430
|
+
// Re-check provider status in case rate limits reset early
|
|
1431
|
+
const freshSelection = await getProviderConfig();
|
|
1432
|
+
if (freshSelection.status === 'ok') {
|
|
1433
|
+
console.log(chalk.green(`✅ ${nextProvider.displayName} is now available!\n`));
|
|
1434
|
+
|
|
1435
|
+
// Notify GUI about returning to active mode
|
|
1436
|
+
try {
|
|
1437
|
+
if (process.versions && process.versions.electron) {
|
|
1438
|
+
const { ipcRenderer } = require('electron');
|
|
1439
|
+
if (ipcRenderer) {
|
|
1440
|
+
ipcRenderer.send('requirements-progress', {
|
|
1441
|
+
stage: currentStatus,
|
|
1442
|
+
requirement: currentTitle,
|
|
1443
|
+
mode: 'active',
|
|
1444
|
+
timestamp: new Date().toISOString()
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
// Ignore if not in Electron context
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
return freshSelection;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
} else {
|
|
1456
|
+
// No providers available soon, wait for the next reset
|
|
1457
|
+
console.log(chalk.gray(` DEBUG: selection = ${JSON.stringify(selection, null, 2)}\n`));
|
|
1458
|
+
if (selection.nextProvider && selection.nextResetTime) {
|
|
1459
|
+
const resetTime = new Date(selection.nextResetTime).toLocaleTimeString();
|
|
1460
|
+
const resetDate = new Date(selection.nextResetTime).toLocaleDateString();
|
|
1461
|
+
console.log(chalk.gray(` Waiting for ${selection.nextProvider.displayName} to reset at ${resetTime} MST on ${resetDate}...\n`));
|
|
1462
|
+
} else {
|
|
1463
|
+
console.log(chalk.gray(` Waiting for rate limits to reset...\n`));
|
|
1464
|
+
}
|
|
1465
|
+
await sleep(Math.min(selection.nextResetMs, 60000));
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
// No reset time info, wait default 1 minute
|
|
1469
|
+
const nextAvailableProvider = selection.providers?.find(p => p.rateLimitResetTime && p.rateLimitResetTime > Date.now());
|
|
1470
|
+
if (nextAvailableProvider) {
|
|
1471
|
+
const resetTime = new Date(nextAvailableProvider.rateLimitResetTime).toLocaleTimeString();
|
|
1472
|
+
const resetDate = new Date(nextAvailableProvider.rateLimitResetTime).toLocaleDateString();
|
|
1473
|
+
console.log(chalk.gray(` Waiting for ${nextAvailableProvider.displayName} to reset at ${resetTime} MST on ${resetDate}...\n`));
|
|
1474
|
+
|
|
1475
|
+
// Note: CLI cannot directly send IPC events to Electron app
|
|
1476
|
+
// The Electron app's emitAutoModeProgress function will handle rate limit detection
|
|
1477
|
+
} else {
|
|
1478
|
+
console.log(chalk.gray(` Waiting for rate limits to reset...\n`));
|
|
1479
|
+
}
|
|
1480
|
+
printStatusCard(currentTitle, currentStatus, 'waiting');
|
|
1481
|
+
await sleep(60000);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
return null; // Signal to retry provider selection
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1237
1487
|
async function acquireProviderConfig(excludeProvider = null, excludeModel = null, forcedProvider = null) {
|
|
1238
1488
|
// If a specific provider is forced, bypass normal selection
|
|
1239
1489
|
if (forcedProvider) {
|
|
@@ -1305,15 +1555,12 @@ async function acquireProviderConfig(excludeProvider = null, excludeModel = null
|
|
|
1305
1555
|
}
|
|
1306
1556
|
|
|
1307
1557
|
if (selection.status === 'all_rate_limited') {
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1558
|
+
// Use enhanced rate limit handling with agent switching
|
|
1559
|
+
const result = await handleRateLimitWithAgentSwitching(selection, storedStatusTitle || 'Unknown requirement', storedStatus || 'PREPARE');
|
|
1560
|
+
if (result) {
|
|
1561
|
+
return result.provider; // Found an available provider
|
|
1311
1562
|
}
|
|
1312
|
-
|
|
1313
|
-
const waitMinutes = Math.max(1, Math.ceil(waitMs / 60000));
|
|
1314
|
-
console.log(chalk.gray(` Waiting for rate limits to reset (~${waitMinutes}m)...\n`));
|
|
1315
|
-
await sleep(Math.min(waitMs, 60000));
|
|
1316
|
-
continue;
|
|
1563
|
+
continue; // Retry provider selection
|
|
1317
1564
|
}
|
|
1318
1565
|
|
|
1319
1566
|
if (selection.status === 'no_available') {
|
|
@@ -1334,10 +1581,27 @@ async function acquireProviderConfig(excludeProvider = null, excludeModel = null
|
|
|
1334
1581
|
function parseSearchReplaceBlocks(response) {
|
|
1335
1582
|
const changes = [];
|
|
1336
1583
|
|
|
1337
|
-
// Match
|
|
1338
|
-
const
|
|
1584
|
+
// Match CREATE: path CONTENT: ``` content ``` format for new files
|
|
1585
|
+
const createRegex = /CREATE:\s*(.+?)\nCONTENT:\s*```(?:[a-z]*)\n([\s\S]+?)```/g;
|
|
1339
1586
|
|
|
1340
1587
|
let match;
|
|
1588
|
+
while ((match = createRegex.exec(response)) !== null) {
|
|
1589
|
+
let filePath = match[1].trim();
|
|
1590
|
+
const content = match[2];
|
|
1591
|
+
|
|
1592
|
+
// Clean up file path - remove "---" prefix if present
|
|
1593
|
+
filePath = filePath.replace(/^---\s*/, '').trim();
|
|
1594
|
+
|
|
1595
|
+
changes.push({
|
|
1596
|
+
type: 'create',
|
|
1597
|
+
file: filePath,
|
|
1598
|
+
content: content
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// Match FILE: path SEARCH: ``` old ``` REPLACE: ``` new ``` format for modifications
|
|
1603
|
+
const blockRegex = /FILE:\s*(.+?)\nSEARCH:\s*```(?:[a-z]*)\n([\s\S]+?)```\s*REPLACE:\s*```(?:[a-z]*)\n([\s\S]+?)```/g;
|
|
1604
|
+
|
|
1341
1605
|
while ((match = blockRegex.exec(response)) !== null) {
|
|
1342
1606
|
let filePath = match[1].trim();
|
|
1343
1607
|
const searchText = match[2];
|
|
@@ -1347,6 +1611,7 @@ function parseSearchReplaceBlocks(response) {
|
|
|
1347
1611
|
filePath = filePath.replace(/^---\s*/, '').trim();
|
|
1348
1612
|
|
|
1349
1613
|
changes.push({
|
|
1614
|
+
type: 'modify',
|
|
1350
1615
|
file: filePath,
|
|
1351
1616
|
search: searchText,
|
|
1352
1617
|
replace: replaceText
|
|
@@ -1420,8 +1685,38 @@ function extractPattern(code) {
|
|
|
1420
1685
|
*/
|
|
1421
1686
|
async function applyFileChange(change, repoPath) {
|
|
1422
1687
|
try {
|
|
1423
|
-
|
|
1688
|
+
// Normalize file path to handle both absolute and relative paths
|
|
1689
|
+
let fullPath;
|
|
1690
|
+
if (path.isAbsolute(change.file)) {
|
|
1691
|
+
// If absolute path, check if it starts with repoPath
|
|
1692
|
+
if (change.file.startsWith(repoPath)) {
|
|
1693
|
+
fullPath = change.file;
|
|
1694
|
+
} else {
|
|
1695
|
+
// Absolute path outside repo - use as-is
|
|
1696
|
+
fullPath = change.file;
|
|
1697
|
+
}
|
|
1698
|
+
} else {
|
|
1699
|
+
// Relative path - join with repoPath
|
|
1700
|
+
fullPath = path.join(repoPath, change.file);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// Handle file creation
|
|
1704
|
+
if (change.type === 'create') {
|
|
1705
|
+
// Check if file already exists
|
|
1706
|
+
if (await fs.pathExists(fullPath)) {
|
|
1707
|
+
return { success: false, error: `File already exists: ${change.file}` };
|
|
1708
|
+
}
|
|
1424
1709
|
|
|
1710
|
+
// Ensure parent directory exists
|
|
1711
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
1712
|
+
|
|
1713
|
+
// Write new file
|
|
1714
|
+
await fs.writeFile(fullPath, change.content, 'utf8');
|
|
1715
|
+
console.log(chalk.green(` ✓ Created new file`));
|
|
1716
|
+
return { success: true, method: 'create' };
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// Handle file modification (existing behavior)
|
|
1425
1720
|
// Check if file exists
|
|
1426
1721
|
if (!await fs.pathExists(fullPath)) {
|
|
1427
1722
|
return { success: false, error: `File not found: ${change.file}` };
|
|
@@ -1527,6 +1822,20 @@ async function findRelevantFiles(requirement, repoPath) {
|
|
|
1527
1822
|
|
|
1528
1823
|
try {
|
|
1529
1824
|
const reqLower = requirement.toLowerCase();
|
|
1825
|
+
|
|
1826
|
+
// Check if this is a spec task that needs to mark completion in tasks.md
|
|
1827
|
+
const tasksMarkingMatch = requirement.match(/mark the task done in (.+\/tasks\.md)/);
|
|
1828
|
+
if (tasksMarkingMatch) {
|
|
1829
|
+
const tasksFilePath = tasksMarkingMatch[1];
|
|
1830
|
+
// Convert absolute path to relative path if needed
|
|
1831
|
+
let relativePath = tasksFilePath;
|
|
1832
|
+
if (path.isAbsolute(tasksFilePath)) {
|
|
1833
|
+
relativePath = path.relative(repoPath, tasksFilePath);
|
|
1834
|
+
}
|
|
1835
|
+
relevantFiles.push(relativePath);
|
|
1836
|
+
return relevantFiles; // For spec tasks, only need the tasks.md file
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1530
1839
|
const isRemovalRequirement = /remove|delete|eliminate/i.test(requirement) &&
|
|
1531
1840
|
(/menu|item|option|setting|button|ui|element/i.test(requirement));
|
|
1532
1841
|
|
|
@@ -2253,12 +2562,12 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
|
|
|
2253
2562
|
|
|
2254
2563
|
async function runIdeFallbackIteration(requirement, providerConfig, repoPath, providerManager, llm, startTime) {
|
|
2255
2564
|
// Update console and requirements file with PREPARE status
|
|
2256
|
-
printStatusCard(requirement.text, 'PREPARE');
|
|
2565
|
+
printStatusCard(requirement.text, 'PREPARE', 'active');
|
|
2257
2566
|
await updateRequirementsStatus(repoPath, 'PREPARE');
|
|
2258
2567
|
console.log(chalk.gray(`${t('auto.direct.ide.skipping.context')}\n`));
|
|
2259
2568
|
|
|
2260
2569
|
// Update console and requirements file with ACT status
|
|
2261
|
-
printStatusCard(requirement.text, 'ACT');
|
|
2570
|
+
printStatusCard(requirement.text, 'ACT', 'active');
|
|
2262
2571
|
await updateRequirementsStatus(repoPath, 'ACT');
|
|
2263
2572
|
const ideResult = await runIdeProviderIteration(providerConfig, repoPath);
|
|
2264
2573
|
|
|
@@ -2402,10 +2711,10 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
|
|
|
2402
2711
|
return { success: false, error: errorMsg };
|
|
2403
2712
|
}
|
|
2404
2713
|
|
|
2405
|
-
printStatusCard(requirement.text, 'VERIFY');
|
|
2714
|
+
printStatusCard(requirement.text, 'VERIFY', 'active');
|
|
2406
2715
|
console.log(chalk.green(`✅ ${t('auto.direct.provider.completed')}\n`));
|
|
2407
2716
|
|
|
2408
|
-
printStatusCard(requirement.text, 'DONE');
|
|
2717
|
+
printStatusCard(requirement.text, 'DONE', 'active');
|
|
2409
2718
|
const duration = Date.now() - startTime;
|
|
2410
2719
|
providerManager.recordPerformance(providerConfig.provider, providerConfig.model, duration);
|
|
2411
2720
|
|
|
@@ -2453,13 +2762,13 @@ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig) {
|
|
|
2453
2762
|
// This lets the agent work through multiple tasks autonomously rather than one at a time.
|
|
2454
2763
|
let instruction;
|
|
2455
2764
|
if (spec.hasTasks) {
|
|
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.
|
|
2765
|
+
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. When ALL tasks are checked off, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM.`;
|
|
2457
2766
|
} else if (spec.hasPlan) {
|
|
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.
|
|
2767
|
+
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. When ALL tasks are done, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM.`;
|
|
2459
2768
|
} else {
|
|
2460
2769
|
const planPromptNote = spec.hasPlanPrompt
|
|
2461
2770
|
? `Use plan prompt from ${spec.path}/plan-prompt.md to guide planning. ` : '';
|
|
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.
|
|
2771
|
+
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. When ALL tasks are done, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM.`;
|
|
2463
2772
|
}
|
|
2464
2773
|
|
|
2465
2774
|
// Send the spec instruction to the IDE via AppleScript.
|
|
@@ -2514,7 +2823,7 @@ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig) {
|
|
|
2514
2823
|
const TIMEOUT_MS = PROGRESS_TIMEOUT_MS; // Use configurable progress timeout
|
|
2515
2824
|
const PROGRESS_COOLDOWN_MS = 2 * 60 * 1000; // 2 minutes cooldown after progress
|
|
2516
2825
|
|
|
2517
|
-
|
|
2826
|
+
let startTime = Date.now();
|
|
2518
2827
|
let lastContinueSent = Date.now();
|
|
2519
2828
|
let lastProgressTime = Date.now(); // Track when we last saw progress
|
|
2520
2829
|
let lastProgressDetectionTime = 0; // Track when we last detected progress to implement cooldown
|
|
@@ -2540,41 +2849,70 @@ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig) {
|
|
|
2540
2849
|
process.once('SIGINT', onSigint);
|
|
2541
2850
|
|
|
2542
2851
|
interval = setInterval(async () => {
|
|
2543
|
-
|
|
2852
|
+
try {
|
|
2853
|
+
const elapsed = Date.now() - startTime;
|
|
2544
2854
|
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2855
|
+
if (elapsed >= TIMEOUT_MS) {
|
|
2856
|
+
console.log(chalk.yellow(`⏰ Overall timeout reached - but continuing to monitor existing Windsurf instance\n`));
|
|
2857
|
+
// Don't create new instance - just reset timer and continue monitoring
|
|
2858
|
+
startTime = Date.now(); // Reset the overall timer
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2551
2861
|
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2862
|
+
// Detect progress AND check if IDE agent is done working
|
|
2863
|
+
const { done: doneNow, total: totalNow } = countSpecCheckboxes(spec.path);
|
|
2864
|
+
const currentTime = Date.now();
|
|
2555
2865
|
|
|
2556
|
-
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2866
|
+
// Check for completion signals from IDE agent via status file
|
|
2867
|
+
const statusFilePath = spec.path.replace(/\/$/, '') + '/.vcm-status.json';
|
|
2868
|
+
let agentSignaledCompletionViaFile = false;
|
|
2869
|
+
try {
|
|
2870
|
+
if (await fs.pathExists(statusFilePath)) {
|
|
2871
|
+
const statusContent = await fs.readFile(statusFilePath, 'utf-8');
|
|
2872
|
+
const statusData = JSON.parse(statusContent);
|
|
2873
|
+
|
|
2874
|
+
// Check for completion signals
|
|
2875
|
+
if (statusData.completion === 100 && statusData.status === 'WAITING') {
|
|
2876
|
+
console.log(chalk.green(`🎯 Agent signaled completion via status file: COMPLETION: 100%, STATUS:WAITING\n`));
|
|
2877
|
+
agentSignaledCompletionViaFile = true;
|
|
2878
|
+
|
|
2879
|
+
// Clean up status file
|
|
2880
|
+
await fs.remove(statusFilePath);
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
} catch (error) {
|
|
2884
|
+
// Ignore status file errors - file might not exist or be invalid JSON
|
|
2885
|
+
}
|
|
2559
2886
|
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2887
|
+
if (doneNow > doneBefore) {
|
|
2888
|
+
const pctNow = totalNow > 0 ? Math.round((doneNow / totalNow) * 100) : 0;
|
|
2889
|
+
safeLog(chalk.green(`✓ Progress detected: ${doneNow}/${totalNow} tasks (${pctNow}%) complete\n`));
|
|
2563
2890
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2891
|
+
// Update progress tracking
|
|
2892
|
+
lastProgressTime = currentTime;
|
|
2893
|
+
lastProgressDetectionTime = currentTime;
|
|
2894
|
+
|
|
2895
|
+
// Check if all tasks are complete - if so, wait for agent completion signal
|
|
2896
|
+
if (doneNow >= totalNow && totalNow > 0) {
|
|
2897
|
+
safeLog(chalk.green(`🎯 All tasks checked off! Waiting for agent completion signal...\n`));
|
|
2898
|
+
// Don't immediately return - wait for STATUS:WAITING signal or timeout
|
|
2899
|
+
agentSignaledCompletion = true; // Flag that we're waiting for completion signal
|
|
2900
|
+
}
|
|
2570
2901
|
|
|
2571
2902
|
// If progress detected but not all tasks done, continue waiting for agent to finish current work
|
|
2572
2903
|
return; // Continue polling, don't send new task yet
|
|
2573
2904
|
}
|
|
2574
2905
|
|
|
2906
|
+
// Check for immediate completion via status file signal
|
|
2907
|
+
if (agentSignaledCompletionViaFile) {
|
|
2908
|
+
safeLog(chalk.green(`✅ Agent completed all tasks via status signal! Finishing iteration.\n`));
|
|
2909
|
+
cleanup({ success: true, changes: [] });
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2575
2913
|
// If agent signaled completion (all tasks done) and we've waited a reasonable time, consider it complete
|
|
2576
2914
|
if (agentSignaledCompletion && (currentTime - lastProgressDetectionTime) >= 30 * 1000) {
|
|
2577
|
-
|
|
2915
|
+
safeLog(chalk.green(`✅ Agent completed all tasks! Finishing iteration.\n`));
|
|
2578
2916
|
cleanup({ success: true, changes: [] });
|
|
2579
2917
|
return;
|
|
2580
2918
|
}
|
|
@@ -2582,18 +2920,22 @@ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig) {
|
|
|
2582
2920
|
// Implement cooldown period after progress detection to prevent rapid spawning
|
|
2583
2921
|
if (lastProgressDetectionTime > 0 && (currentTime - lastProgressDetectionTime) < PROGRESS_COOLDOWN_MS) {
|
|
2584
2922
|
const cooldownRemaining = Math.round((PROGRESS_COOLDOWN_MS - (currentTime - lastProgressDetectionTime)) / 1000);
|
|
2585
|
-
|
|
2923
|
+
safeLog(chalk.gray(`⏱️ In cooldown period (${cooldownRemaining}s remaining) - allowing agent time to complete work\n`));
|
|
2586
2924
|
return;
|
|
2587
2925
|
}
|
|
2588
2926
|
|
|
2589
2927
|
// Check for no progress timeout (configurable, default 15 minutes)
|
|
2590
2928
|
const timeSinceLastProgress = Date.now() - lastProgressTime;
|
|
2591
2929
|
if (timeSinceLastProgress >= PROGRESS_TIMEOUT_MS) {
|
|
2592
|
-
|
|
2930
|
+
safeLog(chalk.yellow(`⚠️ No progress detected for ${Math.round(timeSinceLastProgress / 60000)} minutes on ${providerConfig.displayName}\n`));
|
|
2593
2931
|
|
|
2594
2932
|
// Check if we've exceeded max IDE attempts
|
|
2595
2933
|
if (totalAttempts >= MAX_IDE_ATTEMPTS) {
|
|
2596
|
-
|
|
2934
|
+
safeLog(chalk.red(`✗ Maximum IDE attempts (${MAX_IDE_ATTEMPTS}) reached. Stopping auto mode.\n`));
|
|
2935
|
+
// Update status card to stopped mode
|
|
2936
|
+
if (storedStatusTitle && storedStatus) {
|
|
2937
|
+
printStatusCard(storedStatusTitle, storedStatus, 'stopped');
|
|
2938
|
+
}
|
|
2597
2939
|
// Stop auto mode - call async function properly
|
|
2598
2940
|
stopAutoMode().then(() => {
|
|
2599
2941
|
cleanup({ success: false, error: 'max_ide_attempts_exceeded', shouldRetry: false });
|
|
@@ -2622,7 +2964,7 @@ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig) {
|
|
|
2622
2964
|
let continueMsg;
|
|
2623
2965
|
if (agentSignaledCompletion) {
|
|
2624
2966
|
// All tasks done, waiting for completion signal
|
|
2625
|
-
continueMsg = `All tasks appear to be completed (${doneNow}/${totalNow} tasks). If you are truly finished,
|
|
2967
|
+
continueMsg = `All tasks appear to be completed (${doneNow}/${totalNow} tasks). If you are truly finished, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM. If you need more time, continue working and report progress. (${mins}min elapsed) PLEASE RESPOND`;
|
|
2626
2968
|
} else {
|
|
2627
2969
|
// Normal continuation prompt
|
|
2628
2970
|
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`;
|
|
@@ -2632,9 +2974,30 @@ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig) {
|
|
|
2632
2974
|
const appleScriptManager = new AppleScriptManager();
|
|
2633
2975
|
// Use plain sendText for continuations (no need to open new thread)
|
|
2634
2976
|
await appleScriptManager.sendText(continueMsg, ideType);
|
|
2635
|
-
|
|
2977
|
+
try {
|
|
2978
|
+
// Check if stdout is still writable before attempting to log
|
|
2979
|
+
if (process.stdout && !process.stdout.destroyed) {
|
|
2980
|
+
const message = `📤 Sent continuation prompt to ${providerConfig.displayName} (${mins}min elapsed)\n`;
|
|
2981
|
+
console.log(chalk.gray(message));
|
|
2982
|
+
}
|
|
2983
|
+
} catch (logError) {
|
|
2984
|
+
// Ignore EPIPE and other stdout errors - process may be terminating
|
|
2985
|
+
if (logError.code !== 'EPIPE') {
|
|
2986
|
+
// Re-throw non-EPIPE errors
|
|
2987
|
+
throw logError;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2636
2990
|
} catch (_) { /* ignore continuation errors */ }
|
|
2637
2991
|
}
|
|
2992
|
+
} catch (error) {
|
|
2993
|
+
// Handle any errors in the setInterval callback, especially EPIPE
|
|
2994
|
+
if (error.code === 'EPIPE') {
|
|
2995
|
+
// Silently ignore EPIPE errors - process is terminating
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
// Log other errors but don't crash
|
|
2999
|
+
console.error('Error in polling interval:', error.message);
|
|
3000
|
+
}
|
|
2638
3001
|
}, POLL_INTERVAL_MS);
|
|
2639
3002
|
});
|
|
2640
3003
|
}
|
|
@@ -2654,7 +3017,7 @@ async function runIteration(requirement, providerConfig, repoPath) {
|
|
|
2654
3017
|
// ═══════════════════════════════════════════════════════════
|
|
2655
3018
|
// PREPARE PHASE - SEARCH AND READ ACTUAL FILES
|
|
2656
3019
|
// ═══════════════════════════════════════════════════════════
|
|
2657
|
-
printStatusCard(requirement.text, 'PREPARE');
|
|
3020
|
+
printStatusCard(requirement.text, 'PREPARE', 'active');
|
|
2658
3021
|
|
|
2659
3022
|
console.log(chalk.bold.white('📋 REQUIREMENT:'));
|
|
2660
3023
|
console.log(chalk.cyan(` ${requirement.text}\n`));
|
|
@@ -2681,19 +3044,24 @@ async function runIteration(requirement, providerConfig, repoPath) {
|
|
|
2681
3044
|
// ═══════════════════════════════════════════════════════════
|
|
2682
3045
|
// ACT PHASE - GET STRUCTURED CHANGES FROM LLM
|
|
2683
3046
|
// ═══════════════════════════════════════════════════════════
|
|
2684
|
-
printStatusCard(requirement.text, 'ACT');
|
|
3047
|
+
printStatusCard(requirement.text, 'ACT', 'active');
|
|
2685
3048
|
|
|
2686
3049
|
console.log(chalk.cyan(` ${getLogTimestamp()} - 🤖 Asking LLM for implementation...\n`));
|
|
2687
3050
|
console.log(chalk.gray('─'.repeat(80)));
|
|
2688
3051
|
console.log(chalk.yellow('💭 LLM Response (streaming):'));
|
|
2689
3052
|
console.log(chalk.gray('─'.repeat(80)));
|
|
2690
3053
|
|
|
2691
|
-
// Build context with actual file snippets
|
|
3054
|
+
// Build context with actual file snippets (use relative paths)
|
|
2692
3055
|
let contextSection = '';
|
|
2693
3056
|
if (fileSnippets.length > 0) {
|
|
2694
3057
|
contextSection = '\n\nCURRENT CODE CONTEXT:\n';
|
|
2695
3058
|
fileSnippets.forEach(({ file, snippet, startLine }) => {
|
|
2696
|
-
|
|
3059
|
+
// Convert to relative path if absolute
|
|
3060
|
+
let displayPath = file;
|
|
3061
|
+
if (path.isAbsolute(file)) {
|
|
3062
|
+
displayPath = path.relative(repoPath, file);
|
|
3063
|
+
}
|
|
3064
|
+
contextSection += `\n--- ${displayPath} (around line ${startLine}) ---\n${snippet}\n`;
|
|
2697
3065
|
});
|
|
2698
3066
|
}
|
|
2699
3067
|
|
|
@@ -2755,7 +3123,8 @@ ${isRemovalRequirement ? '5. **REMOVE ALL CODE** related to the item - do NOT co
|
|
|
2755
3123
|
|
|
2756
3124
|
OUTPUT FORMAT:
|
|
2757
3125
|
|
|
2758
|
-
|
|
3126
|
+
For modifying existing files:
|
|
3127
|
+
FILE: <exact path from the "---" line - must be relative to repo root>
|
|
2759
3128
|
SEARCH: \`\`\`javascript
|
|
2760
3129
|
<COPY 10+ lines EXACTLY - preserve indentation, spacing, comments>
|
|
2761
3130
|
\`\`\`
|
|
@@ -2763,22 +3132,31 @@ REPLACE: \`\`\`javascript
|
|
|
2763
3132
|
<SAME lines but with necessary modifications>
|
|
2764
3133
|
\`\`\`
|
|
2765
3134
|
|
|
3135
|
+
For creating new files:
|
|
3136
|
+
CREATE: <relative path to new file>
|
|
3137
|
+
CONTENT: \`\`\`javascript
|
|
3138
|
+
<full content of new file>
|
|
3139
|
+
\`\`\`
|
|
3140
|
+
|
|
2766
3141
|
CRITICAL RULES - READ CAREFULLY:
|
|
2767
|
-
1.
|
|
2768
|
-
2.
|
|
2769
|
-
3.
|
|
2770
|
-
4.
|
|
2771
|
-
5. Include
|
|
3142
|
+
1. If the file does NOT exist, use CREATE: format to create it
|
|
3143
|
+
2. If the file DOES exist and you need to modify it, use FILE:/SEARCH:/REPLACE: format
|
|
3144
|
+
3. For FILE: blocks, SEARCH block must be COPIED CHARACTER-BY-CHARACTER from CURRENT CODE CONTEXT above
|
|
3145
|
+
4. Use the EXACT code as it appears NOW - do NOT use old/outdated code from memory
|
|
3146
|
+
5. Include ALL property values EXACTLY as shown (e.g., if context shows 'type: "info"', use 'type: "info"', NOT 'type: "setting"')
|
|
3147
|
+
6. Include indentation EXACTLY as shown (count the spaces!)
|
|
3148
|
+
7. For modifications, include AT LEAST 20-30 LINES of context:
|
|
2772
3149
|
- Start 10-15 lines BEFORE the code you need to change
|
|
2773
3150
|
- End 10-15 lines AFTER the code you need to change
|
|
2774
3151
|
- MORE context is BETTER than less - include extra lines if unsure
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
3152
|
+
8. Do NOT paraphrase or rewrite - COPY EXACTLY from CURRENT CODE CONTEXT
|
|
3153
|
+
9. FILE path must match exactly what's after "---" in CURRENT CODE CONTEXT (DO NOT include the "---" itself)
|
|
3154
|
+
10. All paths must be RELATIVE to repo root (e.g., "specs/005-beta-pricing/tasks.md" NOT "/Users/.../tasks.md")
|
|
3155
|
+
11. Output ONLY the FILE/SEARCH/REPLACE or CREATE/CONTENT blocks
|
|
3156
|
+
12. NO explanations, NO markdown outside the blocks, NO additional text
|
|
3157
|
+
13. If you cannot find the exact code to modify, output: ERROR: CANNOT LOCATE CODE IN CONTEXT
|
|
3158
|
+
14. IMPORTANT: Include ALL intermediate code between the before/after context - do NOT skip lines
|
|
3159
|
+
15. DOUBLE-CHECK: Compare your SEARCH block line-by-line with CURRENT CODE CONTEXT to ensure they match
|
|
2782
3160
|
|
|
2783
3161
|
EXAMPLE (notice EXACT copying of indentation and spacing):
|
|
2784
3162
|
|
|
@@ -2802,7 +3180,24 @@ if (counts.todoCount === 0) {
|
|
|
2802
3180
|
}
|
|
2803
3181
|
\`\`\`
|
|
2804
3182
|
|
|
2805
|
-
|
|
3183
|
+
CREATE EXAMPLE (for new files):
|
|
3184
|
+
|
|
3185
|
+
CREATE: packages/web/src/utils/paypal-config.js
|
|
3186
|
+
CONTENT: \`\`\`javascript
|
|
3187
|
+
/**
|
|
3188
|
+
* PayPal Configuration Utility
|
|
3189
|
+
*/
|
|
3190
|
+
|
|
3191
|
+
export const getPayPalClientId = () => {
|
|
3192
|
+
return import.meta.env.VITE_PAYPAL_CLIENT_ID;
|
|
3193
|
+
};
|
|
3194
|
+
|
|
3195
|
+
export const getPayPalEnvironment = () => {
|
|
3196
|
+
return import.meta.env.VITE_PAYPAL_ENVIRONMENT || 'sandbox';
|
|
3197
|
+
};
|
|
3198
|
+
\`\`\`
|
|
3199
|
+
|
|
3200
|
+
Now implement the requirement. Remember: For modifications, COPY THE SEARCH BLOCK EXACTLY! For new files, use CREATE: format.`;
|
|
2806
3201
|
|
|
2807
3202
|
let fullResponse = '';
|
|
2808
3203
|
let chunkCount = 0;
|
|
@@ -2902,7 +3297,7 @@ Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
|
2902
3297
|
// ═══════════════════════════════════════════════════════════
|
|
2903
3298
|
// CLEAN UP PHASE - APPLY CHANGES TO ACTUAL FILES
|
|
2904
3299
|
// ═══════════════════════════════════════════════════════════
|
|
2905
|
-
printStatusCard(requirement.text, 'CLEAN UP');
|
|
3300
|
+
printStatusCard(requirement.text, 'CLEAN UP', 'active');
|
|
2906
3301
|
|
|
2907
3302
|
console.log(chalk.cyan('🧹 Parsing and applying changes...\n'));
|
|
2908
3303
|
|
|
@@ -2972,7 +3367,7 @@ Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
|
2972
3367
|
// ═══════════════════════════════════════════════════════════
|
|
2973
3368
|
// VERIFY PHASE - CONFIRM CHANGES WERE APPLIED
|
|
2974
3369
|
// ═══════════════════════════════════════════════════════════
|
|
2975
|
-
printStatusCard(requirement.text, 'VERIFY');
|
|
3370
|
+
printStatusCard(requirement.text, 'VERIFY', 'active');
|
|
2976
3371
|
|
|
2977
3372
|
console.log(chalk.cyan('✓ Verifying changes...\n'));
|
|
2978
3373
|
|
|
@@ -2982,7 +3377,14 @@ Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
|
2982
3377
|
const fullPath = path.join(repoPath, change.file);
|
|
2983
3378
|
if (await fs.pathExists(fullPath)) {
|
|
2984
3379
|
const content = await fs.readFile(fullPath, 'utf8');
|
|
2985
|
-
|
|
3380
|
+
|
|
3381
|
+
// Handle both create and modify types
|
|
3382
|
+
let hasChange = false;
|
|
3383
|
+
if (change.type === 'create') {
|
|
3384
|
+
hasChange = change.content && content.includes(change.content.trim());
|
|
3385
|
+
} else {
|
|
3386
|
+
hasChange = change.replace && content.includes(change.replace.trim());
|
|
3387
|
+
}
|
|
2986
3388
|
|
|
2987
3389
|
if (hasChange) {
|
|
2988
3390
|
console.log(chalk.green(` ✓ ${change.file}`));
|
|
@@ -2999,7 +3401,7 @@ Now implement the requirement. Remember: COPY THE SEARCH BLOCK EXACTLY!`;
|
|
|
2999
3401
|
// ═══════════════════════════════════════════════════════════
|
|
3000
3402
|
// DONE PHASE
|
|
3001
3403
|
// ═══════════════════════════════════════════════════════════
|
|
3002
|
-
printStatusCard(requirement.text, 'DONE');
|
|
3404
|
+
printStatusCard(requirement.text, 'DONE', 'active');
|
|
3003
3405
|
|
|
3004
3406
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
3005
3407
|
|
|
@@ -3213,6 +3615,10 @@ async function handleAutoStart(options) {
|
|
|
3213
3615
|
// Update status to stopped
|
|
3214
3616
|
updateAutoModeStatus(repoPath, { running: false });
|
|
3215
3617
|
console.log(chalk.gray('Auto mode stopped'));
|
|
3618
|
+
// Update status card to stopped mode
|
|
3619
|
+
if (storedStatusTitle && storedStatus) {
|
|
3620
|
+
printStatusCard(storedStatusTitle, storedStatus, 'stopped');
|
|
3621
|
+
}
|
|
3216
3622
|
}
|
|
3217
3623
|
});
|
|
3218
3624
|
|