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.
- package/.babelrc +13 -13
- package/README.md +28 -28
- package/__tests__/applescript-manager-claude-fix.test.js +286 -286
- package/__tests__/requirement-2-auto-start-looping.test.js +69 -69
- package/__tests__/requirement-3-auto-start-looping.test.js +69 -69
- package/__tests__/requirement-4-auto-start-looping.test.js +69 -69
- package/__tests__/requirement-6-auto-start-looping.test.js +73 -73
- package/__tests__/requirement-7-status-tracking.test.js +332 -332
- package/jest.config.js +18 -18
- package/jest.setup.js +12 -12
- package/package.json +48 -48
- package/src/auth/access-denied.html +119 -119
- package/src/auth/shared-auth-storage.js +230 -230
- package/src/autonomous-mode/feature-implementer.cjs +70 -70
- package/src/autonomous-mode/feature-implementer.js +425 -425
- package/src/chat-management/chat-manager.cjs +71 -71
- package/src/chat-management/chat-manager.js +342 -342
- package/src/ide-integration/__tests__/applescript-manager-thread-closure.test.js +227 -227
- package/src/ide-integration/aider-cli-manager.cjs +850 -850
- package/src/ide-integration/applescript-manager.cjs +1088 -1088
- package/src/ide-integration/applescript-manager.js +2802 -2802
- package/src/ide-integration/applescript-utils.js +306 -306
- package/src/ide-integration/cdp-manager.cjs +221 -221
- package/src/ide-integration/cdp-manager.js +321 -321
- package/src/ide-integration/claude-code-cli-manager.cjs +301 -301
- package/src/ide-integration/cline-cli-manager.cjs +2252 -2252
- package/src/ide-integration/continue-cli-manager.js +431 -431
- package/src/ide-integration/provider-manager.cjs +354 -354
- package/src/ide-integration/quota-detector.cjs +34 -34
- package/src/ide-integration/quota-detector.js +349 -349
- package/src/ide-integration/windows-automation-manager.js +262 -262
- package/src/index.cjs +47 -43
- package/src/index.js +17 -17
- package/src/llm/direct-llm-manager.cjs +609 -609
- package/src/ui/ButtonComponents.js +247 -247
- package/src/ui/ChatInterface.js +499 -499
- package/src/ui/StateManager.js +259 -259
- package/src/utils/audit-logger.cjs +116 -116
- package/src/utils/config-helpers.cjs +94 -94
- package/src/utils/config-helpers.js +94 -94
- package/src/utils/electron-update-checker.js +113 -85
- package/src/utils/gcloud-auth.cjs +394 -394
- package/src/utils/logger.cjs +193 -193
- package/src/utils/logger.js +191 -191
- package/src/utils/repo-helpers.cjs +120 -120
- package/src/utils/repo-helpers.js +120 -120
- package/src/utils/requirement-helpers.js +432 -432
- package/src/utils/update-checker.js +227 -167
- 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
|
+
|