vibecodingmachine-core 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.
@@ -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.)
@@ -61,67 +66,87 @@ async function promoteToVerified(reqPath, requirementTitle) {
61
66
  try {
62
67
  const content = await fs.readFile(reqPath, 'utf-8');
63
68
  const lines = content.split('\n');
64
- const updatedLines = [];
65
69
  let inVerifySection = false;
66
- let verifyCount = 0;
67
- let requirementToMove = null;
70
+ let requirementStartIndex = -1;
71
+ let requirementEndIndex = -1;
72
+ const normalizedTitle = requirementTitle.trim();
68
73
 
69
- for (const line of lines) {
70
- if (line.includes('## Verified by AI screenshot')) {
74
+ // Find the requirement in TO VERIFY section (new ### format)
75
+ for (let i = 0; i < lines.length; i++) {
76
+ const line = lines[i];
77
+ const trimmed = line.trim();
78
+
79
+ // Check if we're entering TO VERIFY section (multiple variants)
80
+ if (trimmed.startsWith('##') && !trimmed.startsWith('###') &&
81
+ (trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot'))) {
71
82
  inVerifySection = true;
72
- updatedLines.push(line);
73
83
  continue;
74
84
  }
75
85
 
76
- if (inVerifySection && line.startsWith('## ')) {
86
+ // Check if we're leaving TO VERIFY section
87
+ if (inVerifySection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
77
88
  inVerifySection = false;
78
- updatedLines.push(line);
79
- continue;
80
89
  }
81
90
 
82
- if (inVerifySection && line.startsWith('- ')) {
83
- const reqText = parseRequirementLine(line);
84
- if (reqText === requirementTitle || reqText.includes(requirementTitle)) {
85
- requirementToMove = reqText;
86
- verifyCount++;
87
- continue;
91
+ // Look for requirement in TO VERIFY section (### header format)
92
+ if (inVerifySection && trimmed.startsWith('###')) {
93
+ const title = trimmed.replace(/^###\s*/, '').trim();
94
+ if (title === normalizedTitle || title.includes(normalizedTitle) || normalizedTitle.includes(title)) {
95
+ requirementStartIndex = i;
96
+
97
+ // Find the end of this requirement block
98
+ for (let j = i + 1; j < lines.length; j++) {
99
+ const nextLine = lines[j].trim();
100
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
101
+ requirementEndIndex = j;
102
+ break;
103
+ }
104
+ }
105
+ if (requirementEndIndex === -1) {
106
+ requirementEndIndex = lines.length;
107
+ }
108
+ break;
88
109
  }
89
- verifyCount++;
90
110
  }
111
+ }
91
112
 
92
- updatedLines.push(line);
113
+ if (requirementStartIndex === -1) {
114
+ return false;
93
115
  }
94
116
 
95
- if (requirementToMove) {
96
- // CHANGELOG.md should be at repository root, not in .vibecodingmachine directory
97
- const allnightDir = path.dirname(reqPath); // .vibecodingmachine directory
98
- const repoRoot = path.dirname(allnightDir); // repository root (one level up)
99
- const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
100
- const timestamp = new Date().toISOString().split('T')[0];
101
- const changelogEntry = `- ${requirementToMove} (${timestamp})`;
117
+ // Extract requirement block
118
+ const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
119
+ const extractedTitle = lines[requirementStartIndex].replace(/^###\s*/, '').trim();
102
120
 
103
- let changelogContent = '';
104
- if (await fs.pathExists(changelogPath)) {
105
- changelogContent = await fs.readFile(changelogPath, 'utf-8');
106
- } else {
107
- changelogContent = '# Changelog\n\n## Verified Requirements\n\n';
108
- }
121
+ // Remove requirement from TO VERIFY section
122
+ lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
109
123
 
110
- if (changelogContent.includes('## Verified Requirements')) {
111
- changelogContent = changelogContent.replace(
112
- '## Verified Requirements\n',
113
- `## Verified Requirements\n${changelogEntry}\n`
114
- );
115
- } else {
116
- changelogContent += `\n## Verified Requirements\n${changelogEntry}\n`;
117
- }
124
+ // Add to CHANGELOG.md
125
+ const allnightDir = path.dirname(reqPath);
126
+ const repoRoot = path.dirname(allnightDir);
127
+ const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
128
+ const timestamp = new Date().toISOString().split('T')[0];
129
+ const changelogEntry = `- ${extractedTitle} (${timestamp})`;
118
130
 
119
- await fs.writeFile(changelogPath, changelogContent);
120
- await fs.writeFile(reqPath, updatedLines.join('\n'));
121
- return true;
131
+ let changelogContent = '';
132
+ if (await fs.pathExists(changelogPath)) {
133
+ changelogContent = await fs.readFile(changelogPath, 'utf-8');
134
+ } else {
135
+ changelogContent = '# Changelog\n\n## Verified Requirements\n\n';
122
136
  }
123
137
 
124
- return false;
138
+ if (changelogContent.includes('## Verified Requirements')) {
139
+ changelogContent = changelogContent.replace(
140
+ '## Verified Requirements\n',
141
+ `## Verified Requirements\n${changelogEntry}\n`
142
+ );
143
+ } else {
144
+ changelogContent += `\n## Verified Requirements\n${changelogEntry}\n`;
145
+ }
146
+
147
+ await fs.writeFile(changelogPath, changelogContent);
148
+ await fs.writeFile(reqPath, lines.join('\n'));
149
+ return true;
125
150
  } catch (error) {
126
151
  throw new Error(`Failed to promote requirement to verified: ${error.message}`);
127
152
  }
@@ -155,12 +180,12 @@ async function demoteFromVerifiedToTodo(reqPath, requirementTitle) {
155
180
  // Extract title part (before timestamp in parentheses)
156
181
  const titleMatch = lineText.match(/^(.+?)\s*\([\d-]+\)$/);
157
182
  const lineTitle = titleMatch ? titleMatch[1] : lineText;
158
-
183
+
159
184
  // Check if this line matches the requirement title
160
185
  // Handle both cases: requirementTitle might include timestamp or not
161
186
  const reqTitleMatch = requirementTitle.match(/^(.+?)\s*\([\d-]+\)$/);
162
187
  const reqTitleOnly = reqTitleMatch ? reqTitleMatch[1] : requirementTitle;
163
-
188
+
164
189
  if (lineTitle === reqTitleOnly || lineTitle.includes(reqTitleOnly) || reqTitleOnly.includes(lineTitle)) {
165
190
  requirementToMove = lineTitle;
166
191
  continue;
@@ -182,7 +207,7 @@ async function demoteFromVerifiedToTodo(reqPath, requirementTitle) {
182
207
 
183
208
  for (let i = 0; i < lines.length; i++) {
184
209
  const line = lines[i];
185
-
210
+
186
211
  if (line.includes('## ⏳ Requirements not yet completed')) {
187
212
  foundTodoSection = true;
188
213
  updatedLines.push(line);
@@ -217,24 +242,24 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
217
242
  try {
218
243
  const content = await fs.readFile(reqPath, 'utf-8');
219
244
  const lines = content.split('\n');
220
-
245
+
221
246
  // Find the requirement block (### header format)
222
247
  let requirementStartIndex = -1;
223
248
  let requirementEndIndex = -1;
224
249
  let inTodoSection = false;
225
-
250
+
226
251
  for (let i = 0; i < lines.length; i++) {
227
252
  const line = lines[i].trim();
228
-
253
+
229
254
  if (line.includes('## ⏳ Requirements not yet completed')) {
230
255
  inTodoSection = true;
231
256
  continue;
232
257
  }
233
-
258
+
234
259
  if (inTodoSection && line.startsWith('##') && !line.startsWith('###')) {
235
260
  break;
236
261
  }
237
-
262
+
238
263
  if (inTodoSection && line.startsWith('###')) {
239
264
  const title = line.replace(/^###\s*/, '').trim();
240
265
  if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
@@ -254,20 +279,20 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
254
279
  }
255
280
  }
256
281
  }
257
-
282
+
258
283
  if (requirementStartIndex === -1) {
259
284
  return false;
260
285
  }
261
-
286
+
262
287
  // Extract the requirement block
263
288
  const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
264
-
289
+
265
290
  // Remove the requirement from its current location
266
291
  const updatedLines = [
267
292
  ...lines.slice(0, requirementStartIndex),
268
293
  ...lines.slice(requirementEndIndex)
269
294
  ];
270
-
295
+
271
296
  // Find or create TO VERIFY section
272
297
  const verifySectionVariants = [
273
298
  '## 🔍 TO VERIFY BY HUMAN',
@@ -276,7 +301,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
276
301
  '## ✅ TO VERIFY',
277
302
  '## ✅ Verified by AI screenshot'
278
303
  ];
279
-
304
+
280
305
  let verifyIndex = -1;
281
306
  for (let i = 0; i < updatedLines.length; i++) {
282
307
  if (verifySectionVariants.some(variant => updatedLines[i].includes(variant))) {
@@ -284,7 +309,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
284
309
  break;
285
310
  }
286
311
  }
287
-
312
+
288
313
  if (verifyIndex === -1) {
289
314
  // Create TO VERIFY section before CHANGELOG or at end
290
315
  const changelogIndex = updatedLines.findIndex(line => line.includes('## CHANGELOG'));
@@ -292,7 +317,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
292
317
  updatedLines.splice(insertIndex, 0, '', '## 🔍 TO VERIFY BY HUMAN', '');
293
318
  verifyIndex = insertIndex + 1;
294
319
  }
295
-
320
+
296
321
  // Insert requirement block after section header
297
322
  let insertIndex = verifyIndex + 1;
298
323
  while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
@@ -303,7 +328,7 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
303
328
  if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
304
329
  updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
305
330
  }
306
-
331
+
307
332
  await fs.writeFile(reqPath, updatedLines.join('\n'));
308
333
  return true;
309
334
  } catch (error) {
@@ -315,43 +340,74 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
315
340
  * Move requirement from TO VERIFY back to TODO section
316
341
  * @param {string} reqPath - Path to REQUIREMENTS file
317
342
  * @param {string} requirementTitle - Title of requirement to move
343
+ * @param {string} explanation - Optional explanation of what went wrong
318
344
  * @returns {Promise<boolean>} Success status
319
345
  */
320
- async function demoteVerifyToTodo(reqPath, requirementTitle) {
346
+ async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
321
347
  try {
322
348
  const content = await fs.readFile(reqPath, 'utf-8');
323
349
  const lines = content.split('\n');
324
-
325
- // Find the requirement block in TO VERIFY section (### header format)
326
- let requirementStartIndex = -1;
327
- let requirementEndIndex = -1;
350
+
351
+ // Find ALL matching requirements in TO VERIFY section and remove them
352
+ // We'll collect all requirement blocks to remove, then process them
353
+ const requirementsToRemove = [];
328
354
  let inVerifySection = false;
329
-
355
+
330
356
  const verifySectionVariants = [
331
357
  '## 🔍 TO VERIFY BY HUMAN',
332
358
  '## 🔍 TO VERIFY',
333
359
  '## TO VERIFY',
334
360
  '## ✅ TO VERIFY',
361
+ '## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
335
362
  '## ✅ Verified by AI screenshot'
336
363
  ];
337
-
364
+
365
+ // First pass: find all matching requirements in TO VERIFY section
338
366
  for (let i = 0; i < lines.length; i++) {
339
- const line = lines[i].trim();
340
-
341
- if (verifySectionVariants.some(variant => line.includes(variant))) {
342
- inVerifySection = true;
343
- continue;
344
- }
345
-
346
- if (inVerifySection && line.startsWith('##') && !line.startsWith('###')) {
347
- break;
367
+ const line = lines[i];
368
+ const trimmed = line.trim();
369
+
370
+ // Check if this is a TO VERIFY section header
371
+ if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
372
+ const isToVerifyHeader = verifySectionVariants.some(variant => {
373
+ return trimmed === variant || trimmed.startsWith(variant) ||
374
+ (trimmed.includes('Verified by AI screenshot') && trimmed.includes('Needs Human to Verify'));
375
+ });
376
+
377
+ if (isToVerifyHeader) {
378
+ // Make sure it's not a VERIFIED section (without TO VERIFY)
379
+ if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) && !trimmed.includes('📝 VERIFIED')) {
380
+ inVerifySection = true;
381
+ continue;
382
+ }
383
+ } else if (inVerifySection) {
384
+ // Check if we're leaving TO VERIFY section (hit a different section)
385
+ if (trimmed.includes('⏳ Requirements not yet completed') ||
386
+ trimmed.includes('## 📝 VERIFIED') ||
387
+ trimmed.includes('## ♻️ RECYCLED') ||
388
+ trimmed.includes('## 📦 RECYCLED') ||
389
+ trimmed.includes('## ❓ Requirements needing')) {
390
+ // We've left the TO VERIFY section
391
+ inVerifySection = false;
392
+ }
393
+ }
348
394
  }
349
-
350
- if (inVerifySection && line.startsWith('###')) {
351
- const title = line.replace(/^###\s*/, '').trim();
352
- if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
353
- requirementStartIndex = i;
395
+
396
+ // Look for requirement in TO VERIFY section
397
+ if (inVerifySection && trimmed.startsWith('###')) {
398
+ const title = trimmed.replace(/^###\s*/, '').trim();
399
+ // Normalize titles for matching (handle TRY AGAIN prefixes)
400
+ const normalizedTitle = title.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
401
+ const normalizedRequirementTitle = requirementTitle.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
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))) {
354
409
  // Find the end of this requirement (next ### or ## header)
410
+ let requirementEndIndex = lines.length;
355
411
  for (let j = i + 1; j < lines.length; j++) {
356
412
  const nextLine = lines[j].trim();
357
413
  if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
@@ -359,32 +415,50 @@ async function demoteVerifyToTodo(reqPath, requirementTitle) {
359
415
  break;
360
416
  }
361
417
  }
362
- if (requirementEndIndex === -1) {
363
- requirementEndIndex = lines.length;
364
- }
365
- break;
418
+
419
+ // Store this requirement to remove (we'll use the first one for moving to TODO)
420
+ requirementsToRemove.push({
421
+ start: i,
422
+ end: requirementEndIndex,
423
+ block: lines.slice(i, requirementEndIndex)
424
+ });
366
425
  }
367
426
  }
368
427
  }
369
-
370
- if (requirementStartIndex === -1) {
428
+
429
+ if (requirementsToRemove.length === 0) {
371
430
  return false;
372
431
  }
373
-
374
- // Extract the requirement block
375
- const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
376
-
432
+
433
+ // Use the first matching requirement for moving to TODO (with TRY AGAIN prefix)
434
+ const firstRequirement = requirementsToRemove[0];
435
+ const requirementBlock = [...firstRequirement.block];
436
+
377
437
  // Update title with TRY AGAIN prefix
378
438
  const originalTitle = requirementBlock[0].replace(/^###\s*/, '').trim();
379
439
  const titleWithPrefix = addTryAgainPrefix(originalTitle);
380
440
  requirementBlock[0] = `### ${titleWithPrefix}`;
381
-
382
- // Remove the requirement from its current location
383
- const updatedLines = [
384
- ...lines.slice(0, requirementStartIndex),
385
- ...lines.slice(requirementEndIndex)
386
- ];
387
-
441
+
442
+ // Add explanation to the requirement description if provided
443
+ if (explanation && explanation.trim()) {
444
+ // Find where to insert the explanation (after the title, before any existing content)
445
+ // Insert after first line (title) with a blank line and "What went wrong:" section
446
+ const explanationLines = [
447
+ '',
448
+ '**What went wrong (from previous attempt):**',
449
+ explanation.trim(),
450
+ ''
451
+ ];
452
+ requirementBlock.splice(1, 0, ...explanationLines);
453
+ }
454
+
455
+ // Remove ALL matching requirements from TO VERIFY section (work backwards to preserve indices)
456
+ const updatedLines = [...lines];
457
+ for (let i = requirementsToRemove.length - 1; i >= 0; i--) {
458
+ const req = requirementsToRemove[i];
459
+ updatedLines.splice(req.start, req.end - req.start);
460
+ }
461
+
388
462
  // Find or create TODO section
389
463
  let todoIndex = -1;
390
464
  for (let i = 0; i < updatedLines.length; i++) {
@@ -393,7 +467,7 @@ async function demoteVerifyToTodo(reqPath, requirementTitle) {
393
467
  break;
394
468
  }
395
469
  }
396
-
470
+
397
471
  if (todoIndex === -1) {
398
472
  // Create TODO section at the top (after initial headers)
399
473
  const firstSectionIndex = updatedLines.findIndex(line => line.startsWith('##') && !line.startsWith('###'));
@@ -401,7 +475,7 @@ async function demoteVerifyToTodo(reqPath, requirementTitle) {
401
475
  updatedLines.splice(insertIndex, 0, '## ⏳ Requirements not yet completed', '');
402
476
  todoIndex = insertIndex;
403
477
  }
404
-
478
+
405
479
  // Insert requirement block after section header
406
480
  let insertIndex = todoIndex + 1;
407
481
  while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
@@ -412,7 +486,7 @@ async function demoteVerifyToTodo(reqPath, requirementTitle) {
412
486
  if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
413
487
  updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
414
488
  }
415
-
489
+
416
490
  await fs.writeFile(reqPath, updatedLines.join('\n'));
417
491
  return true;
418
492
  } catch (error) {
@@ -420,6 +494,125 @@ async function demoteVerifyToTodo(reqPath, requirementTitle) {
420
494
  }
421
495
  }
422
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
+
423
616
  module.exports = {
424
617
  getOrdinalSuffix,
425
618
  addTryAgainPrefix,
@@ -427,6 +620,578 @@ module.exports = {
427
620
  promoteToVerified,
428
621
  demoteFromVerifiedToTodo,
429
622
  promoteTodoToVerify,
430
- 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
431
635
  };
432
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
+