vibecodingmachine-core 1.0.1 → 2025.11.2-7.1239

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.
Files changed (49) hide show
  1. package/.babelrc +13 -13
  2. package/README.md +28 -28
  3. package/__tests__/applescript-manager-claude-fix.test.js +286 -286
  4. package/__tests__/requirement-2-auto-start-looping.test.js +69 -69
  5. package/__tests__/requirement-3-auto-start-looping.test.js +69 -69
  6. package/__tests__/requirement-4-auto-start-looping.test.js +69 -69
  7. package/__tests__/requirement-6-auto-start-looping.test.js +73 -73
  8. package/__tests__/requirement-7-status-tracking.test.js +332 -332
  9. package/jest.config.js +18 -18
  10. package/jest.setup.js +12 -12
  11. package/package.json +48 -48
  12. package/src/auth/access-denied.html +119 -119
  13. package/src/auth/shared-auth-storage.js +230 -230
  14. package/src/autonomous-mode/feature-implementer.cjs +70 -70
  15. package/src/autonomous-mode/feature-implementer.js +425 -425
  16. package/src/chat-management/chat-manager.cjs +71 -71
  17. package/src/chat-management/chat-manager.js +342 -342
  18. package/src/ide-integration/__tests__/applescript-manager-thread-closure.test.js +227 -227
  19. package/src/ide-integration/aider-cli-manager.cjs +850 -850
  20. package/src/ide-integration/applescript-manager.cjs +1088 -1088
  21. package/src/ide-integration/applescript-manager.js +2802 -2802
  22. package/src/ide-integration/applescript-utils.js +306 -306
  23. package/src/ide-integration/cdp-manager.cjs +221 -221
  24. package/src/ide-integration/cdp-manager.js +321 -321
  25. package/src/ide-integration/claude-code-cli-manager.cjs +301 -301
  26. package/src/ide-integration/cline-cli-manager.cjs +2252 -2252
  27. package/src/ide-integration/continue-cli-manager.js +431 -431
  28. package/src/ide-integration/provider-manager.cjs +354 -354
  29. package/src/ide-integration/quota-detector.cjs +34 -34
  30. package/src/ide-integration/quota-detector.js +349 -349
  31. package/src/ide-integration/windows-automation-manager.js +262 -262
  32. package/src/index.cjs +47 -43
  33. package/src/index.js +17 -17
  34. package/src/llm/direct-llm-manager.cjs +609 -609
  35. package/src/ui/ButtonComponents.js +247 -247
  36. package/src/ui/ChatInterface.js +499 -499
  37. package/src/ui/StateManager.js +259 -259
  38. package/src/utils/audit-logger.cjs +116 -116
  39. package/src/utils/config-helpers.cjs +94 -94
  40. package/src/utils/config-helpers.js +94 -94
  41. package/src/utils/electron-update-checker.js +113 -85
  42. package/src/utils/gcloud-auth.cjs +394 -394
  43. package/src/utils/logger.cjs +193 -193
  44. package/src/utils/logger.js +191 -191
  45. package/src/utils/repo-helpers.cjs +120 -120
  46. package/src/utils/repo-helpers.js +120 -120
  47. package/src/utils/requirement-helpers.js +432 -432
  48. package/src/utils/update-checker.js +227 -167
  49. package/src/utils/version-checker.js +169 -0
