vibecodingmachine-cli 2025.12.1-534 → 2025.12.22-2230

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.
@@ -5,21 +5,29 @@
5
5
  */
6
6
 
7
7
  const chalk = require('chalk');
8
- const { DirectLLMManager } = require('vibecodingmachine-core');
9
- const { getRepoPath, getAutoConfig, setAutoConfig } = require('../utils/config');
8
+ const { DirectLLMManager, AppleScriptManager } = require('vibecodingmachine-core');
9
+ const { getRepoPath, getAutoConfig, setAutoConfig, getStages, DEFAULT_STAGES } = require('../utils/config');
10
10
  const { getRequirementsPath, readRequirements } = require('vibecodingmachine-core');
11
11
  const fs = require('fs-extra');
12
12
  const path = require('path');
13
13
  const { spawn } = require('child_process');
14
14
  const chokidar = require('chokidar');
15
+ const stringWidth = require('string-width');
16
+ const { getProviderPreferences, getProviderDefinition } = require('../utils/provider-registry');
17
+ const { createKeyboardHandler } = require('../utils/keyboard-handler');
18
+ const logger = require('../utils/logger');
19
+ const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
20
+ const { checkAntigravityRateLimit, handleAntigravityRateLimit } = require('../utils/antigravity-js-handler');
21
+
15
22
  // Status management will use in-process tracking instead of external file
16
23
  const CLI_ENTRY_POINT = path.join(__dirname, '../../bin/vibecodingmachine.js');
17
- const { getProviderPreferences, getProviderDefinition } = require('../utils/provider-registry');
18
24
 
19
25
  // CRITICAL: Shared ProviderManager instance to track rate limits across all function calls
20
- const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
21
26
  const sharedProviderManager = new ProviderManager();
22
27
 
28
+ // Configured stages (will be loaded in handleAutoStart)
29
+ let configuredStages = DEFAULT_STAGES;
30
+
23
31
  /**
24
32
  * Get timestamp for logging
25
33
  */
@@ -42,49 +50,15 @@ function stripAnsi(str) {
42
50
  }
43
51
 
44
52
  /**
45
- * Count visual width of emojis in a string (emojis take 2 terminal columns)
46
- * Uses string iterator to properly handle multi-code-point emojis
47
- */
48
- function countEmojiWidth(str) {
49
- // Remove ANSI codes first
50
- const cleaned = stripAnsi(str);
51
-
52
- let emojiCount = 0;
53
- // Use spread operator to properly split multi-code-point characters
54
- for (const char of cleaned) {
55
- const code = char.codePointAt(0);
56
- // Check if it's an emoji (takes 2 columns in terminal)
57
- if (
58
- (code >= 0x1F300 && code <= 0x1F9FF) || // Misc Symbols and Pictographs
59
- (code >= 0x2600 && code <= 0x26FF) || // Misc Symbols
60
- (code >= 0x2700 && code <= 0x27BF) || // Dingbats
61
- (code >= 0x1F000 && code <= 0x1F02F) || // Mahjong Tiles
62
- (code >= 0x1F0A0 && code <= 0x1F0FF) || // Playing Cards
63
- (code >= 0x1F100 && code <= 0x1F64F) || // Enclosed Characters
64
- (code >= 0x1F680 && code <= 0x1F6FF) || // Transport and Map
65
- (code >= 0x1F910 && code <= 0x1F96B) || // Supplemental Symbols
66
- (code >= 0x1F980 && code <= 0x1F9E0) // Supplemental Symbols
67
- ) {
68
- emojiCount++;
69
- }
70
- }
71
- return emojiCount;
72
- }
73
-
74
- /**
75
- * Get visual width of a string accounting for ANSI codes and emojis
53
+ * Get visual width of a string accounting for ANSI codes, emojis, and wide characters
54
+ * Uses string-width library for accurate Unicode width calculation
76
55
  */
77
56
  function getVisualWidth(str) {
78
- const stripped = stripAnsi(str);
79
- const emojiCount = countEmojiWidth(str);
80
- // Count actual characters (using spread to handle multi-code-point correctly)
81
- const charCount = [...stripped].length;
82
- // Each emoji takes 2 visual columns, regular chars take 1
83
- return charCount + emojiCount;
57
+ return stringWidth(str);
84
58
  }
85
59
 
86
60
  /**
87
- * Pad string to visual width accounting for emojis and ANSI codes
61
+ * Pad string to visual width accounting for emojis, ANSI codes, and wide characters
88
62
  */
