vibecodingmachine-core 2025.12.6-1702 → 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.
- package/package.json +1 -1
- package/scripts/setup-database.js +108 -0
- package/src/compliance/compliance-manager.js +249 -0
- package/src/compliance/compliance-prompt.js +183 -0
- package/src/database/migrations.js +289 -0
- package/src/database/user-database-client.js +266 -0
- package/src/database/user-schema.js +118 -0
- package/src/ide-integration/applescript-manager.cjs +73 -127
- package/src/ide-integration/applescript-manager.js +62 -12
- package/src/ide-integration/claude-code-cli-manager.cjs +120 -1
- package/src/ide-integration/provider-manager.cjs +67 -1
- package/src/index.js +4 -0
- package/src/llm/direct-llm-manager.cjs +110 -73
- package/src/quota-management/index.js +108 -0
- package/src/sync/sync-engine.js +32 -10
- package/src/utils/download-with-progress.js +92 -0
- package/src/utils/electron-update-checker.js +7 -0
- package/src/utils/env-helpers.js +54 -0
- package/src/utils/requirement-helpers.js +745 -49
- package/src/utils/requirements-parser.js +21 -7
- package/src/utils/update-checker.js +7 -0
- package/test-quota-system.js +67 -0
- package/test-requirement-stats.js +66 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
+
|