aios-core 3.7.0 → 3.9.1
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/.aios-core/core/session/context-detector.js +3 -0
- package/.aios-core/core/session/context-loader.js +154 -0
- package/.aios-core/data/learned-patterns.yaml +3 -0
- package/.aios-core/data/workflow-patterns.yaml +347 -3
- package/.aios-core/development/agents/dev.md +6 -0
- package/.aios-core/development/agents/squad-creator.md +30 -0
- package/.aios-core/development/scripts/squad/squad-analyzer.js +638 -0
- package/.aios-core/development/scripts/squad/squad-extender.js +871 -0
- package/.aios-core/development/scripts/squad/squad-generator.js +107 -19
- package/.aios-core/development/scripts/squad/squad-migrator.js +3 -5
- package/.aios-core/development/scripts/squad/squad-validator.js +98 -0
- package/.aios-core/development/tasks/next.md +294 -0
- package/.aios-core/development/tasks/patterns.md +334 -0
- package/.aios-core/development/tasks/squad-creator-analyze.md +315 -0
- package/.aios-core/development/tasks/squad-creator-create.md +26 -3
- package/.aios-core/development/tasks/squad-creator-extend.md +411 -0
- package/.aios-core/development/tasks/squad-creator-validate.md +9 -1
- package/.aios-core/development/tasks/waves.md +205 -0
- package/.aios-core/development/templates/squad/agent-template.md +69 -0
- package/.aios-core/development/templates/squad/checklist-template.md +82 -0
- package/.aios-core/development/templates/squad/data-template.yaml +105 -0
- package/.aios-core/development/templates/squad/script-template.js +179 -0
- package/.aios-core/development/templates/squad/task-template.md +125 -0
- package/.aios-core/development/templates/squad/template-template.md +97 -0
- package/.aios-core/development/templates/squad/tool-template.js +103 -0
- package/.aios-core/development/templates/squad/workflow-template.yaml +108 -0
- package/.aios-core/infrastructure/scripts/test-generator.js +8 -8
- package/.aios-core/infrastructure/scripts/test-quality-assessment.js +5 -5
- package/.aios-core/infrastructure/scripts/test-utilities.js +3 -3
- package/.aios-core/install-manifest.yaml +97 -33
- package/.aios-core/quality/metrics-collector.js +27 -0
- package/.aios-core/scripts/session-context-loader.js +13 -254
- package/.aios-core/scripts/test-template-system.js +6 -6
- package/.aios-core/utils/aios-validator.js +25 -0
- package/.aios-core/workflow-intelligence/__tests__/confidence-scorer.test.js +334 -0
- package/.aios-core/workflow-intelligence/__tests__/integration.test.js +337 -0
- package/.aios-core/workflow-intelligence/__tests__/suggestion-engine.test.js +431 -0
- package/.aios-core/workflow-intelligence/__tests__/wave-analyzer.test.js +458 -0
- package/.aios-core/workflow-intelligence/__tests__/workflow-registry.test.js +302 -0
- package/.aios-core/workflow-intelligence/engine/confidence-scorer.js +305 -0
- package/.aios-core/workflow-intelligence/engine/output-formatter.js +285 -0
- package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +603 -0
- package/.aios-core/workflow-intelligence/engine/wave-analyzer.js +676 -0
- package/.aios-core/workflow-intelligence/index.js +327 -0
- package/.aios-core/workflow-intelligence/learning/capture-hook.js +147 -0
- package/.aios-core/workflow-intelligence/learning/index.js +230 -0
- package/.aios-core/workflow-intelligence/learning/pattern-capture.js +340 -0
- package/.aios-core/workflow-intelligence/learning/pattern-store.js +498 -0
- package/.aios-core/workflow-intelligence/learning/pattern-validator.js +309 -0
- package/.aios-core/workflow-intelligence/registry/workflow-registry.js +358 -0
- package/package.json +1 -1
- package/src/installer/brownfield-upgrader.js +1 -1
- package/bin/aios-init.backup-v1.1.4.js +0 -352
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for SuggestionEngine
|
|
3
|
+
* @story WIS-3 - *next Task Implementation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
// Mock dependencies before requiring SuggestionEngine
|
|
12
|
+
jest.mock('../../core/session/context-loader', () => {
|
|
13
|
+
return jest.fn().mockImplementation(() => ({
|
|
14
|
+
loadContext: jest.fn().mockReturnValue({
|
|
15
|
+
sessionType: 'existing',
|
|
16
|
+
lastCommands: ['develop'],
|
|
17
|
+
currentStory: 'docs/stories/test-story.md',
|
|
18
|
+
workflowActive: 'story_development'
|
|
19
|
+
})
|
|
20
|
+
}));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
SuggestionEngine,
|
|
25
|
+
createSuggestionEngine,
|
|
26
|
+
SUGGESTION_CACHE_TTL,
|
|
27
|
+
LOW_CONFIDENCE_THRESHOLD
|
|
28
|
+
} = require('../engine/suggestion-engine');
|
|
29
|
+
|
|
30
|
+
describe('SuggestionEngine', () => {
|
|
31
|
+
let engine;
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
engine = createSuggestionEngine({ lazyLoad: true });
|
|
35
|
+
engine.invalidateCache();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
jest.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('constructor', () => {
|
|
43
|
+
it('should create engine with default options', () => {
|
|
44
|
+
const defaultEngine = createSuggestionEngine();
|
|
45
|
+
expect(defaultEngine).toBeInstanceOf(SuggestionEngine);
|
|
46
|
+
expect(defaultEngine.cacheTTL).toBe(SUGGESTION_CACHE_TTL);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should accept custom cache TTL', () => {
|
|
50
|
+
const customEngine = createSuggestionEngine({ cacheTTL: 60000 });
|
|
51
|
+
expect(customEngine.cacheTTL).toBe(60000);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should support lazy loading option', () => {
|
|
55
|
+
const lazyEngine = createSuggestionEngine({ lazyLoad: true });
|
|
56
|
+
expect(lazyEngine.lazyLoad).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('buildContext()', () => {
|
|
61
|
+
it('should return context object with required properties', async () => {
|
|
62
|
+
const context = await engine.buildContext({});
|
|
63
|
+
|
|
64
|
+
expect(context).toHaveProperty('agentId');
|
|
65
|
+
expect(context).toHaveProperty('lastCommand');
|
|
66
|
+
expect(context).toHaveProperty('lastCommands');
|
|
67
|
+
expect(context).toHaveProperty('storyPath');
|
|
68
|
+
expect(context).toHaveProperty('branch');
|
|
69
|
+
expect(context).toHaveProperty('projectState');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should use story override when provided', async () => {
|
|
73
|
+
// Create a temporary file for testing
|
|
74
|
+
const testStoryPath = path.join(process.cwd(), 'test-story-temp.md');
|
|
75
|
+
fs.writeFileSync(testStoryPath, '# Test Story');
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const context = await engine.buildContext({
|
|
79
|
+
storyOverride: testStoryPath
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(context.storyPath).toBe(testStoryPath);
|
|
83
|
+
} finally {
|
|
84
|
+
// Cleanup
|
|
85
|
+
if (fs.existsSync(testStoryPath)) {
|
|
86
|
+
fs.unlinkSync(testStoryPath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should use provided agent ID', async () => {
|
|
92
|
+
const context = await engine.buildContext({
|
|
93
|
+
agentId: 'qa'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(context.agentId).toBe('qa');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should detect git branch when in git repo', async () => {
|
|
100
|
+
const context = await engine.buildContext({});
|
|
101
|
+
|
|
102
|
+
// Branch should be detected or null (if not in git repo)
|
|
103
|
+
expect(context.branch === null || typeof context.branch === 'string').toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should build project state', async () => {
|
|
107
|
+
const context = await engine.buildContext({});
|
|
108
|
+
|
|
109
|
+
expect(context.projectState).toBeDefined();
|
|
110
|
+
expect(typeof context.projectState.activeStory).toBe('boolean');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('suggestNext()', () => {
|
|
115
|
+
it('should return result object with required properties', async () => {
|
|
116
|
+
const context = {
|
|
117
|
+
agentId: 'dev',
|
|
118
|
+
lastCommand: 'develop',
|
|
119
|
+
lastCommands: ['develop'],
|
|
120
|
+
storyPath: null,
|
|
121
|
+
branch: 'main',
|
|
122
|
+
projectState: {}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const result = await engine.suggestNext(context);
|
|
126
|
+
|
|
127
|
+
expect(result).toHaveProperty('workflow');
|
|
128
|
+
expect(result).toHaveProperty('currentState');
|
|
129
|
+
expect(result).toHaveProperty('confidence');
|
|
130
|
+
expect(result).toHaveProperty('suggestions');
|
|
131
|
+
expect(result).toHaveProperty('isUncertain');
|
|
132
|
+
expect(Array.isArray(result.suggestions)).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should return suggestions with proper structure', async () => {
|
|
136
|
+
const context = {
|
|
137
|
+
agentId: 'dev',
|
|
138
|
+
lastCommand: 'develop',
|
|
139
|
+
lastCommands: ['validate-story-draft', 'develop'],
|
|
140
|
+
storyPath: 'docs/stories/test.md',
|
|
141
|
+
branch: 'feature/test',
|
|
142
|
+
projectState: {}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const result = await engine.suggestNext(context);
|
|
146
|
+
|
|
147
|
+
if (result.suggestions.length > 0) {
|
|
148
|
+
const suggestion = result.suggestions[0];
|
|
149
|
+
expect(suggestion).toHaveProperty('command');
|
|
150
|
+
expect(suggestion).toHaveProperty('args');
|
|
151
|
+
expect(suggestion).toHaveProperty('description');
|
|
152
|
+
expect(suggestion).toHaveProperty('confidence');
|
|
153
|
+
expect(suggestion).toHaveProperty('priority');
|
|
154
|
+
expect(suggestion.command.startsWith('*')).toBe(true);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should mark low confidence results as uncertain', async () => {
|
|
159
|
+
const context = {
|
|
160
|
+
agentId: 'unknown',
|
|
161
|
+
lastCommand: 'random-command',
|
|
162
|
+
lastCommands: ['random-command'],
|
|
163
|
+
storyPath: null,
|
|
164
|
+
branch: null,
|
|
165
|
+
projectState: {}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const result = await engine.suggestNext(context);
|
|
169
|
+
|
|
170
|
+
// When no match, should be uncertain
|
|
171
|
+
if (result.confidence < LOW_CONFIDENCE_THRESHOLD) {
|
|
172
|
+
expect(result.isUncertain).toBe(true);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should use cache for repeated calls with same context', async () => {
|
|
177
|
+
const context = {
|
|
178
|
+
agentId: 'dev',
|
|
179
|
+
lastCommand: 'develop',
|
|
180
|
+
lastCommands: ['develop'],
|
|
181
|
+
storyPath: null,
|
|
182
|
+
branch: 'main',
|
|
183
|
+
projectState: {}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// First call
|
|
187
|
+
const result1 = await engine.suggestNext(context);
|
|
188
|
+
|
|
189
|
+
// Second call should use cache
|
|
190
|
+
const result2 = await engine.suggestNext(context);
|
|
191
|
+
|
|
192
|
+
expect(result1).toEqual(result2);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should invalidate cache on different context', async () => {
|
|
196
|
+
const context1 = {
|
|
197
|
+
agentId: 'dev',
|
|
198
|
+
lastCommand: 'develop',
|
|
199
|
+
lastCommands: ['develop'],
|
|
200
|
+
storyPath: null,
|
|
201
|
+
branch: 'main',
|
|
202
|
+
projectState: {}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const context2 = {
|
|
206
|
+
agentId: 'qa',
|
|
207
|
+
lastCommand: 'review-qa',
|
|
208
|
+
lastCommands: ['review-qa'],
|
|
209
|
+
storyPath: null,
|
|
210
|
+
branch: 'main',
|
|
211
|
+
projectState: {}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
await engine.suggestNext(context1);
|
|
215
|
+
const result2 = await engine.suggestNext(context2);
|
|
216
|
+
|
|
217
|
+
// Should get different result for different context
|
|
218
|
+
expect(result2.workflow).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('getFallbackSuggestions()', () => {
|
|
223
|
+
it('should return fallback for dev agent', () => {
|
|
224
|
+
const result = engine.getFallbackSuggestions({ agentId: 'dev' });
|
|
225
|
+
|
|
226
|
+
expect(result.suggestions.length).toBeGreaterThan(0);
|
|
227
|
+
expect(result.isUncertain).toBe(true);
|
|
228
|
+
expect(result.suggestions.some(s => s.command === '*help')).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should return fallback for po agent', () => {
|
|
232
|
+
const result = engine.getFallbackSuggestions({ agentId: 'po' });
|
|
233
|
+
|
|
234
|
+
expect(result.suggestions.length).toBeGreaterThan(0);
|
|
235
|
+
expect(result.suggestions.some(s => s.command.includes('backlog') || s.command.includes('story'))).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should return fallback for qa agent', () => {
|
|
239
|
+
const result = engine.getFallbackSuggestions({ agentId: 'qa' });
|
|
240
|
+
|
|
241
|
+
expect(result.suggestions.length).toBeGreaterThan(0);
|
|
242
|
+
expect(result.suggestions.some(s => s.command.includes('test') || s.command.includes('qa'))).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should return default fallback for unknown agent', () => {
|
|
246
|
+
const result = engine.getFallbackSuggestions({ agentId: 'unknown' });
|
|
247
|
+
|
|
248
|
+
expect(result.suggestions.length).toBeGreaterThan(0);
|
|
249
|
+
expect(result.suggestions.some(s => s.command === '*help')).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('invalidateCache()', () => {
|
|
254
|
+
it('should clear cached suggestions', async () => {
|
|
255
|
+
const context = {
|
|
256
|
+
agentId: 'dev',
|
|
257
|
+
lastCommand: 'develop',
|
|
258
|
+
lastCommands: ['develop'],
|
|
259
|
+
storyPath: null,
|
|
260
|
+
branch: 'main',
|
|
261
|
+
projectState: {}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Prime the cache
|
|
265
|
+
await engine.suggestNext(context);
|
|
266
|
+
|
|
267
|
+
// Invalidate
|
|
268
|
+
engine.invalidateCache();
|
|
269
|
+
|
|
270
|
+
// Cache should be null
|
|
271
|
+
expect(engine.suggestionCache).toBeNull();
|
|
272
|
+
expect(engine.cacheTimestamp).toBeNull();
|
|
273
|
+
expect(engine.cacheKey).toBeNull();
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('_interpolateArgs()', () => {
|
|
278
|
+
it('should interpolate story_path variable', () => {
|
|
279
|
+
const context = {
|
|
280
|
+
storyPath: 'docs/stories/test.md'
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const result = engine._interpolateArgs('${story_path}', context);
|
|
284
|
+
expect(result).toBe('docs/stories/test.md');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should interpolate branch variable', () => {
|
|
288
|
+
const context = {
|
|
289
|
+
branch: 'feature/test'
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const result = engine._interpolateArgs('${branch}', context);
|
|
293
|
+
expect(result).toBe('feature/test');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should handle missing variables', () => {
|
|
297
|
+
const context = {};
|
|
298
|
+
|
|
299
|
+
const result = engine._interpolateArgs('${story_path}', context);
|
|
300
|
+
expect(result).toBe('');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should handle null template', () => {
|
|
304
|
+
const result = engine._interpolateArgs(null, {});
|
|
305
|
+
expect(result).toBe('');
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('_resolveStoryPath()', () => {
|
|
310
|
+
it('should return null for non-existent file', () => {
|
|
311
|
+
const result = engine._resolveStoryPath('/non/existent/file.md');
|
|
312
|
+
expect(result).toBeNull();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should return resolved path for existing file', () => {
|
|
316
|
+
const testPath = path.join(process.cwd(), 'package.json');
|
|
317
|
+
const result = engine._resolveStoryPath(testPath);
|
|
318
|
+
expect(result).toBe(testPath);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should handle relative paths', () => {
|
|
322
|
+
const result = engine._resolveStoryPath('package.json');
|
|
323
|
+
expect(result).toBe(path.resolve(process.cwd(), 'package.json'));
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should return null for null input', () => {
|
|
327
|
+
const result = engine._resolveStoryPath(null);
|
|
328
|
+
expect(result).toBeNull();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('_generateCacheKey()', () => {
|
|
333
|
+
it('should generate consistent key for same context', () => {
|
|
334
|
+
const context = {
|
|
335
|
+
agentId: 'dev',
|
|
336
|
+
lastCommand: 'develop',
|
|
337
|
+
lastCommands: ['a', 'b', 'c'],
|
|
338
|
+
storyPath: 'test.md',
|
|
339
|
+
branch: 'main'
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const key1 = engine._generateCacheKey(context);
|
|
343
|
+
const key2 = engine._generateCacheKey(context);
|
|
344
|
+
|
|
345
|
+
expect(key1).toBe(key2);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should generate different key for different context', () => {
|
|
349
|
+
const context1 = {
|
|
350
|
+
agentId: 'dev',
|
|
351
|
+
lastCommand: 'develop'
|
|
352
|
+
};
|
|
353
|
+
const context2 = {
|
|
354
|
+
agentId: 'qa',
|
|
355
|
+
lastCommand: 'review'
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const key1 = engine._generateCacheKey(context1);
|
|
359
|
+
const key2 = engine._generateCacheKey(context2);
|
|
360
|
+
|
|
361
|
+
expect(key1).not.toBe(key2);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe('constants', () => {
|
|
366
|
+
it('should export SUGGESTION_CACHE_TTL', () => {
|
|
367
|
+
expect(SUGGESTION_CACHE_TTL).toBe(5 * 60 * 1000); // 5 minutes
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should export LOW_CONFIDENCE_THRESHOLD', () => {
|
|
371
|
+
expect(LOW_CONFIDENCE_THRESHOLD).toBe(0.50);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe('SuggestionEngine Performance', () => {
|
|
377
|
+
let engine;
|
|
378
|
+
|
|
379
|
+
beforeEach(() => {
|
|
380
|
+
engine = createSuggestionEngine();
|
|
381
|
+
engine.invalidateCache();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should complete suggestNext within 100ms', async () => {
|
|
385
|
+
const context = {
|
|
386
|
+
agentId: 'dev',
|
|
387
|
+
lastCommand: 'develop',
|
|
388
|
+
lastCommands: ['develop'],
|
|
389
|
+
storyPath: null,
|
|
390
|
+
branch: 'main',
|
|
391
|
+
projectState: {}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const start = Date.now();
|
|
395
|
+
await engine.suggestNext(context);
|
|
396
|
+
const duration = Date.now() - start;
|
|
397
|
+
|
|
398
|
+
expect(duration).toBeLessThan(100);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should complete buildContext within 50ms', async () => {
|
|
402
|
+
const start = Date.now();
|
|
403
|
+
await engine.buildContext({});
|
|
404
|
+
const duration = Date.now() - start;
|
|
405
|
+
|
|
406
|
+
expect(duration).toBeLessThan(50);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should be faster on cache hit', async () => {
|
|
410
|
+
const context = {
|
|
411
|
+
agentId: 'dev',
|
|
412
|
+
lastCommand: 'develop',
|
|
413
|
+
lastCommands: ['develop'],
|
|
414
|
+
storyPath: null,
|
|
415
|
+
branch: 'main',
|
|
416
|
+
projectState: {}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Cold start
|
|
420
|
+
const start1 = Date.now();
|
|
421
|
+
await engine.suggestNext(context);
|
|
422
|
+
const coldDuration = Date.now() - start1;
|
|
423
|
+
|
|
424
|
+
// Warm cache
|
|
425
|
+
const start2 = Date.now();
|
|
426
|
+
await engine.suggestNext(context);
|
|
427
|
+
const warmDuration = Date.now() - start2;
|
|
428
|
+
|
|
429
|
+
expect(warmDuration).toBeLessThanOrEqual(coldDuration);
|
|
430
|
+
});
|
|
431
|
+
});
|