vibecodingmachine-core 2026.1.3-2209 → 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 (32) hide show
  1. package/__tests__/provider-manager-fallback.test.js +43 -0
  2. package/__tests__/provider-manager-rate-limit.test.js +61 -0
  3. package/package.json +1 -1
  4. package/src/compliance/compliance-manager.js +5 -2
  5. package/src/database/migrations.js +135 -12
  6. package/src/database/user-database-client.js +63 -8
  7. package/src/database/user-schema.js +7 -0
  8. package/src/health-tracking/__tests__/ide-health-tracker.test.js +420 -0
  9. package/src/health-tracking/__tests__/interaction-recorder.test.js +392 -0
  10. package/src/health-tracking/errors.js +50 -0
  11. package/src/health-tracking/health-reporter.js +331 -0
  12. package/src/health-tracking/ide-health-tracker.js +446 -0
  13. package/src/health-tracking/interaction-recorder.js +161 -0
  14. package/src/health-tracking/json-storage.js +276 -0
  15. package/src/health-tracking/storage-interface.js +63 -0
  16. package/src/health-tracking/validators.js +277 -0
  17. package/src/ide-integration/applescript-manager.cjs +1062 -4
  18. package/src/ide-integration/applescript-manager.js +560 -11
  19. package/src/ide-integration/provider-manager.cjs +158 -28
  20. package/src/ide-integration/quota-detector.cjs +339 -16
  21. package/src/ide-integration/quota-detector.js +6 -1
  22. package/src/index.cjs +32 -1
  23. package/src/index.js +16 -0
  24. package/src/localization/translations/en.js +13 -1
  25. package/src/localization/translations/es.js +12 -0
  26. package/src/utils/admin-utils.js +33 -0
  27. package/src/utils/error-reporter.js +12 -4
  28. package/src/utils/requirement-helpers.js +34 -4
  29. package/src/utils/requirements-parser.js +3 -3
  30. package/tests/health-tracking/health-reporter.test.js +329 -0
  31. package/tests/health-tracking/ide-health-tracker.test.js +368 -0
  32. package/tests/health-tracking/interaction-recorder.test.js +309 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Unit tests for InteractionRecorder
