vibecodingmachine-core 2025.12.6-1702 → 2025.12.24-2348

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.
@@ -1,6 +1,11 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
+ const os = require('os');
3
4
  const { getRequirementsPath } = require('./repo-helpers.cjs');
5
+ const { logger } = require('./logger.cjs');
6
+
7
+ // Default instruction text
8
+ const DEFAULT_INSTRUCTION_TEXT = 'Follow INSTRUCTIONS.md from .vibecodingmachine directory. CRITICAL: You MUST work through ALL status stages (PREPARE → ACT → CLEAN UP → VERIFY → DONE) and set the status to DONE in the REQUIREMENTS file before stopping. DO NOT stop working until the "🚦 Current Status" section shows "DONE". The VibeCodingMachine app is running in autonomous mode and depends on you completing the requirement fully.';
4
9
 
5
10
  /**
6
11
  * Get ordinal suffix for numbers (1st, 2nd, 3rd, 4th, etc.)
@@ -72,8 +77,8 @@ async function promoteToVerified(reqPath, requirementTitle) {
72
77
  const trimmed = line.trim();
73
78
 
74
79
  // Check if we're entering TO VERIFY section (multiple variants)
75
- if (trimmed.startsWith('##') && !trimmed.startsWith('###') &&
76
- (trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot'))) {
80
+ if (trimmed.startsWith('##') && !trimmed.startsWith('###') &&
81
+ (trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot'))) {
77
82
  inVerifySection = true;
78
83
  continue;
79
84
  }
@@ -88,7 +93,7 @@ async function promoteToVerified(reqPath, requirementTitle) {
88
93
  const title = trimmed.replace(/^###\s*/, '').trim();
