vibecodingmachine-core 2025.12.25-1541 → 2026.1.3-2209
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/ERROR_REPORTING_API.md +212 -0
- package/ERROR_REPORTING_USAGE.md +380 -0
- package/__tests__/utils/git-branch-manager.test.js +61 -0
- package/package.json +1 -1
- package/src/database/user-database-client.js +26 -0
- package/src/database/user-schema.js +7 -0
- package/src/ide-integration/applescript-manager.cjs +26 -6
- package/src/ide-integration/applescript-manager.js +6 -5
- package/src/ide-integration/applescript-utils.js +26 -18
- package/src/index.cjs +4 -0
- package/src/index.js +4 -0
- package/src/localization/translations/en.js +2 -0
- package/src/localization/translations/es.js +2 -0
- package/src/requirement-numbering.js +164 -0
- package/src/utils/error-reporter.js +109 -0
- package/src/utils/git-branch-manager.js +278 -0
- package/src/utils/requirement-helpers.js +10 -1
- package/src/utils/requirements-parser.js +25 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { createBranchName, extractRequirementNumber } = require('../requirement-numbering');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if a directory is a git repository
|
|
7
|
+
* @param {string} repoPath - Path to check
|
|
8
|
+
* @returns {boolean} True if it's a git repo
|
|
9
|
+
*/
|
|
10
|
+
function isGitRepo(repoPath) {
|
|
11
|
+
try {
|
|
12
|
+
execSync('git rev-parse --git-dir', {
|
|
13
|
+
cwd: repoPath,
|
|
14
|
+
stdio: 'ignore'
|
|
15
|
+
});
|
|
16
|
+
return true;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get current git branch name
|
|
24
|
+
* @param {string} repoPath - Path to repository
|
|
25
|
+
* @returns {string|null} Current branch name or null
|
|
26
|
+
*/
|
|
27
|
+
function getCurrentBranch(repoPath) {
|
|
28
|
+
try {
|
|
29
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
30
|
+
cwd: repoPath,
|
|
31
|
+
encoding: 'utf8'
|
|
32
|
+
}).trim();
|
|
33
|
+
return branch;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a feature branch for a requirement
|
|
41
|
+
* @param {string} repoPath - Path to repository
|
|
42
|
+
* @param {string} requirementTitle - Full requirement title (e.g., "R1: Email System")
|
|
43
|
+
* @returns {Promise<{success: boolean, branchName: string|null, parentBranch: string|null, error: string|null}>}
|
|
44
|
+
*/
|
|
45
|
+
async function createRequirementBranch(repoPath, requirementTitle) {
|
|
46
|
+
try {
|
|
47
|
+
if (!isGitRepo(repoPath)) {
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
branchName: null,
|
|
51
|
+
parentBranch: null,
|
|
52
|
+
error: 'Not a git repository'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extract requirement number from title
|
|
57
|
+
const reqNumber = extractRequirementNumber(requirementTitle);
|
|
58
|
+
if (!reqNumber) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
branchName: null,
|
|
62
|
+
parentBranch: null,
|
|
63
|
+
error: 'Requirement number not found in title'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Get parent branch (current branch before creating new one)
|
|
68
|
+
const parentBranch = getCurrentBranch(repoPath);
|
|
69
|
+
if (!parentBranch) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
branchName: null,
|
|
73
|
+
parentBranch: null,
|
|
74
|
+
error: 'Could not determine current branch'
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create branch name
|
|
79
|
+
const branchName = createBranchName(reqNumber, requirementTitle);
|
|
80
|
+
|
|
81
|
+
// Check if branch already exists
|
|
82
|
+
try {
|
|
83
|
+
execSync(`git rev-parse --verify ${branchName}`, {
|
|
84
|
+
cwd: repoPath,
|
|
85
|
+
stdio: 'ignore'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Branch exists, check it out
|
|
89
|
+
execSync(`git checkout ${branchName}`, {
|
|
90
|
+
cwd: repoPath,
|
|
91
|
+
stdio: 'ignore'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
branchName,
|
|
97
|
+
parentBranch,
|
|
98
|
+
error: null,
|
|
99
|
+
alreadyExists: true
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// Branch doesn't exist, create it
|
|
103
|
+
execSync(`git checkout -b ${branchName}`, {
|
|
104
|
+
cwd: repoPath,
|
|
105
|
+
stdio: 'ignore'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
branchName,
|
|
111
|
+
parentBranch,
|
|
112
|
+
error: null,
|
|
113
|
+
alreadyExists: false
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
branchName: null,
|
|
120
|
+
parentBranch: null,
|
|
121
|
+
error: error.message
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Merge a requirement branch back to parent and delete it
|
|
128
|
+
* @param {string} repoPath - Path to repository
|
|
129
|
+
* @param {string} requirementTitle - Full requirement title
|
|
130
|
+
* @param {string} parentBranch - Parent branch to merge into
|
|
131
|
+
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
132
|
+
*/
|
|
133
|
+
async function mergeRequirementBranch(repoPath, requirementTitle, parentBranch = 'main') {
|
|
134
|
+
try {
|
|
135
|
+
if (!isGitRepo(repoPath)) {
|
|
136
|
+
return { success: false, error: 'Not a git repository' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Extract requirement number
|
|
140
|
+
const reqNumber = extractRequirementNumber(requirementTitle);
|
|
141
|
+
if (!reqNumber) {
|
|
142
|
+
return { success: false, error: 'Requirement number not found in title' };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create branch name
|
|
146
|
+
const branchName = createBranchName(reqNumber, requirementTitle);
|
|
147
|
+
|
|
148
|
+
// Checkout parent branch
|
|
149
|
+
execSync(`git checkout ${parentBranch}`, {
|
|
150
|
+
cwd: repoPath,
|
|
151
|
+
stdio: 'ignore'
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Merge the feature branch
|
|
155
|
+
execSync(`git merge --no-ff ${branchName} -m "Merge ${branchName}"`, {
|
|
156
|
+
cwd: repoPath,
|
|
157
|
+
stdio: 'ignore'
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Delete the feature branch
|
|
161
|
+
execSync(`git branch -d ${branchName}`, {
|
|
162
|
+
cwd: repoPath,
|
|
163
|
+
stdio: 'ignore'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return { success: true, error: null };
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return { success: false, error: error.message };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Remove a feature by reverting its merge commit
|
|
174
|
+
* @param {string} repoPath - Path to repository
|
|
175
|
+
* @param {string} requirementTitle - Full requirement title
|
|
176
|
+
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
177
|
+
*/
|
|
178
|
+
async function removeRequirementFeature(repoPath, requirementTitle) {
|
|
179
|
+
try {
|
|
180
|
+
if (!isGitRepo(repoPath)) {
|
|
181
|
+
return { success: false, error: 'Not a git repository' };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Extract requirement number
|
|
185
|
+
const reqNumber = extractRequirementNumber(requirementTitle);
|
|
186
|
+
if (!reqNumber) {
|
|
187
|
+
return { success: false, error: 'Requirement number not found in title' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create branch name to search for
|
|
191
|
+
const branchName = createBranchName(reqNumber, requirementTitle);
|
|
192
|
+
|
|
193
|
+
// Find the merge commit for this branch
|
|
194
|
+
const mergeCommit = execSync(
|
|
195
|
+
`git log --grep="Merge ${branchName}" --oneline --format="%H" -1`,
|
|
196
|
+
{
|
|
197
|
+
cwd: repoPath,
|
|
198
|
+
encoding: 'utf8'
|
|
199
|
+
}
|
|
200
|
+
).trim();
|
|
201
|
+
|
|
202
|
+
if (!mergeCommit) {
|
|
203
|
+
return { success: false, error: 'Merge commit not found for this requirement' };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Revert the merge commit
|
|
207
|
+
execSync(`git revert -m 1 ${mergeCommit} --no-edit`, {
|
|
208
|
+
cwd: repoPath,
|
|
209
|
+
stdio: 'ignore'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return { success: true, error: null };
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return { success: false, error: error.message };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get list of all requirement branches
|
|
220
|
+
* @param {string} repoPath - Path to repository
|
|
221
|
+
* @returns {Array<{branchName: string, reqNumber: number}>}
|
|
222
|
+
*/
|
|
223
|
+
function listRequirementBranches(repoPath) {
|
|
224
|
+
try {
|
|
225
|
+
if (!isGitRepo(repoPath)) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const branches = execSync('git branch --list "R*"', {
|
|
230
|
+
cwd: repoPath,
|
|
231
|
+
encoding: 'utf8'
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return branches
|
|
235
|
+
.split('\n')
|
|
236
|
+
.map(b => b.trim().replace(/^\*\s*/, ''))
|
|
237
|
+
.filter(b => b && b.startsWith('R'))
|
|
238
|
+
.map(b => {
|
|
239
|
+
const match = b.match(/^R(\d+)/);
|
|
240
|
+
return {
|
|
241
|
+
branchName: b,
|
|
242
|
+
reqNumber: match ? parseInt(match[1], 10) : 0
|
|
243
|
+
};
|
|
244
|
+
})
|
|
245
|
+
.filter(b => b.reqNumber > 0)
|
|
246
|
+
.sort((a, b) => a.reqNumber - b.reqNumber);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check if there are any uncommitted changes in the repository
|
|
254
|
+
* @param {string} repoPath - Path to repository
|
|
255
|
+
* @returns {boolean} True if there are uncommitted changes
|
|
256
|
+
*/
|
|
257
|
+
function hasUncommittedChanges(repoPath) {
|
|
258
|
+
try {
|
|
259
|
+
// Check for staged and unstaged changes
|
|
260
|
+
const status = execSync('git status --porcelain', {
|
|
261
|
+
cwd: repoPath,
|
|
262
|
+
encoding: 'utf8'
|
|
263
|
+
}).trim();
|
|
264
|
+
return status.length > 0;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = {
|
|
271
|
+
isGitRepo,
|
|
272
|
+
getCurrentBranch,
|
|
273
|
+
hasUncommittedChanges,
|
|
274
|
+
createRequirementBranch,
|
|
275
|
+
mergeRequirementBranch,
|
|
276
|
+
removeRequirementFeature,
|
|
277
|
+
listRequirementBranches
|
|
278
|
+
};
|
|
@@ -514,8 +514,17 @@ async function loadVerifiedFromChangelog(repoPath) {
|
|
|
514
514
|
for (const line of lines) {
|
|
515
515
|
const trimmed = line.trim();
|
|
516
516
|
|
|
517
|
+
if (trimmed.includes('## Verified Requirements')) {
|
|
518
|
+
inVerifiedSection = true;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (inVerifiedSection && trimmed.startsWith('##') && !trimmed.includes('Verified Requirements')) {
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
|
|
517
526
|
// Collect items starting with "- " as verified requirements
|
|
518
|
-
if (trimmed.startsWith('- ') && trimmed.length > 5) {
|
|
527
|
+
if (inVerifiedSection && trimmed.startsWith('- ') && trimmed.length > 5) {
|
|
519
528
|
let reqText = trimmed.substring(2);
|
|
520
529
|
// Extract title part (before timestamp in parentheses if present)
|
|
521
530
|
const titleMatch = reqText.match(/^(.+?)\s*\([\d-]+\)$/);
|
|
@@ -112,9 +112,11 @@ function parseRequirementsFile(content) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
const details = [];
|
|
115
|
+
const questions = [];
|
|
115
116
|
let package = null;
|
|
116
117
|
let options = null;
|
|
117
118
|
let optionsType = null;
|
|
119
|
+
let currentQuestion = null;
|
|
118
120
|
|
|
119
121
|
// Read package, description, and options
|
|
120
122
|
for (let j = i + 1; j < lines.length; j++) {
|
|
@@ -140,18 +142,41 @@ function parseRequirementsFile(content) {
|
|
|
140
142
|
options = [optionsText];
|
|
141
143
|
}
|
|
142
144
|
}
|
|
145
|
+
// Check for QUESTIONS line (pipe-separated)
|
|
146
|
+
else if (nextLine.startsWith('QUESTIONS:')) {
|
|
147
|
+
const questionsText = nextLine.replace(/^QUESTIONS:\s*/, '').trim();
|
|
148
|
+
const parsedQuestions = questionsText.split('|').map(q => q.trim()).filter(Boolean);
|
|
149
|
+
for (const q of parsedQuestions) {
|
|
150
|
+
questions.push({ question: q, response: null });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Parse numbered questions/responses in needInformation section (CLI-style)
|
|
154
|
+
else if (currentSection === 'needInformation' && nextLine.match(/^\d+\./)) {
|
|
155
|
+
if (currentQuestion) {
|
|
156
|
+
questions.push(currentQuestion);
|
|
157
|
+
}
|
|
158
|
+
currentQuestion = { question: nextLine, response: null };
|
|
159
|
+
}
|
|
160
|
+
else if (currentSection === 'needInformation' && currentQuestion && nextLine.startsWith('Response:')) {
|
|
161
|
+
currentQuestion.response = nextLine.replace(/^Response:\s*/, '').trim();
|
|
162
|
+
}
|
|
143
163
|
// Description line (include all lines including empty ones to preserve formatting)
|
|
144
164
|
else if (nextLine !== undefined) {
|
|
145
165
|
details.push(nextLine);
|
|
146
166
|
}
|
|
147
167
|
}
|
|
148
168
|
|
|
169
|
+
if (currentQuestion) {
|
|
170
|
+
questions.push(currentQuestion);
|
|
171
|
+
}
|
|
172
|
+
|
|
149
173
|
const requirement = {
|
|
150
174
|
title,
|
|
151
175
|
description: details.join('\n'),
|
|
152
176
|
package,
|
|
153
177
|
options,
|
|
154
178
|
optionsType,
|
|
179
|
+
questions: currentSection === 'needInformation' ? questions : undefined,
|
|
155
180
|
lineIndex: i
|
|
156
181
|
};
|
|
157
182
|
|