vibecodingmachine-core 2025.12.25-25 → 2026.1.22-1441

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 (40) hide show
  1. package/ERROR_REPORTING_API.md +212 -0
  2. package/ERROR_REPORTING_USAGE.md +380 -0
  3. package/__tests__/provider-manager-fallback.test.js +43 -0
  4. package/__tests__/provider-manager-rate-limit.test.js +61 -0
  5. package/__tests__/utils/git-branch-manager.test.js +61 -0
  6. package/package.json +1 -1
  7. package/src/beta-request.js +160 -0
  8. package/src/compliance/compliance-manager.js +5 -2
  9. package/src/database/migrations.js +135 -12
  10. package/src/database/user-database-client.js +127 -8
  11. package/src/database/user-schema.js +28 -0
  12. package/src/health-tracking/__tests__/ide-health-tracker.test.js +420 -0
  13. package/src/health-tracking/__tests__/interaction-recorder.test.js +392 -0
  14. package/src/health-tracking/errors.js +50 -0
  15. package/src/health-tracking/health-reporter.js +331 -0
  16. package/src/health-tracking/ide-health-tracker.js +446 -0
  17. package/src/health-tracking/interaction-recorder.js +161 -0
  18. package/src/health-tracking/json-storage.js +276 -0
  19. package/src/health-tracking/storage-interface.js +63 -0
  20. package/src/health-tracking/validators.js +277 -0
  21. package/src/ide-integration/applescript-manager.cjs +1087 -9
  22. package/src/ide-integration/applescript-manager.js +565 -15
  23. package/src/ide-integration/applescript-utils.js +26 -18
  24. package/src/ide-integration/provider-manager.cjs +158 -28
  25. package/src/ide-integration/quota-detector.cjs +339 -16
  26. package/src/ide-integration/quota-detector.js +6 -1
  27. package/src/index.cjs +36 -1
  28. package/src/index.js +20 -0
  29. package/src/localization/translations/en.js +15 -1
  30. package/src/localization/translations/es.js +14 -0
  31. package/src/requirement-numbering.js +164 -0
  32. package/src/sync/aws-setup.js +4 -4
  33. package/src/utils/admin-utils.js +33 -0
  34. package/src/utils/error-reporter.js +117 -0
  35. package/src/utils/git-branch-manager.js +278 -0
  36. package/src/utils/requirement-helpers.js +44 -5
  37. package/src/utils/requirements-parser.js +28 -3
  38. package/tests/health-tracking/health-reporter.test.js +329 -0
  39. package/tests/health-tracking/ide-health-tracker.test.js +368 -0
  40. package/tests/health-tracking/interaction-recorder.test.js +309 -0
