aios-core 3.7.0 → 3.8.0

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 (47) 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/install-manifest.yaml +89 -25
  28. package/.aios-core/quality/metrics-collector.js +27 -0
  29. package/.aios-core/scripts/session-context-loader.js +13 -254
  30. package/.aios-core/utils/aios-validator.js +25 -0
  31. package/.aios-core/workflow-intelligence/__tests__/confidence-scorer.test.js +334 -0
  32. package/.aios-core/workflow-intelligence/__tests__/integration.test.js +337 -0
  33. package/.aios-core/workflow-intelligence/__tests__/suggestion-engine.test.js +431 -0
  34. package/.aios-core/workflow-intelligence/__tests__/wave-analyzer.test.js +458 -0
  35. package/.aios-core/workflow-intelligence/__tests__/workflow-registry.test.js +302 -0
  36. package/.aios-core/workflow-intelligence/engine/confidence-scorer.js +305 -0
  37. package/.aios-core/workflow-intelligence/engine/output-formatter.js +285 -0
  38. package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +603 -0
  39. package/.aios-core/workflow-intelligence/engine/wave-analyzer.js +676 -0
  40. package/.aios-core/workflow-intelligence/index.js +327 -0
  41. package/.aios-core/workflow-intelligence/learning/capture-hook.js +147 -0
  42. package/.aios-core/workflow-intelligence/learning/index.js +230 -0
  43. package/.aios-core/workflow-intelligence/learning/pattern-capture.js +340 -0
  44. package/.aios-core/workflow-intelligence/learning/pattern-store.js +498 -0
  45. package/.aios-core/workflow-intelligence/learning/pattern-validator.js +309 -0
  46. package/.aios-core/workflow-intelligence/registry/workflow-registry.js +358 -0
  47. package/package.json +1 -1