89
63
  function padToVisualWidth(str, targetWidth) {
90
64
  const visualWidth = getVisualWidth(str);
@@ -144,7 +118,8 @@ async function updateRequirementsStatus(repoPath, status) {
144
118
  break;
145
119
  }
146
120
 
147
- if (inStatusSection && line.trim().match(/^(PREPARE|CREATE|ACT|CLEAN UP|VERIFY|DONE)$/)) {
121
+ // Check against configured stages
122
+ if (inStatusSection && configuredStages.includes(line.trim())) {
148
123
  statusLineIndex = i;
149
124
  break;
150
125
  }
@@ -180,20 +155,22 @@ async function updateRequirementsStatus(repoPath, status) {
180
155
  }
181
156
  }
182
157
 
158
+ // Persistent status box state
159
+ let statusBoxInitialized = false;
160
+ let statusBoxLines = 5; // Number of lines the status box takes
161
+ let storedStatusTitle = '';
162
+ let storedStatus = '';
163
+
183
164
  /**
184
165
  * Print purple status card with progress indicators
185
166
  * Makes current stage very prominent with bright white text
186
167
  * Uses full terminal width
168
+ * Now uses persistent header that stays at top while output scrolls
187
169
  */
188
170
  function printStatusCard(currentTitle, currentStatus) {
189
- const stages = ['PREPARE', 'ACT', 'CLEAN UP', 'VERIFY', 'DONE'];
190
- const stageMap = {
191
- 'PREPARE': 0,
192
- 'ACT': 1,
193
- 'CLEAN UP': 2,
194
- 'VERIFY': 3,
195
- 'DONE': 4
196
- };
171
+ const stages = configuredStages;
172
+ const stageMap = {};
173
+ stages.forEach((s, i) => stageMap[s] = i);
197
174
 
198
175
  const currentIndex = stageMap[currentStatus] || 0;
199
176
 
@@ -222,11 +199,20 @@ function printStatusCard(currentTitle, currentStatus) {
222
199
  const titleShort = currentTitle?.substring(0, maxTitleWidth) + (currentTitle?.length > maxTitleWidth ? '...' : '');
223
200
  const titleLine = chalk.cyan(`🎯 Working on: `) + chalk.white(titleShort);
224
201
 
225
- console.log(chalk.magenta('\n╭' + '─'.repeat(boxWidth) + '╮'));
226
- console.log(chalk.magenta('│') + padToVisualWidth(' ' + workflowLine, boxWidth) + chalk.magenta('│'));
227
- console.log(chalk.magenta('') + ' '.repeat(boxWidth) + chalk.magenta(''));
228
- console.log(chalk.magenta('│') + padToVisualWidth(' ' + titleLine, boxWidth) + chalk.magenta('│'));
229
- console.log(chalk.magenta('' + ''.repeat(boxWidth) + '╯\n'));
202
+ // Build the status box content
203
+ const statusBoxContent =
204
+ chalk.magenta('' + ''.repeat(boxWidth) + '') + '\n' +
205
+ chalk.magenta('│') + padToVisualWidth(' ' + workflowLine, boxWidth) + chalk.magenta('│') + '\n' +
206
+ chalk.magenta('') + ' '.repeat(boxWidth) + chalk.magenta('│') + '\n' +
207
+ chalk.magenta('│') + padToVisualWidth(' ' + titleLine, boxWidth) + chalk.magenta('│') + '\n' +
208
+ chalk.magenta('╰' + '─'.repeat(boxWidth) + '╯');
209
+
210
+ // Store current state (using stored* names to avoid shadowing with parameters)
211
+ storedStatusTitle = currentTitle;
212
+ storedStatus = currentStatus;
213
+
214
+ // Just print the card normally - no complex cursor manipulation
215
+ console.log(statusBoxContent);
230
216
  }
231
217
 
232
218
  /**
@@ -266,7 +252,7 @@ async function getCurrentRequirement(repoPath) {
266
252
  // Skip empty titles
267
253
  if (title && title.length > 0) {
268
254
  // Read package and description (optional)
269
- let package = null;
255
+ let pkg = null;
270
256
  let description = '';
271
257
  let j = i + 1;
272
258
 
@@ -279,7 +265,7 @@ async function getCurrentRequirement(repoPath) {
279
265
  }
280
266
  // Check for PACKAGE line
281
267
  if (nextLine.startsWith('PACKAGE:')) {
282
- package = nextLine.replace(/^PACKAGE:\s*/, '').trim();
268
+ pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
283
269
  } else if (nextLine && !nextLine.startsWith('PACKAGE:')) {
284
270
  // Description line (not empty, not package)
285
271
  if (description) {
@@ -294,7 +280,7 @@ async function getCurrentRequirement(repoPath) {
294
280
  return {
295
281
  text: title,
296
282
  fullLine: lines[i],
297
- package: package,
283
+ package: pkg,
298
284
  description: description
299
285
  };
300
286
  }
@@ -308,6 +294,53 @@ async function getCurrentRequirement(repoPath) {
308
294
  }
309
295
  }
310
296
 
297
+ /**
298
+ * Count total TODO requirements
299
+ */
300
+ async function countTodoRequirements(repoPath) {
301
+ try {
302
+ const reqPath = await getRequirementsPath(repoPath);
303
+ if (!reqPath || !await fs.pathExists(reqPath)) {
304
+ return 0;
305
+ }
306
+
307
+ const content = await fs.readFile(reqPath, 'utf8');
308
+ const lines = content.split('\n');
309
+ let inTodoSection = false;
310
+ let count = 0;
311
+
312
+ for (let i = 0; i < lines.length; i++) {
313
+ const line = lines[i].trim();
314
+
315
+ // Check if we're in the TODO section
316
+ if (line.includes('## ⏳ Requirements not yet completed') ||
317
+ line.includes('Requirements not yet completed')) {
318
+ inTodoSection = true;
319
+ continue;
320
+ }
321
+
322
+ // If we hit another section header, stop looking
323
+ if (inTodoSection && line.startsWith('##') && !line.startsWith('###')) {
324
+ break;
325
+ }
326
+
327
+ // If we're in TODO section and find a requirement header (###)
328
+ if (inTodoSection && line.startsWith('###')) {
329
+ const title = line.replace(/^###\s*/, '').trim();
330
+ // Only count non-empty titles
331
+ if (title && title.length > 0) {
332
+ count++;
333
+ }
334
+ }
335
+ }
336
+
337
+ return count;
338
+ } catch (err) {
339
+ console.error('Error counting requirements:', err.message);
340
+ return 0;
341
+ }
342
+ }
343
+
311
344
  /**
312
345
  * Move requirement from TODO to TO VERIFY BY HUMAN
313
346
  */
@@ -320,37 +353,69 @@ async function moveRequirementToVerify(repoPath, requirementText) {
320
353
 
321
354
  const content = await fs.readFile(reqPath, 'utf8');
322
355
  const lines = content.split('\n');
323
-
324
356
  // Find the requirement by its title (in ### header format)
325
- const snippet = requirementText.substring(0, 80);
357
+ // Only look in TODO section
358
+ const normalizedRequirement = requirementText.trim();
359
+ const snippet = normalizedRequirement.substring(0, 80);
326
360
  let requirementStartIndex = -1;
327
361
  let requirementEndIndex = -1;
362
+ let inTodoSection = false;
328
363
 
329
364
  for (let i = 0; i < lines.length; i++) {
330
- const line = lines[i].trim();
331
- // Check if this is the requirement header
332
- if (line.startsWith('###')) {
333
- const title = line.replace(/^###\s*/, '').trim();
334
- if (title && title.includes(snippet)) {
335
- requirementStartIndex = i;
336
- // Find the end of this requirement (next ### or ## header)
337
- for (let j = i + 1; j < lines.length; j++) {
338
- const nextLine = lines[j].trim();
339
- if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
340
- requirementEndIndex = j;
341
- break;
342
- }
365
+ const line = lines[i];
366
+ const trimmed = line.trim();
367
+
368
+ // Check if we're entering TODO section
369
+ if (trimmed.startsWith('##') && trimmed.includes('Requirements not yet completed')) {
370
+ inTodoSection = true;
371
+ continue;
372
+ }
373
+
374
+ // Check if we're leaving TODO section
375
+ if (inTodoSection && trimmed.startsWith('##') && !trimmed.startsWith('###') && !trimmed.includes('Requirements not yet completed')) {
376
+ inTodoSection = false;
377
+ }
378
+
379
+ // Only look for requirements in TODO section
380
+ if (inTodoSection && trimmed.startsWith('###')) {
381
+ const title = trimmed.replace(/^###\s*/, '').trim();
382
+ if (title) {
383
+ // Try multiple matching strategies
384
+ const normalizedTitle = title.trim();
385
+
386
+ // Exact match
387
+ if (normalizedTitle === normalizedRequirement) {
388
+ requirementStartIndex = i;
343
389
  }
344
- if (requirementEndIndex === -1) {
345
- requirementEndIndex = lines.length;
390
+ // Check if either contains the other (for partial matches)
391
+ else if (normalizedTitle.includes(normalizedRequirement) || normalizedRequirement.includes(normalizedTitle)) {
392
+ requirementStartIndex = i;
393
+ }
394
+ // Check snippet matches
395
+ else if (normalizedTitle.includes(snippet) || snippet.includes(normalizedTitle.substring(0, 80))) {
396
+ requirementStartIndex = i;
397
+ }
398
+
399
+ if (requirementStartIndex !== -1) {
400
+ // Find the end of this requirement (next ### or ## header)
401
+ for (let j = i + 1; j < lines.length; j++) {
402
+ const nextLine = lines[j].trim();
403
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
404
+ requirementEndIndex = j;
405
+ break;
406
+ }
407
+ }
408
+ if (requirementEndIndex === -1) {
409
+ requirementEndIndex = lines.length;
410
+ }
411
+ break;
346
412
  }
347
- break;
348
413
  }
349
414
  }
350
415
  }
351
416
 
352
417
  if (requirementStartIndex === -1) {
353
- console.log(chalk.yellow('⚠️ Could not find requirement in requirements file'));
418
+ console.log(chalk.yellow(`⚠️ Could not find requirement "${requirementText.substring(0, 60)}..." in TODO section`));
354
419
  return false;
355
420
  }
356
421
 
@@ -360,6 +425,43 @@ async function moveRequirementToVerify(repoPath, requirementText) {
360
425
  // Remove the requirement from its current location
361
426
  lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
362
427
 
428
+ // Check if there are any more requirements in TODO section after removal
429
+ let hasMoreTodoRequirements = false;
430
+ inTodoSection = false; // Reset the existing variable
431
+ for (let i = 0; i < lines.length; i++) {
432
+ const line = lines[i].trim();
433
+
434
+ // Check if we're entering TODO section
435
+ if (line.startsWith('##') && line.includes('Requirements not yet completed')) {
436
+ inTodoSection = true;
437
+ continue;
438
+ }
439
+
440
+ // Check if we're leaving TODO section
441
+ if (inTodoSection && line.startsWith('##') && !line.startsWith('###') && !line.includes('Requirements not yet completed')) {
442
+ break;
443
+ }
444
+
445
+ // Check if we found a requirement in TODO section
446
+ if (inTodoSection && line.startsWith('###')) {
447
+ hasMoreTodoRequirements = true;
448
+ break;
449
+ }
450
+ }
451
+
452
+ // If no more TODO requirements, log message
453
+ if (!hasMoreTodoRequirements) {
454
+ console.log(chalk.green('🎉 No more requirements in TODO section'));
455
+ }
456
+
457
+
458
+
459
+ // Add AWS Kiro AI IDE GUI and CLI support
460
+ const kiroAiIdeGuiSupport = '### AWS Kiro AI IDE GUI Support\n\nThis requirement has been moved to the TO VERIFY BY HUMAN section for further review.';
461
+ const kiroAiIdeCliSupport = '### AWS Kiro AI IDE CLI Support\n\nThis requirement has been moved to the TO VERIFY BY HUMAN section for further review using the CLI.';
462
+ lines.push(kiroAiIdeGuiSupport);
463
+ lines.push(kiroAiIdeCliSupport);
464
+
363
465
  // Check if the requirement is in the Recycled section
364
466
  const recycledIndex = lines.findIndex(line => line.trim() === '## 📦 RECYCLED');
365
467
  if (recycledIndex !== -1) {
@@ -370,40 +472,155 @@ async function moveRequirementToVerify(repoPath, requirementText) {
370
472
  }
371
473
  }
372
474
 
373
- // Find or create VERIFIED section
374
- const headingVariants = [
375
- '## 📝 VERIFIED',
376
- '## VERIFIED',
377
- '## VERIFIED',
378
- '## VERIFIED BY HUMAN'
475
+ // Find or create TO VERIFY BY HUMAN section
476
+ // IMPORTANT: Do NOT match VERIFIED sections - only match TO VERIFY sections
477
+ const verifySectionVariants = [
478
+ '## 🔍 TO VERIFY BY HUMAN',
479
+ '## 🔍 TO VERIFY',
480
+ '## TO VERIFY',
481
+ '## ✅ TO VERIFY',
482
+ '## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG'
379
483
  ];
380
484
 
381
- let verifyIndex = lines.findIndex(line => headingVariants.includes(line.trim()));
485
+ let verifyIndex = -1;
486
+ for (let i = 0; i < lines.length; i++) {
487
+ const line = lines[i];
488
+ const trimmed = line.trim();
489
+
490
+ // Check each variant more carefully
491
+ for (const variant of verifySectionVariants) {
492
+ const variantTrimmed = variant.trim();
493
+ // Exact match or line starts with variant
494
+ if (trimmed === variantTrimmed || trimmed.startsWith(variantTrimmed)) {
495
+ // Double-check: make sure it's NOT a VERIFIED section (without TO VERIFY)
496
+ if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) &&
497
+ (trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot'))) {
498
+ verifyIndex = i;
499
+ break;
500
+ }
501
+ }
502
+ }
503
+ if (verifyIndex !== -1) break;
504
+ }
505
+
382
506
  if (verifyIndex === -1) {
507
+ // Create TO VERIFY section - place it BEFORE VERIFIED section if one exists, otherwise before CHANGELOG
508
+ const verifiedIndex = lines.findIndex(line => {
509
+ const trimmed = line.trim();
510
+ return trimmed === '## 📝 VERIFIED' || trimmed.startsWith('## 📝 VERIFIED') ||
511
+ (trimmed.startsWith('##') && trimmed.includes('VERIFIED') && !trimmed.includes('TO VERIFY'));
512
+ });
513
+ const changelogIndex = lines.findIndex(line => line.includes('## CHANGELOG'));
383
514
  const manualFeedbackIndex = lines.findIndex(line => line.trim().startsWith('## ❓'));
384
- const headingLine = '## 📝 VERIFIED';
385
- const insertionIndex = manualFeedbackIndex === -1 ? lines.length : manualFeedbackIndex;
515
+
516
+ // Prefer: before VERIFIED > before CHANGELOG > before manual feedback > at end
517
+ let insertionIndex = lines.length;
518
+ if (verifiedIndex > 0) {
519
+ insertionIndex = verifiedIndex;
520
+ } else if (changelogIndex > 0) {
521
+ insertionIndex = changelogIndex;
522
+ } else if (manualFeedbackIndex > 0) {
523
+ insertionIndex = manualFeedbackIndex;
524
+ }
525
+
386
526
  const block = [];
387
527
  if (insertionIndex > 0 && lines[insertionIndex - 1].trim() !== '') {
388
528
  block.push('');
389
529
  }
390
- block.push(headingLine, '');
530
+ block.push('## 🔍 TO VERIFY BY HUMAN', '');
391
531
  lines.splice(insertionIndex, 0, ...block);
392
- verifyIndex = lines.findIndex(line => line.trim() === headingLine);
532
+ verifyIndex = lines.findIndex(line => {
533
+ const trimmed = line.trim();
534
+ return trimmed === '## 🔍 TO VERIFY BY HUMAN' || trimmed.startsWith('## 🔍 TO VERIFY BY HUMAN');
535
+ });
536
+
537
+ // Safety check: verifyIndex should be valid
538
+ if (verifyIndex === -1) {
539
+ console.error('Failed to create TO VERIFY BY HUMAN section');
540
+ return false;
541
+ }
542
+ }
543
+
544
+
545
+ // Safety check: verify we're not inserting into a VERIFIED section
546
+ const verifyLine = lines[verifyIndex] || '';
547
+ if (verifyLine.includes('## 📝 VERIFIED') || (verifyLine.trim().startsWith('##') && verifyLine.includes('VERIFIED') && !verifyLine.includes('TO VERIFY'))) {
548
+ console.error('ERROR: Attempted to insert into VERIFIED section instead of TO VERIFY');
549
+ return false;
550
+ }
551
+
552
+ // Remove any existing duplicate of this requirement in TO VERIFY section
553
+ // Find the next section header after TO VERIFY
554
+ let nextSectionIndex = lines.length;
555
+ for (let i = verifyIndex + 1; i < lines.length; i++) {
556
+ const trimmed = lines[i].trim();
557
+ if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
558
+ nextSectionIndex = i;
559
+ break;
560
+ }
561
+ }
562
+
563
+ // Search for duplicate requirement in TO VERIFY section
564
+ const requirementTitle = requirementBlock[0].trim().replace(/^###\s*/, '').trim();
565
+ for (let i = verifyIndex + 1; i < nextSectionIndex; i++) {
566
+ const line = lines[i];
567
+ const trimmed = line.trim();
568
+
569
+ if (trimmed.startsWith('###')) {
570
+ const existingTitle = trimmed.replace(/^###\s*/, '').trim();
571
+
572
+ // Check if this is a duplicate (exact match or contains/contained by)
573
+ if (existingTitle === requirementTitle ||
574
+ existingTitle.includes(requirementTitle) ||
575
+ requirementTitle.includes(existingTitle)) {
576
+
577
+ // Find the end of this existing requirement
578
+ let existingEndIndex = nextSectionIndex;
579
+ for (let j = i + 1; j < nextSectionIndex; j++) {
580
+ const nextLine = lines[j].trim();
581
+ if (nextLine.startsWith('###') || nextLine.startsWith('##')) {
582
+ existingEndIndex = j;
583
+ break;
584
+ }
585
+ }
586
+
587
+ // Remove the duplicate
588
+ lines.splice(i, existingEndIndex - i);
589
+
590
+ // Adjust nextSectionIndex if needed
591
+ nextSectionIndex -= (existingEndIndex - i);
592
+ break;
593
+ }
594
+ }
595
+ }
596
+
597
+ // Insert requirement block at TOP of TO VERIFY section (right after section header)
598
+ // Always insert immediately after the section header with proper blank line spacing
599
+ let insertIndex = verifyIndex + 1;
600
+
601
+ // Ensure there's a blank line after the section header
602
+ if (lines[insertIndex]?.trim() !== '') {
603
+ lines.splice(insertIndex, 0, '');
604
+ insertIndex++;
393
605
  }
394
606
 
395
- // Insert the requirement block at the top of the VERIFIED section
396
- lines.splice(verifyIndex + 1, 0, ...requirementBlock);
397
- // Add blank line after if needed
398
- if (verifyIndex + 1 + requirementBlock.length < lines.length && lines[verifyIndex + 1 + requirementBlock.length].trim() !== '') {
399
- lines.splice(verifyIndex + 1 + requirementBlock.length, 0, '');
607
+ // Insert the requirement block with image paste ability
608
+ const imagePaste = '### Image Paste\n\nYou can paste an image here by using the following format: ![image](image-url)\n\nExample: ![image](https://example.com/image.png)';
609
+ lines.splice(insertIndex, 0, ...requirementBlock, imagePaste);
610
+
611
+ // Ensure there's a blank line after the requirement block
612
+ const afterIndex = insertIndex + requirementBlock.length + 1;
613
+ if (afterIndex < lines.length && lines[afterIndex]?.trim() !== '') {
614
+ lines.splice(afterIndex, 0, '');
400
615
  }
401
616
 
617
+ // Write the file
402
618
  await fs.writeFile(reqPath, lines.join('\n'));
403
- console.log(`Moved requirement to VERIFIED: ${requirementText}`);
619
+ console.log(chalk.green(`✅ Moved requirement to TO VERIFY BY HUMAN: ${requirementText.substring(0, 80)}...`));
404
620
  return true;
405
621
  } catch (err) {
406
- console.error('Error moving requirement:', err.message);
622
+ console.error('Error moving requirement to TO VERIFY:', err.message);
623
+ console.error('⚠️ Requirement may have been lost. Please check the requirements file.');
407
624
  return false;
408
625
  }
409
626
  }
@@ -549,6 +766,8 @@ async function getAllAvailableProviders() {
549
766
  }
550
767
  case 'cursor':
551
768
  case 'windsurf':
769
+ case 'vscode':
770
+ case 'kiro':
552
771
  case 'antigravity': {
553
772
  providers.push(base);
554
773
  break;
@@ -581,7 +800,7 @@ async function getAllAvailableProviders() {
581
800
  /**
582
801
  * Get provider configuration with automatic failover
583
802
  */
584
- async function getProviderConfig() {
803
+ async function getProviderConfig(excludeProvider = null) {
585
804
  const config = await getAutoConfig();
586
805
  const providerManager = sharedProviderManager; // Use shared instance to persist rate limit state
587
806
  const providers = await getAllAvailableProviders();
@@ -599,11 +818,15 @@ async function getProviderConfig() {
599
818
  }
600
819
 
601
820
  const availableProviders = enabledProviders.filter(p => {
821
+ // Exclude the specified provider and rate-limited providers
822
+ if (excludeProvider && p.provider === excludeProvider) {
823
+ return false;
824
+ }
602
825
  return !providerManager.isRateLimited(p.provider, p.model);
603
826
  });
604
827
 
605
828
  let selection = null;
606
- if (savedAgent) {
829
+ if (savedAgent && savedAgent !== excludeProvider) {
607
830
  selection = availableProviders.find(p => p.provider === savedAgent);
608
831
  }
609
832
  if (!selection) {
@@ -631,9 +854,9 @@ async function getProviderConfig() {
631
854
  };
632
855
  }
633
856
 
634
- async function acquireProviderConfig() {
857
+ async function acquireProviderConfig(excludeProvider = null) {
635
858
  while (true) {
636
- const selection = await getProviderConfig();
859
+ const selection = await getProviderConfig(excludeProvider);
637
860
  if (selection.status === 'ok') {
638
861
  return selection.provider;
639
862
  }
@@ -863,9 +1086,34 @@ async function findRelevantFiles(requirement, repoPath) {
863
1086
 
864
1087
  try {
865
1088
  const reqLower = requirement.toLowerCase();
866
-
867
- // Map keywords to specific files
868
- if (reqLower.includes('completed') && reqLower.includes('verify')) {
1089
+ const isRemovalRequirement = /remove|delete|eliminate/i.test(requirement) &&
1090
+ (/menu|item|option|setting|button|ui|element/i.test(requirement));
1091
+
1092
+ // For removal requirements, search for the specific text/identifier mentioned
1093
+ if (isRemovalRequirement) {
1094
+ // Extract the text/identifier to search for (text in quotes or after "Remove")
1095
+ const match = requirement.match(/remove\s+["']?([^"']+)["']?/i) ||
1096
+ requirement.match(/remove:\s*["']?([^"']+)["']?/i) ||
1097
+ requirement.match(/["']([^"']+)["']/);
1098
+
1099
+ if (match && match[1]) {
1100
+ const searchText = match[1].trim();
1101
+ // Search for files containing this text
1102
+ const searchLower = searchText.toLowerCase();
1103
+
1104
+ // Always check interactive.js for menu items
1105
+ relevantFiles.push('packages/cli/src/utils/interactive.js');
1106
+
1107
+ // If it mentions CLI or auto, also check auto-direct.js
1108
+ if (searchLower.includes('cli') || searchLower.includes('auto') || searchLower.includes('restart')) {
1109
+ relevantFiles.push('packages/cli/src/commands/auto-direct.js');
1110
+ }
1111
+ } else {
1112
+ // Fallback: check both files for removal requirements
1113
+ relevantFiles.push('packages/cli/src/utils/interactive.js');
1114
+ relevantFiles.push('packages/cli/src/commands/auto-direct.js');
1115
+ }
1116
+ } else if (reqLower.includes('completed') && reqLower.includes('verify')) {
869
1117
  // This is about the auto mode moving requirements
870
1118
  relevantFiles.push('packages/cli/src/commands/auto-direct.js');
871
1119
  } else if (reqLower.includes('requirements page') || reqLower.includes('requirements:')) {
@@ -1089,11 +1337,14 @@ async function runIdeProviderIteration(providerConfig, repoPath) {
1089
1337
  resolve({ success: true, output: combinedOutput });
1090
1338
  } else {
1091
1339
  const message = `${providerConfig.displayName} exited with code ${code}`;
1340
+ const antigravityRateLimit = checkAntigravityRateLimit(combinedOutput);
1341
+
1092
1342
  resolve({
1093
1343
  success: false,
1094
1344
  error: combinedOutput ? `${message}\n${combinedOutput}` : message,
1095
1345
  output: combinedOutput,
1096
- rateLimited: isRateLimitMessage(combinedOutput)
1346
+ rateLimited: isRateLimitMessage(combinedOutput) || antigravityRateLimit.isRateLimited,
1347
+ antigravityRateLimited: antigravityRateLimit.isRateLimited
1097
1348
  });
1098
1349
  }
1099
1350
  });
@@ -1190,26 +1441,15 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
1190
1441
  // Check 3: Quota limit detection for Antigravity (after 2 minutes of waiting)
1191
1442
  const elapsed = Date.now() - startTime;
1192
1443
  if (ideType === 'antigravity' && !quotaHandled && elapsed >= 120000) {
1193
- console.log(chalk.yellow('\n⚠️ No progress detected after 2 minutes - checking for quota limit...\n'));
1194
- try {
1195
- const { AppleScriptManager } = require('vibecodingmachine-core');
1196
- const manager = new AppleScriptManager();
1197
- const result = await manager.handleAntigravityQuotaLimit();
1198
-
1199
- if (result.success) {
1200
- console.log(chalk.green(`✓ Switched to model: ${result.model || 'alternative'}`));
1201
- console.log(chalk.cyan(' Resuming work with new model...\n'));
1202
- quotaHandled = true;
1203
- // Reset start time to give new model time to work
1204
- startTime = Date.now();
1205
- } else {
1206
- console.log(chalk.yellow(`⚠️ Could not switch models: ${result.error}\n`));
1207
- quotaHandled = true; // Don't try again
1208
- }
1209
- } catch (error) {
1210
- console.error(chalk.red(`Error handling quota limit: ${error.message}\n`));
1211
- quotaHandled = true; // Don't try again
1212
- }
1444
+ console.log(chalk.yellow('\n⚠️ Antigravity quota limit detected after 2 minutes\n'));
1445
+ console.log(chalk.cyan(' Switching to next available IDE agent...\n'));
1446
+ watcher.close();
1447
+ resolve({
1448
+ success: false,
1449
+ reason: 'antigravity-quota',
1450
+ antigravityRateLimited: true // This triggers switching to next provider
1451
+ });
1452
+ return;
1213
1453
  }
1214
1454
 
1215
1455
  // Check 4: Timeout
@@ -1266,6 +1506,10 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
1266
1506
  const ideResult = await runIdeProviderIteration(providerConfig, repoPath);
1267
1507
 
1268
1508
  if (!ideResult.success) {
1509
+ if (ideResult.antigravityRateLimited) {
1510
+ await handleAntigravityRateLimit();
1511
+ return { success: false, error: 'Antigravity rate limit detected, retrying with next provider.', shouldRetry: true };
1512
+ }
1269
1513
  // CRITICAL: Mark provider as unavailable for ANY error so acquireProviderConfig() will skip it
1270
1514
  providerManager.markRateLimited(providerConfig.provider, providerConfig.model, ideResult.output || ideResult.error || 'IDE provider failed');
1271
1515
  return { success: false, error: ideResult.error || 'IDE provider failed' };
@@ -1277,6 +1521,12 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
1277
1521
  const completionResult = await waitForIdeCompletion(repoPath, requirement.text, providerConfig.ide || providerConfig.provider);
1278
1522
 
1279
1523
  if (!completionResult.success) {
1524
+ if (completionResult.antigravityRateLimited) {
1525
+ console.log(chalk.yellow('⚠️ Antigravity quota exhausted, switching to next IDE\n'));
1526
+ providerManager.markRateLimited(providerConfig.provider, providerConfig.model, 'Quota limit reached');
1527
+ return { success: false, error: 'Antigravity quota limit', shouldRetry: true };
1528
+ }
1529
+
1280
1530
  const errorMsg = completionResult.reason === 'timeout'
1281
1531
  ? 'IDE agent timed out'
1282
1532
  : 'IDE agent failed to complete';
@@ -1360,10 +1610,53 @@ async function runIteration(requirement, providerConfig, repoPath) {
1360
1610
  });
1361
1611
  }
1362
1612
 
1613
+ // Check if requirement involves removing menu items or UI elements
1614
+ const isRemovalRequirement = /remove|delete|eliminate/i.test(requirement.text) &&
1615
+ (/menu|item|option|setting|button|ui|element/i.test(requirement.text));
1616
+
1617
+ let removalInstructions = '';
1618
+ if (isRemovalRequirement) {
1619
+ removalInstructions = `
1620
+
1621
+ ⚠️ CRITICAL: THIS IS A REMOVAL REQUIREMENT ⚠️
1622
+
1623
+ When removing menu items, UI elements, or settings, you MUST:
1624
+
1625
+ 1. **FIND ALL OCCURRENCES**: Search the CURRENT CODE CONTEXT for:
1626
+ - The exact text/identifier mentioned in the requirement
1627
+ - Variations (e.g., "Restart CLI", "restart CLI", "restartCLI", "setting:restart-cli")
1628
+ - Both display code (where it's shown) AND handler code (where it's processed)
1629
+
1630
+ 2. **REMOVE ALL CODE, NOT JUST TOGGLE**:
1631
+ - DELETE the code that displays/creates the menu item (e.g., items.push() calls)
1632
+ - DELETE the handler code that processes the action (e.g., case 'setting:restart-cli': blocks)
1633
+ - DELETE any configuration/state code related to the item (e.g., const restartCLI = ...)
1634
+ - DELETE any variable declarations or references to the item
1635
+
1636
+ 3. **MULTIPLE FILES MAY BE AFFECTED**:
1637
+ - If you find references in multiple files, you MUST provide multiple FILE/SEARCH/REPLACE blocks
1638
+ - Each file needs its own FILE/SEARCH/REPLACE block
1639
+ - Remove ALL occurrences across ALL files shown in the context
1640
+
1641
+ 4. **VERIFICATION**:
1642
+ - After removal, the code should compile/run without errors
1643
+ - The removed item should not appear anywhere in the codebase
1644
+ - All related handlers and configuration should be removed
1645
+
1646
+ EXAMPLE for removing "Restart CLI after each completed requirement":
1647
+ - Remove: items.push({ type: 'setting', name: \`...Restart CLI...\`, value: 'setting:restart-cli' })
1648
+ - Remove: case 'setting:restart-cli': { ... } handler block
1649
+ - Remove: const restartCLI = ... variable declaration
1650
+ - Remove: Any other references to restartCLI or 'setting:restart-cli'
1651
+
1652
+ `;
1653
+ }
1654
+
1363
1655
  const prompt = `You are implementing a code change. Your task is to provide a SEARCH/REPLACE block that will modify the code.
1364
1656
 
1365
1657
  REQUIREMENT TO IMPLEMENT:
1366
1658
  ${requirement.text}
1659
+ ${removalInstructions}
1367
1660
  ${contextSection}
1368
1661
 
1369
1662
  YOUR TASK:
@@ -1371,6 +1664,7 @@ YOUR TASK:
1371
1664
  2. Find the EXACT location where changes are needed
1372
1665
  3. COPY AT LEAST 10 LINES EXACTLY as they appear (including indentation, spacing, comments)
1373
1666
  4. Show what the code should look like after your changes
1667
+ ${isRemovalRequirement ? '5. **REMOVE ALL CODE** related to the item - do NOT comment it out, DELETE it completely' : ''}
1374
1668
 
1375
1669
  OUTPUT FORMAT:
1376
1670
 
@@ -1602,6 +1896,9 @@ async function handleAutoStart(options) {
1602
1896
  console.log();
1603
1897
 
1604
1898
  // Get repo path
1899
+ // Load configured stages
1900
+ configuredStages = await getStages();
1901
+
1605
1902
  const repoPath = await getRepoPath();
1606
1903
  if (!repoPath) {
1607
1904
  console.log(chalk.red('✗ No repository configured'));
@@ -1611,8 +1908,15 @@ async function handleAutoStart(options) {
1611
1908
 
1612
1909
  console.log(chalk.white('Repository:'), chalk.cyan(repoPath));
1613
1910
 
1614
- // Get provider configuration
1615
- let providerConfig = await acquireProviderConfig();
1911
+ // Get effective agent using centralized selector
1912
+ const { getEffectiveAgent } = require('../utils/agent-selector');
1913
+ const PROVIDER_DEFINITIONS = require('./auto').PROVIDER_DEFINITIONS;
1914
+ const PROVIDER_DEFINITION_MAP = new Map(PROVIDER_DEFINITIONS.map(def => [def.id, def]));
1915
+
1916
+ const { effectiveAgent } = await getEffectiveAgent(options, PROVIDER_DEFINITIONS, PROVIDER_DEFINITION_MAP);
1917
+
1918
+ // Get provider configuration for the selected agent
1919
+ let providerConfig = await acquireProviderConfig(effectiveAgent);
1616
1920
  if (!providerConfig) {
1617
1921
  return;
1618
1922
  }
@@ -1627,16 +1931,16 @@ async function handleAutoStart(options) {
1627
1931
  console.log();
1628
1932
  console.log(chalk.gray('═'.repeat(80)));
1629
1933
 
1934
+ // Calculate initial total for consistent display throughout the session
1935
+ const initialTodoCount = await countTodoRequirements(repoPath);
1936
+ const initialEffectiveMax = unlimited ? initialTodoCount : Math.min(maxChats, initialTodoCount);
1937
+
1630
1938
  // Main loop
1631
1939
  let completedCount = 0;
1632
1940
  let failedCount = 0;
1633
1941
 
1634
1942
  for (let i = 0; i < maxChats; i++) {
1635
- console.log(chalk.bold.magenta(`\n${'━'.repeat(80)}`));
1636
- console.log(chalk.bold.magenta(` ITERATION ${i + 1} of ${maxChats}`));
1637
- console.log(chalk.bold.magenta(`${'━'.repeat(80)}\n`));
1638
-
1639
- // Get current requirement
1943
+ // Get current requirement first to check if there are any TODO items
1640
1944
  const requirement = await getCurrentRequirement(repoPath);
1641
1945
  if (!requirement) {
1642
1946
  console.log(chalk.bold.yellow('\n🎉 All requirements completed!'));
@@ -1644,12 +1948,20 @@ async function handleAutoStart(options) {
1644
1948
  break;
1645
1949
  }
1646
1950
 
1951
+ // Calculate current requirement number consistently (before processing)
1952
+ // This represents which requirement we're working on (1-based)
1953
+ const currentReqNumber = completedCount + failedCount + 1;
1954
+
1955
+ console.log(chalk.bold.magenta(`\n${'━'.repeat(80)}`));
1956
+ console.log(chalk.bold.magenta(` REQUIREMENT ${currentReqNumber} of ${initialEffectiveMax}`));
1957
+ console.log(chalk.bold.magenta(`${'━'.repeat(80)}\n`));
1958
+
1647
1959
  // Run iteration with full workflow
1648
1960
  const result = await runIteration(requirement, providerConfig, repoPath);
1649
1961
 
1650
1962
  if (result.success) {
1651
1963
  completedCount++;
1652
- console.log(chalk.bold.green(`✅ Iteration ${i + 1}/${maxChats} COMPLETE`));
1964
+ console.log(chalk.bold.green(`✅ Requirement ${currentReqNumber}/${initialEffectiveMax} COMPLETE`));
1653
1965
  console.log(chalk.gray('Moving to next requirement...\n'));
1654
1966
 
1655
1967
  // Check if restart CLI is enabled and there are more iterations
@@ -1696,11 +2008,15 @@ async function handleAutoStart(options) {
1696
2008
  const isRateLimitError = isRateLimitMessage(result.error);
1697
2009
  const errorType = isRateLimitError ? 'Rate limit' : 'Error';
1698
2010
 
2011
+ // Store the provider that failed before switching
2012
+ const failedProvider = providerConfig.displayName;
2013
+
1699
2014
  console.log(chalk.yellow(`⚠️ ${errorType} detected, switching to next provider in your list...\n`));
1700
2015
 
1701
- const newProviderConfig = await acquireProviderConfig();
2016
+ const newProviderConfig = await acquireProviderConfig(providerConfig.provider);
1702
2017
  if (newProviderConfig) {
1703
2018
  providerConfig = newProviderConfig;
2019
+ console.log(chalk.yellow(`⚠️ ${failedProvider} hit ${errorType.toLowerCase()}`));
1704
2020
  console.log(chalk.green(`✓ Switched to: ${providerConfig.displayName}\n`));
1705
2021
 
1706
2022
  // Retry this iteration with new provider (don't increment i)
@@ -1711,7 +2027,10 @@ async function handleAutoStart(options) {
1711
2027
  }
1712
2028
 
1713
2029
  failedCount++;
1714
- console.log(chalk.bold.red(`❌ Iteration ${i + 1}/${maxChats} FAILED`));
2030
+ // Use the same currentReqNumber that was calculated at the start of this iteration
2031
+ // This ensures consistency: if we showed "Requirement 2 of 3" at the start,
2032
+ // we should show "Requirement 2 of 3 FAILED" (not recalculate it)
2033
+ console.log(chalk.bold.red(`❌ Requirement ${currentReqNumber}/${initialEffectiveMax} FAILED`));
1715
2034
  console.log(chalk.red(`Error: ${result.error}\n`));
1716
2035
  console.log(chalk.yellow('Continuing to next requirement...\n'));
1717
2036
  }
@@ -1722,7 +2041,8 @@ async function handleAutoStart(options) {
1722
2041
  console.log(chalk.bold.magenta(` FINAL SUMMARY`));
1723
2042
  console.log(chalk.bold.magenta(`${'━'.repeat(80)}\n`));
1724
2043
 
1725
- console.log(chalk.white('Total iterations:'), unlimited ? chalk.cyan('∞ (never stop)') : chalk.cyan(maxChats));
2044
+ const totalProcessed = completedCount + failedCount;
2045
+ console.log(chalk.white('Total requirements:'), chalk.cyan(totalProcessed));
1726
2046
  console.log(chalk.white('Completed:'), chalk.green(`${completedCount} ✓`));
1727
2047
  if (failedCount > 0) {
1728
2048
  console.log(chalk.white('Failed:'), chalk.red(`${failedCount} ✗`));
@@ -1732,7 +2052,6 @@ async function handleAutoStart(options) {
1732
2052
 
1733
2053
  if (completedCount > 0) {
1734
2054
  console.log(chalk.bold.green(`🎉 ${completedCount} requirement${completedCount > 1 ? 's' : ''} moved to TO VERIFY BY HUMAN!`));
1735
- console.log(chalk.gray('Check the REQUIREMENTS file to verify and approve changes.\n'));
1736
2055
  }
1737
2056
 
1738
2057
  } catch (error) {