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.
- 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/install-manifest.yaml +89 -25
- package/.aios-core/quality/metrics-collector.js +27 -0
- package/.aios-core/scripts/session-context-loader.js +13 -254
- 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
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for ConfidenceScorer
|
|
3
|
+
* @story WIS-2 - Workflow Registry Enhancement
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
ConfidenceScorer,
|
|
10
|
+
createConfidenceScorer,
|
|
11
|
+
SCORING_WEIGHTS
|
|
12
|
+
} = require('../engine/confidence-scorer');
|
|
13
|
+
|
|
14
|
+
describe('ConfidenceScorer', () => {
|
|
15
|
+
let scorer;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
scorer = createConfidenceScorer();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('constructor', () => {
|
|
22
|
+
it('should create scorer with default weights', () => {
|
|
23
|
+
const weights = scorer.getWeights();
|
|
24
|
+
expect(weights.COMMAND_MATCH).toBe(0.40);
|
|
25
|
+
expect(weights.AGENT_MATCH).toBe(0.25);
|
|
26
|
+
expect(weights.HISTORY_DEPTH).toBe(0.20);
|
|
27
|
+
expect(weights.PROJECT_STATE).toBe(0.15);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should accept custom weights', () => {
|
|
31
|
+
const customScorer = createConfidenceScorer({
|
|
32
|
+
weights: {
|
|
33
|
+
COMMAND_MATCH: 0.50,
|
|
34
|
+
AGENT_MATCH: 0.20,
|
|
35
|
+
HISTORY_DEPTH: 0.15,
|
|
36
|
+
PROJECT_STATE: 0.15
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const weights = customScorer.getWeights();
|
|
40
|
+
expect(weights.COMMAND_MATCH).toBe(0.50);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should throw error if weights do not sum to 1.0', () => {
|
|
44
|
+
expect(() => {
|
|
45
|
+
createConfidenceScorer({
|
|
46
|
+
weights: {
|
|
47
|
+
COMMAND_MATCH: 0.50,
|
|
48
|
+
AGENT_MATCH: 0.50,
|
|
49
|
+
HISTORY_DEPTH: 0.20,
|
|
50
|
+
PROJECT_STATE: 0.15
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}).toThrow('Scoring weights must sum to 1.0');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('score()', () => {
|
|
58
|
+
it('should return 0 for null suggestion', () => {
|
|
59
|
+
const result = scorer.score(null, { lastCommand: 'test' });
|
|
60
|
+
expect(result).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return 0 for null context', () => {
|
|
64
|
+
const result = scorer.score({ trigger: 'test' }, null);
|
|
65
|
+
expect(result).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return high score for exact command match', () => {
|
|
69
|
+
const suggestion = {
|
|
70
|
+
trigger: 'create-epic',
|
|
71
|
+
agentSequence: ['po', 'sm'],
|
|
72
|
+
keyCommands: ['create-epic', 'create-story']
|
|
73
|
+
};
|
|
74
|
+
const context = {
|
|
75
|
+
lastCommand: 'create-epic',
|
|
76
|
+
lastCommands: ['create-epic'],
|
|
77
|
+
agentId: '@po',
|
|
78
|
+
projectState: {}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = scorer.score(suggestion, context);
|
|
82
|
+
// Score is composed of: command(40%) + agent(25%) + history(20%) + state(15%)
|
|
83
|
+
expect(result).toBeGreaterThanOrEqual(0.70);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should return medium score for partial command match', () => {
|
|
87
|
+
const suggestion = {
|
|
88
|
+
trigger: 'create-epic',
|
|
89
|
+
agentSequence: ['po', 'sm'],
|
|
90
|
+
keyCommands: ['create-epic', 'create-story']
|
|
91
|
+
};
|
|
92
|
+
const context = {
|
|
93
|
+
lastCommand: 'create-story',
|
|
94
|
+
lastCommands: ['create-story'],
|
|
95
|
+
agentId: '@sm',
|
|
96
|
+
projectState: {}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = scorer.score(suggestion, context);
|
|
100
|
+
expect(result).toBeGreaterThan(0.40);
|
|
101
|
+
expect(result).toBeLessThan(0.80);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return low score for no command match', () => {
|
|
105
|
+
const suggestion = {
|
|
106
|
+
trigger: 'create-epic',
|
|
107
|
+
agentSequence: ['po', 'sm'],
|
|
108
|
+
keyCommands: ['create-epic', 'create-story']
|
|
109
|
+
};
|
|
110
|
+
const context = {
|
|
111
|
+
lastCommand: 'push',
|
|
112
|
+
lastCommands: ['push'],
|
|
113
|
+
agentId: '@devops',
|
|
114
|
+
projectState: {}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const result = scorer.score(suggestion, context);
|
|
118
|
+
expect(result).toBeLessThanOrEqual(0.30);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return normalized score between 0 and 1', () => {
|
|
122
|
+
const suggestion = { trigger: 'any', agentSequence: [], keyCommands: [] };
|
|
123
|
+
const context = { lastCommand: 'any', lastCommands: [], agentId: '' };
|
|
124
|
+
|
|
125
|
+
const result = scorer.score(suggestion, context);
|
|
126
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
|
127
|
+
expect(result).toBeLessThanOrEqual(1);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('matchCommand()', () => {
|
|
132
|
+
it('should return 1.0 for exact match', () => {
|
|
133
|
+
const result = scorer.matchCommand('create-epic', 'create-epic');
|
|
134
|
+
expect(result).toBe(1.0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should return 1.0 when normalized trigger equals command', () => {
|
|
138
|
+
// "create-epic completed" normalizes to "create-epic"
|
|
139
|
+
const result = scorer.matchCommand('create-epic completed', 'create-epic');
|
|
140
|
+
expect(result).toBe(1.0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return partial score for keyword match', () => {
|
|
144
|
+
const result = scorer.matchCommand('create-epic', 'create-story');
|
|
145
|
+
// Both share "create" word, so partial match
|
|
146
|
+
expect(result).toBeGreaterThan(0);
|
|
147
|
+
expect(result).toBeLessThan(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should return 0 for no match', () => {
|
|
151
|
+
const result = scorer.matchCommand('create-epic', 'push');
|
|
152
|
+
expect(result).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should handle null trigger', () => {
|
|
156
|
+
const result = scorer.matchCommand(null, 'test');
|
|
157
|
+
expect(result).toBe(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should handle null command', () => {
|
|
161
|
+
const result = scorer.matchCommand('test', null);
|
|
162
|
+
expect(result).toBe(0);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('matchAgent()', () => {
|
|
167
|
+
it('should return high score for first agent in sequence', () => {
|
|
168
|
+
const result = scorer.matchAgent(['po', 'sm', 'dev'], '@po');
|
|
169
|
+
expect(result).toBeGreaterThan(0.6);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should return higher score for later agents', () => {
|
|
173
|
+
const result1 = scorer.matchAgent(['po', 'sm', 'dev'], '@po');
|
|
174
|
+
const result2 = scorer.matchAgent(['po', 'sm', 'dev'], '@dev');
|
|
175
|
+
expect(result2).toBeGreaterThan(result1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should return 0 for agent not in sequence', () => {
|
|
179
|
+
const result = scorer.matchAgent(['po', 'sm'], '@devops');
|
|
180
|
+
expect(result).toBe(0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should handle agent with @ prefix', () => {
|
|
184
|
+
const result = scorer.matchAgent(['po'], '@po');
|
|
185
|
+
expect(result).toBeGreaterThan(0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should handle null agent sequence', () => {
|
|
189
|
+
const result = scorer.matchAgent(null, '@po');
|
|
190
|
+
expect(result).toBe(0);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('matchHistory()', () => {
|
|
195
|
+
it('should return high score when history matches key commands', () => {
|
|
196
|
+
const keyCommands = ['create-epic', 'create-story'];
|
|
197
|
+
const history = ['create-epic', 'create-story', 'validate'];
|
|
198
|
+
|
|
199
|
+
const result = scorer.matchHistory(keyCommands, history);
|
|
200
|
+
expect(result).toBeGreaterThan(0.8);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should give recency bonus for recent commands', () => {
|
|
204
|
+
const keyCommands = ['create-epic'];
|
|
205
|
+
const historyRecent = ['create-epic', 'other', 'another'];
|
|
206
|
+
const historyOld = ['other', 'another', 'create-epic'];
|
|
207
|
+
|
|
208
|
+
const resultRecent = scorer.matchHistory(keyCommands, historyRecent);
|
|
209
|
+
const resultOld = scorer.matchHistory(keyCommands, historyOld);
|
|
210
|
+
|
|
211
|
+
// Recent should get higher score due to recency bonus
|
|
212
|
+
expect(resultRecent).toBeGreaterThanOrEqual(resultOld);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should return 0 for no matching commands', () => {
|
|
216
|
+
const keyCommands = ['create-epic'];
|
|
217
|
+
const history = ['push', 'commit'];
|
|
218
|
+
|
|
219
|
+
const result = scorer.matchHistory(keyCommands, history);
|
|
220
|
+
expect(result).toBe(0);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should handle empty history', () => {
|
|
224
|
+
const result = scorer.matchHistory(['test'], []);
|
|
225
|
+
expect(result).toBe(0);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should handle empty key commands', () => {
|
|
229
|
+
const result = scorer.matchHistory([], ['test']);
|
|
230
|
+
expect(result).toBe(0);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('matchProjectState()', () => {
|
|
235
|
+
it('should return neutral score for empty state', () => {
|
|
236
|
+
const result = scorer.matchProjectState({}, {});
|
|
237
|
+
expect(result).toBe(0.5);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should boost score for git-related suggestions when uncommitted changes', () => {
|
|
241
|
+
const suggestion = { trigger: 'git commit' };
|
|
242
|
+
const state = { hasUncommittedChanges: true };
|
|
243
|
+
|
|
244
|
+
const result = scorer.matchProjectState(suggestion, state);
|
|
245
|
+
expect(result).toBeGreaterThan(0.5);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should boost score for test suggestions when tests failing', () => {
|
|
249
|
+
const suggestion = { trigger: 'run tests' };
|
|
250
|
+
const state = { failingTests: true };
|
|
251
|
+
|
|
252
|
+
const result = scorer.matchProjectState(suggestion, state);
|
|
253
|
+
expect(result).toBeGreaterThan(0.5);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should return 0.5 for null project state', () => {
|
|
257
|
+
const result = scorer.matchProjectState({}, null);
|
|
258
|
+
expect(result).toBe(0.5);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('normalizeCommand()', () => {
|
|
263
|
+
it('should convert to lowercase', () => {
|
|
264
|
+
const result = scorer.normalizeCommand('CREATE-EPIC');
|
|
265
|
+
expect(result).toBe('create-epic');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should remove "completed" suffix', () => {
|
|
269
|
+
const result = scorer.normalizeCommand('create-epic completed');
|
|
270
|
+
expect(result).toBe('create-epic');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should remove "successfully" suffix', () => {
|
|
274
|
+
const result = scorer.normalizeCommand('create-epic successfully');
|
|
275
|
+
expect(result).toBe('create-epic');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should remove * prefix', () => {
|
|
279
|
+
const result = scorer.normalizeCommand('*create-epic');
|
|
280
|
+
expect(result).toBe('create-epic');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should handle null', () => {
|
|
284
|
+
const result = scorer.normalizeCommand(null);
|
|
285
|
+
expect(result).toBe('');
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('rankSuggestions()', () => {
|
|
290
|
+
it('should return empty array for empty suggestions', () => {
|
|
291
|
+
const result = scorer.rankSuggestions([], {});
|
|
292
|
+
expect(result).toEqual([]);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should rank suggestions by score descending', () => {
|
|
296
|
+
const suggestions = [
|
|
297
|
+
{ trigger: 'push', agentSequence: ['devops'] },
|
|
298
|
+
{ trigger: 'create-epic', agentSequence: ['po'] }
|
|
299
|
+
];
|
|
300
|
+
const context = {
|
|
301
|
+
lastCommand: 'create-epic',
|
|
302
|
+
lastCommands: ['create-epic'],
|
|
303
|
+
agentId: '@po'
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const result = scorer.rankSuggestions(suggestions, context);
|
|
307
|
+
expect(result[0].trigger).toBe('create-epic');
|
|
308
|
+
expect(result[0].score).toBeGreaterThan(result[1].score);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should add score property to each suggestion', () => {
|
|
312
|
+
const suggestions = [{ trigger: 'test' }];
|
|
313
|
+
const context = { lastCommand: 'test' };
|
|
314
|
+
|
|
315
|
+
const result = scorer.rankSuggestions(suggestions, context);
|
|
316
|
+
expect(result[0]).toHaveProperty('score');
|
|
317
|
+
expect(typeof result[0].score).toBe('number');
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe('SCORING_WEIGHTS constant', () => {
|
|
322
|
+
it('should have correct default weights', () => {
|
|
323
|
+
expect(SCORING_WEIGHTS.COMMAND_MATCH).toBe(0.40);
|
|
324
|
+
expect(SCORING_WEIGHTS.AGENT_MATCH).toBe(0.25);
|
|
325
|
+
expect(SCORING_WEIGHTS.HISTORY_DEPTH).toBe(0.20);
|
|
326
|
+
expect(SCORING_WEIGHTS.PROJECT_STATE).toBe(0.15);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should sum to 1.0', () => {
|
|
330
|
+
const sum = Object.values(SCORING_WEIGHTS).reduce((a, b) => a + b, 0);
|
|
331
|
+
expect(sum).toBe(1.0);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
});
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Integration tests for Workflow Intelligence System
|
|
3
|
+
* @story WIS-2 - Workflow Registry Enhancement
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const wis = require('../index');
|
|
9
|
+
|
|
10
|
+
describe('Workflow Intelligence System Integration', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
wis.reset();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
wis.invalidateCache();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Context → Workflow Match → Scored Suggestions flow', () => {
|
|
20
|
+
it('should provide scored suggestions for epic creation context', () => {
|
|
21
|
+
// Need 2 matching commands to meet trigger_threshold
|
|
22
|
+
const context = {
|
|
23
|
+
lastCommand: 'create-story',
|
|
24
|
+
lastCommands: ['create-epic', 'create-story'],
|
|
25
|
+
agentId: '@po',
|
|
26
|
+
projectState: {}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const suggestions = wis.getSuggestions(context);
|
|
30
|
+
|
|
31
|
+
expect(Array.isArray(suggestions)).toBe(true);
|
|
32
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
33
|
+
expect(suggestions[0]).toHaveProperty('command');
|
|
34
|
+
expect(suggestions[0]).toHaveProperty('confidence');
|
|
35
|
+
expect(suggestions[0].workflow).toBe('epic_creation');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should provide scored suggestions for story development context', () => {
|
|
39
|
+
const context = {
|
|
40
|
+
lastCommand: 'validate-story-draft',
|
|
41
|
+
lastCommands: ['validate-story-draft', 'develop'],
|
|
42
|
+
agentId: '@po',
|
|
43
|
+
projectState: {}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const suggestions = wis.getSuggestions(context);
|
|
47
|
+
|
|
48
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
49
|
+
expect(suggestions[0].workflow).toBe('story_development');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return empty array for unrecognized context', () => {
|
|
53
|
+
const context = {
|
|
54
|
+
lastCommand: 'random-unknown-command',
|
|
55
|
+
lastCommands: ['random-unknown-command'],
|
|
56
|
+
agentId: '@unknown',
|
|
57
|
+
projectState: {}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const suggestions = wis.getSuggestions(context);
|
|
61
|
+
expect(suggestions).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should rank suggestions by confidence', () => {
|
|
65
|
+
const context = {
|
|
66
|
+
lastCommand: 'create-story',
|
|
67
|
+
lastCommands: ['create-epic', 'create-story'],
|
|
68
|
+
agentId: '@sm',
|
|
69
|
+
projectState: {}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const suggestions = wis.getSuggestions(context);
|
|
73
|
+
|
|
74
|
+
if (suggestions.length > 1) {
|
|
75
|
+
expect(suggestions[0].confidence).toBeGreaterThanOrEqual(suggestions[1].confidence);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should include workflow and state information', () => {
|
|
80
|
+
// Need 2 matching commands to meet trigger_threshold
|
|
81
|
+
const context = {
|
|
82
|
+
lastCommand: 'create-story',
|
|
83
|
+
lastCommands: ['create-epic', 'create-story'],
|
|
84
|
+
agentId: '@po',
|
|
85
|
+
projectState: {}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const suggestions = wis.getSuggestions(context);
|
|
89
|
+
|
|
90
|
+
expect(suggestions[0].workflow).toBe('epic_creation');
|
|
91
|
+
expect(suggestions[0].state).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('End-to-end workflow navigation', () => {
|
|
96
|
+
it('should navigate through epic creation workflow', () => {
|
|
97
|
+
// Need 2 commands to meet trigger_threshold
|
|
98
|
+
const context1 = {
|
|
99
|
+
lastCommand: 'create-story',
|
|
100
|
+
lastCommands: ['create-epic', 'create-story'],
|
|
101
|
+
agentId: '@po',
|
|
102
|
+
projectState: {}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const suggestions1 = wis.getSuggestions(context1);
|
|
106
|
+
// Should suggest validation after stories created
|
|
107
|
+
expect(suggestions1.some(s =>
|
|
108
|
+
s.command === 'validate-story-draft' || s.command === 'create-next-story'
|
|
109
|
+
)).toBe(true);
|
|
110
|
+
|
|
111
|
+
// Step 2: After validation
|
|
112
|
+
const context2 = {
|
|
113
|
+
lastCommand: 'validate-story-draft',
|
|
114
|
+
lastCommands: ['create-epic', 'create-story', 'validate-story-draft'],
|
|
115
|
+
agentId: '@sm',
|
|
116
|
+
projectState: {}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const suggestions2 = wis.getSuggestions(context2);
|
|
120
|
+
expect(suggestions2.some(s =>
|
|
121
|
+
s.command === 'analyze-impact' || s.command === 'develop'
|
|
122
|
+
)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should navigate through story development workflow', () => {
|
|
126
|
+
// Step 1: Story validated - need 2 commands to meet trigger threshold
|
|
127
|
+
const context1 = {
|
|
128
|
+
lastCommand: 'validate-story-draft',
|
|
129
|
+
lastCommands: ['validate-story-draft', 'develop'],
|
|
130
|
+
agentId: '@po',
|
|
131
|
+
projectState: {}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const suggestions1 = wis.getSuggestions(context1);
|
|
135
|
+
if (suggestions1.length > 0) {
|
|
136
|
+
const hasDevelopCommand = suggestions1.some(s =>
|
|
137
|
+
s.command.includes('develop') || s.command.includes('review')
|
|
138
|
+
);
|
|
139
|
+
expect(hasDevelopCommand).toBe(true);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should navigate through database workflow', () => {
|
|
144
|
+
// Need 2 commands to meet trigger threshold
|
|
145
|
+
const context = {
|
|
146
|
+
lastCommand: 'db-schema-audit',
|
|
147
|
+
lastCommands: ['db-domain-modeling', 'db-schema-audit'],
|
|
148
|
+
agentId: '@data-engineer',
|
|
149
|
+
projectState: {}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const suggestions = wis.getSuggestions(context);
|
|
153
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
154
|
+
expect(suggestions[0].workflow).toBe('database_workflow');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('Multi-agent workflow support', () => {
|
|
159
|
+
it('should support multiple agents in epic creation', () => {
|
|
160
|
+
const poWorkflows = wis.getWorkflowsByAgent('@po');
|
|
161
|
+
const smWorkflows = wis.getWorkflowsByAgent('@sm');
|
|
162
|
+
|
|
163
|
+
const epicInPo = poWorkflows.some(w => w.name === 'epic_creation');
|
|
164
|
+
const epicInSm = smWorkflows.some(w => w.name === 'epic_creation');
|
|
165
|
+
|
|
166
|
+
expect(epicInPo).toBe(true);
|
|
167
|
+
expect(epicInSm).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should support agent transitions within workflow', () => {
|
|
171
|
+
// PO validates story - need 2 matching commands
|
|
172
|
+
const poContext = {
|
|
173
|
+
lastCommand: 'validate-story-draft',
|
|
174
|
+
lastCommands: ['validate-story-draft', 'develop'],
|
|
175
|
+
agentId: '@po',
|
|
176
|
+
projectState: {}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const poSuggestions = wis.getSuggestions(poContext);
|
|
180
|
+
expect(poSuggestions.length).toBeGreaterThan(0);
|
|
181
|
+
|
|
182
|
+
// Dev implements
|
|
183
|
+
const devContext = {
|
|
184
|
+
lastCommand: 'develop',
|
|
185
|
+
lastCommands: ['validate-story-draft', 'develop'],
|
|
186
|
+
agentId: '@dev',
|
|
187
|
+
projectState: {}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const devSuggestions = wis.getSuggestions(devContext);
|
|
191
|
+
expect(devSuggestions.length).toBeGreaterThan(0);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('Public API completeness', () => {
|
|
196
|
+
it('should expose matchWorkflow function', () => {
|
|
197
|
+
const match = wis.matchWorkflow(['create-epic', 'create-story']);
|
|
198
|
+
expect(match).not.toBeNull();
|
|
199
|
+
expect(match.name).toBe('epic_creation');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should expose getNextSteps function', () => {
|
|
203
|
+
const steps = wis.getNextSteps('epic_creation', 'epic_drafted');
|
|
204
|
+
expect(Array.isArray(steps)).toBe(true);
|
|
205
|
+
expect(steps.length).toBeGreaterThan(0);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should expose getTransitions function', () => {
|
|
209
|
+
const transition = wis.getTransitions('epic_creation', 'epic_drafted');
|
|
210
|
+
expect(transition).not.toBeNull();
|
|
211
|
+
expect(transition.trigger).toBeDefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should expose getWorkflow function', () => {
|
|
215
|
+
const workflow = wis.getWorkflow('epic_creation');
|
|
216
|
+
expect(workflow).not.toBeNull();
|
|
217
|
+
expect(workflow.description).toBeDefined();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should expose getWorkflowNames function', () => {
|
|
221
|
+
const names = wis.getWorkflowNames();
|
|
222
|
+
expect(names.length).toBe(10);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should expose getWorkflowsByAgent function', () => {
|
|
226
|
+
const workflows = wis.getWorkflowsByAgent('@po');
|
|
227
|
+
expect(workflows.length).toBeGreaterThan(0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should expose findCurrentState function', () => {
|
|
231
|
+
const state = wis.findCurrentState('epic_creation', 'create-epic');
|
|
232
|
+
expect(state).toBe('epic_drafted');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should expose getStats function', () => {
|
|
236
|
+
const stats = wis.getStats();
|
|
237
|
+
expect(stats.totalWorkflows).toBe(10);
|
|
238
|
+
expect(stats.workflowsWithTransitions).toBe(10);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should expose factory functions', () => {
|
|
242
|
+
expect(typeof wis.createWorkflowRegistry).toBe('function');
|
|
243
|
+
expect(typeof wis.createConfidenceScorer).toBe('function');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should expose class constructors', () => {
|
|
247
|
+
expect(wis.WorkflowRegistry).toBeDefined();
|
|
248
|
+
expect(wis.ConfidenceScorer).toBeDefined();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should expose constants', () => {
|
|
252
|
+
expect(wis.SCORING_WEIGHTS).toBeDefined();
|
|
253
|
+
expect(wis.DEFAULT_CACHE_TTL).toBeDefined();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('Cache behavior', () => {
|
|
258
|
+
it('should use cache for repeated calls', () => {
|
|
259
|
+
const stats1 = wis.getStats();
|
|
260
|
+
expect(stats1.cacheValid).toBe(true);
|
|
261
|
+
|
|
262
|
+
const stats2 = wis.getStats();
|
|
263
|
+
expect(stats2.cacheValid).toBe(true);
|
|
264
|
+
expect(stats2.cacheAge).toBeGreaterThanOrEqual(stats1.cacheAge);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should invalidate cache when requested', () => {
|
|
268
|
+
wis.getStats(); // Populate cache
|
|
269
|
+
wis.invalidateCache();
|
|
270
|
+
|
|
271
|
+
// Force reload
|
|
272
|
+
wis.reset();
|
|
273
|
+
const stats = wis.getStats();
|
|
274
|
+
expect(stats.cacheAge).toBeLessThan(100); // Fresh cache
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('Error handling', () => {
|
|
279
|
+
it('should handle missing context gracefully', () => {
|
|
280
|
+
const suggestions = wis.getSuggestions({});
|
|
281
|
+
expect(Array.isArray(suggestions)).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should handle null context', () => {
|
|
285
|
+
// This should not throw and return empty array
|
|
286
|
+
const result = wis.getSuggestions(null);
|
|
287
|
+
expect(result).toEqual([]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should return empty for invalid workflow name', () => {
|
|
291
|
+
const steps = wis.getNextSteps('invalid_workflow', 'any_state');
|
|
292
|
+
expect(steps).toEqual([]);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('All 10 workflows have transitions', () => {
|
|
297
|
+
const workflows = [
|
|
298
|
+
'story_development',
|
|
299
|
+
'epic_creation',
|
|
300
|
+
'backlog_management',
|
|
301
|
+
'architecture_review',
|
|
302
|
+
'git_workflow',
|
|
303
|
+
'database_workflow',
|
|
304
|
+
'code_quality_workflow',
|
|
305
|
+
'documentation_workflow',
|
|
306
|
+
'ux_workflow',
|
|
307
|
+
'research_workflow'
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
workflows.forEach(workflowName => {
|
|
311
|
+
it(`${workflowName} should have transitions defined`, () => {
|
|
312
|
+
const workflow = wis.getWorkflow(workflowName);
|
|
313
|
+
expect(workflow.transitions).toBeDefined();
|
|
314
|
+
expect(Object.keys(workflow.transitions).length).toBeGreaterThanOrEqual(2);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it(`${workflowName} should have at least 2 states`, () => {
|
|
318
|
+
const workflow = wis.getWorkflow(workflowName);
|
|
319
|
+
const stateCount = Object.keys(workflow.transitions).length;
|
|
320
|
+
expect(stateCount).toBeGreaterThanOrEqual(2);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it(`${workflowName} transitions should have required fields`, () => {
|
|
324
|
+
const workflow = wis.getWorkflow(workflowName);
|
|
325
|
+
|
|
326
|
+
Object.entries(workflow.transitions).forEach(([state, transition]) => {
|
|
327
|
+
expect(transition.trigger).toBeDefined();
|
|
328
|
+
expect(transition.confidence).toBeDefined();
|
|
329
|
+
expect(transition.confidence).toBeGreaterThanOrEqual(0);
|
|
330
|
+
expect(transition.confidence).toBeLessThanOrEqual(1);
|
|
331
|
+
expect(transition.next_steps).toBeDefined();
|
|
332
|
+
expect(Array.isArray(transition.next_steps)).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
});
|