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.
- package/package.json +4 -1
- package/scripts/setup-database.js +108 -0
- package/src/auth/shared-auth-storage.js +43 -6
- 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 +199 -145
- package/src/ide-integration/applescript-manager.js +234 -96
- package/src/ide-integration/claude-code-cli-manager.cjs +120 -1
- package/src/ide-integration/provider-manager.cjs +67 -1
- package/src/index.cjs +2 -0
- package/src/index.js +6 -0
- package/src/llm/direct-llm-manager.cjs +110 -73
- package/src/quota-management/index.js +108 -0
- package/src/sync/aws-setup.js +445 -0
- package/src/sync/sync-engine.js +410 -0
- 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 +865 -100
- package/src/utils/requirements-parser.js +324 -0
- 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.)
|
|
@@ -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
|
|
67
|
-
let
|
|
70
|
+
let requirementStartIndex = -1;
|
|
71
|
+
let requirementEndIndex = -1;
|
|
72
|
+
const normalizedTitle = requirementTitle.trim();
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
113
|
+
if (requirementStartIndex === -1) {
|
|
114
|
+
return false;
|
|
93
115
|
}
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
326
|
-
|
|
327
|
-
|
|
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]
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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 (
|
|
428
|
+
|
|
429
|
+
if (requirementsToRemove.length === 0) {
|
|
371
430
|
return false;
|
|
372
431
|
}
|
|
373
|
-
|
|
374
|
-
//
|
|
375
|
-
const
|
|
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
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
+
|