@@ -0,0 +1,164 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Get metadata file path for storing requirement counter
6
+ * @param {string} repoPath - Path to repository
7
+ * @returns {string} Path to metadata file
8
+ */
9
+ function getMetadataPath(repoPath) {
10
+ return path.join(repoPath, '.vibecodingmachine', 'requirement-metadata.json');
11
+ }
12
+
13
+ /**
14
+ * Get the next requirement number for this repository
15
+ * @param {string} repoPath - Path to repository
16
+ * @returns {Promise<number>} Next requirement number
17
+ */
18
+ async function getNextRequirementNumber(repoPath) {
19
+ const metadataPath = getMetadataPath(repoPath);
20
+
21
+ try {
22
+ await fs.ensureFile(metadataPath);
23
+ const metadata = await fs.readJson(metadataPath).catch(() => ({ lastRequirementNumber: 0 }));
24
+
25
+ const nextNumber = (metadata.lastRequirementNumber || 0) + 1;
26
+
27
+ // Save the incremented number
28
+ metadata.lastRequirementNumber = nextNumber;
29
+ await fs.writeJson(metadataPath, metadata, { spaces: 2 });
30
+
31
+ return nextNumber;
32
+ } catch (error) {
33
+ // Default to 1 if there's any error
34
+ console.error('Error getting next requirement number:', error.message);
35
+ return 1;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Set the last requirement number (used during initialization)
41
+ * @param {string} repoPath - Path to repository
42
+ * @param {number} lastNumber - Last requirement number
43
+ */
44
+ async function setLastRequirementNumber(repoPath, lastNumber) {
45
+ const metadataPath = getMetadataPath(repoPath);
46
+
47
+ await fs.ensureFile(metadataPath);
48
+ const metadata = await fs.readJson(metadataPath).catch(() => ({}));
49
+ metadata.lastRequirementNumber = lastNumber;
50
+ await fs.writeJson(metadataPath, metadata, { spaces: 2 });
51
+ }
52
+
53
+ /**
54
+ * Extract requirement number from requirement title
55
+ * @param {string} title - Requirement title
56
+ * @returns {number|null} Requirement number or null if not found
57
+ */
58
+ function extractRequirementNumber(title) {
59
+ const match = title.match(/^R(\d+):/);
60
+ return match ? parseInt(match[1], 10) : null;
61
+ }
62
+
63
+ /**
64
+ * Number all existing requirements in a requirements file
65
+ * @param {string} reqPath - Path to requirements file
66
+ * @param {string} repoPath - Path to repository
67
+ * @returns {Promise<number>} Number of requirements numbered
68
+ */
69
+ async function numberAllRequirements(reqPath, repoPath) {
70
+ if (!await fs.pathExists(reqPath)) {
71
+ return 0;
72
+ }
73
+
74
+ const content = await fs.readFile(reqPath, 'utf8');
75
+ const lines = content.split('\n');
76
+ const newLines = [];
77
+
78
+ let requirementCount = 0;
79
+ let maxExistingNumber = 0;
80
+
81
+ // First pass: collect existing numbers and count requirements
82
+ for (const line of lines) {
83
+ if (line.trim().startsWith('###')) {
84
+ const title = line.trim().substring(3).trim();
85
+ const existingNumber = extractRequirementNumber(title);
86
+
87
+ if (existingNumber) {
88
+ maxExistingNumber = Math.max(maxExistingNumber, existingNumber);
89
+ } else {
90
+ // This requirement needs numbering
91
+ requirementCount++;
92
+ }
93
+ }
94
+ }
95
+
96
+ // Start numbering from max existing + 1
97
+ let currentNumber = maxExistingNumber + 1;
98
+ let numbered = 0;
99
+
100
+ // Second pass: add numbers to requirements that don't have them
101
+ for (const line of lines) {
102
+ if (line.trim().startsWith('###')) {
103
+ const title = line.trim().substring(3).trim();
104
+ const existingNumber = extractRequirementNumber(title);
105
+
106
+ if (!existingNumber) {
107
+ // Add number to this requirement
108
+ newLines.push(`### R${currentNumber}: ${title}`);
109
+ currentNumber++;
110
+ numbered++;
111
+ } else {
112
+ // Keep existing numbered requirement as-is
113
+ newLines.push(line);
114
+ }
115
+ } else {
116
+ newLines.push(line);
117
+ }
118
+ }
119
+
120
+ // Save the updated file
121
+ await fs.writeFile(reqPath, newLines.join('\n'));
122
+
123
+ // Update metadata with the highest number used
124
+ await setLastRequirementNumber(repoPath, currentNumber - 1);
125
+
126
+ return numbered;
127
+ }
128
+
129
+ /**
130
+ * Create a git branch name from requirement number and title
131
+ * @param {number} reqNumber - Requirement number
132
+ * @param {string} title - Requirement title
133
+ * @returns {string} Branch name (e.g., "R1_email_system")
134
+ */
135
+ function createBranchName(reqNumber, title) {
136
+ // Remove requirement number from title if present
137
+ let cleanTitle = title.replace(/^R\d+:\s*/, '').trim();
138
+
139
+ // Take first few words (up to 3-4 words, max 30 chars)
140
+ const words = cleanTitle.split(/\s+/);
141
+ let branchSuffix = '';
142
+
143
+ for (const word of words) {
144
+ const candidate = branchSuffix ? `${branchSuffix}_${word}` : word;
145
+ if (candidate.length > 30) break;
146
+ branchSuffix = candidate;
147
+ }
148
+
149
+ // Clean up branch suffix: lowercase, remove special chars
150
+ branchSuffix = branchSuffix
151
+ .toLowerCase()
152
+ .replace(/[^a-z0-9]+/g, '_')
153
+ .replace(/^_+|_+$/g, '');
154
+
155
+ return branchSuffix ? `R${reqNumber}_${branchSuffix}` : `R${reqNumber}`;
156
+ }
157
+
158
+ module.exports = {
159
+ getNextRequirementNumber,
160
+ setLastRequirementNumber,
161
+ extractRequirementNumber,
162
+ numberAllRequirements,
163
+ createBranchName
164
+ };
@@ -116,12 +116,12 @@ async function setupCognitoWithGoogle() {
116
116
 
117
117
  // Add Google OAuth provider if not already configured
118
118
  try {
119
- const googleClientId = process.env.GOOGLE_CLIENT_ID;
119
+ const googleClientId = process.env.REACT_APP_GOOGLE_CLIENT_ID;
120
120
  const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
121
121
 
122
122
  if (!googleClientId || !googleClientSecret) {
123
123
  console.log('⚠ Google OAuth credentials not provided. Skipping Google provider setup.');
124
- console.log(' Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables.');
124
+ console.log(' Set REACT_APP_GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables.');
125
125
  return { userPoolId: existingPoolId, status: 'existing' };
126
126
  }
127
127
 
@@ -198,7 +198,7 @@ async function setupCognitoWithGoogle() {
198
198
  console.log(`✓ Created Cognito User Pool: ${userPoolId}`);
199
199
 
200
200
  // Add Google OAuth provider first (if credentials provided)
201
- const googleClientId = process.env.GOOGLE_CLIENT_ID;
201
+ const googleClientId = process.env.REACT_APP_GOOGLE_CLIENT_ID;
202
202
  const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
203
203
 
204
204
  let supportedProviders = ['COGNITO'];
@@ -231,7 +231,7 @@ async function setupCognitoWithGoogle() {
231
231
  }
232
232
  } else {
233
233
  console.log('⚠ Google OAuth credentials not provided');
234
- console.log(' Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to enable Google login');
234
+ console.log(' Set REACT_APP_GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to enable Google login');
235
235
  }
236
236
 
237
237
  // Create app client
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Admin Utilities for VibeCodingMachine
3
+ */
4
+
5
+ const ADMIN_EMAILS = [
6
+ 'jesse.d.olsen@gmail.com'
7
+ ];
8
+
9
+ /**
10
+ * Check if an email belongs to an administrator
11
+ * @param {string} email - The email to check
12
+ * @returns {boolean} True if the email is an admin email
13
+ */
14
+ function isAdmin(email) {
15
+ if (!email) return false;
16
+ return ADMIN_EMAILS.includes(email.toLowerCase().trim());
17
+ }
18
+
19
+ /**
20
+ * Check if the application should automatically start implementation
21
+ * based on the user's email (admin feedback)
22
+ * @param {string} email - The email of the feedback sender
23
+ * @returns {boolean} True if auto-start should be triggered
24
+ */
25
+ function shouldAutoStart(email) {
26
+ return isAdmin(email);
27
+ }
28
+
29
+ module.exports = {
30
+ isAdmin,
31
+ shouldAutoStart,
32
+ ADMIN_EMAILS
33
+ };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Global Error Reporter for VibeCodingMachine
3
+ *
4
+ * Automatically reports errors to the admin database for tracking and fixing.
5
+ * This helps the admin know about issues and use VibeCodingMachine to fix itself.
6
+ */
7
+
8
+ class ErrorReporter {
9
+ constructor() {
10
+ this.enabled = true;
11
+ this.db = null;
12
+
13
+ try {
14
+ const UserDatabase = require('../database/user-schema');
15
+ this.db = new UserDatabase();
16
+ } catch (error) {
17
+ console.warn('ErrorReporter: Failed to initialize UserDatabase:', error.message);
18
+ this.enabled = false;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Set authentication token for error reporting
24
+ */
25
+ setAuthToken(token) {
26
+ if (this.db) {
27
+ this.db.setAuthToken(token);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Enable or disable error reporting
33
+ */
34
+ setEnabled(enabled) {
35
+ this.enabled = enabled;
36
+ }
37
+
38
+ /**
39
+ * Report an error to the admin database
40
+ */
41
+ async reportError(error, context = {}) {
42
+ if (!this.enabled) {
43
+ return false;
44
+ }
45
+
46
+ try {
47
+ const errorData = {
48
+ message: error.message || String(error),
49
+ stack: error.stack || '',
50
+ name: error.name || 'Error',
51
+ code: error.code || '',
52
+ context: {
53
+ ...context,
54
+ cwd: process.cwd(),
55
+ argv: process.argv.slice(2).filter(arg =>
56
+ !arg.includes('password') &&
57
+ !arg.includes('token') &&
58
+ !arg.includes('secret')
59
+ )
60
+ }
61
+ };
62
+
63
+ await this.db.reportError(errorData);
64
+ return true;
65
+ } catch (reportError) {
66
+ // Silently fail - don't block the application
67
+ console.warn('Failed to report error:', reportError.message);
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Wrap a function to automatically report errors
74
+ */
75
+ wrapFunction(fn, context = {}) {
76
+ return async (...args) => {
77
+ try {
78
+ return await fn(...args);
79
+ } catch (error) {
80
+ await this.reportError(error, {
81
+ ...context,
82
+ functionName: fn.name,
83
+ args: args.filter(arg =>
84
+ typeof arg !== 'object' ||
85
+ !JSON.stringify(arg).match(/password|token|secret/i)
86
+ )
87
+ });
88
+ throw error;
89
+ }
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Create a global error handler for uncaught exceptions
95
+ */
96
+ setupGlobalHandlers() {
97
+ process.on('uncaughtException', async (error) => {
98
+ await this.reportError(error, {
99
+ type: 'uncaughtException'
100
+ });
101
+ });
102
+
103
+ process.on('unhandledRejection', async (error) => {
104
+ await this.reportError(error, {
105
+ type: 'unhandledRejection'
106
+ });
107
+ });
108
+ }
109
+ }
110
+
111
+ // Singleton instance
112
+ const errorReporter = new ErrorReporter();
113
+
114
+ module.exports = {
115
+ ErrorReporter,
116
+ errorReporter
117
+ };
@@ -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
+ };