89
94
  if (title === normalizedTitle || title.includes(normalizedTitle) || normalizedTitle.includes(title)) {
90
95
  requirementStartIndex = i;
91
-
96
+
92
97
  // Find the end of this requirement block
93
98
  for (let j = i + 1; j < lines.length; j++) {
94
99
  const nextLine = lines[j].trim();
@@ -175,12 +180,12 @@ async function demoteFromVerifiedToTodo(reqPath, requirementTitle) {
175
180
  // Extract title part (before timestamp in parentheses)
176
181
  const titleMatch = lineText.match(/^(.+?)\s*\([\d-]+\)$/);
177
182
  const lineTitle = titleMatch ? titleMatch[1] : lineText;
178
-
183
+
179
184
  // Check if this line matches the requirement title
180
185
  // Handle both cases: requirementTitle might include timestamp or not
181
186
  const reqTitleMatch = requirementTitle.match(/^(.+?)\s*\([\d-]+\)$/);
182
187
  const reqTitleOnly = reqTitleMatch ? reqTitleMatch[1] : requirementTitle;
183
-
188
+
184
189
  if (lineTitle === reqTitleOnly || lineTitle.includes(reqTitleOnly) || reqTitleOnly.includes(lineTitle)) {
185
190
  requirementToMove = lineTitle;
186
191
  continue;
@@ -202,7 +207,7 @@ async function demoteFromVerifiedToTodo(reqPath, requirementTitle) {
202
207
 
203
208
  for (let i = 0; i < lines.length; i++) {
204
209
  const line = lines[i];
205
-
210
+
206
211
  if (line.includes('## ⏳ Requirements not yet completed')) {
207
212
  foundTodoSection = true;
208
213
  updatedLines.push(line);
@@ -237,24 +242,24 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
237
242
  try {
238
243
  const content = await fs.readFile(reqPath, 'utf-8');
239
244
  const lines = content.split('\n');
240
-
245
+
241
246
  // Find the requirement block (### header format)
242
247
  let requirementStartIndex = -1;
243
248
  let requirementEndIndex = -1;
244
249
  let inTodoSection = false;
245
-
250
+
246
251
  for (let i = 0; i < lines.length; i++) {
247
252
  const line = lines[i].trim();
248
-
253
+
249
254
  if (line.includes('## ⏳ Requirements not yet completed')) {
250
255
  inTodoSection = true;
251
256
  continue;
252
257
  }
253
-
258
+
254
259
  if (inTodoSection && line.startsWith('##') && !line.startsWith('###')) {
255
260
  break;
256
261
  }
257
-
262
+
258
263
  if (inTodoSection && line.startsWith('###')) {
259
264
  const title = line.replace(/^###\s*/, '').trim();
260
265
  if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
@@ -274,20 +279,20 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
274
279
  }
275
280
  }
276
281
  }
277
-
282
+
278
283
  if (requirementStartIndex === -1) {
279
284
  return false;
280
285
  }
281
-
286
+
282
287
  // Extract the requirement block
283
288
  const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
284
-
289
+
285
290
  // Remove the requirement from its current location
286
291
  const updatedLines = [
287
292
  ...lines.slice(0, requirementStartIndex),
288
293
  ...lines.slice(requirementEndIndex)
289
294
  ];
290
-
295
+
291
296
  // Find or create TO VERIFY section
292
297
  const verifySectionVariants = [
293
298
  '## 🔍 TO VERIFY BY HUMAN',
@@ -296,7 +301,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
296
301
  '## ✅ TO VERIFY',
297
302
  '## ✅ Verified by AI screenshot'
298
303
  ];
299
-
304
+
300
305
  let verifyIndex = -1;
301
306
  for (let i = 0; i < updatedLines.length; i++) {
302
307
  if (verifySectionVariants.some(variant => updatedLines[i].includes(variant))) {
@@ -304,7 +309,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
304
309
  break;
305
310
  }
306
311
  }
307
-
312
+
308
313
  if (verifyIndex === -1) {
309
314
  // Create TO VERIFY section before CHANGELOG or at end
310
315
  const changelogIndex = updatedLines.findIndex(line => line.includes('## CHANGELOG'));
@@ -312,7 +317,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
312
317
  updatedLines.splice(insertIndex, 0, '', '## 🔍 TO VERIFY BY HUMAN', '');
313
318
  verifyIndex = insertIndex + 1;
314
319
  }
315
-
320
+
316
321
  // Insert requirement block after section header
317
322
  let insertIndex = verifyIndex + 1;
318
323
  while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
@@ -323,7 +328,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
323
328
  if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
324
329
  updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
325
330
  }
326
-
331
+
327
332
  await fs.writeFile(reqPath, updatedLines.join('\n'));
328
333
  return true;
329
334
  } catch (error) {
@@ -342,12 +347,12 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
342
347
  try {
343
348
  const content = await fs.readFile(reqPath, 'utf-8');
344
349
  const lines = content.split('\n');
345
-
350
+
346
351
  // Find ALL matching requirements in TO VERIFY section and remove them
347
352
  // We'll collect all requirement blocks to remove, then process them
348
353
  const requirementsToRemove = [];
349
354
  let inVerifySection = false;
350
-
355
+
351
356
  const verifySectionVariants = [
352
357
  '## 🔍 TO VERIFY BY HUMAN',
353
358
  '## 🔍 TO VERIFY',
@@ -356,19 +361,19 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
356
361
  '## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
357
362
  '## ✅ Verified by AI screenshot'
358
363
  ];
359
-
364
+
360
365
  // First pass: find all matching requirements in TO VERIFY section
361
366
  for (let i = 0; i < lines.length; i++) {
362
367
  const line = lines[i];
363
368
  const trimmed = line.trim();
364
-
369
+
365
370
  // Check if this is a TO VERIFY section header
366
371
  if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
367
372
  const isToVerifyHeader = verifySectionVariants.some(variant => {
368
- return trimmed === variant || trimmed.startsWith(variant) ||
369
- (trimmed.includes('Verified by AI screenshot') && trimmed.includes('Needs Human to Verify'));
373
+ return trimmed === variant || trimmed.startsWith(variant) ||
374
+ (trimmed.includes('Verified by AI screenshot') && trimmed.includes('Needs Human to Verify'));
370
375
  });
371
-
376
+
372
377
  if (isToVerifyHeader) {
373
378
  // Make sure it's not a VERIFIED section (without TO VERIFY)
374
379
  if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) && !trimmed.includes('📝 VERIFIED')) {
@@ -378,29 +383,29 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
378
383
  } else if (inVerifySection) {
379
384
  // Check if we're leaving TO VERIFY section (hit a different section)
380
385
  if (trimmed.includes('⏳ Requirements not yet completed') ||
381
- trimmed.includes('## 📝 VERIFIED') ||
382
- trimmed.includes('## ♻️ RECYCLED') ||
383
- trimmed.includes('## 📦 RECYCLED') ||
384
- trimmed.includes('## ❓ Requirements needing')) {
386
+ trimmed.includes('## 📝 VERIFIED') ||
387
+ trimmed.includes('## ♻️ RECYCLED') ||
388
+ trimmed.includes('## 📦 RECYCLED') ||
389
+ trimmed.includes('## ❓ Requirements needing')) {
385
390
  // We've left the TO VERIFY section
386
391
  inVerifySection = false;
387
392
  }
388
393
  }
389
394
  }
390
-
395
+
391
396
  // Look for requirement in TO VERIFY section
392
397
  if (inVerifySection && trimmed.startsWith('###')) {
393
398
  const title = trimmed.replace(/^###\s*/, '').trim();
394
399
  // Normalize titles for matching (handle TRY AGAIN prefixes)
395
400
  const normalizedTitle = title.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
396
401
  const normalizedRequirementTitle = requirementTitle.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
397
-
398
- if (title && (title === requirementTitle ||
399
- normalizedTitle === normalizedRequirementTitle ||
400
- title.includes(requirementTitle) ||
401
- requirementTitle.includes(title) ||
402
- normalizedTitle.includes(normalizedRequirementTitle) ||
403
- normalizedRequirementTitle.includes(normalizedTitle))) {
402
+
403
+ if (title && (title === requirementTitle ||
404
+ normalizedTitle === normalizedRequirementTitle ||
405
+ title.includes(requirementTitle) ||
406
+ requirementTitle.includes(title) ||
407
+ normalizedTitle.includes(normalizedRequirementTitle) ||
408
+ normalizedRequirementTitle.includes(normalizedTitle))) {
404
409
  // Find the end of this requirement (next ### or ## header)
405
410
  let requirementEndIndex = lines.length;
406
411
  for (let j = i + 1; j < lines.length; j++) {
@@ -410,7 +415,7 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
410
415
  break;
411
416
  }
412
417
  }
413
-
418
+
414
419
  // Store this requirement to remove (we'll use the first one for moving to TODO)
415
420
  requirementsToRemove.push({
416
421
  start: i,
@@ -420,20 +425,20 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
420
425
  }
421
426
  }
422
427
  }
423
-
428
+
424
429
  if (requirementsToRemove.length === 0) {
425
430
  return false;
426
431
  }
427
-
432
+
428
433
  // Use the first matching requirement for moving to TODO (with TRY AGAIN prefix)
429
434
  const firstRequirement = requirementsToRemove[0];
430
435
  const requirementBlock = [...firstRequirement.block];
431
-
436
+
432
437
  // Update title with TRY AGAIN prefix
433
438
  const originalTitle = requirementBlock[0].replace(/^###\s*/, '').trim();
434
439
  const titleWithPrefix = addTryAgainPrefix(originalTitle);
435
440
  requirementBlock[0] = `### ${titleWithPrefix}`;
436
-
441
+
437
442
  // Add explanation to the requirement description if provided
438
443
  if (explanation && explanation.trim()) {
439
444
  // Find where to insert the explanation (after the title, before any existing content)
@@ -446,14 +451,14 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
446
451
  ];
447
452
  requirementBlock.splice(1, 0, ...explanationLines);
448
453
  }
449
-
454
+
450
455
  // Remove ALL matching requirements from TO VERIFY section (work backwards to preserve indices)
451
456
  const updatedLines = [...lines];
452
457
  for (let i = requirementsToRemove.length - 1; i >= 0; i--) {
453
458
  const req = requirementsToRemove[i];
454
459
  updatedLines.splice(req.start, req.end - req.start);
455
460
  }
456
-
461
+
457
462
  // Find or create TODO section
458
463
  let todoIndex = -1;
459
464
  for (let i = 0; i < updatedLines.length; i++) {
@@ -462,7 +467,7 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
462
467
  break;
463
468
  }
464
469
  }
465
-
470
+
466
471
  if (todoIndex === -1) {
467
472
  // Create TODO section at the top (after initial headers)
468
473
  const firstSectionIndex = updatedLines.findIndex(line => line.startsWith('##') && !line.startsWith('###'));
@@ -470,7 +475,7 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
470
475
  updatedLines.splice(insertIndex, 0, '## ⏳ Requirements not yet completed', '');
471
476
  todoIndex = insertIndex;
472
477
  }
473
-
478
+
474
479
  // Insert requirement block after section header
475
480
  let insertIndex = todoIndex + 1;
476
481
  while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
@@ -481,7 +486,7 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
481
486
  if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
482
487
  updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
483
488
  }
484
-
489
+
485
490
  await fs.writeFile(reqPath, updatedLines.join('\n'));
486
491
  return true;
487
492
  } catch (error) {
@@ -489,6 +494,125 @@ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
489
494
  }
490
495
  }
491
496
 
497
+ /**
498
+ * Load verified requirements from CHANGELOG.md
499
+ * @param {string} repoPath - Repository root path
500
+ * @returns {Promise<string[]>} List of verified requirement titles
501
+ */
502
+ async function loadVerifiedFromChangelog(repoPath) {
503
+ try {
504
+ const changelogPath = path.join(repoPath, 'CHANGELOG.md');
505
+ if (!(await fs.pathExists(changelogPath))) {
506
+ return [];
507
+ }
508
+
509
+ const content = await fs.readFile(changelogPath, 'utf8');
510
+ const lines = content.split('\n');
511
+ const requirements = [];
512
+ let inVerifiedSection = false;
513
+
514
+ for (const line of lines) {
515
+ const trimmed = line.trim();
516
+
517
+ // Collect items starting with "- " as verified requirements
518
+ if (trimmed.startsWith('- ') && trimmed.length > 5) {
519
+ let reqText = trimmed.substring(2);
520
+ // Extract title part (before timestamp in parentheses if present)
521
+ const titleMatch = reqText.match(/^(.+?)\s*\([\d-]+\)$/);
522
+ if (titleMatch) {
523
+ reqText = titleMatch[1];
524
+ }
525
+ requirements.push(reqText.trim());
526
+ }
527
+ }
528
+
529
+ return requirements;
530
+ } catch (error) {
531
+ logger.error('❌ Error loading verified requirements from CHANGELOG.md:', error);
532
+ return [];
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Get unified project requirement statistics (TODO, TO VERIFY, VERIFIED)
538
+ * @param {string} repoPath - Repository root path
539
+ * @returns {Promise<Object>} Statistics with counts and formatted percentages
540
+ */
541
+ async function getProjectRequirementStats(repoPath) {
542
+ try {
543
+ const hostname = os.hostname();
544
+ const { getRequirementsPath } = require('./repo-helpers.cjs');
545
+ const reqPath = await getRequirementsPath(repoPath, hostname);
546
+
547
+ let todoCount = 0;
548
+ let toVerifyCount = 0;
549
+ let verifiedCount = 0;
550
+
551
+ if (reqPath && await fs.pathExists(reqPath)) {
552
+ const content = await fs.readFile(reqPath, 'utf8');
553
+ const lines = content.split('\n');
554
+ let currentSection = '';
555
+
556
+ const verifySectionVariants = [
557
+ '🔍 TO VERIFY BY HUMAN',
558
+ 'TO VERIFY BY HUMAN',
559
+ '🔍 TO VERIFY',
560
+ 'TO VERIFY',
561
+ '✅ Verified by AI screenshot',
562
+ 'Verified by AI screenshot'
563
+ ];
564
+
565
+ for (const line of lines) {
566
+ const trimmed = line.trim();
567
+
568
+ if (trimmed.startsWith('###')) {
569
+ if (currentSection) {
570
+ const requirementText = trimmed.replace(/^###\s*/, '').trim();
571
+ if (requirementText) {
572
+ if (currentSection === 'todo') todoCount++;
573
+ else if (currentSection === 'toverify') toVerifyCount++;
574
+ }
575
+ }
576
+ } else if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
577
+ if (trimmed.includes('⏳ Requirements not yet completed') ||
578
+ trimmed.includes('Requirements not yet completed')) {
579
+ currentSection = 'todo';
580
+ } else if (verifySectionVariants.some(v => trimmed.includes(v))) {
581
+ currentSection = 'toverify';
582
+ } else {
583
+ currentSection = '';
584
+ }
585
+ }
586
+ }
587
+ }
588
+
589
+ // Load verified counts from CHANGELOG.md
590
+ const verifiedReqs = await loadVerifiedFromChangelog(repoPath);
591
+ verifiedCount = verifiedReqs.length;
592
+
593
+ const total = todoCount + toVerifyCount + verifiedCount;
594
+ const todoPercent = total > 0 ? Math.round((todoCount / total) * 100) : 0;
595
+ const toVerifyPercent = total > 0 ? Math.round((toVerifyCount / total) * 100) : 0;
596
+ const verifiedPercent = total > 0 ? Math.round((verifiedCount / total) * 100) : 0;
597
+
598
+ return {
599
+ todoCount,
600
+ toVerifyCount,
601
+ verifiedCount,
602
+ total,
603
+ todoPercent,
604
+ toVerifyPercent,
605
+ verifiedPercent,
606
+ todoLabel: `TODO (${todoCount} - ${todoPercent}%)`,
607
+ toVerifyLabel: `TO VERIFY (${toVerifyCount} - ${toVerifyPercent}%)`,
608
+ verifiedLabel: `VERIFIED (${verifiedCount} - ${verifiedPercent}%)`
609
+ };
610
+ } catch (error) {
611
+ logger.error('❌ Error getting project requirement stats:', error);
612
+ return null;
613
+ }
614
+ }
615
+
492
616
  module.exports = {
493
617
  getOrdinalSuffix,
494
618
  addTryAgainPrefix,
@@ -496,6 +620,578 @@ module.exports = {
496
620
  promoteToVerified,
497
621
  demoteFromVerifiedToTodo,
498
622
  promoteTodoToVerify,
499
- demoteVerifyToTodo
623
+ demoteVerifyToTodo,
624
+ // New shared functions
625
+ getCurrentRequirement,
626
+ getCurrentRequirementName,
627
+ getCurrentRequirementProgress,
628
+ isCurrentRequirementDone,
629
+ moveToNextRequirement,
630
+ createDefaultRequirementsFile,
631
+ getOrCreateRequirementsFilePath,
632
+ loadVerifiedFromChangelog,
633
+ getProjectRequirementStats,
634
+ DEFAULT_INSTRUCTION_TEXT
500
635
  };
501
636
 
637
+ // --- Ported Functions from Electron App ---
638
+
639
+ // Function to create a default requirements file
640
+ function createDefaultRequirementsFile(repoPath) {
641
+ try {
642
+ const hostname = os.hostname();
643
+ // Use the core logic to find the .vibecodingmachine directory
644
+ // Note: This logic assumes we want to create it INSIDE the repo if it doesn't exist?
645
+ // Electron app logic was: path.join(repoPath, '.vibecodingmachine')
646
+ // We'll stick to that for creation.
647
+ const vibecodingmachineDir = path.join(repoPath, '.vibecodingmachine');
648
+
649
+ // Ensure .vibecodingmachine directory exists
650
+ if (!fs.existsSync(vibecodingmachineDir)) {
651
+ fs.mkdirSync(vibecodingmachineDir, { recursive: true });
652
+ logger.log('📁 Created .vibecodingmachine directory');
653
+ }
654
+
655
+ // Create hostname-specific requirements file
656
+ const requirementsFilePath = path.join(vibecodingmachineDir, `REQUIREMENTS-${hostname}.md`);
657
+
658
+ const defaultContent = `# 🌙 VibeCodingMachine – Requirements File
659
+
660
+ This is a Markdown (.md) file.
661
+
662
+ You should not need to modify this file, as VibeCodingMachine will modify it for you, but you can add requirements and move them around either here directly, or via AI.
663
+
664
+ This file contains the requirements to complete the project. Each item in a list is a self contained requirement that should be able to stand on its own, and not require reference to other requirements. Each requirement starts in the section of requirements to complete, then it moved to the section with the current requirement being worked on. VibeCodingMachine will then, after implementing the requirement, move it to the completed requirements list in the CHANGELOG.md file, or to the section in this file for requirements that need attention. These will be available for you to review. They should have options to choose, and you decide which option to take, and VibeCodingMachine will work on that again the next time it is run.
665
+
666
+ ## RESPONSE FROM LAST CHAT
667
+
668
+ ### ONE LINE STATUS: READY
669
+
670
+ ### ONE LINE SUMMARY: New requirements file created
671
+
672
+ ### MULTILINE DETAILS: (1-20 lines max)
673
+
674
+ **REQUIREMENT:**
675
+ - Initial setup complete
676
+
677
+ **ALL PHASES COMPLETED:**
678
+ - ✅ Requirements file created
679
+ - ✅ Ready for new requirements
680
+
681
+ ## 🔨 Current In Progress Requirement
682
+
683
+ *No current requirement in progress*
684
+
685
+ ## 📋 Requirements to Complete
686
+
687
+ *No requirements to complete*
688
+
689
+ ## ⚠️ Requirements That Need Attention
690
+
691
+ *No requirements need attention*
692
+
693
+ ## ✅ Completed Requirements
694
+
695
+ *No completed requirements yet*
696
+
697
+ ---
698
+
699
+ *This file was automatically created by VibeCodingMachine*
700
+ `;
701
+
702
+ fs.writeFileSync(requirementsFilePath, defaultContent, 'utf8');
703
+ logger.log(`📄 Created default requirements file: ${requirementsFilePath}`);
704
+
705
+ return requirementsFilePath;
706
+ } catch (error) {
707
+ logger.error('❌ Error creating default requirements file:', error);
708
+ throw error;
709
+ }
710
+ }
711
+
712
+ // Function to get or create requirements file path
713
+ async function getOrCreateRequirementsFilePath(repoPath) {
714
+ try {
715
+ const hostname = os.hostname();
716
+
717
+ // Use shared logic to find existing path first
718
+ let requirementsFilePath = await getRequirementsPath(repoPath, hostname);
719
+
720
+ if (requirementsFilePath && await fs.pathExists(requirementsFilePath)) {
721
+ return requirementsFilePath;
722
+ } else {
723
+ // Create a new hostname-specific requirements file
724
+ // We default to creating it inside the repo under .vibecodingmachine
725
+ requirementsFilePath = path.join(repoPath, '.vibecodingmachine', `REQUIREMENTS-${hostname}.md`);
726
+
727
+ const fs = require('fs-extra');
728
+ const defaultRequirementsContent = `# 🌙 VibeCodingMachine – Requirements File
729
+
730
+ This is a Markdown (.md) file.
731
+
732
+ You should not need to modify this file, as VibeCodingMachine will modify it for you, but you can add requirements and move them around either here directly, or via AI.
733
+
734
+ This file contains the requirements to complete the project. Each item in a list is a self contained requirement that should be able to stand on its own, and not require reference to other requirements. Each requirement starts in the section of requirements to complete, then it moved to the section with the current requirement being worked on. VibeCodingMachine will then, after implementing the requirement, move it to the completed requirements list in the CHANGELOG.md file, or to the section in this file for requirements that need attention. These will be available for you to review. They should have options to choose, and you decide which option to take, and VibeCodingMachine will work on that again the next time it is run.
735
+
736
+ ## RESPONSE FROM LAST CHAT
737
+
738
+ ### ONE LINE STATUS:
739
+
740
+ ### ONE LINE SUMMARY:
741
+
742
+ ### MULTILINE DETAILS: (1-20 lines max)
743
+
744
+ ## 🔨 Current In Progress Requirement
745
+
746
+ -
747
+
748
+ ## 🚦 Current Status
749
+
750
+ PREPARE
751
+
752
+ ## ⏳ Requirements not yet completed
753
+
754
+ -
755
+
756
+ ## ✅ Verified by AI screenshot
757
+
758
+ `;
759
+ fs.ensureDirSync(path.join(repoPath, '.vibecodingmachine'));
760
+ fs.writeFileSync(requirementsFilePath, defaultRequirementsContent, 'utf8');
761
+ }
762
+
763
+ return requirementsFilePath;
764
+ } catch (error) {
765
+ logger.error('❌ Error getting or creating requirements file path:', error);
766
+ throw error;
767
+ }
768
+ }
769
+
770
+ // Function to get current requirement being worked on
771
+ async function getCurrentRequirement(repoPath) {
772
+ try {
773
+ const rPath = repoPath || process.cwd();
774
+ // Get the requirements file path (create if doesn't exist)
775
+ const requirementsFilePath = await getOrCreateRequirementsFilePath(rPath);
776
+
777
+ // Read the requirements file content
778
+ const content = await fs.readFile(requirementsFilePath, 'utf8');
779
+ const lines = content.split('\n');
780
+
781
+ // Look for the current in progress requirement
782
+ for (let i = 0; i < lines.length; i++) {
783
+ if (lines[i].includes('## 🔨 Current In Progress Requirement')) {
784
+ // Look for the next requirement line
785
+ for (let j = i + 1; j < lines.length; j++) {
786
+ if (lines[j].trim().startsWith('- ')) {
787
+ let requirementText = lines[j].substring(2).trim();
788
+
789
+ // Extract the requirement title (remove FAILED prefix if present)
790
+ if (requirementText.startsWith('FAILED ')) {
791
+ const failedMatch = requirementText.match(/^FAILED \d+ TIMES?: (.+)$/);
792
+ if (failedMatch) {
793
+ requirementText = failedMatch[1];
794
+ }
795
+ }
796
+
797
+ // Extract just the title part (before the colon)
798
+ const colonIndex = requirementText.indexOf(':');
799
+ if (colonIndex !== -1) {
800
+ requirementText = requirementText.substring(0, colonIndex).trim();
801
+ }
802
+
803
+ // Remove markdown formatting
804
+ requirementText = requirementText.replace(/\*\*/g, '');
805
+
806
+ return `Working on: ${requirementText}`;
807
+ }
808
+ }
809
+ break;
810
+ }
811
+ }
812
+
813
+ return DEFAULT_INSTRUCTION_TEXT;
814
+ } catch (error) {
815
+ logger.error('❌ Error getting current requirement:', error);
816
+ return DEFAULT_INSTRUCTION_TEXT;
817
+ }
818
+ }
819
+
820
+ // Function to check if the current requirement status is DONE
821
+ async function isCurrentRequirementDone(repoPath) {
822
+ try {
823
+ const rPath = repoPath || process.cwd();
824
+ const hostname = os.hostname();
825
+
826
+ // Use shared logic to find the file
827
+ const requirementsFilePath = await getRequirementsPath(rPath, hostname);
828
+
829
+ if (!requirementsFilePath || !(await fs.pathExists(requirementsFilePath))) {
830
+ return false;
831
+ }
832
+
833
+ // Read the requirements file content
834
+ const content = await fs.readFile(requirementsFilePath, 'utf8');
835
+ const lines = content.split('\n');
836
+
837
+ // First pass: check if "Current Status" section contains DONE
838
+ let inStatusSection = false;
839
+ for (let i = 0; i < lines.length; i++) {
840
+ const line = lines[i].trim();
841
+
842
+ if (line.includes('## 🚦 Current Status')) {
843
+ inStatusSection = true;
844
+ continue;
845
+ }
846
+
847
+ // If we're in the status section
848
+ if (inStatusSection) {
849
+ // Hit another section header, exit
850
+ if (line.startsWith('##')) {
851
+ break;
852
+ }
853
+
854
+ // Check if this line contains DONE (case-insensitive, exact word match)
855
+ if (line) {
856
+ const upperLine = line.toUpperCase();
857
+ // Match DONE as a standalone word or at the start of the line
858
+ if (upperLine === 'DONE' || upperLine.startsWith('DONE:') || upperLine.startsWith('DONE ')) {
859
+ logger.log('✅ DONE status detected in Current Status section:', line);
860
+ return true;
861
+ }
862
+ }
863
+ }
864
+ }
865
+
866
+ // Second pass: check if "Current In Progress Requirement" section is empty
867
+ let inCurrentSection = false;
868
+ let foundRequirement = false;
869
+ for (let i = 0; i < lines.length; i++) {
870
+ const line = lines[i].trim();
871
+
872
+ if (line.includes('## 🔨 Current In Progress Requirement')) {
873
+ inCurrentSection = true;
874
+ continue;
875
+ }
876
+
877
+ // If we're in the current section and hit another section header, exit
878
+ if (inCurrentSection && line.startsWith('##')) {
879
+ // If section was empty (no requirement found), it's done
880
+ if (!foundRequirement) {
881
+ logger.log('✅ Current requirement section is empty - considering as done');
882
+ return true;
883
+ }
884
+ break;
885
+ }
886
+
887
+ // Track if we found a requirement in the current section
888
+ if (inCurrentSection && line.startsWith('- ')) {
889
+ foundRequirement = true;
890
+ }
891
+ }
892
+
893
+ // If we found a requirement but no DONE status, it's not done yet
894
+ if (foundRequirement) {
895
+ logger.log('❌ Found requirement but status is not DONE');
896
+ return false;
897
+ }
898
+
899
+ // Default: not done
900
+ return false;
901
+ } catch (error) {
902
+ logger.error('❌ Error checking requirement status:', error);
903
+ return false;
904
+ }
905
+ }
906
+
907
+ // Shared function to move to next requirement (used by both IPC and auto mode)
908
+ let isMovingToNextRequirement = false; // Prevent duplicate calls
909
+ let lastMoveTime = 0; // Track when last move happened
910
+
911
+ async function moveToNextRequirement(repoPath) {
912
+ const now = Date.now();
913
+ const rPath = repoPath || process.cwd();
914
+
915
+ // Prevent duplicate calls within 5 seconds
916
+ if (isMovingToNextRequirement || (now - lastMoveTime < 5000)) {
917
+ logger.log('⚠️ moveToNextRequirement already in progress or called too recently, skipping duplicate call');
918
+ return { success: false, error: 'Already moving to next requirement or called too recently' };
919
+ }
920
+
921
+ isMovingToNextRequirement = true;
922
+ lastMoveTime = now;
923
+
924
+ try {
925
+ logger.log('🔄 Moving to next requirement');
926
+
927
+ const hostname = os.hostname();
928
+ // Use shared logic
929
+ const requirementsFilePath = await getRequirementsPath(rPath, hostname);
930
+
931
+ if (!requirementsFilePath || !(await fs.pathExists(requirementsFilePath))) {
932
+ return { success: false, error: 'Requirements file not found' };
933
+ }
934
+
935
+ // Read current content
936
+ const content = await fs.readFile(requirementsFilePath, 'utf8');
937
+ const lines = content.split('\n');
938
+
939
+ // Extract current requirement and remove from "Current In Progress"
940
+ let currentRequirement = null;
941
+ let inCurrentSection = false;
942
+ for (let i = 0; i < lines.length; i++) {
943
+ if (lines[i].includes('## 🔨 Current In Progress Requirement')) {
944
+ inCurrentSection = true;
945
+ continue;
946
+ }
947
+ if (inCurrentSection && lines[i].trim().startsWith('- ')) {
948
+ currentRequirement = lines[i].substring(2).trim();
949
+ break;
950
+ }
951
+ if (inCurrentSection && lines[i].trim().startsWith('##')) {
952
+ break;
953
+ }
954
+ }
955
+
956
+ // Get first requirement from "Requirements not yet completed"
957
+ let nextRequirement = null;
958
+ let inNotYetCompleted = false;
959
+ for (let i = 0; i < lines.length; i++) {
960
+ if (lines[i].includes('## ⏳ Requirements not yet completed')) {
961
+ inNotYetCompleted = true;
962
+ continue;
963
+ }
964
+ if (inNotYetCompleted && lines[i].trim().startsWith('- ')) {
965
+ nextRequirement = lines[i].substring(2).trim();
966
+ break;
967
+ }
968
+ if (inNotYetCompleted && lines[i].trim().startsWith('##')) {
969
+ break;
970
+ }
971
+ }
972
+
973
+ if (!nextRequirement) {
974
+ return { success: false, error: 'No more requirements to process' };
975
+ }
976
+
977
+ // Count requirements in "not yet completed" (before moving)
978
+ let totalRequirementsNotYetCompleted = 0;
979
+ inNotYetCompleted = false;
980
+ for (const line of lines) {
981
+ if (line.includes('## ⏳ Requirements not yet completed')) {
982
+ inNotYetCompleted = true;
983
+ continue;
984
+ }
985
+ if (inNotYetCompleted && line.trim().startsWith('##')) {
986
+ break;
987
+ }
988
+ if (inNotYetCompleted && line.trim().startsWith('- ')) {
989
+ totalRequirementsNotYetCompleted++;
990
+ }
991
+ }
992
+
993
+ // Count completed requirements in "Verified by AI" section
994
+ let completedCount = 0;
995
+ let inVerifiedSection = false;
996
+ for (const line of lines) {
997
+ if (line.includes('## ✅ Verified by AI screenshot')) {
998
+ inVerifiedSection = true;
999
+ continue;
1000
+ }
1001
+ if (inVerifiedSection && line.trim().startsWith('##')) {
1002
+ break;
1003
+ }
1004
+ if (inVerifiedSection && line.trim().startsWith('- ')) {
1005
+ completedCount++;
1006
+ }
1007
+ }
1008
+
1009
+ // Update the file: move current to "Verified by AI", move next to "Current In Progress"
1010
+ const updatedLines = [];
1011
+ inCurrentSection = false;
1012
+ inNotYetCompleted = false;
1013
+ inVerifiedSection = false;
1014
+ let addedCurrentToVerified = false;
1015
+ let addedNextToCurrent = false;
1016
+ let removedNextFromNotYetCompleted = false;
1017
+
1018
+ for (let i = 0; i < lines.length; i++) {
1019
+ const line = lines[i];
1020
+
1021
+ // Handle "Current In Progress Requirement" section
1022
+ if (line.includes('## 🔨 Current In Progress Requirement')) {
1023
+ inCurrentSection = true;
1024
+ updatedLines.push(line);
1025
+ updatedLines.push('');
1026
+ updatedLines.push(`- ${nextRequirement}`);
1027
+ updatedLines.push('');
1028
+ addedNextToCurrent = true;
1029
+ continue;
1030
+ }
1031
+
1032
+ // Skip old current requirement and any text in the current section
1033
+ if (inCurrentSection && !line.trim().startsWith('##')) {
1034
+ // Skip lines until we hit the next section header
1035
+ continue;
1036
+ }
1037
+ if (inCurrentSection && line.trim().startsWith('##')) {
1038
+ inCurrentSection = false;
1039
+ // Don't continue - we want to process this section header
1040
+ }
1041
+
1042
+ // Handle "Current Status" section
1043
+ if (line.includes('## 🚦 Current Status')) {
1044
+ updatedLines.push(line);
1045
+ updatedLines.push('');
1046
+ updatedLines.push('PREPARE');
1047
+ updatedLines.push('');
1048
+
1049
+ // Skip all old status lines until we hit the next section
1050
+ let j = i + 1;
1051
+ while (j < lines.length && !lines[j].trim().startsWith('##')) {
1052
+ j++;
1053
+ }
1054
+ i = j - 1; // Set i to the line before the next section (loop will increment)
1055
+ continue;
1056
+ }
1057
+
1058
+ // Handle "Requirements not yet completed" section
1059
+ if (line.includes('## ⏳ Requirements not yet completed')) {
1060
+ inNotYetCompleted = true;
1061
+ updatedLines.push(line);
1062
+ continue;
1063
+ }
1064
+
1065
+ // Remove the next requirement from "not yet completed"
1066
+ if (inNotYetCompleted && line.trim().startsWith('- ') && !removedNextFromNotYetCompleted) {
1067
+ inNotYetCompleted = false;
1068
+ removedNextFromNotYetCompleted = true;
1069
+ continue; // Skip this line
1070
+ }
1071
+
1072
+ // Handle "Verified by AI" section
1073
+ if (line.includes('## ✅ Verified by AI screenshot')) {
1074
+ inNotYetCompleted = false;
1075
+ if (currentRequirement && !addedCurrentToVerified) {
1076
+ // Check if this requirement is already in the verified section
1077
+ const requirementAlreadyExists = updatedLines.some(existingLine =>
1078
+ existingLine.includes(currentRequirement) && existingLine.trim().startsWith('- ')
1079
+ );
1080
+
1081
+ if (!requirementAlreadyExists) {
1082
+ updatedLines.push(line);
1083
+ updatedLines.push('');
1084
+ // Don't add date prefix - just add the requirement as-is to avoid nesting
1085
+ updatedLines.push(`- ${currentRequirement}`);
1086
+ updatedLines.push('');
1087
+ addedCurrentToVerified = true;
1088
+ continue;
1089
+ } else {
1090
+ logger.log(`⚠️ Requirement "${currentRequirement}" already exists in verified section, skipping duplicate`);
1091
+ addedCurrentToVerified = true;
1092
+ }
1093
+ }
1094
+ updatedLines.push(line);
1095
+ continue;
1096
+ }
1097
+
1098
+ updatedLines.push(line);
1099
+ }
1100
+
1101
+ // Write updated content
1102
+ await fs.writeFile(requirementsFilePath, updatedLines.join('\n'), 'utf8');
1103
+
1104
+ // Calculate current number:
1105
+ // - We just completed 1 requirement (moved to "Verified by AI")
1106
+ // - completedCount already includes previously completed requirements
1107
+ // - So current number = completedCount + 1 (the one we just completed) + 1 (the new current one)
1108
+ // - Which simplifies to: completedCount + 2
1109
+ const currentNumber = completedCount + 2; // +1 for the one we just completed, +1 for the new current one we're starting
1110
+
1111
+ // Total requirements = completedCount + 1 (current) + totalRequirementsNotYetCompleted
1112
+ const totalRequirements = completedCount + 1 + totalRequirementsNotYetCompleted;
1113
+
1114
+ logger.log(`✅ Moved to next requirement: ${nextRequirement}`);
1115
+ logger.log(`📊 Progress: ${currentNumber}/${totalRequirements} (completed: ${completedCount + 1}, remaining: ${totalRequirementsNotYetCompleted - 1})`);
1116
+ logger.log(`📝 Moved to requirement: ${nextRequirement}`);
1117
+
1118
+ return {
1119
+ success: true,
1120
+ nextRequirement: nextRequirement,
1121
+ currentNumber: currentNumber,
1122
+ totalRemaining: totalRequirementsNotYetCompleted - 1
1123
+ };
1124
+ } catch (error) {
1125
+ logger.error('❌ Error moving to next requirement:', error);
1126
+ return { success: false, error: error.message };
1127
+ } finally {
1128
+ // Always reset the flag, even if there was an error
1129
+ isMovingToNextRequirement = false;
1130
+ }
1131
+ }
1132
+
1133
+ // Function to get current requirement progress numbers
1134
+ async function getCurrentRequirementProgress(repoPath) {
1135
+ try {
1136
+ const stats = await getProjectRequirementStats(repoPath);
1137
+ if (!stats) {
1138
+ return { currentNumber: 1, totalRequirements: 1 };
1139
+ }
1140
+
1141
+ // currentNumber = verified + toVerify + 1 (the one we're on)
1142
+ const currentNumber = stats.verifiedCount + stats.toVerifyCount + 1;
1143
+ const totalRequirements = stats.total;
1144
+
1145
+ // Ensure currentNumber doesn't exceed total
1146
+ const sanitizedCurrent = Math.min(currentNumber, totalRequirements || 1);
1147
+
1148
+ return { currentNumber: sanitizedCurrent, totalRequirements: totalRequirements || 1 };
1149
+ } catch (error) {
1150
+ logger.error('❌ Error getting current requirement progress:', error);
1151
+ return { currentNumber: 1, totalRequirements: 1 };
1152
+ }
1153
+ }
1154
+
1155
+ // Function to get current requirement name only (without "Working on:" prefix)
1156
+ async function getCurrentRequirementName(repoPath) {
1157
+ try {
1158
+ const rPath = repoPath || process.cwd();
1159
+ const hostname = os.hostname();
1160
+
1161
+ const requirementsFilePath = await getRequirementsPath(rPath, hostname);
1162
+
1163
+ if (!requirementsFilePath || !(await fs.pathExists(requirementsFilePath))) {
1164
+ return null;
1165
+ }
1166
+
1167
+ // Read the requirements file content
1168
+ const content = await fs.readFile(requirementsFilePath, 'utf8');
1169
+ const lines = content.split('\n');
1170
+
1171
+ // Look for the current in progress requirement
1172
+ for (let i = 0; i < lines.length; i++) {
1173
+ if (lines[i].includes('## 🔨 Current In Progress Requirement')) {
1174
+ // Look for the next requirement line
1175
+ for (let j = i + 1; j < lines.length; j++) {
1176
+ if (lines[j].trim().startsWith('- ')) {
1177
+ let requirementText = lines[j].substring(2).trim();
1178
+
1179
+ // Remove markdown formatting (bold **)
1180
+ requirementText = requirementText.replace(/\*\*/g, '');
1181
+
1182
+ // Return the full requirement text without any prefix
1183
+ return requirementText;
1184
+ }
1185
+ }
1186
+ break;
1187
+ }
1188
+ }
1189
+
1190
+ return null;
1191
+ } catch (error) {
1192
+ logger.error('❌ Error getting current requirement name:', error);
1193
+ return null;
1194
+ }
1195
+ }
1196
+
1197
+