@@ -1,432 +1,432 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const { getRequirementsPath } = require('./repo-helpers.cjs');
4
-
5
- /**
6
- * Get ordinal suffix for numbers (1st, 2nd, 3rd, 4th, etc.)
7
- * @param {number} num - The number
8
- * @returns {string} The ordinal suffix
9
- */
10
- function getOrdinalSuffix(num) {
11
- const j = num % 10;
12
- const k = num % 100;
13
- if (j === 1 && k !== 11) {
14
- return 'st';
15
- }
16
- if (j === 2 && k !== 12) {
17
- return 'nd';
18
- }
19
- if (j === 3 && k !== 13) {
20
- return 'rd';
21
- }
22
- return 'th';
23
- }
24
-
25
- /**
26
- * Add or increment TRY AGAIN prefix to requirement text
27
- * @param {string} requirementText - The requirement text
28
- * @returns {string} Requirement text with TRY AGAIN prefix
29
- */
30
- function addTryAgainPrefix(requirementText) {
31
- const tryAgainMatch = requirementText.match(/^TRY AGAIN \((\d+)(?:st|nd|rd|th) time\): (.+)$/);
32
-
33
- if (tryAgainMatch) {
34
- const currentCount = parseInt(tryAgainMatch[1]);
35
- const baseRequirement = tryAgainMatch[2];
36
- return `TRY AGAIN (${currentCount + 1}${getOrdinalSuffix(currentCount + 1)} time): ${baseRequirement}`;
37
- } else {
38
- return `TRY AGAIN (1st time): ${requirementText}`;
39
- }
40
- }
41
-
42
- /**
43
- * Parse requirement from markdown line
44
- * @param {string} line - Markdown line (e.g., "- Requirement text")
45
- * @returns {string} Requirement text without markdown prefix
46
- */
47
- function parseRequirementLine(line) {
48
- if (line.startsWith('- ')) {
49
- return line.substring(2);
50
- }
51
- return line;
52
- }
53
-
54
- /**
55
- * Move requirement from TO VERIFY section to VERIFIED (CHANGELOG)
56
- * @param {string} reqPath - Path to REQUIREMENTS file
57
- * @param {string} requirementTitle - Title of requirement to move
58
- * @returns {Promise<boolean>} Success status
59
- */
60
- async function promoteToVerified(reqPath, requirementTitle) {
61
- try {
62
- const content = await fs.readFile(reqPath, 'utf-8');
63
- const lines = content.split('\n');
64
- const updatedLines = [];
65
- let inVerifySection = false;
66
- let verifyCount = 0;
67
- let requirementToMove = null;
68
-
69
- for (const line of lines) {
70
- if (line.includes('## ✅ Verified by AI screenshot')) {
71
- inVerifySection = true;
72
- updatedLines.push(line);
73
- continue;
74
- }
75
-
76
- if (inVerifySection && line.startsWith('## ')) {
77
- inVerifySection = false;
78
- updatedLines.push(line);
79
- continue;
80
- }
81
-
82
- if (inVerifySection && line.startsWith('- ')) {
83
- const reqText = parseRequirementLine(line);
84
- if (reqText === requirementTitle || reqText.includes(requirementTitle)) {
85
- requirementToMove = reqText;
86
- verifyCount++;
87
- continue;
88
- }
89
- verifyCount++;
90
- }
91
-
92
- updatedLines.push(line);
93
- }
94
-
95
- if (requirementToMove) {
96
- // CHANGELOG.md should be at repository root, not in .vibecodingmachine directory
97
- const allnightDir = path.dirname(reqPath); // .vibecodingmachine directory
98
- const repoRoot = path.dirname(allnightDir); // repository root (one level up)
99
- const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
100
- const timestamp = new Date().toISOString().split('T')[0];
101
- const changelogEntry = `- ${requirementToMove} (${timestamp})`;
102
-
103
- let changelogContent = '';
104
- if (await fs.pathExists(changelogPath)) {
105
- changelogContent = await fs.readFile(changelogPath, 'utf-8');
106
- } else {
107
- changelogContent = '# Changelog\n\n## Verified Requirements\n\n';
108
- }
109
-
110
- if (changelogContent.includes('## Verified Requirements')) {
111
- changelogContent = changelogContent.replace(
112
- '## Verified Requirements\n',
113
- `## Verified Requirements\n${changelogEntry}\n`
114
- );
115
- } else {
116
- changelogContent += `\n## Verified Requirements\n${changelogEntry}\n`;
117
- }
118
-
119
- await fs.writeFile(changelogPath, changelogContent);
120
- await fs.writeFile(reqPath, updatedLines.join('\n'));
121
- return true;
122
- }
123
-
124
- return false;
125
- } catch (error) {
126
- throw new Error(`Failed to promote requirement to verified: ${error.message}`);
127
- }
128
- }
129
-
130
- /**
131
- * Move requirement from VERIFIED (CHANGELOG) back to TODO with TRY AGAIN prefix
132
- * @param {string} reqPath - Path to REQUIREMENTS file
133
- * @param {string} requirementTitle - Title of requirement to move
134
- * @returns {Promise<boolean>} Success status
135
- */
136
- async function demoteFromVerifiedToTodo(reqPath, requirementTitle) {
137
- try {
138
- // CHANGELOG.md should be at repository root, not in .vibecodingmachine directory
139
- const allnightDir = path.dirname(reqPath); // .vibecodingmachine directory
140
- const repoRoot = path.dirname(allnightDir); // repository root (one level up)
141
- const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
142
-
143
- if (!(await fs.pathExists(changelogPath))) {
144
- return false;
145
- }
146
-
147
- let changelogContent = await fs.readFile(changelogPath, 'utf-8');
148
- const changelogLines = changelogContent.split('\n');
149
- const updatedChangelogLines = [];
150
- let requirementToMove = null;
151
-
152
- for (const line of changelogLines) {
153
- if (line.startsWith('- ')) {
154
- const lineText = parseRequirementLine(line);
155
- // Extract title part (before timestamp in parentheses)
156
- const titleMatch = lineText.match(/^(.+?)\s*\([\d-]+\)$/);
157
- const lineTitle = titleMatch ? titleMatch[1] : lineText;
158
-
159
- // Check if this line matches the requirement title
160
- // Handle both cases: requirementTitle might include timestamp or not
161
- const reqTitleMatch = requirementTitle.match(/^(.+?)\s*\([\d-]+\)$/);
162
- const reqTitleOnly = reqTitleMatch ? reqTitleMatch[1] : requirementTitle;
163
-
164
- if (lineTitle === reqTitleOnly || lineTitle.includes(reqTitleOnly) || reqTitleOnly.includes(lineTitle)) {
165
- requirementToMove = lineTitle;
166
- continue;
167
- }
168
- }
169
- updatedChangelogLines.push(line);
170
- }
171
-
172
- if (!requirementToMove) {
173
- return false;
174
- }
175
-
176
- await fs.writeFile(changelogPath, updatedChangelogLines.join('\n'));
177
-
178
- const content = await fs.readFile(reqPath, 'utf-8');
179
- const lines = content.split('\n');
180
- const updatedLines = [];
181
- let foundTodoSection = false;
182
-
183
- for (let i = 0; i < lines.length; i++) {
184
- const line = lines[i];
185
-
186
- if (line.includes('## ⏳ Requirements not yet completed')) {
187
- foundTodoSection = true;
188
- updatedLines.push(line);
189
- const requirementText = addTryAgainPrefix(requirementToMove);
190
- updatedLines.push(`- ${requirementText}`);
191
- continue;
192
- }
193
-
194
- updatedLines.push(line);
195
- }
196
-
197
- if (!foundTodoSection) {
198
- updatedLines.push('## ⏳ Requirements not yet completed');
199
- const requirementText = addTryAgainPrefix(requirementToMove);
200
- updatedLines.push(`- ${requirementText}`);
201
- }
202
-
203
- await fs.writeFile(reqPath, updatedLines.join('\n'));
204
- return true;
205
- } catch (error) {
206
- throw new Error(`Failed to demote requirement from verified: ${error.message}`);
207
- }
208
- }
209
-
210
- /**
211
- * Move requirement from TODO to TO VERIFY section
212
- * @param {string} reqPath - Path to REQUIREMENTS file
213
- * @param {string} requirementTitle - Title of requirement to move
214
- * @returns {Promise<boolean>} Success status
215
- */
216
- async function promoteTodoToVerify(reqPath, requirementTitle) {
217
- try {
218
- const content = await fs.readFile(reqPath, 'utf-8');
219
- const lines = content.split('\n');
220
-
221
- // Find the requirement block (### header format)
222
- let requirementStartIndex = -1;
223
- let requirementEndIndex = -1;
224
- let inTodoSection = false;
225
-
226
- for (let i = 0; i < lines.length; i++) {
227
- const line = lines[i].trim();
228
-
229
- if (line.includes('## ⏳ Requirements not yet completed')) {
230
- inTodoSection = true;
231
- continue;
232
- }
233
-
234
- if (inTodoSection && line.startsWith('##') && !line.startsWith('###')) {
235
- break;
236
- }
237
-
238
- if (inTodoSection && line.startsWith('###')) {
239
- const title = line.replace(/^###\s*/, '').trim();
240
- if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
241
- requirementStartIndex = i;
242
- // Find the end of this requirement (next ### or ## header)
243
- for (let j = i + 1; j < lines.length; j++) {
244
- const nextLine = lines[j].trim();
245
- if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
246
- requirementEndIndex = j;
247
- break;
248
- }
249
- }
250
- if (requirementEndIndex === -1) {
251
- requirementEndIndex = lines.length;
252
- }
253
- break;
254
- }
255
- }
256
- }
257
-
258
- if (requirementStartIndex === -1) {
259
- return false;
260
- }
261
-
262
- // Extract the requirement block
263
- const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
264
-
265
- // Remove the requirement from its current location
266
- const updatedLines = [
267
- ...lines.slice(0, requirementStartIndex),
268
- ...lines.slice(requirementEndIndex)
269
- ];
270
-
271
- // Find or create TO VERIFY section
272
- const verifySectionVariants = [
273
- '## 🔍 TO VERIFY BY HUMAN',
274
- '## 🔍 TO VERIFY',
275
- '## TO VERIFY',
276
- '## ✅ TO VERIFY',
277
- '## ✅ Verified by AI screenshot'
278
- ];
279
-
280
- let verifyIndex = -1;
281
- for (let i = 0; i < updatedLines.length; i++) {
282
- if (verifySectionVariants.some(variant => updatedLines[i].includes(variant))) {
283
- verifyIndex = i;
284
- break;
285
- }
286
- }
287
-
288
- if (verifyIndex === -1) {
289
- // Create TO VERIFY section before CHANGELOG or at end
290
- const changelogIndex = updatedLines.findIndex(line => line.includes('## CHANGELOG'));
291
- const insertIndex = changelogIndex > 0 ? changelogIndex : updatedLines.length;
292
- updatedLines.splice(insertIndex, 0, '', '## 🔍 TO VERIFY BY HUMAN', '');
293
- verifyIndex = insertIndex + 1;
294
- }
295
-
296
- // Insert requirement block after section header
297
- let insertIndex = verifyIndex + 1;
298
- while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
299
- insertIndex++;
300
- }
301
- updatedLines.splice(insertIndex, 0, ...requirementBlock);
302
- // Add blank line after if needed
303
- if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
304
- updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
305
- }
306
-
307
- await fs.writeFile(reqPath, updatedLines.join('\n'));
308
- return true;
309
- } catch (error) {
310
- throw new Error(`Failed to promote requirement from TODO to TO VERIFY: ${error.message}`);
311
- }
312
- }
313
-
314
- /**
315
- * Move requirement from TO VERIFY back to TODO section
316
- * @param {string} reqPath - Path to REQUIREMENTS file
317
- * @param {string} requirementTitle - Title of requirement to move
318
- * @returns {Promise<boolean>} Success status
319
- */
320
- async function demoteVerifyToTodo(reqPath, requirementTitle) {
321
- try {
322
- const content = await fs.readFile(reqPath, 'utf-8');
323
- const lines = content.split('\n');
324
-
325
- // Find the requirement block in TO VERIFY section (### header format)
326
- let requirementStartIndex = -1;
327
- let requirementEndIndex = -1;
328
- let inVerifySection = false;
329
-
330
- const verifySectionVariants = [
331
- '## 🔍 TO VERIFY BY HUMAN',
332
- '## 🔍 TO VERIFY',
333
- '## TO VERIFY',
334
- '## ✅ TO VERIFY',
335
- '## ✅ Verified by AI screenshot'
336
- ];
337
-
338
- for (let i = 0; i < lines.length; i++) {
339
- const line = lines[i].trim();
340
-
341
- if (verifySectionVariants.some(variant => line.includes(variant))) {
342
- inVerifySection = true;
343
- continue;
344
- }
345
-
346
- if (inVerifySection && line.startsWith('##') && !line.startsWith('###')) {
347
- break;
348
- }
349
-
350
- if (inVerifySection && line.startsWith('###')) {
351
- const title = line.replace(/^###\s*/, '').trim();
352
- if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
353
- requirementStartIndex = i;
354
- // Find the end of this requirement (next ### or ## header)
355
- for (let j = i + 1; j < lines.length; j++) {
356
- const nextLine = lines[j].trim();
357
- if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
358
- requirementEndIndex = j;
359
- break;
360
- }
361
- }
362
- if (requirementEndIndex === -1) {
363
- requirementEndIndex = lines.length;
364
- }
365
- break;
366
- }
367
- }
368
- }
369
-
370
- if (requirementStartIndex === -1) {
371
- return false;
372
- }
373
-
374
- // Extract the requirement block
375
- const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
376
-
377
- // Update title with TRY AGAIN prefix
378
- const originalTitle = requirementBlock[0].replace(/^###\s*/, '').trim();
379
- const titleWithPrefix = addTryAgainPrefix(originalTitle);
380
- requirementBlock[0] = `### ${titleWithPrefix}`;
381
-
382
- // Remove the requirement from its current location
383
- const updatedLines = [
384
- ...lines.slice(0, requirementStartIndex),
385
- ...lines.slice(requirementEndIndex)
386
- ];
387
-
388
- // Find or create TODO section
389
- let todoIndex = -1;
390
- for (let i = 0; i < updatedLines.length; i++) {
391
- if (updatedLines[i].includes('## ⏳ Requirements not yet completed')) {
392
- todoIndex = i;
393
- break;
394
- }
395
- }
396
-
397
- if (todoIndex === -1) {
398
- // Create TODO section at the top (after initial headers)
399
- const firstSectionIndex = updatedLines.findIndex(line => line.startsWith('##') && !line.startsWith('###'));
400
- const insertIndex = firstSectionIndex > 0 ? firstSectionIndex : updatedLines.length;
401
- updatedLines.splice(insertIndex, 0, '## ⏳ Requirements not yet completed', '');
402
- todoIndex = insertIndex;
403
- }
404
-
405
- // Insert requirement block after section header
406
- let insertIndex = todoIndex + 1;
407
- while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
408
- insertIndex++;
409
- }
410
- updatedLines.splice(insertIndex, 0, ...requirementBlock);
411
- // Add blank line after if needed
412
- if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
413
- updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
414
- }
415
-
416
- await fs.writeFile(reqPath, updatedLines.join('\n'));
417
- return true;
418
- } catch (error) {
419
- throw new Error(`Failed to demote requirement from TO VERIFY to TODO: ${error.message}`);
420
- }
421
- }
422
-
423
- module.exports = {
424
- getOrdinalSuffix,
425
- addTryAgainPrefix,
426
- parseRequirementLine,
427
- promoteToVerified,
428
- demoteFromVerifiedToTodo,
429
- promoteTodoToVerify,
430
- demoteVerifyToTodo
431
- };
432
-
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { getRequirementsPath } = require('./repo-helpers.cjs');
4
+
5
+ /**
6
+ * Get ordinal suffix for numbers (1st, 2nd, 3rd, 4th, etc.)
7
+ * @param {number} num - The number
8
+ * @returns {string} The ordinal suffix
9
+ */
10
+ function getOrdinalSuffix(num) {
11
+ const j = num % 10;
12
+ const k = num % 100;
13
+ if (j === 1 && k !== 11) {
14
+ return 'st';
15
+ }
16
+ if (j === 2 && k !== 12) {
17
+ return 'nd';
18
+ }
19
+ if (j === 3 && k !== 13) {
20
+ return 'rd';
21
+ }
22
+ return 'th';
23
+ }
24
+
25
+ /**
26
+ * Add or increment TRY AGAIN prefix to requirement text
27
+ * @param {string} requirementText - The requirement text
28
+ * @returns {string} Requirement text with TRY AGAIN prefix
29
+ */
30
+ function addTryAgainPrefix(requirementText) {
31
+ const tryAgainMatch = requirementText.match(/^TRY AGAIN \((\d+)(?:st|nd|rd|th) time\): (.+)$/);
32
+
33
+ if (tryAgainMatch) {
34
+ const currentCount = parseInt(tryAgainMatch[1]);
35
+ const baseRequirement = tryAgainMatch[2];
36
+ return `TRY AGAIN (${currentCount + 1}${getOrdinalSuffix(currentCount + 1)} time): ${baseRequirement}`;
37
+ } else {
38
+ return `TRY AGAIN (1st time): ${requirementText}`;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Parse requirement from markdown line
44
+ * @param {string} line - Markdown line (e.g., "- Requirement text")
45
+ * @returns {string} Requirement text without markdown prefix
46
+ */
47
+ function parseRequirementLine(line) {
48
+ if (line.startsWith('- ')) {
49
+ return line.substring(2);
50
+ }
51
+ return line;
52
+ }
53
+
54
+ /**
55
+ * Move requirement from TO VERIFY section to VERIFIED (CHANGELOG)
56
+ * @param {string} reqPath - Path to REQUIREMENTS file
57
+ * @param {string} requirementTitle - Title of requirement to move
58
+ * @returns {Promise<boolean>} Success status
59
+ */
60
+ async function promoteToVerified(reqPath, requirementTitle) {
61
+ try {
62
+ const content = await fs.readFile(reqPath, 'utf-8');
63
+ const lines = content.split('\n');
64
+ const updatedLines = [];
65
+ let inVerifySection = false;
66
+ let verifyCount = 0;
67
+ let requirementToMove = null;
68
+
69
+ for (const line of lines) {
70
+ if (line.includes('## ✅ Verified by AI screenshot')) {
71
+ inVerifySection = true;
72
+ updatedLines.push(line);
73
+ continue;
74
+ }
75
+
76
+ if (inVerifySection && line.startsWith('## ')) {
77
+ inVerifySection = false;
78
+ updatedLines.push(line);
79
+ continue;
80
+ }
81
+
82
+ if (inVerifySection && line.startsWith('- ')) {
83
+ const reqText = parseRequirementLine(line);
84
+ if (reqText === requirementTitle || reqText.includes(requirementTitle)) {
85
+ requirementToMove = reqText;
86
+ verifyCount++;
87
+ continue;
88
+ }
89
+ verifyCount++;
90
+ }
91
+
92
+ updatedLines.push(line);
93
+ }
94
+
95
+ if (requirementToMove) {
96
+ // CHANGELOG.md should be at repository root, not in .vibecodingmachine directory
97
+ const allnightDir = path.dirname(reqPath); // .vibecodingmachine directory
98
+ const repoRoot = path.dirname(allnightDir); // repository root (one level up)
99
+ const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
100
+ const timestamp = new Date().toISOString().split('T')[0];
101
+ const changelogEntry = `- ${requirementToMove} (${timestamp})`;
102
+
103
+ let changelogContent = '';
104
+ if (await fs.pathExists(changelogPath)) {
105
+ changelogContent = await fs.readFile(changelogPath, 'utf-8');
106
+ } else {
107
+ changelogContent = '# Changelog\n\n## Verified Requirements\n\n';
108
+ }
109
+
110
+ if (changelogContent.includes('## Verified Requirements')) {
111
+ changelogContent = changelogContent.replace(
112
+ '## Verified Requirements\n',
113
+ `## Verified Requirements\n${changelogEntry}\n`
114
+ );
115
+ } else {
116
+ changelogContent += `\n## Verified Requirements\n${changelogEntry}\n`;
117
+ }
118
+
119
+ await fs.writeFile(changelogPath, changelogContent);
120
+ await fs.writeFile(reqPath, updatedLines.join('\n'));
121
+ return true;
122
+ }
123
+
124
+ return false;
125
+ } catch (error) {
126
+ throw new Error(`Failed to promote requirement to verified: ${error.message}`);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Move requirement from VERIFIED (CHANGELOG) back to TODO with TRY AGAIN prefix
132
+ * @param {string} reqPath - Path to REQUIREMENTS file
133
+ * @param {string} requirementTitle - Title of requirement to move
134
+ * @returns {Promise<boolean>} Success status
135
+ */
136
+ async function demoteFromVerifiedToTodo(reqPath, requirementTitle) {
137
+ try {
138
+ // CHANGELOG.md should be at repository root, not in .vibecodingmachine directory
139
+ const allnightDir = path.dirname(reqPath); // .vibecodingmachine directory
140
+ const repoRoot = path.dirname(allnightDir); // repository root (one level up)
141
+ const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
142
+
143
+ if (!(await fs.pathExists(changelogPath))) {
144
+ return false;
145
+ }
146
+
147
+ let changelogContent = await fs.readFile(changelogPath, 'utf-8');
148
+ const changelogLines = changelogContent.split('\n');
149
+ const updatedChangelogLines = [];
150
+ let requirementToMove = null;
151
+
152
+ for (const line of changelogLines) {
153
+ if (line.startsWith('- ')) {
154
+ const lineText = parseRequirementLine(line);
155
+ // Extract title part (before timestamp in parentheses)
156
+ const titleMatch = lineText.match(/^(.+?)\s*\([\d-]+\)$/);
157
+ const lineTitle = titleMatch ? titleMatch[1] : lineText;
158
+
159
+ // Check if this line matches the requirement title
160
+ // Handle both cases: requirementTitle might include timestamp or not
161
+ const reqTitleMatch = requirementTitle.match(/^(.+?)\s*\([\d-]+\)$/);
162
+ const reqTitleOnly = reqTitleMatch ? reqTitleMatch[1] : requirementTitle;
163
+
164
+ if (lineTitle === reqTitleOnly || lineTitle.includes(reqTitleOnly) || reqTitleOnly.includes(lineTitle)) {
165
+ requirementToMove = lineTitle;
166
+ continue;
167
+ }
168
+ }
169
+ updatedChangelogLines.push(line);
170
+ }
171
+
172
+ if (!requirementToMove) {
173
+ return false;
174
+ }
175
+
176
+ await fs.writeFile(changelogPath, updatedChangelogLines.join('\n'));
177
+
178
+ const content = await fs.readFile(reqPath, 'utf-8');
179
+ const lines = content.split('\n');
180
+ const updatedLines = [];
181
+ let foundTodoSection = false;
182
+
183
+ for (let i = 0; i < lines.length; i++) {
184
+ const line = lines[i];
185
+
186
+ if (line.includes('## ⏳ Requirements not yet completed')) {
187
+ foundTodoSection = true;
188
+ updatedLines.push(line);
189
+ const requirementText = addTryAgainPrefix(requirementToMove);
190
+ updatedLines.push(`- ${requirementText}`);
191
+ continue;
192
+ }
193
+
194
+ updatedLines.push(line);
195
+ }
196
+
197
+ if (!foundTodoSection) {
198
+ updatedLines.push('## ⏳ Requirements not yet completed');
199
+ const requirementText = addTryAgainPrefix(requirementToMove);
200
+ updatedLines.push(`- ${requirementText}`);
201
+ }
202
+
203
+ await fs.writeFile(reqPath, updatedLines.join('\n'));
204
+ return true;
205
+ } catch (error) {
206
+ throw new Error(`Failed to demote requirement from verified: ${error.message}`);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Move requirement from TODO to TO VERIFY section
212
+ * @param {string} reqPath - Path to REQUIREMENTS file
213
+ * @param {string} requirementTitle - Title of requirement to move
214
+ * @returns {Promise<boolean>} Success status
215
+ */
216
+ async function promoteTodoToVerify(reqPath, requirementTitle) {
217
+ try {
218
+ const content = await fs.readFile(reqPath, 'utf-8');
219
+ const lines = content.split('\n');
220
+
221
+ // Find the requirement block (### header format)
222
+ let requirementStartIndex = -1;
223
+ let requirementEndIndex = -1;
224
+ let inTodoSection = false;
225
+
226
+ for (let i = 0; i < lines.length; i++) {
227
+ const line = lines[i].trim();
228
+
229
+ if (line.includes('## ⏳ Requirements not yet completed')) {
230
+ inTodoSection = true;
231
+ continue;
232
+ }
233
+
234
+ if (inTodoSection && line.startsWith('##') && !line.startsWith('###')) {
235
+ break;
236
+ }
237
+
238
+ if (inTodoSection && line.startsWith('###')) {
239
+ const title = line.replace(/^###\s*/, '').trim();
240
+ if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
241
+ requirementStartIndex = i;
242
+ // Find the end of this requirement (next ### or ## header)
243
+ for (let j = i + 1; j < lines.length; j++) {
244
+ const nextLine = lines[j].trim();
245
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
246
+ requirementEndIndex = j;
247
+ break;
248
+ }
249
+ }
250
+ if (requirementEndIndex === -1) {
251
+ requirementEndIndex = lines.length;
252
+ }
253
+ break;
254
+ }
255
+ }
256
+ }
257
+
258
+ if (requirementStartIndex === -1) {
259
+ return false;
260
+ }
261
+
262
+ // Extract the requirement block
263
+ const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
264
+
265
+ // Remove the requirement from its current location
266
+ const updatedLines = [
267
+ ...lines.slice(0, requirementStartIndex),
268
+ ...lines.slice(requirementEndIndex)
269
+ ];
270
+
271
+ // Find or create TO VERIFY section
272
+ const verifySectionVariants = [
273
+ '## 🔍 TO VERIFY BY HUMAN',
274
+ '## 🔍 TO VERIFY',
275
+ '## TO VERIFY',
276
+ '## ✅ TO VERIFY',
277
+ '## ✅ Verified by AI screenshot'
278
+ ];
279
+
280
+ let verifyIndex = -1;
281
+ for (let i = 0; i < updatedLines.length; i++) {
282
+ if (verifySectionVariants.some(variant => updatedLines[i].includes(variant))) {
283
+ verifyIndex = i;
284
+ break;
285
+ }
286
+ }
287
+
288
+ if (verifyIndex === -1) {
289
+ // Create TO VERIFY section before CHANGELOG or at end
290
+ const changelogIndex = updatedLines.findIndex(line => line.includes('## CHANGELOG'));
291
+ const insertIndex = changelogIndex > 0 ? changelogIndex : updatedLines.length;
292
+ updatedLines.splice(insertIndex, 0, '', '## 🔍 TO VERIFY BY HUMAN', '');
293
+ verifyIndex = insertIndex + 1;
294
+ }
295
+
296
+ // Insert requirement block after section header
297
+ let insertIndex = verifyIndex + 1;
298
+ while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
299
+ insertIndex++;
300
+ }
301
+ updatedLines.splice(insertIndex, 0, ...requirementBlock);
302
+ // Add blank line after if needed
303
+ if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
304
+ updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
305
+ }
306
+
307
+ await fs.writeFile(reqPath, updatedLines.join('\n'));
308
+ return true;
309
+ } catch (error) {
310
+ throw new Error(`Failed to promote requirement from TODO to TO VERIFY: ${error.message}`);
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Move requirement from TO VERIFY back to TODO section
316
+ * @param {string} reqPath - Path to REQUIREMENTS file
317
+ * @param {string} requirementTitle - Title of requirement to move
318
+ * @returns {Promise<boolean>} Success status
319
+ */
320
+ async function demoteVerifyToTodo(reqPath, requirementTitle) {
321
+ try {
322
+ const content = await fs.readFile(reqPath, 'utf-8');
323
+ const lines = content.split('\n');
324
+
325
+ // Find the requirement block in TO VERIFY section (### header format)
326
+ let requirementStartIndex = -1;
327
+ let requirementEndIndex = -1;
328
+ let inVerifySection = false;
329
+
330
+ const verifySectionVariants = [
331
+ '## 🔍 TO VERIFY BY HUMAN',
332
+ '## 🔍 TO VERIFY',
333
+ '## TO VERIFY',
334
+ '## ✅ TO VERIFY',
335
+ '## ✅ Verified by AI screenshot'
336
+ ];
337
+
338
+ for (let i = 0; i < lines.length; i++) {
339
+ const line = lines[i].trim();
340
+
341
+ if (verifySectionVariants.some(variant => line.includes(variant))) {
342
+ inVerifySection = true;
343
+ continue;
344
+ }
345
+
346
+ if (inVerifySection && line.startsWith('##') && !line.startsWith('###')) {
347
+ break;
348
+ }
349
+
350
+ if (inVerifySection && line.startsWith('###')) {
351
+ const title = line.replace(/^###\s*/, '').trim();
352
+ if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
353
+ requirementStartIndex = i;
354
+ // Find the end of this requirement (next ### or ## header)
355
+ for (let j = i + 1; j < lines.length; j++) {
356
+ const nextLine = lines[j].trim();
357
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
358
+ requirementEndIndex = j;
359
+ break;
360
+ }
361
+ }
362
+ if (requirementEndIndex === -1) {
363
+ requirementEndIndex = lines.length;
364
+ }
365
+ break;
366
+ }
367
+ }
368
+ }
369
+
370
+ if (requirementStartIndex === -1) {
371
+ return false;
372
+ }
373
+
374
+ // Extract the requirement block
375
+ const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
376
+
377
+ // Update title with TRY AGAIN prefix
378
+ const originalTitle = requirementBlock[0].replace(/^###\s*/, '').trim();
379
+ const titleWithPrefix = addTryAgainPrefix(originalTitle);
380
+ requirementBlock[0] = `### ${titleWithPrefix}`;
381
+
382
+ // Remove the requirement from its current location
383
+ const updatedLines = [
384
+ ...lines.slice(0, requirementStartIndex),
385
+ ...lines.slice(requirementEndIndex)
386
+ ];
387
+
388
+ // Find or create TODO section
389
+ let todoIndex = -1;
390
+ for (let i = 0; i < updatedLines.length; i++) {
391
+ if (updatedLines[i].includes('## ⏳ Requirements not yet completed')) {
392
+ todoIndex = i;
393
+ break;
394
+ }
395
+ }
396
+
397
+ if (todoIndex === -1) {
398
+ // Create TODO section at the top (after initial headers)
399
+ const firstSectionIndex = updatedLines.findIndex(line => line.startsWith('##') && !line.startsWith('###'));
400
+ const insertIndex = firstSectionIndex > 0 ? firstSectionIndex : updatedLines.length;
401
+ updatedLines.splice(insertIndex, 0, '## ⏳ Requirements not yet completed', '');
402
+ todoIndex = insertIndex;
403
+ }
404
+
405
+ // Insert requirement block after section header
406
+ let insertIndex = todoIndex + 1;
407
+ while (insertIndex < updatedLines.length && updatedLines[insertIndex].trim() === '') {
408
+ insertIndex++;
409
+ }
410
+ updatedLines.splice(insertIndex, 0, ...requirementBlock);
411
+ // Add blank line after if needed
412
+ if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
413
+ updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
414
+ }
415
+
416
+ await fs.writeFile(reqPath, updatedLines.join('\n'));
417
+ return true;
418
+ } catch (error) {
419
+ throw new Error(`Failed to demote requirement from TO VERIFY to TODO: ${error.message}`);
420
+ }
421
+ }
422
+
423
+ module.exports = {
424
+ getOrdinalSuffix,
425
+ addTryAgainPrefix,
426
+ parseRequirementLine,
427
+ promoteToVerified,
428
+ demoteFromVerifiedToTodo,
429
+ promoteTodoToVerify,
430
+ demoteVerifyToTodo
431
+ };
432
+