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.
Files changed (53) hide show
  1. package/.aios-core/core/session/context-detector.js +3 -0
  2. package/.aios-core/core/session/context-loader.js +154 -0
  3. package/.aios-core/data/learned-patterns.yaml +3 -0
  4. package/.aios-core/data/workflow-patterns.yaml +347 -3
  5. package/.aios-core/development/agents/dev.md +6 -0
  6. package/.aios-core/development/agents/squad-creator.md +30 -0
  7. package/.aios-core/development/scripts/squad/squad-analyzer.js +638 -0
  8. package/.aios-core/development/scripts/squad/squad-extender.js +871 -0
  9. package/.aios-core/development/scripts/squad/squad-generator.js +107 -19
  10. package/.aios-core/development/scripts/squad/squad-migrator.js +3 -5
  11. package/.aios-core/development/scripts/squad/squad-validator.js +98 -0
  12. package/.aios-core/development/tasks/next.md +294 -0
  13. package/.aios-core/development/tasks/patterns.md +334 -0
  14. package/.aios-core/development/tasks/squad-creator-analyze.md +315 -0
  15. package/.aios-core/development/tasks/squad-creator-create.md +26 -3
  16. package/.aios-core/development/tasks/squad-creator-extend.md +411 -0
  17. package/.aios-core/development/tasks/squad-creator-validate.md +9 -1
  18. package/.aios-core/development/tasks/waves.md +205 -0
  19. package/.aios-core/development/templates/squad/agent-template.md +69 -0
  20. package/.aios-core/development/templates/squad/checklist-template.md +82 -0
  21. package/.aios-core/development/templates/squad/data-template.yaml +105 -0
  22. package/.aios-core/development/templates/squad/script-template.js +179 -0
  23. package/.aios-core/development/templates/squad/task-template.md +125 -0
  24. package/.aios-core/development/templates/squad/template-template.md +97 -0
  25. package/.aios-core/development/templates/squad/tool-template.js +103 -0
  26. package/.aios-core/development/templates/squad/workflow-template.yaml +108 -0
  27. package/.aios-core/infrastructure/scripts/test-generator.js +8 -8
  28. package/.aios-core/infrastructure/scripts/test-quality-assessment.js +5 -5
  29. package/.aios-core/infrastructure/scripts/test-utilities.js +3 -3
  30. package/.aios-core/install-manifest.yaml +97 -33
  31. package/.aios-core/quality/metrics-collector.js +27 -0
  32. package/.aios-core/scripts/session-context-loader.js +13 -254
  33. package/.aios-core/scripts/test-template-system.js +6 -6
  34. package/.aios-core/utils/aios-validator.js +25 -0
  35. package/.aios-core/workflow-intelligence/__tests__/confidence-scorer.test.js +334 -0
  36. package/.aios-core/workflow-intelligence/__tests__/integration.test.js +337 -0
  37. package/.aios-core/workflow-intelligence/__tests__/suggestion-engine.test.js +431 -0
  38. package/.aios-core/workflow-intelligence/__tests__/wave-analyzer.test.js +458 -0
  39. package/.aios-core/workflow-intelligence/__tests__/workflow-registry.test.js +302 -0
  40. package/.aios-core/workflow-intelligence/engine/confidence-scorer.js +305 -0
  41. package/.aios-core/workflow-intelligence/engine/output-formatter.js +285 -0
  42. package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +603 -0
  43. package/.aios-core/workflow-intelligence/engine/wave-analyzer.js +676 -0
  44. package/.aios-core/workflow-intelligence/index.js +327 -0
  45. package/.aios-core/workflow-intelligence/learning/capture-hook.js +147 -0
  46. package/.aios-core/workflow-intelligence/learning/index.js +230 -0
  47. package/.aios-core/workflow-intelligence/learning/pattern-capture.js +340 -0
  48. package/.aios-core/workflow-intelligence/learning/pattern-store.js +498 -0
  49. package/.aios-core/workflow-intelligence/learning/pattern-validator.js +309 -0
  50. package/.aios-core/workflow-intelligence/registry/workflow-registry.js +358 -0
  51. package/package.json +1 -1
  52. package/src/installer/brownfield-upgrader.js +1 -1
  53. 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
+ });