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.
- package/__tests__/provider-manager-fallback.test.js +43 -0
- package/__tests__/provider-manager-rate-limit.test.js +61 -0
- package/package.json +1 -1
- package/src/compliance/compliance-manager.js +5 -2
- package/src/database/migrations.js +135 -12
- package/src/database/user-database-client.js +63 -8
- package/src/database/user-schema.js +7 -0
- package/src/health-tracking/__tests__/ide-health-tracker.test.js +420 -0
- package/src/health-tracking/__tests__/interaction-recorder.test.js +392 -0
- package/src/health-tracking/errors.js +50 -0
- package/src/health-tracking/health-reporter.js +331 -0
- package/src/health-tracking/ide-health-tracker.js +446 -0
- package/src/health-tracking/interaction-recorder.js +161 -0
- package/src/health-tracking/json-storage.js +276 -0
- package/src/health-tracking/storage-interface.js +63 -0
- package/src/health-tracking/validators.js +277 -0
- package/src/ide-integration/applescript-manager.cjs +1062 -4
- package/src/ide-integration/applescript-manager.js +560 -11
- package/src/ide-integration/provider-manager.cjs +158 -28
- package/src/ide-integration/quota-detector.cjs +339 -16
- package/src/ide-integration/quota-detector.js +6 -1
- package/src/index.cjs +32 -1
- package/src/index.js +16 -0
- package/src/localization/translations/en.js +13 -1
- package/src/localization/translations/es.js +12 -0
- package/src/utils/admin-utils.js +33 -0
- package/src/utils/error-reporter.js +12 -4
- package/src/utils/requirement-helpers.js +34 -4
- package/src/utils/requirements-parser.js +3 -3
- package/tests/health-tracking/health-reporter.test.js +329 -0
- package/tests/health-tracking/ide-health-tracker.test.js +368 -0
- package/tests/health-tracking/interaction-recorder.test.js +309 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for IDEHealthTracker
|
|
3
|
+
* @jest-environment node
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { IDEHealthTracker } = require('../ide-health-tracker');
|
|
10
|
+
const { ValidationError } = require('../errors');
|
|
11
|
+
|
|
12
|
+
describe('IDEHealthTracker', () => {
|
|
13
|
+
let tracker;
|
|
14
|
+
let testStorageFile;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Create temporary storage file for testing
|
|
18
|
+
testStorageFile = path.join(os.tmpdir(), `test-health-${Date.now()}.json`);
|
|
19
|
+
tracker = new IDEHealthTracker({
|
|
20
|
+
storageFile: testStorageFile,
|
|
21
|
+
autoSave: false, // Disable auto-save for testing
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
// Clean up test files
|
|
27
|
+
if (await fs.pathExists(testStorageFile)) {
|
|
28
|
+
await fs.remove(testStorageFile);
|
|
29
|
+
}
|
|
30
|
+
const backupFile = `${testStorageFile}.bak`;
|
|
31
|
+
if (await fs.pathExists(backupFile)) {
|
|
32
|
+
await fs.remove(backupFile);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('recordSuccess()', () => {
|
|
37
|
+
it('should increment success count on recordSuccess', async () => {
|
|
38
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
39
|
+
|
|
40
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
41
|
+
expect(metrics.successCount).toBe(1);
|
|
42
|
+
expect(metrics.failureCount).toBe(0);
|
|
43
|
+
expect(metrics.consecutiveFailures).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should reset consecutive failures on success after failure', async () => {
|
|
47
|
+
await tracker.recordFailure('cursor', 'Test error');
|
|
48
|
+
await tracker.recordFailure('cursor', 'Test error');
|
|
49
|
+
const metricsAfterFailures = await tracker.getHealthMetrics('cursor');
|
|
50
|
+
expect(metricsAfterFailures.consecutiveFailures).toBe(2);
|
|
51
|
+
|
|
52
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
53
|
+
const metricsAfterSuccess = await tracker.getHealthMetrics('cursor');
|
|
54
|
+
expect(metricsAfterSuccess.consecutiveFailures).toBe(0);
|
|
55
|
+
expect(metricsAfterSuccess.successCount).toBe(1);
|
|
56
|
+
expect(metricsAfterSuccess.failureCount).toBe(2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should add responseTime to responseTimes array', async () => {
|
|
60
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
61
|
+
await tracker.recordSuccess('cursor', 115000);
|
|
62
|
+
await tracker.recordSuccess('cursor', 125000);
|
|
63
|
+
|
|
64
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
65
|
+
expect(metrics.successCount).toBe(3);
|
|
66
|
+
// Note: We can't directly access responseTimes in metrics,
|
|
67
|
+
// but we can verify EWMA was calculated
|
|
68
|
+
expect(metrics.averageResponseTime).toBeGreaterThan(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should enforce max 50 response times', async () => {
|
|
72
|
+
// Add 60 response times
|
|
73
|
+
for (let i = 0; i < 60; i++) {
|
|
74
|
+
await tracker.recordSuccess('cursor', 120000 + i * 100);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Verify it doesn't throw and counts are correct
|
|
78
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
79
|
+
expect(metrics.successCount).toBe(60);
|
|
80
|
+
// Internal responseTimes array should be capped at 50
|
|
81
|
+
// This will be verified in internal state
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should update lastSuccess timestamp', async () => {
|
|
85
|
+
const beforeTime = new Date().toISOString();
|
|
86
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
87
|
+
const afterTime = new Date().toISOString();
|
|
88
|
+
|
|
89
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
90
|
+
expect(metrics.lastSuccess).not.toBeNull();
|
|
91
|
+
expect(metrics.lastSuccess >= beforeTime).toBe(true);
|
|
92
|
+
expect(metrics.lastSuccess <= afterTime).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should throw ValidationError if ideId is empty', async () => {
|
|
96
|
+
await expect(tracker.recordSuccess('', 120000))
|
|
97
|
+
.rejects
|
|
98
|
+
.toThrow(ValidationError);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should throw ValidationError if responseTime is negative', async () => {
|
|
102
|
+
await expect(tracker.recordSuccess('cursor', -1000))
|
|
103
|
+
.rejects
|
|
104
|
+
.toThrow(ValidationError);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should accept metadata with continuationPromptsDetected', async () => {
|
|
108
|
+
await tracker.recordSuccess('cursor', 120000, {
|
|
109
|
+
continuationPromptsDetected: 2,
|
|
110
|
+
requirementId: 'req-042',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
114
|
+
expect(metrics.successCount).toBe(1);
|
|
115
|
+
// Interaction record should have continuationPromptsDetected
|
|
116
|
+
expect(metrics.recentInteractions).toHaveLength(1);
|
|
117
|
+
expect(metrics.recentInteractions[0].continuationPromptsDetected).toBe(2);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should recalculate EWMA on each success', async () => {
|
|
121
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
122
|
+
const metrics1 = await tracker.getHealthMetrics('cursor');
|
|
123
|
+
const ewma1 = metrics1.averageResponseTime;
|
|
124
|
+
|
|
125
|
+
await tracker.recordSuccess('cursor', 150000);
|
|
126
|
+
const metrics2 = await tracker.getHealthMetrics('cursor');
|
|
127
|
+
const ewma2 = metrics2.averageResponseTime;
|
|
128
|
+
|
|
129
|
+
// EWMA should have changed
|
|
130
|
+
expect(ewma2).not.toBe(ewma1);
|
|
131
|
+
expect(ewma2).toBeGreaterThan(ewma1);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should emit health-updated event on success', (done) => {
|
|
135
|
+
tracker.on('health-updated', ({ ideId, outcome }) => {
|
|
136
|
+
expect(ideId).toBe('cursor');
|
|
137
|
+
expect(outcome).toBe('success');
|
|
138
|
+
done();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
tracker.recordSuccess('cursor', 120000);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('recordFailure()', () => {
|
|
146
|
+
it('should increment failure count on recordFailure', async () => {
|
|
147
|
+
await tracker.recordFailure('cursor', 'Timeout exceeded');
|
|
148
|
+
|
|
149
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
150
|
+
expect(metrics.successCount).toBe(0);
|
|
151
|
+
expect(metrics.failureCount).toBe(1);
|
|
152
|
+
expect(metrics.consecutiveFailures).toBe(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should increment consecutive failures on multiple failures', async () => {
|
|
156
|
+
await tracker.recordFailure('cursor', 'Error 1');
|
|
157
|
+
await tracker.recordFailure('cursor', 'Error 2');
|
|
158
|
+
await tracker.recordFailure('cursor', 'Error 3');
|
|
159
|
+
|
|
160
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
161
|
+
expect(metrics.failureCount).toBe(3);
|
|
162
|
+
expect(metrics.consecutiveFailures).toBe(3);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should emit consecutive-failures event at threshold 5', (done) => {
|
|
166
|
+
tracker.on('consecutive-failures', ({ ideId, count, lastError }) => {
|
|
167
|
+
expect(ideId).toBe('cursor');
|
|
168
|
+
expect(count).toBe(5);
|
|
169
|
+
expect(lastError).toBe('Error 5');
|
|
170
|
+
done();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
(async () => {
|
|
174
|
+
for (let i = 1; i <= 5; i++) {
|
|
175
|
+
await tracker.recordFailure('cursor', `Error ${i}`);
|
|
176
|
+
}
|
|
177
|
+
})();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should update lastFailure timestamp', async () => {
|
|
181
|
+
const beforeTime = new Date().toISOString();
|
|
182
|
+
await tracker.recordFailure('cursor', 'Test error');
|
|
183
|
+
const afterTime = new Date().toISOString();
|
|
184
|
+
|
|
185
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
186
|
+
expect(metrics.lastFailure).not.toBeNull();
|
|
187
|
+
expect(metrics.lastFailure >= beforeTime).toBe(true);
|
|
188
|
+
expect(metrics.lastFailure <= afterTime).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should accept metadata with timeoutUsed', async () => {
|
|
192
|
+
await tracker.recordFailure('cursor', 'Timeout', {
|
|
193
|
+
timeoutUsed: 1800000,
|
|
194
|
+
requirementId: 'req-043',
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
198
|
+
expect(metrics.failureCount).toBe(1);
|
|
199
|
+
expect(metrics.recentInteractions).toHaveLength(1);
|
|
200
|
+
expect(metrics.recentInteractions[0].errorMessage).toBe('Timeout');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should emit health-updated event on failure', (done) => {
|
|
204
|
+
tracker.on('health-updated', ({ ideId, outcome }) => {
|
|
205
|
+
expect(ideId).toBe('cursor');
|
|
206
|
+
expect(outcome).toBe('failure');
|
|
207
|
+
done();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
tracker.recordFailure('cursor', 'Test error');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('recordQuota()', () => {
|
|
215
|
+
it('should NOT increment success or failure counters on quota', async () => {
|
|
216
|
+
await tracker.recordQuota('cursor', 'Monthly chat messages quota exceeded');
|
|
217
|
+
|
|
218
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
219
|
+
expect(metrics.successCount).toBe(0);
|
|
220
|
+
expect(metrics.failureCount).toBe(0);
|
|
221
|
+
expect(metrics.consecutiveFailures).toBe(0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should add interaction record with outcome=quota', async () => {
|
|
225
|
+
await tracker.recordQuota('cursor', 'Quota exceeded', {
|
|
226
|
+
requirementId: 'req-044',
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
230
|
+
expect(metrics.recentInteractions).toHaveLength(1);
|
|
231
|
+
expect(metrics.recentInteractions[0].outcome).toBe('quota');
|
|
232
|
+
expect(metrics.recentInteractions[0].errorMessage).toBe('Quota exceeded');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should emit health-updated event with outcome=quota', (done) => {
|
|
236
|
+
tracker.on('health-updated', ({ ideId, outcome }) => {
|
|
237
|
+
expect(ideId).toBe('cursor');
|
|
238
|
+
expect(outcome).toBe('quota');
|
|
239
|
+
done();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
tracker.recordQuota('cursor', 'Quota message');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('getHealthMetrics()', () => {
|
|
247
|
+
it('should return metrics for specific IDE', async () => {
|
|
248
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
249
|
+
await tracker.recordFailure('cursor', 'Error');
|
|
250
|
+
|
|
251
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
252
|
+
expect(metrics.successCount).toBe(1);
|
|
253
|
+
expect(metrics.failureCount).toBe(1);
|
|
254
|
+
expect(metrics.successRate).toBeCloseTo(0.5);
|
|
255
|
+
expect(metrics.totalInteractions).toBe(2);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should calculate success rate correctly', async () => {
|
|
259
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
260
|
+
await tracker.recordSuccess('cursor', 115000);
|
|
261
|
+
await tracker.recordSuccess('cursor', 125000);
|
|
262
|
+
await tracker.recordFailure('cursor', 'Error');
|
|
263
|
+
|
|
264
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
265
|
+
expect(metrics.successRate).toBeCloseTo(0.75); // 3/4 = 0.75
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should return recent 10 interactions', async () => {
|
|
269
|
+
// Add 15 interactions
|
|
270
|
+
for (let i = 0; i < 15; i++) {
|
|
271
|
+
if (i % 2 === 0) {
|
|
272
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
273
|
+
} else {
|
|
274
|
+
await tracker.recordFailure('cursor', 'Error');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
279
|
+
expect(metrics.recentInteractions).toHaveLength(10);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should return default metrics for unknown IDE', async () => {
|
|
283
|
+
const metrics = await tracker.getHealthMetrics('unknown-ide');
|
|
284
|
+
expect(metrics.successCount).toBe(0);
|
|
285
|
+
expect(metrics.failureCount).toBe(0);
|
|
286
|
+
expect(metrics.successRate).toBe(0);
|
|
287
|
+
expect(metrics.totalInteractions).toBe(0);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('getAllHealthMetrics()', () => {
|
|
292
|
+
it('should return metrics for all tracked IDEs', async () => {
|
|
293
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
294
|
+
await tracker.recordSuccess('windsurf', 180000);
|
|
295
|
+
await tracker.recordFailure('vscode', 'Error');
|
|
296
|
+
|
|
297
|
+
const allMetrics = await tracker.getAllHealthMetrics();
|
|
298
|
+
expect(allMetrics.size).toBe(3);
|
|
299
|
+
expect(allMetrics.has('cursor')).toBe(true);
|
|
300
|
+
expect(allMetrics.has('windsurf')).toBe(true);
|
|
301
|
+
expect(allMetrics.has('vscode')).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should return empty Map if no IDEs tracked', async () => {
|
|
305
|
+
const allMetrics = await tracker.getAllHealthMetrics();
|
|
306
|
+
expect(allMetrics.size).toBe(0);
|
|
307
|
+
expect(allMetrics instanceof Map).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('getRecommendedIDE()', () => {
|
|
312
|
+
it('should return IDE with highest success rate', async () => {
|
|
313
|
+
// Cursor: 80% success (4/5)
|
|
314
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
315
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
316
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
317
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
318
|
+
await tracker.recordFailure('cursor', 'Error');
|
|
319
|
+
|
|
320
|
+
// Windsurf: 60% success (3/5)
|
|
321
|
+
await tracker.recordSuccess('windsurf', 180000);
|
|
322
|
+
await tracker.recordSuccess('windsurf', 180000);
|
|
323
|
+
await tracker.recordSuccess('windsurf', 180000);
|
|
324
|
+
await tracker.recordFailure('windsurf', 'Error');
|
|
325
|
+
await tracker.recordFailure('windsurf', 'Error');
|
|
326
|
+
|
|
327
|
+
const recommended = await tracker.getRecommendedIDE({ minInteractions: 5 });
|
|
328
|
+
expect(recommended).toBe('cursor');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should return null if no IDEs meet minInteractions threshold', async () => {
|
|
332
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
333
|
+
|
|
334
|
+
const recommended = await tracker.getRecommendedIDE({ minInteractions: 10 });
|
|
335
|
+
expect(recommended).toBeNull();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should exclude IDEs below minInteractions threshold', async () => {
|
|
339
|
+
// Cursor: only 2 interactions
|
|
340
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
341
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
342
|
+
|
|
343
|
+
// Windsurf: 12 interactions with 75% success
|
|
344
|
+
for (let i = 0; i < 12; i++) {
|
|
345
|
+
if (i < 9) {
|
|
346
|
+
await tracker.recordSuccess('windsurf', 180000);
|
|
347
|
+
} else {
|
|
348
|
+
await tracker.recordFailure('windsurf', 'Error');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const recommended = await tracker.getRecommendedIDE({ minInteractions: 10 });
|
|
353
|
+
expect(recommended).toBe('windsurf');
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('save() and load()', () => {
|
|
358
|
+
it('should persist health data across instances', async () => {
|
|
359
|
+
const tracker1 = new IDEHealthTracker({ storageFile: testStorageFile });
|
|
360
|
+
await tracker1.recordSuccess('cursor', 120000);
|
|
361
|
+
await tracker1.save();
|
|
362
|
+
|
|
363
|
+
const tracker2 = new IDEHealthTracker({ storageFile: testStorageFile });
|
|
364
|
+
await tracker2.load();
|
|
365
|
+
const metrics = await tracker2.getHealthMetrics('cursor');
|
|
366
|
+
|
|
367
|
+
expect(metrics.successCount).toBe(1);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should load default data if file does not exist', async () => {
|
|
371
|
+
const tracker2 = new IDEHealthTracker({ storageFile: testStorageFile });
|
|
372
|
+
await tracker2.load();
|
|
373
|
+
const metrics = await tracker2.getHealthMetrics('cursor');
|
|
374
|
+
|
|
375
|
+
expect(metrics.successCount).toBe(0);
|
|
376
|
+
expect(metrics.failureCount).toBe(0);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should throw FileSystemError on corrupted data without backup', async () => {
|
|
380
|
+
// Create corrupted file
|
|
381
|
+
await fs.writeFile(testStorageFile, 'invalid json{{{');
|
|
382
|
+
|
|
383
|
+
const tracker2 = new IDEHealthTracker({ storageFile: testStorageFile });
|
|
384
|
+
await expect(tracker2.load()).rejects.toThrow();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('autoSave with debouncing', () => {
|
|
389
|
+
it('should auto-save after debounce period when autoSave is enabled', async () => {
|
|
390
|
+
const autoSaveTracker = new IDEHealthTracker({
|
|
391
|
+
storageFile: testStorageFile,
|
|
392
|
+
autoSave: true,
|
|
393
|
+
debounceMs: 100,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
await autoSaveTracker.recordSuccess('cursor', 120000);
|
|
397
|
+
|
|
398
|
+
// Wait for debounce using Promise
|
|
399
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
400
|
+
|
|
401
|
+
const fileExists = await fs.pathExists(testStorageFile);
|
|
402
|
+
expect(fileExists).toBe(true);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
describe('interaction records', () => {
|
|
407
|
+
it('should enforce max 100 interactions per IDE', async () => {
|
|
408
|
+
// Add 120 interactions
|
|
409
|
+
for (let i = 0; i < 120; i++) {
|
|
410
|
+
await tracker.recordSuccess('cursor', 120000);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const metrics = await tracker.getHealthMetrics('cursor');
|
|
414
|
+
// Recent interactions should be capped at 10 (for display)
|
|
415
|
+
expect(metrics.recentInteractions).toHaveLength(10);
|
|
416
|
+
// Internal storage should be capped at 100
|
|
417
|
+
// This will be verified in implementation
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
});
|