3
+ * @jest-environment node
4
+ */
5
+
6
+ const { InteractionRecorder } = require('../interaction-recorder');
7
+ const { ValidationError } = require('../errors');
8
+
9
+ describe('InteractionRecorder', () => {
10
+ let recorder;
11
+ let mockStorage;
12
+
13
+ beforeEach(() => {
14
+ // Create mock storage
15
+ mockStorage = {
16
+ data: {
17
+ version: '1.0.0',
18
+ lastUpdated: new Date().toISOString(),
19
+ ides: {},
20
+ timeoutConfig: {},
21
+ defaultRequirement: null,
22
+ },
23
+ async read() {
24
+ return JSON.parse(JSON.stringify(this.data));
25
+ },
26
+ async write(data) {
27
+ this.data = JSON.parse(JSON.stringify(data));
28
+ },
29
+ };
30
+
31
+ recorder = new InteractionRecorder(mockStorage);
32
+ });
33
+
34
+ describe('record()', () => {
35
+ it('should record a success interaction', async () => {
36
+ const interaction = {
37
+ ideId: 'cursor',
38
+ timestamp: new Date(),
39
+ outcome: 'success',
40
+ responseTime: 120000,
41
+ timeoutUsed: 180000,
42
+ continuationPromptsDetected: 1,
43
+ requirementId: 'req-001',
44
+ errorMessage: null,
45
+ };
46
+
47
+ await recorder.record(interaction);
48
+
49
+ const data = await mockStorage.read();
50
+ expect(data.ides.cursor).toBeDefined();
51
+ expect(data.ides.cursor.interactions).toHaveLength(1);
52
+ expect(data.ides.cursor.interactions[0].outcome).toBe('success');
53
+ expect(data.ides.cursor.interactions[0].responseTime).toBe(120000);
54
+ });
55
+
56
+ it('should record a failure interaction', async () => {
57
+ const interaction = {
58
+ ideId: 'windsurf',
59
+ timestamp: new Date(),
60
+ outcome: 'failure',
61
+ responseTime: null,
62
+ timeoutUsed: 1800000,
63
+ continuationPromptsDetected: 0,
64
+ requirementId: 'req-002',
65
+ errorMessage: 'Timeout exceeded',
66
+ };
67
+
68
+ await recorder.record(interaction);
69
+
70
+ const data = await mockStorage.read();
71
+ expect(data.ides.windsurf.interactions).toHaveLength(1);
72
+ expect(data.ides.windsurf.interactions[0].outcome).toBe('failure');
73
+ expect(data.ides.windsurf.interactions[0].errorMessage).toBe('Timeout exceeded');
74
+ expect(data.ides.windsurf.interactions[0].responseTime).toBeNull();
75
+ });
76
+
77
+ it('should record a quota interaction', async () => {
78
+ const interaction = {
79
+ ideId: 'cursor',
80
+ timestamp: new Date(),
81
+ outcome: 'quota',
82
+ responseTime: null,
83
+ timeoutUsed: 1800000,
84
+ continuationPromptsDetected: 0,
85
+ requirementId: 'req-003',
86
+ errorMessage: 'Monthly quota exceeded',
87
+ };
88
+
89
+ await recorder.record(interaction);
90
+
91
+ const data = await mockStorage.read();
92
+ expect(data.ides.cursor.interactions).toHaveLength(1);
93
+ expect(data.ides.cursor.interactions[0].outcome).toBe('quota');
94
+ });
95
+
96
+ it('should convert Date timestamp to ISO string', async () => {
97
+ const timestamp = new Date('2025-01-21T12:00:00Z');
98
+ const interaction = {
99
+ ideId: 'cursor',
100
+ timestamp,
101
+ outcome: 'success',
102
+ responseTime: 120000,
103
+ timeoutUsed: 180000,
104
+ continuationPromptsDetected: 0,
105
+ requirementId: null,
106
+ errorMessage: null,
107
+ };
108
+
109
+ await recorder.record(interaction);
110
+
111
+ const data = await mockStorage.read();
112
+ expect(data.ides.cursor.interactions[0].timestamp).toBe('2025-01-21T12:00:00.000Z');
113
+ expect(typeof data.ides.cursor.interactions[0].timestamp).toBe('string');
114
+ });
115
+
116
+ it('should initialize IDE record if it does not exist', async () => {
117
+ const interaction = {
118
+ ideId: 'new-ide',
119
+ timestamp: new Date(),
120
+ outcome: 'success',
121
+ responseTime: 120000,
122
+ timeoutUsed: 180000,
123
+ continuationPromptsDetected: 0,
124
+ requirementId: null,
125
+ errorMessage: null,
126
+ };
127
+
128
+ await recorder.record(interaction);
129
+
130
+ const data = await mockStorage.read();
131
+ expect(data.ides['new-ide']).toBeDefined();
132
+ expect(data.ides['new-ide'].interactions).toHaveLength(1);
133
+ expect(data.ides['new-ide'].successCount).toBe(0);
134
+ expect(data.ides['new-ide'].failureCount).toBe(0);
135
+ });
136
+
137
+ it('should enforce max 100 interactions per IDE', async () => {
138
+ // Add 120 interactions
139
+ for (let i = 0; i < 120; i++) {
140
+ const interaction = {
141
+ ideId: 'cursor',
142
+ timestamp: new Date(),
143
+ outcome: 'success',
144
+ responseTime: 120000 + i,
145
+ timeoutUsed: 180000,
146
+ continuationPromptsDetected: 0,
147
+ requirementId: null,
148
+ errorMessage: null,
149
+ };
150
+ await recorder.record(interaction);
151
+ }
152
+
153
+ const data = await mockStorage.read();
154
+ expect(data.ides.cursor.interactions.length).toBeLessThanOrEqual(100);
155
+ });
156
+
157
+ it('should remove oldest interaction when exceeding max', async () => {
158
+ // Add interactions with distinct response times
159
+ for (let i = 0; i < 101; i++) {
160
+ const interaction = {
161
+ ideId: 'cursor',
162
+ timestamp: new Date(Date.now() + i * 1000),
163
+ outcome: 'success',
164
+ responseTime: 100000 + i,
165
+ timeoutUsed: 180000,
166
+ continuationPromptsDetected: 0,
167
+ requirementId: `req-${i}`,
168
+ errorMessage: null,
169
+ };
170
+ await recorder.record(interaction);
171
+ }
172
+
173
+ const data = await mockStorage.read();
174
+ expect(data.ides.cursor.interactions).toHaveLength(100);
175
+ // First interaction (100000ms) should be removed
176
+ const firstResponseTime = data.ides.cursor.interactions[0].responseTime;
177
+ expect(firstResponseTime).toBe(100001); // Second interaction is now first
178
+ });
179
+
180
+ it('should throw ValidationError for invalid outcome', async () => {
181
+ const interaction = {
182
+ ideId: 'cursor',
183
+ timestamp: new Date(),
184
+ outcome: 'invalid-outcome',
185
+ responseTime: 120000,
186
+ timeoutUsed: 180000,
187
+ continuationPromptsDetected: 0,
188
+ requirementId: null,
189
+ errorMessage: null,
190
+ };
191
+
192
+ await expect(recorder.record(interaction))
193
+ .rejects
194
+ .toThrow(ValidationError);
195
+ });
196
+
197
+ it('should throw ValidationError for empty ideId', async () => {
198
+ const interaction = {
199
+ ideId: '',
200
+ timestamp: new Date(),
201
+ outcome: 'success',
202
+ responseTime: 120000,
203
+ timeoutUsed: 180000,
204
+ continuationPromptsDetected: 0,
205
+ requirementId: null,
206
+ errorMessage: null,
207
+ };
208
+
209
+ await expect(recorder.record(interaction))
210
+ .rejects
211
+ .toThrow(ValidationError);
212
+ });
213
+
214
+ it('should throw ValidationError for negative responseTime', async () => {
215
+ const interaction = {
216
+ ideId: 'cursor',
217
+ timestamp: new Date(),
218
+ outcome: 'success',
219
+ responseTime: -1000,
220
+ timeoutUsed: 180000,
221
+ continuationPromptsDetected: 0,
222
+ requirementId: null,
223
+ errorMessage: null,
224
+ };
225
+
226
+ await expect(recorder.record(interaction))
227
+ .rejects
228
+ .toThrow(ValidationError);
229
+ });
230
+
231
+ it('should allow null responseTime for failures', async () => {
232
+ const interaction = {
233
+ ideId: 'cursor',
234
+ timestamp: new Date(),
235
+ outcome: 'failure',
236
+ responseTime: null,
237
+ timeoutUsed: 180000,
238
+ continuationPromptsDetected: 0,
239
+ requirementId: null,
240
+ errorMessage: 'Timeout',
241
+ };
242
+
243
+ await recorder.record(interaction);
244
+
245
+ const data = await mockStorage.read();
246
+ expect(data.ides.cursor.interactions[0].responseTime).toBeNull();
247
+ });
248
+
249
+ it('should handle multiple IDEs independently', async () => {
250
+ const cursorInteraction = {
251
+ ideId: 'cursor',
252
+ timestamp: new Date(),
253
+ outcome: 'success',
254
+ responseTime: 120000,
255
+ timeoutUsed: 180000,
256
+ continuationPromptsDetected: 0,
257
+ requirementId: null,
258
+ errorMessage: null,
259
+ };
260
+
261
+ const windsurfInteraction = {
262
+ ideId: 'windsurf',
263
+ timestamp: new Date(),
264
+ outcome: 'failure',
265
+ responseTime: null,
266
+ timeoutUsed: 1800000,
267
+ continuationPromptsDetected: 0,
268
+ requirementId: null,
269
+ errorMessage: 'Error',
270
+ };
271
+
272
+ await recorder.record(cursorInteraction);
273
+ await recorder.record(windsurfInteraction);
274
+
275
+ const data = await mockStorage.read();
276
+ expect(data.ides.cursor.interactions).toHaveLength(1);
277
+ expect(data.ides.windsurf.interactions).toHaveLength(1);
278
+ expect(data.ides.cursor.interactions[0].outcome).toBe('success');
279
+ expect(data.ides.windsurf.interactions[0].outcome).toBe('failure');
280
+ });
281
+ });
282
+
283
+ describe('getInteractions()', () => {
284
+ it('should retrieve interactions for a specific IDE', async () => {
285
+ const interaction1 = {
286
+ ideId: 'cursor',
287
+ timestamp: new Date(),
288
+ outcome: 'success',
289
+ responseTime: 120000,
290
+ timeoutUsed: 180000,
291
+ continuationPromptsDetected: 0,
292
+ requirementId: null,
293
+ errorMessage: null,
294
+ };
295
+
296
+ const interaction2 = {
297
+ ideId: 'cursor',
298
+ timestamp: new Date(),
299
+ outcome: 'failure',
300
+ responseTime: null,
301
+ timeoutUsed: 180000,
302
+ continuationPromptsDetected: 0,
303
+ requirementId: null,
304
+ errorMessage: 'Error',
305
+ };
306
+
307
+ await recorder.record(interaction1);
308
+ await recorder.record(interaction2);
309
+
310
+ const interactions = await recorder.getInteractions('cursor');
311
+ expect(interactions).toHaveLength(2);
312
+ expect(interactions[0].outcome).toBe('success');
313
+ expect(interactions[1].outcome).toBe('failure');
314
+ });
315
+
316
+ it('should return empty array for IDE with no interactions', async () => {
317
+ const interactions = await recorder.getInteractions('nonexistent-ide');
318
+ expect(interactions).toEqual([]);
319
+ });
320
+
321
+ it('should support limiting number of returned interactions', async () => {
322
+ // Add 15 interactions
323
+ for (let i = 0; i < 15; i++) {
324
+ await recorder.record({
325
+ ideId: 'cursor',
326
+ timestamp: new Date(),
327
+ outcome: 'success',
328
+ responseTime: 120000,
329
+ timeoutUsed: 180000,
330
+ continuationPromptsDetected: 0,
331
+ requirementId: null,
332
+ errorMessage: null,
333
+ });
334
+ }
335
+
336
+ const recentTen = await recorder.getInteractions('cursor', 10);
337
+ expect(recentTen).toHaveLength(10);
338
+ });
339
+ });
340
+
341
+ describe('clearInteractions()', () => {
342
+ it('should clear all interactions for a specific IDE', async () => {
343
+ await recorder.record({
344
+ ideId: 'cursor',
345
+ timestamp: new Date(),
346
+ outcome: 'success',
347
+ responseTime: 120000,
348
+ timeoutUsed: 180000,
349
+ continuationPromptsDetected: 0,
350
+ requirementId: null,
351
+ errorMessage: null,
352
+ });
353
+
354
+ await recorder.clearInteractions('cursor');
355
+
356
+ const interactions = await recorder.getInteractions('cursor');
357
+ expect(interactions).toEqual([]);
358
+ });
359
+
360
+ it('should not affect other IDEs when clearing one', async () => {
361
+ await recorder.record({
362
+ ideId: 'cursor',
363
+ timestamp: new Date(),
364
+ outcome: 'success',
365
+ responseTime: 120000,
366
+ timeoutUsed: 180000,
367
+ continuationPromptsDetected: 0,
368
+ requirementId: null,
369
+ errorMessage: null,
370
+ });
371
+
372
+ await recorder.record({
373
+ ideId: 'windsurf',
374
+ timestamp: new Date(),
375
+ outcome: 'success',
376
+ responseTime: 180000,
377
+ timeoutUsed: 180000,
378
+ continuationPromptsDetected: 0,
379
+ requirementId: null,
380
+ errorMessage: null,
381
+ });
382
+
383
+ await recorder.clearInteractions('cursor');
384
+
385
+ const cursorInteractions = await recorder.getInteractions('cursor');
386
+ const windsurfInteractions = await recorder.getInteractions('windsurf');
387
+
388
+ expect(cursorInteractions).toEqual([]);
389
+ expect(windsurfInteractions).toHaveLength(1);
390
+ });
391
+ });
392
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Custom Error Classes for Health Tracking Module
3
+ *
4
+ * @module errors
5
+ */
6
+
7
+ /**
8
+ * Thrown when input validation fails
9
+ * @extends Error
10
+ */
11
+ class ValidationError extends Error {
12
+ /**
13
+ * @param {string} message - Description of validation failure
14
+ * @param {string} field - Field name that failed validation
15
+ * @param {*} value - Invalid value
16
+ */
17
+ constructor(message, field, value) {
18
+ super(message);
19
+ this.name = 'ValidationError';
20
+ this.field = field;
21
+ this.value = value;
22
+ Error.captureStackTrace(this, this.constructor);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Thrown when file system operations fail
28
+ * @extends Error
29
+ */
30
+ class FileSystemError extends Error {
31
+ /**
32
+ * @param {string} message - Description of file system error
33
+ * @param {string} path - File path that caused the error
34
+ * @param {string} operation - Operation that failed ('read' | 'write' | 'delete' | 'backup' | 'restore')
35
+ * @param {Error} [originalError] - Original error that caused this error
36
+ */
37
+ constructor(message, path, operation, originalError) {
38
+ super(message);
39
+ this.name = 'FileSystemError';
40
+ this.path = path;
41
+ this.operation = operation;
42
+ this.originalError = originalError;
43
+ Error.captureStackTrace(this, this.constructor);
44
+ }
45
+ }
46
+
47
+ module.exports = {
48
+ ValidationError,
49
+ FileSystemError,
50
+ };