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
@@ -54,21 +54,21 @@ end tell`;
54
54
  try
55
55
  -- Use Cmd+L to focus AI panel (faster and more reliable)
56
56
  key code 37 using {command down} -- Cmd+L
57
- delay 1.5
57
+ delay 2.0
58
58
 
59
59
  -- Open new chat session with Cmd+T to prevent crashes
60
60
  key code 17 using {command down} -- Cmd+T
61
- delay 2.5
61
+ delay 3.0
62
62
 
63
63
  -- Skip clearing text to avoid selecting file content
64
64
  -- The chat input should be empty when opening new chat session
65
65
 
66
66
  -- Wait additional time for chat input field to be fully ready
67
- delay 1.0
67
+ delay 1.5
68
68
 
69
69
  -- Type the message
70
70
  keystroke "${escapedText}"
71
- delay 1.5
71
+ delay 2.0
72
72
 
73
73
  -- Send with Cmd+Enter (standard for chat interfaces)
74
74
  key code 36 using {command down}
@@ -86,33 +86,41 @@ end tell`;
86
86
  on error
87
87
  -- STRATEGY 2: Command Palette Approach (Fallback 1) - NO AI PANEL TOGGLE
88
88
  try
89
- -- Step 1: Open Command Palette (Cmd+Shift+P)
89
+ -- Step 1: Press Escape first to ensure we're starting clean
90
+ key code 53 -- Escape
91
+ delay 0.5
92
+
93
+ -- Step 2: Open Command Palette (Cmd+Shift+P)
90
94
  key code 35 using {command down, shift down} -- Cmd+Shift+P
91
- delay 0.8
95
+ delay 1.0
92
96
 
93
- -- Step 2: Type "View: Focus into Secondary Side Bar" to focus AI panel
97
+ -- Step 3: Type "View: Focus into Secondary Side Bar" to focus AI panel
94
98
  keystroke "View: Focus into Secondary Side Bar"
95
- delay 0.8
99
+ delay 1.0
96
100
 
97
- -- Step 3: Press Enter to focus AI Panel
101
+ -- Step 4: Press Enter to execute the command
98
102
  key code 36 -- Enter
99
- delay 2.0
103
+ delay 2.5
100
104
 
101
- -- Step 4: Open new chat session with Cmd+T to prevent crashes
105
+ -- Step 5: CRITICAL - Press Escape to close command palette
106
+ key code 53 -- Escape
107
+ delay 0.5
108
+
109
+ -- Step 6: Open new chat session with Cmd+T to prevent crashes
102
110
  key code 17 using {command down} -- Cmd+T
103
- delay 2.5
111
+ delay 3.0
104
112
 
105
- -- Step 5: Skip clearing text to avoid selecting file content
113
+ -- Step 7: Skip clearing text to avoid selecting file content
106
114
  -- The chat input should be empty when opening new chat session
107
115
 
108
- -- Step 6: Wait additional time for chat input field to be fully ready
109
- delay 1.0
116
+ -- Step 8: Wait additional time for chat input field to be fully ready
117
+ delay 1.5
110
118
 
111
- -- Step 7: Type the message
119
+ -- Step 9: Type the message
112
120
  keystroke "${escapedText}"
113
- delay 1.5
121
+ delay 2.0
114
122
 
115
- -- Step 8: Send with Cmd+Enter (standard for chat interfaces)
123
+ -- Step 10: Send with Cmd+Enter (standard for chat interfaces)
116
124
  key code 36 using {command down}
117
125
  delay 0.5
118
126
 
@@ -23,7 +23,7 @@ class ProviderManager {
23
23
  if (fs.existsSync(this.rateLimitFile)) {
24
24
  const data = fs.readFileSync(this.rateLimitFile, 'utf8');
25
25
  const limits = JSON.parse(data);
26
-
26
+
27
27
  // Auto-cleanup expired rate limits
28
28
  const now = Date.now();
29
29
  let cleaned = false;
@@ -33,12 +33,12 @@ class ProviderManager {
33
33
  cleaned = true;
34
34
  }
35
35
  }
36
-
36
+
37
37
  // Save cleaned data if any expired limits were removed
38
38
  if (cleaned) {
39
39
  this.saveRateLimitsInternal(limits);
40
40
  }
41
-
41
+
42
42
  return limits;
43
43
  }
