vibecodingmachine-core 2025.11.2-9.855 → 2025.12.6-1702
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/src/auth/shared-auth-storage.js +43 -6
- package/src/ide-integration/applescript-manager.cjs +126 -18
- package/src/ide-integration/applescript-manager.js +172 -84
- package/src/index.cjs +2 -0
- package/src/index.js +2 -0
- package/src/sync/aws-setup.js +445 -0
- package/src/sync/sync-engine.js +388 -0
- package/src/utils/electron-update-checker.js +23 -8
- package/src/utils/requirement-helpers.js +139 -70
- package/src/utils/requirements-parser.js +310 -0
- package/src/utils/update-checker.js +15 -3
- package/src/utils/version-checker.js +6 -5
|
@@ -61,67 +61,87 @@ async function promoteToVerified(reqPath, requirementTitle) {
|
|
|
61
61
|
try {
|
|
62
62
|
const content = await fs.readFile(reqPath, 'utf-8');
|
|
63
63
|
const lines = content.split('\n');
|
|
64
|
-
const updatedLines = [];
|
|
65
64
|
let inVerifySection = false;
|
|
66
|
-
let
|
|
67
|
-
let
|
|
65
|
+
let requirementStartIndex = -1;
|
|
66
|
+
let requirementEndIndex = -1;
|
|
67
|
+
const normalizedTitle = requirementTitle.trim();
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Find the requirement in TO VERIFY section (new ### format)
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const line = lines[i];
|
|
72
|
+
const trimmed = line.trim();
|
|
73
|
+
|
|
74
|
+
// Check if we're entering TO VERIFY section (multiple variants)
|
|
75
|
+
if (trimmed.startsWith('##') && !trimmed.startsWith('###') &&
|
|
76
|
+
(trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot'))) {
|
|
71
77
|
inVerifySection = true;
|
|
72
|
-
updatedLines.push(line);
|
|
73
78
|
continue;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
|
|
81
|
+
// Check if we're leaving TO VERIFY section
|
|
82
|
+
if (inVerifySection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
77
83
|
inVerifySection = false;
|
|
78
|
-
updatedLines.push(line);
|
|
79
|
-
continue;
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
// Look for requirement in TO VERIFY section (### header format)
|
|
87
|
+
if (inVerifySection && trimmed.startsWith('###')) {
|
|
88
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
89
|
+
if (title === normalizedTitle || title.includes(normalizedTitle) || normalizedTitle.includes(title)) {
|
|
90
|
+
requirementStartIndex = i;
|
|
91
|
+
|
|
92
|
+
// Find the end of this requirement block
|
|
93
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
94
|
+
const nextLine = lines[j].trim();
|
|
95
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
96
|
+
requirementEndIndex = j;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (requirementEndIndex === -1) {
|
|
101
|
+
requirementEndIndex = lines.length;
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
88
104
|
}
|
|
89
|
-
verifyCount++;
|
|
90
105
|
}
|
|
106
|
+
}
|
|
91
107
|
|
|
92
|
-
|
|
108
|
+
if (requirementStartIndex === -1) {
|
|
109
|
+
return false;
|
|
93
110
|
}
|
|
94
111
|
|
|
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})`;
|
|
112
|
+
// Extract requirement block
|
|
113
|
+
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
114
|
+
const extractedTitle = lines[requirementStartIndex].replace(/^###\s*/, '').trim();
|
|
102
115
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
changelogContent = await fs.readFile(changelogPath, 'utf-8');
|
|
106
|
-
} else {
|
|
107
|
-
changelogContent = '# Changelog\n\n## Verified Requirements\n\n';
|
|
108
|
-
}
|
|
116
|
+
// Remove requirement from TO VERIFY section
|
|
117
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
// Add to CHANGELOG.md
|
|
120
|
+
const allnightDir = path.dirname(reqPath);
|
|
121
|
+
const repoRoot = path.dirname(allnightDir);
|
|
122
|
+
const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
|
|
123
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
124
|
+
const changelogEntry = `- ${extractedTitle} (${timestamp})`;
|
|
125
|
+
|
|
126
|
+
let changelogContent = '';
|
|
127
|
+
if (await fs.pathExists(changelogPath)) {
|
|
128
|
+
changelogContent = await fs.readFile(changelogPath, 'utf-8');
|
|
129
|
+
} else {
|
|
130
|
+
changelogContent = '# Changelog\n\n## Verified Requirements\n\n';
|
|
131
|
+
}
|
|
118
132
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
if (changelogContent.includes('## Verified Requirements')) {
|
|
134
|
+
changelogContent = changelogContent.replace(
|
|
135
|
+
'## Verified Requirements\n',
|
|
136
|
+
`## Verified Requirements\n${changelogEntry}\n`
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
changelogContent += `\n## Verified Requirements\n${changelogEntry}\n`;
|
|
122
140
|
}
|
|
123
141
|
|
|
124
|
-
|
|
142
|
+
await fs.writeFile(changelogPath, changelogContent);
|
|
143
|
+
await fs.writeFile(reqPath, lines.join('\n'));
|
|
144
|
+
return true;
|
|
125
145
|
} catch (error) {
|
|
126
146
|
throw new Error(`Failed to promote requirement to verified: ${error.message}`);
|
|
127
147
|
}
|
|
@@ -315,16 +335,17 @@ async function promoteTodoToVerify(reqPath, requirementTitle) {
|
|
|
315
335
|
* Move requirement from TO VERIFY back to TODO section
|
|
316
336
|
* @param {string} reqPath - Path to REQUIREMENTS file
|
|
317
337
|
* @param {string} requirementTitle - Title of requirement to move
|
|
338
|
+
* @param {string} explanation - Optional explanation of what went wrong
|
|
318
339
|
* @returns {Promise<boolean>} Success status
|
|
319
340
|
*/
|
|
320
|
-
async function demoteVerifyToTodo(reqPath, requirementTitle) {
|
|
341
|
+
async function demoteVerifyToTodo(reqPath, requirementTitle, explanation = '') {
|
|
321
342
|
try {
|
|
322
343
|
const content = await fs.readFile(reqPath, 'utf-8');
|
|
323
344
|
const lines = content.split('\n');
|
|
324
345
|
|
|
325
|
-
// Find
|
|
326
|
-
|
|
327
|
-
|
|
346
|
+
// Find ALL matching requirements in TO VERIFY section and remove them
|
|
347
|
+
// We'll collect all requirement blocks to remove, then process them
|
|
348
|
+
const requirementsToRemove = [];
|
|
328
349
|
let inVerifySection = false;
|
|
329
350
|
|
|
330
351
|
const verifySectionVariants = [
|
|
@@ -332,26 +353,56 @@ async function demoteVerifyToTodo(reqPath, requirementTitle) {
|
|
|
332
353
|
'## 🔍 TO VERIFY',
|
|
333
354
|
'## TO VERIFY',
|
|
334
355
|
'## ✅ TO VERIFY',
|
|
356
|
+
'## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
|
|
335
357
|
'## ✅ Verified by AI screenshot'
|
|
336
358
|
];
|
|
337
359
|
|
|
360
|
+
// First pass: find all matching requirements in TO VERIFY section
|
|
338
361
|
for (let i = 0; i < lines.length; i++) {
|
|
339
|
-
const line = lines[i]
|
|
340
|
-
|
|
341
|
-
if (verifySectionVariants.some(variant => line.includes(variant))) {
|
|
342
|
-
inVerifySection = true;
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
362
|
+
const line = lines[i];
|
|
363
|
+
const trimmed = line.trim();
|
|
345
364
|
|
|
346
|
-
if
|
|
347
|
-
|
|
365
|
+
// Check if this is a TO VERIFY section header
|
|
366
|
+
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
367
|
+
const isToVerifyHeader = verifySectionVariants.some(variant => {
|
|
368
|
+
return trimmed === variant || trimmed.startsWith(variant) ||
|
|
369
|
+
(trimmed.includes('Verified by AI screenshot') && trimmed.includes('Needs Human to Verify'));
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (isToVerifyHeader) {
|
|
373
|
+
// Make sure it's not a VERIFIED section (without TO VERIFY)
|
|
374
|
+
if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) && !trimmed.includes('📝 VERIFIED')) {
|
|
375
|
+
inVerifySection = true;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
} else if (inVerifySection) {
|
|
379
|
+
// Check if we're leaving TO VERIFY section (hit a different section)
|
|
380
|
+
if (trimmed.includes('⏳ Requirements not yet completed') ||
|
|
381
|
+
trimmed.includes('## 📝 VERIFIED') ||
|
|
382
|
+
trimmed.includes('## ♻️ RECYCLED') ||
|
|
383
|
+
trimmed.includes('## 📦 RECYCLED') ||
|
|
384
|
+
trimmed.includes('## ❓ Requirements needing')) {
|
|
385
|
+
// We've left the TO VERIFY section
|
|
386
|
+
inVerifySection = false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
348
389
|
}
|
|
349
390
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
391
|
+
// Look for requirement in TO VERIFY section
|
|
392
|
+
if (inVerifySection && trimmed.startsWith('###')) {
|
|
393
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
394
|
+
// Normalize titles for matching (handle TRY AGAIN prefixes)
|
|
395
|
+
const normalizedTitle = title.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
|
|
396
|
+
const normalizedRequirementTitle = requirementTitle.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
|
|
397
|
+
|
|
398
|
+
if (title && (title === requirementTitle ||
|
|
399
|
+
normalizedTitle === normalizedRequirementTitle ||
|
|
400
|
+
title.includes(requirementTitle) ||
|
|
401
|
+
requirementTitle.includes(title) ||
|
|
402
|
+
normalizedTitle.includes(normalizedRequirementTitle) ||
|
|
403
|
+
normalizedRequirementTitle.includes(normalizedTitle))) {
|
|
354
404
|
// Find the end of this requirement (next ### or ## header)
|
|
405
|
+
let requirementEndIndex = lines.length;
|
|
355
406
|
for (let j = i + 1; j < lines.length; j++) {
|
|
356
407
|
const nextLine = lines[j].trim();
|
|
357
408
|
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
@@ -359,31 +410,49 @@ async function demoteVerifyToTodo(reqPath, requirementTitle) {
|
|
|
359
410
|
break;
|
|
360
411
|
}
|
|
361
412
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
413
|
+
|
|
414
|
+
// Store this requirement to remove (we'll use the first one for moving to TODO)
|
|
415
|
+
requirementsToRemove.push({
|
|
416
|
+
start: i,
|
|
417
|
+
end: requirementEndIndex,
|
|
418
|
+
block: lines.slice(i, requirementEndIndex)
|
|
419
|
+
});
|
|
366
420
|
}
|
|
367
421
|
}
|
|
368
422
|
}
|
|
369
423
|
|
|
370
|
-
if (
|
|
424
|
+
if (requirementsToRemove.length === 0) {
|
|
371
425
|
return false;
|
|
372
426
|
}
|
|
373
427
|
|
|
374
|
-
//
|
|
375
|
-
const
|
|
428
|
+
// Use the first matching requirement for moving to TODO (with TRY AGAIN prefix)
|
|
429
|
+
const firstRequirement = requirementsToRemove[0];
|
|
430
|
+
const requirementBlock = [...firstRequirement.block];
|
|
376
431
|
|
|
377
432
|
// Update title with TRY AGAIN prefix
|
|
378
433
|
const originalTitle = requirementBlock[0].replace(/^###\s*/, '').trim();
|
|
379
434
|
const titleWithPrefix = addTryAgainPrefix(originalTitle);
|
|
380
435
|
requirementBlock[0] = `### ${titleWithPrefix}`;
|
|
381
436
|
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
437
|
+
// Add explanation to the requirement description if provided
|
|
438
|
+
if (explanation && explanation.trim()) {
|
|
439
|
+
// Find where to insert the explanation (after the title, before any existing content)
|
|
440
|
+
// Insert after first line (title) with a blank line and "What went wrong:" section
|
|
441
|
+
const explanationLines = [
|
|
442
|
+
'',
|
|
443
|
+
'**What went wrong (from previous attempt):**',
|
|
444
|
+
explanation.trim(),
|
|
445
|
+
''
|
|
446
|
+
];
|
|
447
|
+
requirementBlock.splice(1, 0, ...explanationLines);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Remove ALL matching requirements from TO VERIFY section (work backwards to preserve indices)
|
|
451
|
+
const updatedLines = [...lines];
|
|
452
|
+
for (let i = requirementsToRemove.length - 1; i >= 0; i--) {
|
|
453
|
+
const req = requirementsToRemove[i];
|
|
454
|
+
updatedLines.splice(req.start, req.end - req.start);
|
|
455
|
+
}
|
|
387
456
|
|
|
388
457
|
// Find or create TODO section
|
|
389
458
|
let todoIndex = -1;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse requirements file and extract all sections
|
|
6
|
+
* This matches the CLI's parsing logic to ensure consistency
|
|
7
|
+
* @param {string} content - The requirements file content
|
|
8
|
+
* @returns {Object} Parsed requirements with sections: requirements, completed, needInformation, changelog, verified
|
|
9
|
+
*/
|
|
10
|
+
function parseRequirementsFile(content) {
|
|
11
|
+
const lines = content.split('\n');
|
|
12
|
+
const requirements = [];
|
|
13
|
+
const completed = [];
|
|
14
|
+
const needInformation = [];
|
|
15
|
+
const changelog = [];
|
|
16
|
+
const verified = [];
|
|
17
|
+
|
|
18
|
+
let currentSection = '';
|
|
19
|
+
let inSection = false;
|
|
20
|
+
|
|
21
|
+
// Section detection patterns (matching CLI logic)
|
|
22
|
+
const sectionPatterns = {
|
|
23
|
+
todo: ['⏳ Requirements not yet completed', 'Requirements not yet completed'],
|
|
24
|
+
verify: [
|
|
25
|
+
'🔍 TO VERIFY BY HUMAN',
|
|
26
|
+
'TO VERIFY BY HUMAN',
|
|
27
|
+
'🔍 TO VERIFY',
|
|
28
|
+
'TO VERIFY',
|
|
29
|
+
'✅ TO VERIFY',
|
|
30
|
+
'✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
|
|
31
|
+
'Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
|
|
32
|
+
'Verified by AI screenshot'
|
|
33
|
+
],
|
|
34
|
+
verified: ['📝 VERIFIED', 'VERIFIED'],
|
|
35
|
+
changelog: ['CHANGELOG'],
|
|
36
|
+
needInformation: ['❓ Requirements needing', 'Requirements needing', 'needing manual feedback', 'needing information']
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < lines.length; i++) {
|
|
40
|
+
const line = lines[i];
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
|
|
43
|
+
// Check for section headers (## but not ###)
|
|
44
|
+
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
45
|
+
// Determine which section we're entering
|
|
46
|
+
if (sectionPatterns.todo.some(pattern => trimmed.includes(pattern))) {
|
|
47
|
+
currentSection = 'todo';
|
|
48
|
+
inSection = true;
|
|
49
|
+
continue;
|
|
50
|
+
} else if (sectionPatterns.verify.some(pattern => {
|
|
51
|
+
const matches = trimmed.includes(pattern);
|
|
52
|
+
// Make sure it's not a VERIFIED section (without TO VERIFY)
|
|
53
|
+
if (matches && !trimmed.includes('📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) && !trimmed.includes('📝 VERIFIED')) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
})) {
|
|
58
|
+
currentSection = 'verify';
|
|
59
|
+
inSection = true;
|
|
60
|
+
continue;
|
|
61
|
+
} else if (sectionPatterns.verified.some(pattern => trimmed.includes(pattern))) {
|
|
62
|
+
currentSection = 'verified';
|
|
63
|
+
inSection = true;
|
|
64
|
+
continue;
|
|
65
|
+
} else if (sectionPatterns.changelog.some(pattern => trimmed.includes(pattern))) {
|
|
66
|
+
currentSection = 'changelog';
|
|
67
|
+
inSection = true;
|
|
68
|
+
continue;
|
|
69
|
+
} else if (sectionPatterns.needInformation.some(pattern => trimmed.includes(pattern))) {
|
|
70
|
+
currentSection = 'needInformation';
|
|
71
|
+
inSection = true;
|
|
72
|
+
continue;
|
|
73
|
+
} else {
|
|
74
|
+
// Different section header - exit current section
|
|
75
|
+
if (inSection) {
|
|
76
|
+
// Check if we're leaving TO VERIFY section
|
|
77
|
+
if (currentSection === 'verify') {
|
|
78
|
+
const isTodoSection = trimmed.includes('⏳ Requirements not yet completed') || trimmed.includes('Requirements not yet completed');
|
|
79
|
+
const isVerifiedSection = trimmed === '## 📝 VERIFIED' || trimmed.startsWith('## 📝 VERIFIED');
|
|
80
|
+
const isRecycledSection = trimmed === '## ♻️ RECYCLED' || trimmed.startsWith('## ♻️ RECYCLED') ||
|
|
81
|
+
trimmed === '## 📦 RECYCLED' || trimmed.startsWith('## 📦 RECYCLED');
|
|
82
|
+
const isClarificationSection = trimmed.includes('## ❓ Requirements needing') || trimmed.includes('❓ Requirements needing');
|
|
83
|
+
|
|
84
|
+
if (isVerifiedSection || isTodoSection || isRecycledSection || isClarificationSection) {
|
|
85
|
+
inSection = false;
|
|
86
|
+
currentSection = '';
|
|
87
|
+
}
|
|
88
|
+
// Otherwise continue - might be REJECTED or CHANGELOG which are not section boundaries for TO VERIFY
|
|
89
|
+
} else {
|
|
90
|
+
inSection = false;
|
|
91
|
+
currentSection = '';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Parse requirements in new format (### header)
|
|
99
|
+
if (inSection && trimmed.startsWith('###')) {
|
|
100
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
101
|
+
|
|
102
|
+
// Skip malformed requirements (title is just a package name, empty, or too short)
|
|
103
|
+
const packageNames = ['cli', 'core', 'electron-app', 'web', 'mobile', 'vscode-extension', 'sync-server'];
|
|
104
|
+
if (!title || title.length === 0 || packageNames.includes(title.toLowerCase())) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const details = [];
|
|
109
|
+
let package = null;
|
|
110
|
+
let options = null;
|
|
111
|
+
let optionsType = null;
|
|
112
|
+
|
|
113
|
+
// Read package, description, and options
|
|
114
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
115
|
+
const nextLine = lines[j].trim();
|
|
116
|
+
|
|
117
|
+
// Stop if we hit another requirement or section
|
|
118
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check for PACKAGE line
|
|
123
|
+
if (nextLine.startsWith('PACKAGE:')) {
|
|
124
|
+
package = nextLine.replace(/^PACKAGE:\s*/, '').trim();
|
|
125
|
+
}
|
|
126
|
+
// Check for OPTIONS line (for need information requirements)
|
|
127
|
+
else if (nextLine.startsWith('OPTIONS:')) {
|
|
128
|
+
optionsType = 'checkbox'; // default
|
|
129
|
+
const optionsText = nextLine.replace(/^OPTIONS:\s*/, '').trim();
|
|
130
|
+
// Parse options (could be checkbox or radio)
|
|
131
|
+
if (optionsText.includes('|')) {
|
|
132
|
+
options = optionsText.split('|').map(opt => opt.trim()).filter(opt => opt);
|
|
133
|
+
} else {
|
|
134
|
+
options = [optionsText];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Description line (include all lines including empty ones to preserve formatting)
|
|
138
|
+
else if (nextLine !== undefined) {
|
|
139
|
+
details.push(nextLine);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const requirement = {
|
|
144
|
+
title,
|
|
145
|
+
description: details.join('\n'),
|
|
146
|
+
package,
|
|
147
|
+
options,
|
|
148
|
+
optionsType,
|
|
149
|
+
lineIndex: i
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Add to appropriate array based on current section
|
|
153
|
+
if (currentSection === 'todo') {
|
|
154
|
+
requirements.push(requirement);
|
|
155
|
+
} else if (currentSection === 'verify') {
|
|
156
|
+
completed.push(requirement);
|
|
157
|
+
} else if (currentSection === 'needInformation') {
|
|
158
|
+
needInformation.push(requirement);
|
|
159
|
+
} else if (currentSection === 'changelog') {
|
|
160
|
+
changelog.push(requirement);
|
|
161
|
+
} else if (currentSection === 'verified') {
|
|
162
|
+
verified.push(requirement);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Also support old format (lines starting with -) for backwards compatibility
|
|
167
|
+
if (inSection && trimmed.startsWith('- ') && !trimmed.startsWith('###')) {
|
|
168
|
+
const requirementText = trimmed.substring(2).trim();
|
|
169
|
+
if (requirementText) {
|
|
170
|
+
// Extract date if present (format: "2025-12-25 2:35 PM MDT - " or "2025-10-03: ")
|
|
171
|
+
const dateTimeMatch = requirementText.match(/^(\d{4}-\d{2}-\d{2})(?:\s+\d{1,2}:\d{2}\s+[AP]M(?:\s+[A-Z]{2,5})?)?\s*[-:]\s*/);
|
|
172
|
+
const date = dateTimeMatch ? dateTimeMatch[1] : null;
|
|
173
|
+
const title = dateTimeMatch ? requirementText.substring(dateTimeMatch[0].length).trim() : requirementText;
|
|
174
|
+
|
|
175
|
+
const requirement = {
|
|
176
|
+
title: title.length > 100 ? title.substring(0, 100) + '...' : title,
|
|
177
|
+
description: title,
|
|
178
|
+
date: date
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (currentSection === 'todo') {
|
|
182
|
+
requirements.push(requirement);
|
|
183
|
+
} else if (currentSection === 'verify' || currentSection === 'completed') {
|
|
184
|
+
completed.push(requirement);
|
|
185
|
+
} else if (currentSection === 'changelog') {
|
|
186
|
+
changelog.push(requirement);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Remove duplicates based on title (keep first occurrence)
|
|
193
|
+
const seenTitles = new Set();
|
|
194
|
+
const uniqueRequirements = [];
|
|
195
|
+
for (const req of requirements) {
|
|
196
|
+
const normalizedTitle = req.title.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
|
|
197
|
+
if (!seenTitles.has(normalizedTitle)) {
|
|
198
|
+
seenTitles.add(normalizedTitle);
|
|
199
|
+
uniqueRequirements.push(req);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
requirements: uniqueRequirements,
|
|
205
|
+
completed,
|
|
206
|
+
needInformation,
|
|
207
|
+
changelog,
|
|
208
|
+
verified
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Load verified requirements from CHANGELOG.md
|
|
214
|
+
* @param {string} repoPath - Path to repository root
|
|
215
|
+
* @returns {Promise<Array>} Array of verified requirement titles
|
|
216
|
+
*/
|
|
217
|
+
async function loadVerifiedFromChangelog(repoPath) {
|
|
218
|
+
try {
|
|
219
|
+
const changelogPath = path.join(repoPath, 'CHANGELOG.md');
|
|
220
|
+
if (!await fs.pathExists(changelogPath)) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const content = await fs.readFile(changelogPath, 'utf8');
|
|
225
|
+
const lines = content.split('\n');
|
|
226
|
+
const requirements = [];
|
|
227
|
+
let inVerifiedSection = false;
|
|
228
|
+
|
|
229
|
+
for (const line of lines) {
|
|
230
|
+
const trimmed = line.trim();
|
|
231
|
+
|
|
232
|
+
// Check for Verified Requirements section
|
|
233
|
+
if (trimmed.includes('## Verified Requirements')) {
|
|
234
|
+
inVerifiedSection = true;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Exit section if we hit another ## header
|
|
239
|
+
if (inVerifiedSection && trimmed.startsWith('##') && !trimmed.includes('Verified Requirements')) {
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Only collect items from within the Verified Requirements section
|
|
244
|
+
if (inVerifiedSection && trimmed.startsWith('- ') && trimmed.length > 10) {
|
|
245
|
+
const title = trimmed.substring(2).trim();
|
|
246
|
+
// Extract date if present (format: "Title (2025-12-05)")
|
|
247
|
+
const dateMatch = title.match(/^(.+?)\s*\((\d{4}-\d{2}-\d{2})\)$/);
|
|
248
|
+
const cleanTitle = dateMatch ? dateMatch[1].trim() : title;
|
|
249
|
+
const date = dateMatch ? dateMatch[2] : null;
|
|
250
|
+
|
|
251
|
+
requirements.push({
|
|
252
|
+
title: cleanTitle,
|
|
253
|
+
description: cleanTitle,
|
|
254
|
+
date: date
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return requirements;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
// If CHANGELOG.md doesn't exist or can't be read, return empty array
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Load and parse requirements from file
|
|
268
|
+
* @param {string} reqPath - Path to requirements file
|
|
269
|
+
* @param {string} repoPath - Optional path to repository root (for loading CHANGELOG.md)
|
|
270
|
+
* @returns {Promise<Object>} Parsed requirements
|
|
271
|
+
*/
|
|
272
|
+
async function loadRequirementsFromFile(reqPath, repoPath = null) {
|
|
273
|
+
try {
|
|
274
|
+
const parsed = {
|
|
275
|
+
requirements: [],
|
|
276
|
+
completed: [],
|
|
277
|
+
needInformation: [],
|
|
278
|
+
changelog: [],
|
|
279
|
+
verified: []
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (await fs.pathExists(reqPath)) {
|
|
283
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
284
|
+
const fileParsed = parseRequirementsFile(content);
|
|
285
|
+
Object.assign(parsed, fileParsed);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Also load verified from CHANGELOG.md if repoPath is provided
|
|
289
|
+
if (repoPath) {
|
|
290
|
+
const verifiedFromChangelog = await loadVerifiedFromChangelog(repoPath);
|
|
291
|
+
// Merge with verified from requirements file (CHANGELOG.md takes precedence)
|
|
292
|
+
if (verifiedFromChangelog.length > 0) {
|
|
293
|
+
parsed.verified = verifiedFromChangelog;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return parsed;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
throw new Error(`Failed to load requirements from file: ${error.message}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
parseRequirementsFile,
|
|
305
|
+
loadRequirementsFromFile,
|
|
306
|
+
loadVerifiedFromChangelog
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
@@ -62,15 +62,26 @@ async function checkForUpdates(packageName, currentVersion) {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Compare two timestamp-based versions (YYYY.MM.DD-HHMM)
|
|
67
|
+
* Returns true if version1 is newer than version2
|
|
68
|
+
*/
|
|
69
|
+
function isVersionNewer(version1, version2) {
|
|
70
|
+
// Versions are in format YYYY.MM.DD-HHMM, so string comparison works
|
|
71
|
+
// Normalize both versions (handle old format with dots too)
|
|
72
|
+
const normalize = (v) => v.replace(/\.(\d{2})(\d{2})$/, '-$1$2');
|
|
73
|
+
return normalize(version1) > normalize(version2);
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
/**
|
|
66
77
|
* Check for CLI updates from S3 version manifest (unified with Electron)
|
|
67
78
|
* Both CLI and Electron now use the same timestamp-based versioning from git commits
|
|
68
|
-
* @param {string} currentVersion - Current CLI version (e.g., '2025.11.26
|
|
79
|
+
* @param {string} currentVersion - Current CLI version (e.g., '2025.11.26-0519')
|
|
69
80
|
* @returns {Promise<Object>} Update info or null if no update available
|
|
70
81
|
*/
|
|
71
82
|
async function checkForCLIUpdates(currentVersion) {
|
|
72
83
|
return new Promise((resolve, reject) => {
|
|
73
|
-
const manifestUrl = `https://
|
|
84
|
+
const manifestUrl = `https://d3fh7zgi8horze.cloudfront.net/downloads/version.json?t=${Date.now()}`;
|
|
74
85
|
|
|
75
86
|
https.get(manifestUrl, (res) => {
|
|
76
87
|
let data = '';
|
|
@@ -92,7 +103,8 @@ async function checkForCLIUpdates(currentVersion) {
|
|
|
92
103
|
const latestVersion = manifest.version;
|
|
93
104
|
const publishedDate = manifest.date;
|
|
94
105
|
|
|
95
|
-
if
|
|
106
|
+
// Only show update if latest version is actually newer than current
|
|
107
|
+
if (latestVersion !== currentVersion && isVersionNewer(latestVersion, currentVersion)) {
|
|
96
108
|
resolve({
|
|
97
109
|
hasUpdate: true,
|
|
98
110
|
currentVersion,
|
|
@@ -12,15 +12,16 @@ const CACHE_DURATION = 1000 * 60 * 60; // 1 hour
|
|
|
12
12
|
const VERSION_URL = 'https://vibecodingmachine-website.s3.amazonaws.com/downloads/version.json';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Format version timestamp to YYYY.MM.DD
|
|
15
|
+
* Format version timestamp to YYYY.MM.DD-HHMM UTC
|
|
16
16
|
*/
|
|
17
17
|
function formatVersionTimestamp(versionString) {
|
|
18
|
-
// Version format: YYYY.MM.DD.HHMM
|
|
19
|
-
const match = versionString.match(/^(\d{4})\.(\d{2})\.(\d{2})
|
|
18
|
+
// Version format: YYYY.MM.DD-HHMM (or old format YYYY.MM.DD.HHMM for backward compat)
|
|
19
|
+
const match = versionString.match(/^(\d{4})\.(\d{2})\.(\d{2})[-.](\d{2})(\d{2})$/);
|
|
20
20
|
if (!match) return versionString;
|
|
21
21
|
|
|
22
22
|
const [, year, month, day, hour, minute] = match;
|
|
23
|
-
|
|
23
|
+
// Normalize to new format for display
|
|
24
|
+
return `${year}.${month}.${day}-${hour}${minute} UTC`;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -44,7 +45,7 @@ function formatLocalTime(isoTimestamp) {
|
|
|
44
45
|
hour12: false
|
|
45
46
|
}).format(date);
|
|
46
47
|
|
|
47
|
-
// Convert to YYYY.MM.DD
|
|
48
|
+
// Convert to YYYY.MM.DD-HHMM format
|
|
48
49
|
const parts = formatted.match(/(\d{2})\/(\d{2})\/(\d{4}), (\d{2}):(\d{2})/);
|
|
49
50
|
if (parts) {
|
|
50
51
|
const [, month, day, year, hour, minute] = parts;
|