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
@@ -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-]+\)$/);
@@ -547,6 +556,8 @@ async function getProjectRequirementStats(repoPath) {
547
556
  let todoCount = 0;
548
557
  let toVerifyCount = 0;
549
558
  let verifiedCount = 0;
559
+ let clarificationCount = 0;
560
+ let recycledCount = 0;
550
561
 
551
562
  if (reqPath && await fs.pathExists(reqPath)) {
552
563
  const content = await fs.readFile(reqPath, 'utf8');
@@ -562,15 +573,31 @@ async function getProjectRequirementStats(repoPath) {
562
573
  'Verified by AI screenshot'
563
574
  ];
564
575
 
576
+ const clarificationVariants = [
577
+ 'NEEDING CLARIFICATION',
578
+ 'Requirements that need information',
579
+ 'need information'
580
+ ];
581
+
582
+ const recycledVariants = [
583
+ '♻️ Recycled',
584
+ 'Recycled'
585
+ ];
586
+
565
587
  for (const line of lines) {
566
588
  const trimmed = line.trim();
567
589
 
568
590
  if (trimmed.startsWith('###')) {
569
591
  if (currentSection) {
570
- const requirementText = trimmed.replace(/^###\s*/, '').trim();
571
- if (requirementText) {
592
+ // Remove ALL leading ### markers including spaces between them (handles "###", "### ###", "#### ####", etc.)
593
+ const requirementText = trimmed.replace(/^(#{1,}\s*)+/, '').trim();
594
+ // Filter out empty titles and package names
595
+ const packageNames = ['cli', 'core', 'electron-app', 'web', 'mobile', 'vscode-extension', 'sync-server'];
596
+ if (requirementText && requirementText.length > 0 && !packageNames.includes(requirementText.toLowerCase())) {
572
597
  if (currentSection === 'todo') todoCount++;
573
598
  else if (currentSection === 'toverify') toVerifyCount++;
599
+ else if (currentSection === 'clarification') clarificationCount++;
600
+ else if (currentSection === 'recycled') recycledCount++;
574
601
  }
575
602
  }
576
603
  } else if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
@@ -579,6 +606,10 @@ async function getProjectRequirementStats(repoPath) {
579
606
  currentSection = 'todo';
580
607
  } else if (verifySectionVariants.some(v => trimmed.includes(v))) {
581
608
  currentSection = 'toverify';
609
+ } else if (clarificationVariants.some(v => trimmed.includes(v))) {
610
+ currentSection = 'clarification';
611
+ } else if (recycledVariants.some(v => trimmed.includes(v))) {
612
+ currentSection = 'recycled';
582
613
  } else {
583
614
  currentSection = '';
584
615
  }
@@ -590,22 +621,30 @@ async function getProjectRequirementStats(repoPath) {
590
621
  const verifiedReqs = await loadVerifiedFromChangelog(repoPath);
591
622
  verifiedCount = verifiedReqs.length;
592
623
 
593
- const total = todoCount + toVerifyCount + verifiedCount;
624
+ const total = todoCount + toVerifyCount + verifiedCount + clarificationCount + recycledCount;
594
625
  const todoPercent = total > 0 ? Math.round((todoCount / total) * 100) : 0;
595
626
  const toVerifyPercent = total > 0 ? Math.round((toVerifyCount / total) * 100) : 0;
596
627
  const verifiedPercent = total > 0 ? Math.round((verifiedCount / total) * 100) : 0;
628
+ const clarificationPercent = total > 0 ? Math.round((clarificationCount / total) * 100) : 0;
629
+ const recycledPercent = total > 0 ? Math.round((recycledCount / total) * 100) : 0;
597
630
 
598
631
  return {
599
632
  todoCount,
600
633
  toVerifyCount,
601
634
  verifiedCount,
635
+ clarificationCount,
636
+ recycledCount,
602
637
  total,
603
638
  todoPercent,
604
639
  toVerifyPercent,
605
640
  verifiedPercent,
641
+ clarificationPercent,
642
+ recycledPercent,
606
643
  todoLabel: `TODO (${todoCount} - ${todoPercent}%)`,
607
644
  toVerifyLabel: `TO VERIFY (${toVerifyCount} - ${toVerifyPercent}%)`,
608
- verifiedLabel: `VERIFIED (${verifiedCount} - ${verifiedPercent}%)`
645
+ verifiedLabel: `VERIFIED (${verifiedCount} - ${verifiedPercent}%)`,
646
+ clarificationLabel: `CLARIFICATION (${clarificationCount} - ${clarificationPercent}%)`,
647
+ recycledLabel: `RECYCLED (${recycledCount} - ${recycledPercent}%)`
609
648
  };
610
649
  } catch (error) {
611
650
  logger.error('❌ Error getting project requirement stats:', error);
@@ -112,9 +112,11 @@ function parseRequirementsFile(content) {
112
112
  }
113
113
 
114
114
  const details = [];
115
- let package = null;
115
+ const questions = [];
116
+ let pkg = 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++) {
@@ -127,7 +129,7 @@ function parseRequirementsFile(content) {
127
129
 
128
130
  // Check for PACKAGE line
129
131
  if (nextLine.startsWith('PACKAGE:')) {
130
- package = nextLine.replace(/^PACKAGE:\s*/, '').trim();
132
+ pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
131
133
  }
132
134
  // Check for OPTIONS line (for need information requirements)
133
135
  else if (nextLine.startsWith('OPTIONS:')) {
@@ -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
- package,
176
+ package: pkg,
153
177
  options,
154
178
  optionsType,
179
+ questions: currentSection === 'needInformation' ? questions : undefined,
155
180
  lineIndex: i
156
181
  };
157
182
 
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Unit Tests for HealthReporter
3
+ *
4
+ * Tests for formatting health data for CLI and Electron UI display.
5
+ */
6
+
7
+ const { HealthReporter } = require('../../../src/health-tracking/health-reporter');
8
+
9
+ describe('HealthReporter', () => {
10
+ let reporter;
11
+ let mockMetrics;
12
+
13
+ beforeEach(() => {
14
+ reporter = new HealthReporter();
15
+
16
+ mockMetrics = {
17
+ cursor: {
18
+ successCount: 42,
19
+ failureCount: 3,
20
+ successRate: 0.933,
21
+ averageResponseTime: 121500,
22
+ currentTimeout: 170100,
23
+ consecutiveFailures: 0,
24
+ lastSuccess: '2026-01-21T09:15:00.000Z',
25
+ lastFailure: '2026-01-20T23:45:00.000Z',
26
+ totalInteractions: 45,
27
+ recentInteractions: [
28
+ {
29
+ timestamp: '2026-01-21T09:15:00.000Z',
30
+ outcome: 'success',
31
+ responseTime: 120000,
32
+ timeoutUsed: 180000,
33
+ continuationPromptsDetected: 1,
34
+ requirementId: 'req-042',
35
+ errorMessage: null
36
+ }
37
+ ]
38
+ },
39
+ windsurf: {
40
+ successCount: 28,
41
+ failureCount: 15,
42
+ successRate: 0.651,
43
+ averageResponseTime: 1490000,
44
+ currentTimeout: 2086000,
45
+ consecutiveFailures: 2,
46
+ lastSuccess: '2026-01-21T08:30:00.000Z',
47
+ lastFailure: '2026-01-21T07:15:00.000Z',
48
+ totalInteractions: 43,
49
+ recentInteractions: [
50
+ {
51
+ timestamp: '2026-01-21T08:30:00.000Z',
52
+ outcome: 'failure',
53
+ responseTime: null,
54
+ timeoutUsed: 1800000,
55
+ continuationPromptsDetected: 0,
56
+ requirementId: 'req-041',
57
+ errorMessage: 'Timeout exceeded'
58
+ }
59
+ ]
60
+ }
61
+ };
62
+ });
63
+
64
+ describe('formatForCLI', () => {
65
+ it('should format health metrics for CLI display', () => {
66
+ const formatted = reporter.formatForCLI(mockMetrics);
67
+
68
+ expect(formatted).toContain('🖥️ Cursor');
69
+ expect(formatted).toContain('+42 -3');
70
+ expect(formatted).toContain('93.3%');
71
+ expect(formatted).toContain('2.0m');
72
+ expect(formatted).toContain('✅ Healthy');
73
+
74
+ expect(formatted).toContain('🌊 Windsurf');
75
+ expect(formatted).toContain('+28 -15');
76
+ expect(formatted).toContain('65.1%');
77
+ expect(formatted).toContain('24.9m');
78
+ expect(formatted).toContain('⚠️ 2 consecutive failures');
79
+ });
80
+
81
+ it('should handle empty metrics', () => {
82
+ const formatted = reporter.formatForCLI({});
83
+
84
+ expect(formatted).toContain('No IDE health data available');
85
+ });
86
+
87
+ it('should show warning for IDEs with consecutive failures', () => {
88
+ const metricsWithFailures = {
89
+ cursor: {
90
+ ...mockMetrics.cursor,
91
+ consecutiveFailures: 3
92
+ }
93
+ };
94
+
95
+ const formatted = reporter.formatForCLI(metricsWithFailures);
96
+
97
+ expect(formatted).toContain('⚠️ 3 consecutive failures');
98
+ expect(formatted).not.toContain('✅ Healthy');
99
+ });
100
+
101
+ it('should format timestamps relative to now', () => {
102
+ const now = new Date('2026-01-21T10:00:00.000Z');
103
+ const formatted = reporter.formatForCLI(mockMetrics, now);
104
+
105
+ expect(formatted).toContain('Last success: 45m ago');
106
+ expect(formatted).toContain('Last failure: 10h ago');
107
+ });
108
+
109
+ it('should show "Never" for null timestamps', () => {
110
+ const metricsWithNulls = {
111
+ cursor: {
112
+ ...mockMetrics.cursor,
113
+ lastSuccess: null,
114
+ lastFailure: null
115
+ }
116
+ };
117
+
118
+ const formatted = reporter.formatForCLI(metricsWithNulls);
119
+
120
+ expect(formatted).toContain('Last success: Never');
121
+ expect(formatted).toContain('Last failure: Never');
122
+ });
123
+ });
124
+
125
+ describe('formatForElectron', () => {
126
+ it('should format health metrics for Electron UI', () => {
127
+ const formatted = reporter.formatForElectron(mockMetrics);
128
+
129
+ expect(formatted).toHaveProperty('cursor');
130
+ expect(formatted).toHaveProperty('windsurf');
131
+
132
+ const cursorData = formatted.cursor;
133
+ expect(cursorData.ide).toBe('Cursor');
134
+ expect(cursorData.successCount).toBe(42);
135
+ expect(cursorData.failureCount).toBe(3);
136
+ expect(cursorData.successRate).toBe(93.3);
137
+ expect(cursorData.averageResponseTime).toBe('2.0m');
138
+ expect(cursorData.currentTimeout).toBe('2.8m');
139
+ expect(cursorData.status).toBe('healthy');
140
+ expect(cursorData.lastSuccess).toBe('45m ago');
141
+ expect(cursorData.lastFailure).toBe('10h ago');
142
+ });
143
+
144
+ it('should include recent interactions in Electron format', () => {
145
+ const formatted = reporter.formatForElectron(mockMetrics);
146
+
147
+ const cursorData = formatted.cursor;
148
+ expect(cursorData.recentInteractions).toHaveLength(1);
149
+
150
+ const interaction = cursorData.recentInteractions[0];
151
+ expect(interaction.outcome).toBe('success');
152
+ expect(interaction.displayTime).toBe('2.0m');
153
+ expect(interaction.requirementId).toBe('req-042');
154
+ });
155
+
156
+ it('should handle IDEs with no interactions', () => {
157
+ const metricsWithNoInteractions = {
158
+ vscode: {
159
+ successCount: 0,
160
+ failureCount: 0,
161
+ successRate: 0,
162
+ averageResponseTime: 0,
163
+ currentTimeout: 1800000,
164
+ consecutiveFailures: 0,
165
+ lastSuccess: null,
166
+ lastFailure: null,
167
+ totalInteractions: 0,
168
+ recentInteractions: []
169
+ }
170
+ };
171
+
172
+ const formatted = reporter.formatForElectron(metricsWithNoInteractions);
173
+
174
+ const vscodeData = formatted.vscode;
175
+ expect(vscodeData.status).toBe('no-data');
176
+ expect(vscodeData.successRate).toBe(0);
177
+ expect(vscodeData.averageResponseTime).toBe('N/A');
178
+ });
179
+
180
+ it('should format response times in human-readable units', () => {
181
+ const formatted = reporter.formatForElectron(mockMetrics);
182
+
183
+ expect(formatted.cursor.averageResponseTime).toBe('2.0m'); // 121.5s
184
+ expect(formatted.windsurf.averageResponseTime).toBe('24.9m'); // 1490s
185
+ expect(formatted.cursor.currentTimeout).toBe('2.8m'); // 170.1s
186
+ expect(formatted.windsurf.currentTimeout).toBe('34.8m'); // 2086s
187
+ });
188
+ });
189
+
190
+ describe('generateSummary', () => {
191
+ it('should generate overall health summary', () => {
192
+ const summary = reporter.generateSummary(mockMetrics);
193
+
194
+ expect(summary.totalIDEs).toBe(2);
195
+ expect(summary.healthyIDEs).toBe(1);
196
+ expect(summary.problematicIDEs).toBe(1);
197
+ expect(summary.totalInteractions).toBe(88);
198
+ expect(summary.overallSuccessRate).toBe(79.5);
199
+ expect(summary.averageResponseTime).toBe(805750); // Weighted average
200
+ expect(summary.recommendedIDE).toBe('cursor');
201
+ });
202
+
203
+ it('should handle empty metrics in summary', () => {
204
+ const summary = reporter.generateSummary({});
205
+
206
+ expect(summary.totalIDEs).toBe(0);
207
+ expect(summary.healthyIDEs).toBe(0);
208
+ expect(summary.problematicIDEs).toBe(0);
209
+ expect(summary.totalInteractions).toBe(0);
210
+ expect(summary.overallSuccessRate).toBe(0);
211
+ expect(summary.averageResponseTime).toBe(0);
212
+ expect(summary.recommendedIDE).toBeNull();
213
+ });
214
+
215
+ it('should calculate weighted average response time', () => {
216
+ const summary = reporter.generateSummary(mockMetrics);
217
+
218
+ // Weighted average: (45 * 121500 + 43 * 1490000) / 88
219
+ const expected = (45 * 121500 + 43 * 1490000) / 88;
220
+ expect(summary.averageResponseTime).toBeCloseTo(expected, 0);
221
+ });
222
+
223
+ it('should identify recommended IDE based on success rate', () => {
224
+ const summary = reporter.generateSummary(mockMetrics);
225
+
226
+ expect(summary.recommendedIDE).toBe('cursor'); // 93.3% vs 65.1%
227
+ });
228
+
229
+ it('should return null for recommendation when no IDE has sufficient data', () => {
230
+ const metricsWithInsufficientData = {
231
+ cursor: {
232
+ successCount: 1,
233
+ failureCount: 0,
234
+ successRate: 1.0,
235
+ totalInteractions: 1
236
+ }
237
+ };
238
+
239
+ const summary = reporter.generateSummary(metricsWithInsufficientData);
240
+
241
+ expect(summary.recommendedIDE).toBeNull();
242
+ });
243
+ });
244
+
245
+ describe('formatResponseTime', () => {
246
+ it('should format milliseconds to human readable format', () => {
247
+ expect(reporter.formatResponseTime(500)).toBe('500ms');
248
+ expect(reporter.formatResponseTime(1500)).toBe('1.5s');
249
+ expect(reporter.formatResponseTime(60000)).toBe('1.0m');
250
+ expect(reporter.formatResponseTime(121500)).toBe('2.0m');
251
+ expect(reporter.formatResponseTime(3600000)).toBe('1.0h');
252
+ expect(reporter.formatResponseTime(7200000)).toBe('2.0h');
253
+ });
254
+
255
+ it('should handle zero and null values', () => {
256
+ expect(reporter.formatResponseTime(0)).toBe('0ms');
257
+ expect(reporter.formatResponseTime(null)).toBe('N/A');
258
+ expect(reporter.formatResponseTime(undefined)).toBe('N/A');
259
+ });
260
+ });
261
+
262
+ describe('formatTimestamp', () => {
263
+ it('should format timestamps relative to now', () => {
264
+ const now = new Date('2026-01-21T10:00:00.000Z');
265
+
266
+ expect(reporter.formatTimestamp('2026-01-21T09:15:00.000Z', now))
267
+ .toBe('45m ago');
268
+
269
+ expect(reporter.formatTimestamp('2026-01-21T08:00:00.000Z', now))
270
+ .toBe('2h ago');
271
+
272
+ expect(reporter.formatTimestamp('2026-01-20T10:00:00.000Z', now))
273
+ .toBe('24h ago');
274
+
275
+ expect(reporter.formatTimestamp('2026-01-19T10:00:00.000Z', now))
276
+ .toBe('2d ago');
277
+ });
278
+
279
+ it('should handle null timestamps', () => {
280
+ expect(reporter.formatTimestamp(null)).toBe('Never');
281
+ expect(reporter.formatTimestamp(undefined)).toBe('Never');
282
+ });
283
+
284
+ it('should use current time when no reference provided', () => {
285
+ const timestamp = new Date(Date.now() - 5 * 60 * 1000).toISOString(); // 5 minutes ago
286
+ const formatted = reporter.formatTimestamp(timestamp);
287
+
288
+ expect(formatted).toBe('5m ago');
289
+ });
290
+ });
291
+
292
+ describe('getHealthStatus', () => {
293
+ it('should return healthy for IDEs with no consecutive failures', () => {
294
+ const status = reporter.getHealthStatus({
295
+ consecutiveFailures: 0,
296
+ totalInteractions: 10
297
+ });
298
+
299
+ expect(status).toBe('healthy');
300
+ });
301
+
302
+ it('should return warning for IDEs with consecutive failures', () => {
303
+ const status = reporter.getHealthStatus({
304
+ consecutiveFailures: 3,
305
+ totalInteractions: 10
306
+ });
307
+
308
+ expect(status).toBe('warning');
309
+ });
310
+
311
+ it('should return critical for IDEs with many consecutive failures', () => {
312
+ const status = reporter.getHealthStatus({
313
+ consecutiveFailures: 6,
314
+ totalInteractions: 10
315
+ });
316
+
317
+ expect(status).toBe('critical');
318
+ });
319
+
320
+ it('should return no-data for IDEs with no interactions', () => {
321
+ const status = reporter.getHealthStatus({
322
+ consecutiveFailures: 0,
323
+ totalInteractions: 0
324
+ });
325
+
326
+ expect(status).toBe('no-data');
327
+ });
328
+ });
329
+ });