44
44
  } catch (err) {
@@ -46,7 +46,7 @@ class ProviderManager {
46
46
  }
47
47
  return {};
48
48
  }
49
-
49
+
50
50
  /**
51
51
  * Internal save without reload (to prevent infinite loop during cleanup)
52
52
  */
@@ -86,33 +86,107 @@ class ProviderManager {
86
86
  * "Session limit reached ∙ resets 12pm" -> ms until 12pm today/tomorrow
87
87
  */
88
88
  parseRateLimitDuration(errorMessage) {
89
+ // Gemini / Google AI style: "You can resume using this model at 1/12/2026, 4:07:27 PM"
90
+ // Also seen as: "resume using this model at <date>, <time>"
91
+ const resumeAtMatch = errorMessage.match(/resume\s+(?:using\s+this\s+model\s+)?at\s+(\d{1,2}\/\d{1,2}\/\d{4})\s*,?\s*(\d{1,2}:\d{2}(?::\d{2})?)\s*(am|pm)/i);
92
+ if (resumeAtMatch) {
93
+ try {
94
+ const datePart = resumeAtMatch[1];
95
+ const timePart = resumeAtMatch[2];
96
+ const meridiem = resumeAtMatch[3];
97
+ const parsed = new Date(`${datePart}, ${timePart} ${meridiem.toUpperCase()}`);
98
+ if (!Number.isNaN(parsed.getTime())) {
99
+ const ms = parsed.getTime() - Date.now();
100
+ return ms > 0 ? ms : 0;
101
+ }
102
+ } catch (_) { }
103
+ }
104
+
105
+ // Check for "resets [Month] [Day] at [Time]" format (e.g. "Spending cap reached resets Jan 17 at 12pm")
106
+ const dateMatch = errorMessage.match(/resets\s+([a-zA-Z]+)\s+(\d{1,2})\s+at\s+(\d{1,2}(?::\d{2})?)\s*(am|pm)?/i);
107
+ if (dateMatch) {
108
+ try {
109
+ const monthStr = dateMatch[1]; // Jan, February, etc.
110
+ const day = parseInt(dateMatch[2]);
111
+ const timeStr = dateMatch[3];
112
+ const meridiem = dateMatch[4] ? dateMatch[4].toLowerCase() : null;
113
+
114
+ // Parse time
115
+ let [hours, minutes] = timeStr.split(':').map(n => parseInt(n));
116
+ if (!minutes || Number.isNaN(minutes)) minutes = 0;
117
+
118
+ if (meridiem === 'pm' && hours !== 12) hours += 12;
119
+ if (meridiem === 'am' && hours === 12) hours = 0;
120
+
121
+ // Parse month
122
+ const months = {
123
+ jan: 0, january: 0,
124
+ feb: 1, february: 1,
125
+ mar: 2, march: 2,
126
+ apr: 3, april: 3,
127
+ may: 4,
128
+ jun: 5, june: 5,
129
+ jul: 6, july: 6,
130
+ aug: 7, august: 7,
131
+ sep: 8, sept: 8, september: 8,
132
+ oct: 9, october: 9,
133
+ nov: 10, november: 10,
134
+ dec: 11, december: 11
135
+ };
136
+
137
+ const monthIndex = months[monthStr.toLowerCase().substring(0, 3)] ?? months[monthStr.toLowerCase()];
138
+
139
+ if (monthIndex !== undefined) {
140
+ const currentYear = new Date().getFullYear();
141
+ let parsed = new Date(currentYear, monthIndex, day, hours, minutes);
142
+ const now = Date.now();
143
+
144
+ // Logic to handle year crossing (reset in Jan, currently Dec)
145
+ if (parsed.getTime() < now) {
146
+ if (!Number.isNaN(parsed.getTime())) {
147
+ if (parsed.getTime() < now) {
148
+ const nextYearDate = new Date(parsed);
149
+ nextYearDate.setFullYear(nextYearDate.getFullYear() + 1);
150
+ if (nextYearDate.getTime() > now) {
151
+ parsed = nextYearDate;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ const ms = parsed.getTime() - now;
158
+ return ms > 0 ? ms : 0;
159
+ }
160
+ } catch (_) { }
161
+ }
162
+
89
163
  // Check for Claude Code session limit format: "Session limit reached ∙ resets 12pm"
90
164
  const sessionMatch = errorMessage.match(/resets?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i);
91
165
  if (sessionMatch) {
92
166
  let hour = parseInt(sessionMatch[1]);
93
167
  const minute = sessionMatch[2] ? parseInt(sessionMatch[2]) : 0;
94
168
  const meridiem = sessionMatch[3] ? sessionMatch[3].toLowerCase() : null;
95
-
169
+
96
170
  // Convert to 24-hour format
97
171
  if (meridiem === 'pm' && hour !== 12) {
98
172
  hour += 12;
99
173
  } else if (meridiem === 'am' && hour === 12) {
100
174
  hour = 0;
101
175
  }
102
-
176
+
103
177
  // Calculate reset time
104
178
  const now = new Date();
105
179
  const resetTime = new Date(now);
106
180
  resetTime.setHours(hour, minute, 0, 0);
107
-
181
+
108
182
  // If reset time is in the past, it's tomorrow
109
183
  if (resetTime <= now) {
110
184
  resetTime.setDate(resetTime.getDate() + 1);
111
185
  }
112
-
186
+
113
187
  return resetTime.getTime() - now.getTime();
114
188
  }
115
-
189
+
116
190
  // Match patterns like "15m5.472s" or "1h30m" or "45s"
117
191
  const timeMatch = errorMessage.match(/try again in ([\d.]+h)?\s?([\d.]+m)?\s?([\d.]+s)?/i);
118
192
  if (!timeMatch) return null;
@@ -147,8 +221,11 @@ class ProviderManager {
147
221
  * @param {string} errorMessage - Full error message containing duration
148
222
  */
149
223
  markRateLimited(provider, model, errorMessage) {
224
+ // Normalize model: if model is missing/undefined, assume it's the provider name.
225
+ model = model || provider;
226
+
150
227
  let duration = this.parseRateLimitDuration(errorMessage);
151
- if (!duration) {
228
+ if (duration === null || duration === undefined) {
152
229
  console.warn(`Could not parse rate limit duration from: ${errorMessage}`);
153
230
  // Default to 15 minutes if we can't parse
154
231
  duration = 15 * 60 * 1000;
@@ -162,6 +239,7 @@ class ProviderManager {
162
239
  model,
163
240
  resetTime,
164
241
  resetDate: new Date(resetTime).toISOString(),
242
+ reason: errorMessage,
165
243
  markedAt: new Date().toISOString()
166
244
  };
167
245
 
@@ -196,17 +274,28 @@ class ProviderManager {
196
274
  const key = `${provider}:${model}`;
197
275
  const limit = this.rateLimits[key];
198
276
 
199
- if (!limit) return false;
277
+ if (limit) {
278
+ // Check if rate limit has expired
279
+ if (Date.now() >= limit.resetTime) {
280
+ // Clean up expired rate limit
281
+ delete this.rateLimits[key];
282
+ this.saveRateLimits();
283
+ return false;
284
+ }
200
285
 
201
- // Check if rate limit has expired
202
- if (Date.now() >= limit.resetTime) {
203
- // Clean up expired rate limit
204
- delete this.rateLimits[key];
205
- this.saveRateLimits();
206
- return false;
286
+ return true;
207
287
  }
208
288
 
209
- return true;
289
+ // Fall back to any provider-level entries (handles older or model-agnostic keys)
290
+ const providerPrefix = `${provider}:`;
291
+ for (const k in this.rateLimits) {
292
+ if (k.startsWith(providerPrefix)) {
293
+ const l = this.rateLimits[k];
294
+ if (l && Date.now() < l.resetTime) return true;
295
+ }
296
+ }
297
+
298
+ return false;
210
299
  }
211
300
 
212
301
  /**
@@ -252,6 +341,31 @@ class ProviderManager {
252
341
  reason: limit.reason
253
342
  };
254
343
  }
344
+
345
+ // Fallback: check other keys for this provider and return earliest reset
346
+ const providerPrefix = `${provider}:`;
347
+ let earliestReset = null;
348
+ let reason = null;
349
+
350
+ for (const k in this.rateLimits) {
351
+ if (k.startsWith(providerPrefix)) {
352
+ const l = this.rateLimits[k];
353
+ if (l && Date.now() < l.resetTime) {
354
+ if (!earliestReset || l.resetTime < earliestReset) {
355
+ earliestReset = l.resetTime;
356
+ reason = l.reason;
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ if (earliestReset) {
363
+ return {
364
+ isRateLimited: true,
365
+ resetTime: earliestReset,
366
+ reason
367
+ };
368
+ }
255
369
  }
256
370
 
257
371
  return {
@@ -285,9 +399,25 @@ class ProviderManager {
285
399
  const key = `${provider}:${model}`;
286
400
  const limit = this.rateLimits[key];
287
401
 
288
- if (!limit) return null;
402
+ if (limit && Date.now() < limit.resetTime) {
403
+ const remaining = limit.resetTime - Date.now();
404
+ return remaining > 0 ? remaining : null;
405
+ }
289
406
 
290
- const remaining = limit.resetTime - Date.now();
407
+ // Fallback: check other keys for this provider (handles legacy or model-agnostic entries)
408
+ const providerPrefix = `${provider}:`;
409
+ let earliest = null;
410
+ for (const k in this.rateLimits) {
411
+ if (k.startsWith(providerPrefix)) {
412
+ const l = this.rateLimits[k];
413
+ if (l && Date.now() < l.resetTime) {
414
+ if (!earliest || l.resetTime < earliest) earliest = l.resetTime;
415
+ }
416
+ }
417
+ }
418
+
419
+ if (!earliest) return null;
420
+ const remaining = earliest - Date.now();
291
421
  return remaining > 0 ? remaining : null;
292
422
  }
293
423
 
@@ -356,7 +486,7 @@ class ProviderManager {
356
486
  */
357
487
  recordPerformance(provider, model, durationMs) {
358
488
  const key = `${provider}:${model}`;
359
-
489
+
360
490
  if (!this.performance[key]) {
361
491
  this.performance[key] = {
362
492
  provider,
@@ -365,19 +495,19 @@ class ProviderManager {
365
495
  avgSpeed: 0
366
496
  };
367
497
  }
368
-
498
+
369
499
  const perf = this.performance[key];
370
500
  perf.samples.push(durationMs);
371
-
501
+
372
502
  // Keep only last 10 samples
373
503
  if (perf.samples.length > 10) {
374
504
  perf.samples.shift();
375
505
  }
376
-
506
+
377
507
  // Calculate average
378
508
  perf.avgSpeed = perf.samples.reduce((a, b) => a + b, 0) / perf.samples.length;
379
509
  perf.lastUsed = Date.now();
380
-
510
+
381
511
  this.savePerformance();
382
512
  }
383
513
 
@@ -388,10 +518,10 @@ class ProviderManager {
388
518
  */
389
519
  getFastestAvailable(providers) {
390
520
  const available = providers.filter(p => !this.isRateLimited(p.provider, p.model));
391
-
521
+
392
522
  if (available.length === 0) return null;
393
523
  if (available.length === 1) return available[0];
394
-
524
+
395
525
  // Sort by average speed (fastest first)
396
526
  available.sort((a, b) => {
397
527
  const aKey = `${a.provider}:${a.model}`;
@@ -402,7 +532,7 @@ class ProviderManager {
402
532
  const bSpeed = (bAvg ?? b.estimatedSpeed ?? Infinity);
403
533
  return aSpeed - bSpeed;
404
534
  });
405
-
535
+
406
536
  return available[0];
407
537
  }
408
538