@@ -0,0 +1,458 @@
1
+ /**
2
+ * @fileoverview Unit tests for WaveAnalyzer
3
+ * @story WIS-4 - Wave Analysis Engine
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const {
9
+ WaveAnalyzer,
10
+ CircularDependencyError,
11
+ createWaveAnalyzer,
12
+ analyzeWaves,
13
+ DEFAULT_TASK_DURATIONS
14
+ } = require('../engine/wave-analyzer');
15
+
16
+ describe('WaveAnalyzer', () => {
17
+ let analyzer;
18
+
19
+ beforeEach(() => {
20
+ analyzer = new WaveAnalyzer();
21
+ });
22
+
23
+ describe('constructor', () => {
24
+ it('should create instance with default options', () => {
25
+ const instance = new WaveAnalyzer();
26
+ expect(instance.taskDurations).toEqual(DEFAULT_TASK_DURATIONS);
27
+ });
28
+
29
+ it('should accept custom task durations', () => {
30
+ const customDurations = { 'custom-task': 25 };
31
+ const instance = new WaveAnalyzer({ taskDurations: customDurations });
32
+ expect(instance.taskDurations['custom-task']).toBe(25);
33
+ expect(instance.taskDurations['default']).toBe(DEFAULT_TASK_DURATIONS.default);
34
+ });
35
+
36
+ it('should accept custom registry', () => {
37
+ const mockRegistry = { getWorkflow: jest.fn() };
38
+ const instance = new WaveAnalyzer({ registry: mockRegistry });
39
+ expect(instance.registry).toBe(mockRegistry);
40
+ });
41
+ });
42
+
43
+ describe('buildDependencyGraph', () => {
44
+ it('should build graph from tasks with dependencies', () => {
45
+ const tasks = [
46
+ { id: 'task-a', dependsOn: [] },
47
+ { id: 'task-b', dependsOn: [] },
48
+ { id: 'task-c', dependsOn: ['task-a', 'task-b'] },
49
+ { id: 'task-d', dependsOn: ['task-c'] }
50
+ ];
51
+
52
+ const graph = analyzer.buildDependencyGraph(tasks);
53
+
54
+ expect(graph.nodes.size).toBe(4);
55
+ expect(graph.nodes.has('task-a')).toBe(true);
56
+ expect(graph.nodes.has('task-b')).toBe(true);
57
+ expect(graph.nodes.has('task-c')).toBe(true);
58
+ expect(graph.nodes.has('task-d')).toBe(true);
59
+
60
+ // Check edges (a -> c, b -> c, c -> d)
61
+ expect(graph.edges.get('task-a').has('task-c')).toBe(true);
62
+ expect(graph.edges.get('task-b').has('task-c')).toBe(true);
63
+ expect(graph.edges.get('task-c').has('task-d')).toBe(true);
64
+
65
+ // Check in-edges
66
+ expect(graph.inEdges.get('task-a').size).toBe(0);
67
+ expect(graph.inEdges.get('task-b').size).toBe(0);
68
+ expect(graph.inEdges.get('task-c').size).toBe(2);
69
+ expect(graph.inEdges.get('task-d').size).toBe(1);
70
+ });
71
+
72
+ it('should handle tasks without dependencies', () => {
73
+ const tasks = [
74
+ { id: 'task-a' },
75
+ { id: 'task-b' },
76
+ { id: 'task-c' }
77
+ ];
78
+
79
+ const graph = analyzer.buildDependencyGraph(tasks);
80
+
81
+ expect(graph.nodes.size).toBe(3);
82
+ expect(graph.edges.get('task-a').size).toBe(0);
83
+ expect(graph.edges.get('task-b').size).toBe(0);
84
+ expect(graph.edges.get('task-c').size).toBe(0);
85
+ });
86
+
87
+ it('should use task name if id is missing', () => {
88
+ const tasks = [
89
+ { name: 'task-a' },
90
+ { name: 'task-b', dependsOn: ['task-a'] }
91
+ ];
92
+
93
+ const graph = analyzer.buildDependencyGraph(tasks);
94
+
95
+ expect(graph.nodes.has('task-a')).toBe(true);
96
+ expect(graph.nodes.has('task-b')).toBe(true);
97
+ expect(graph.edges.get('task-a').has('task-b')).toBe(true);
98
+ });
99
+
100
+ it('should ignore dependencies to non-existent nodes', () => {
101
+ const tasks = [
102
+ { id: 'task-a', dependsOn: ['non-existent'] }
103
+ ];
104
+
105
+ const graph = analyzer.buildDependencyGraph(tasks);
106
+
107
+ expect(graph.nodes.size).toBe(1);
108
+ expect(graph.inEdges.get('task-a').size).toBe(0);
109
+ });
110
+ });
111
+
112
+ describe('findCycle', () => {
113
+ it('should return null for acyclic graph', () => {
114
+ const tasks = [
115
+ { id: 'a', dependsOn: [] },
116
+ { id: 'b', dependsOn: ['a'] },
117
+ { id: 'c', dependsOn: ['b'] }
118
+ ];
119
+
120
+ const graph = analyzer.buildDependencyGraph(tasks);
121
+ const cycle = analyzer.findCycle(graph);
122
+
123
+ expect(cycle).toBeNull();
124
+ });
125
+
126
+ it('should detect simple cycle', () => {
127
+ const tasks = [
128
+ { id: 'a', dependsOn: ['c'] },
129
+ { id: 'b', dependsOn: ['a'] },
130
+ { id: 'c', dependsOn: ['b'] }
131
+ ];
132
+
133
+ const graph = analyzer.buildDependencyGraph(tasks);
134
+ const cycle = analyzer.findCycle(graph);
135
+
136
+ expect(cycle).not.toBeNull();
137
+ expect(cycle.length).toBeGreaterThan(1);
138
+ });
139
+
140
+ it('should detect self-loop', () => {
141
+ const tasks = [
142
+ { id: 'a', dependsOn: ['a'] }
143
+ ];
144
+
145
+ const graph = analyzer.buildDependencyGraph(tasks);
146
+ const cycle = analyzer.findCycle(graph);
147
+
148
+ expect(cycle).not.toBeNull();
149
+ expect(cycle).toContain('a');
150
+ });
151
+
152
+ it('should detect cycle in diamond pattern with back-edge', () => {
153
+ const tasks = [
154
+ { id: 'a', dependsOn: ['d'] }, // Creates cycle: a -> b -> d -> a
155
+ { id: 'b', dependsOn: ['a'] },
156
+ { id: 'c', dependsOn: ['a'] },
157
+ { id: 'd', dependsOn: ['b', 'c'] }
158
+ ];
159
+
160
+ const graph = analyzer.buildDependencyGraph(tasks);
161
+ const cycle = analyzer.findCycle(graph);
162
+
163
+ expect(cycle).not.toBeNull();
164
+ });
165
+ });
166
+
167
+ describe('analyzeWaves with customTasks', () => {
168
+ it('should group independent tasks into same wave', () => {
169
+ const tasks = [
170
+ { id: 'a', dependsOn: [], duration: 5 },
171
+ { id: 'b', dependsOn: [], duration: 3 },
172
+ { id: 'c', dependsOn: [], duration: 4 }
173
+ ];
174
+
175
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
176
+
177
+ expect(result.waves.length).toBe(1);
178
+ expect(result.waves[0].tasks).toEqual(expect.arrayContaining(['a', 'b', 'c']));
179
+ expect(result.waves[0].parallel).toBe(true);
180
+ });
181
+
182
+ it('should create sequential waves for linear dependencies', () => {
183
+ const tasks = [
184
+ { id: 'a', dependsOn: [], duration: 5 },
185
+ { id: 'b', dependsOn: ['a'], duration: 10 },
186
+ { id: 'c', dependsOn: ['b'], duration: 3 },
187
+ { id: 'd', dependsOn: ['c'], duration: 7 }
188
+ ];
189
+
190
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
191
+
192
+ expect(result.waves.length).toBe(4);
193
+ expect(result.waves[0].tasks).toEqual(['a']);
194
+ expect(result.waves[1].tasks).toEqual(['b']);
195
+ expect(result.waves[2].tasks).toEqual(['c']);
196
+ expect(result.waves[3].tasks).toEqual(['d']);
197
+ });
198
+
199
+ it('should handle diamond dependency pattern', () => {
200
+ const tasks = [
201
+ { id: 'a', dependsOn: [], duration: 5 },
202
+ { id: 'b', dependsOn: ['a'], duration: 10 },
203
+ { id: 'c', dependsOn: ['a'], duration: 8 },
204
+ { id: 'd', dependsOn: ['b', 'c'], duration: 5 }
205
+ ];
206
+
207
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
208
+
209
+ expect(result.waves.length).toBe(3);
210
+ expect(result.waves[0].tasks).toEqual(['a']);
211
+ expect(result.waves[1].tasks).toEqual(expect.arrayContaining(['b', 'c']));
212
+ expect(result.waves[1].parallel).toBe(true);
213
+ expect(result.waves[2].tasks).toEqual(['d']);
214
+ });
215
+
216
+ it('should handle mixed parallel and sequential', () => {
217
+ const tasks = [
218
+ { id: 'a', dependsOn: [], duration: 5 },
219
+ { id: 'b', dependsOn: [], duration: 3 },
220
+ { id: 'c', dependsOn: ['a'], duration: 10 },
221
+ { id: 'd', dependsOn: ['b'], duration: 8 },
222
+ { id: 'e', dependsOn: ['c', 'd'], duration: 5 }
223
+ ];
224
+
225
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
226
+
227
+ expect(result.waves.length).toBe(3);
228
+ expect(result.waves[0].tasks).toEqual(expect.arrayContaining(['a', 'b']));
229
+ expect(result.waves[1].tasks).toEqual(expect.arrayContaining(['c', 'd']));
230
+ expect(result.waves[2].tasks).toEqual(['e']);
231
+ });
232
+
233
+ it('should calculate optimization gain', () => {
234
+ const tasks = [
235
+ { id: 'a', dependsOn: [], duration: 10 },
236
+ { id: 'b', dependsOn: [], duration: 10 },
237
+ { id: 'c', dependsOn: ['a', 'b'], duration: 10 }
238
+ ];
239
+
240
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
241
+
242
+ // Sequential: 10 + 10 + 10 = 30min
243
+ // Parallel: 10 (wave 1: a,b parallel) + 10 (wave 2: c) = 20min
244
+ // Gain: (30 - 20) / 30 = 33%
245
+ expect(result.optimizationGain).toBe('33%');
246
+ });
247
+
248
+ it('should return empty result for empty tasks', () => {
249
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: [] });
250
+
251
+ expect(result.totalTasks).toBe(0);
252
+ expect(result.waves).toEqual([]);
253
+ expect(result.criticalPath).toEqual([]);
254
+ });
255
+
256
+ it('should handle single task workflow', () => {
257
+ const tasks = [{ id: 'a', dependsOn: [], duration: 5 }];
258
+
259
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
260
+
261
+ expect(result.totalTasks).toBe(1);
262
+ expect(result.waves.length).toBe(1);
263
+ expect(result.waves[0].parallel).toBe(false);
264
+ });
265
+
266
+ it('should throw CircularDependencyError for cycles', () => {
267
+ const tasks = [
268
+ { id: 'a', dependsOn: ['c'], duration: 5 },
269
+ { id: 'b', dependsOn: ['a'], duration: 5 },
270
+ { id: 'c', dependsOn: ['b'], duration: 5 }
271
+ ];
272
+
273
+ expect(() => {
274
+ analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
275
+ }).toThrow(CircularDependencyError);
276
+ });
277
+ });
278
+
279
+ describe('CircularDependencyError', () => {
280
+ it('should contain cycle information', () => {
281
+ const cycle = ['a', 'b', 'c', 'a'];
282
+ const error = new CircularDependencyError(cycle);
283
+
284
+ expect(error.name).toBe('CircularDependencyError');
285
+ expect(error.cycle).toEqual(cycle);
286
+ expect(error.message).toContain('a → b → c → a');
287
+ });
288
+
289
+ it('should provide resolution suggestion', () => {
290
+ const cycle = ['a', 'b', 'c', 'a'];
291
+ const error = new CircularDependencyError(cycle);
292
+
293
+ const suggestion = error.getSuggestion();
294
+ expect(suggestion).toBeTruthy();
295
+ expect(typeof suggestion).toBe('string');
296
+ });
297
+ });
298
+
299
+ describe('critical path calculation', () => {
300
+ it('should find longest path in graph', () => {
301
+ const tasks = [
302
+ { id: 'a', dependsOn: [], duration: 5 },
303
+ { id: 'b', dependsOn: ['a'], duration: 20 },
304
+ { id: 'c', dependsOn: ['a'], duration: 3 },
305
+ { id: 'd', dependsOn: ['b', 'c'], duration: 5 }
306
+ ];
307
+
308
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: tasks });
309
+
310
+ // Critical path should be a -> b -> d (5 + 20 + 5 = 30)
311
+ // Not a -> c -> d (5 + 3 + 5 = 13)
312
+ expect(result.criticalPath).toEqual(expect.arrayContaining(['a', 'b', 'd']));
313
+ });
314
+
315
+ it('should return empty for empty workflow', () => {
316
+ const result = analyzer.analyzeWaves('test-workflow', { customTasks: [] });
317
+ expect(result.criticalPath).toEqual([]);
318
+ });
319
+ });
320
+
321
+ describe('getCurrentWave', () => {
322
+ it('should return current wave context for task', () => {
323
+ // Mock the analyzeWaves to return predictable result
324
+ const tasks = [
325
+ { id: 'a', dependsOn: [], duration: 5 },
326
+ { id: 'b', dependsOn: ['a'], duration: 10 },
327
+ { id: 'c', dependsOn: ['b'], duration: 5 }
328
+ ];
329
+
330
+ const context = analyzer.getCurrentWave('test-workflow', 'b');
331
+
332
+ // Since we can't easily mock, this tests the error handling path
333
+ expect(context.workflowId).toBe('test-workflow');
334
+ expect(context.currentTask).toBe('b');
335
+ });
336
+ });
337
+
338
+ describe('formatOutput', () => {
339
+ const mockAnalysis = {
340
+ workflowId: 'test',
341
+ totalTasks: 3,
342
+ waves: [
343
+ { waveNumber: 1, tasks: ['a', 'b'], parallel: true, estimatedDuration: '5min' },
344
+ { waveNumber: 2, tasks: ['c'], parallel: false, estimatedDuration: '10min' }
345
+ ],
346
+ optimizationGain: '25%',
347
+ criticalPath: ['a', 'c'],
348
+ metrics: {
349
+ sequentialTime: '20min',
350
+ parallelTime: '15min'
351
+ }
352
+ };
353
+
354
+ it('should format as JSON when json option is true', () => {
355
+ const output = analyzer.formatOutput(mockAnalysis, { json: true });
356
+
357
+ expect(() => JSON.parse(output)).not.toThrow();
358
+ const parsed = JSON.parse(output);
359
+ expect(parsed.workflowId).toBe('test');
360
+ });
361
+
362
+ it('should include visual representation when visual option is true', () => {
363
+ const output = analyzer.formatOutput(mockAnalysis, { visual: true });
364
+
365
+ expect(output).toContain('Wave 1');
366
+ expect(output).toContain('──');
367
+ });
368
+
369
+ it('should include optimization metrics', () => {
370
+ const output = analyzer.formatOutput(mockAnalysis);
371
+
372
+ expect(output).toContain('25%');
373
+ expect(output).toContain('Critical Path');
374
+ });
375
+ });
376
+
377
+ describe('performance', () => {
378
+ it('should analyze small workflow in <10ms', () => {
379
+ const tasks = Array.from({ length: 5 }, (_, i) => ({
380
+ id: `task-${i}`,
381
+ dependsOn: i > 0 ? [`task-${i - 1}`] : [],
382
+ duration: 5
383
+ }));
384
+
385
+ const start = Date.now();
386
+ const result = analyzer.analyzeWaves('perf-test', { customTasks: tasks });
387
+ const elapsed = Date.now() - start;
388
+
389
+ expect(elapsed).toBeLessThan(10);
390
+ expect(result.metrics.analysisTime).toBeLessThan(10);
391
+ });
392
+
393
+ it('should analyze medium workflow in <30ms', () => {
394
+ const tasks = Array.from({ length: 20 }, (_, i) => ({
395
+ id: `task-${i}`,
396
+ dependsOn: i > 0 ? [`task-${Math.floor(i / 2)}`] : [],
397
+ duration: 5
398
+ }));
399
+
400
+ const start = Date.now();
401
+ const result = analyzer.analyzeWaves('perf-test', { customTasks: tasks });
402
+ const elapsed = Date.now() - start;
403
+
404
+ expect(elapsed).toBeLessThan(30);
405
+ });
406
+
407
+ it('should analyze large workflow in <50ms', () => {
408
+ const tasks = Array.from({ length: 50 }, (_, i) => ({
409
+ id: `task-${i}`,
410
+ dependsOn: i > 0 ? [`task-${Math.max(0, i - 3)}`] : [],
411
+ duration: 5
412
+ }));
413
+
414
+ const start = Date.now();
415
+ const result = analyzer.analyzeWaves('perf-test', { customTasks: tasks });
416
+ const elapsed = Date.now() - start;
417
+
418
+ expect(elapsed).toBeLessThan(50);
419
+ });
420
+ });
421
+ });
422
+
423
+ describe('Factory functions', () => {
424
+ describe('createWaveAnalyzer', () => {
425
+ it('should create WaveAnalyzer instance', () => {
426
+ const instance = createWaveAnalyzer();
427
+ expect(instance).toBeInstanceOf(WaveAnalyzer);
428
+ });
429
+
430
+ it('should pass options to constructor', () => {
431
+ const customDurations = { 'custom': 15 };
432
+ const instance = createWaveAnalyzer({ taskDurations: customDurations });
433
+ expect(instance.taskDurations.custom).toBe(15);
434
+ });
435
+ });
436
+
437
+ describe('analyzeWaves convenience function', () => {
438
+ it('should analyze waves without creating instance manually', () => {
439
+ const tasks = [
440
+ { id: 'a', dependsOn: [], duration: 5 },
441
+ { id: 'b', dependsOn: ['a'], duration: 10 }
442
+ ];
443
+
444
+ const result = analyzeWaves('test', { customTasks: tasks });
445
+
446
+ expect(result.workflowId).toBe('test');
447
+ expect(result.waves.length).toBe(2);
448
+ });
449
+ });
450
+ });
451
+
452
+ describe('DEFAULT_TASK_DURATIONS', () => {
453
+ it('should have expected default durations', () => {
454
+ expect(DEFAULT_TASK_DURATIONS.implement).toBe(30);
455
+ expect(DEFAULT_TASK_DURATIONS['write-tests']).toBe(10);
456
+ expect(DEFAULT_TASK_DURATIONS.default).toBe(10);
457
+ });
458
+ });