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,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for WorkflowRegistry
|
|
3
|
+
* @story WIS-2 - Workflow Registry Enhancement
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const {
|
|
10
|
+
WorkflowRegistry,
|
|
11
|
+
createWorkflowRegistry,
|
|
12
|
+
DEFAULT_CACHE_TTL
|
|
13
|
+
} = require('../registry/workflow-registry');
|
|
14
|
+
|
|
15
|
+
describe('WorkflowRegistry', () => {
|
|
16
|
+
let registry;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
registry = createWorkflowRegistry();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
registry.invalidateCache();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('constructor', () => {
|
|
27
|
+
it('should create registry with default options', () => {
|
|
28
|
+
expect(registry.cacheTTL).toBe(DEFAULT_CACHE_TTL);
|
|
29
|
+
expect(registry.cache).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should accept custom cache TTL', () => {
|
|
33
|
+
const customRegistry = createWorkflowRegistry({ cacheTTL: 1000 });
|
|
34
|
+
expect(customRegistry.cacheTTL).toBe(1000);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should accept custom patterns path', () => {
|
|
38
|
+
const customPath = '/custom/path.yaml';
|
|
39
|
+
const customRegistry = createWorkflowRegistry({ patternsPath: customPath });
|
|
40
|
+
expect(customRegistry.patternsPath).toBe(customPath);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('loadWorkflows()', () => {
|
|
45
|
+
it('should load workflow patterns from file', () => {
|
|
46
|
+
const workflows = registry.loadWorkflows();
|
|
47
|
+
expect(workflows).toBeDefined();
|
|
48
|
+
expect(typeof workflows).toBe('object');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should return 10 workflows', () => {
|
|
52
|
+
const workflows = registry.loadWorkflows();
|
|
53
|
+
const names = Object.keys(workflows);
|
|
54
|
+
expect(names.length).toBe(10);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should include story_development workflow', () => {
|
|
58
|
+
const workflows = registry.loadWorkflows();
|
|
59
|
+
expect(workflows.story_development).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should include epic_creation workflow', () => {
|
|
63
|
+
const workflows = registry.loadWorkflows();
|
|
64
|
+
expect(workflows.epic_creation).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should cache loaded workflows', () => {
|
|
68
|
+
const workflows1 = registry.loadWorkflows();
|
|
69
|
+
const workflows2 = registry.loadWorkflows();
|
|
70
|
+
expect(workflows1).toBe(workflows2);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should throw error for invalid file path', () => {
|
|
74
|
+
const badRegistry = createWorkflowRegistry({
|
|
75
|
+
patternsPath: '/nonexistent/path.yaml'
|
|
76
|
+
});
|
|
77
|
+
expect(() => badRegistry.loadWorkflows()).toThrow('Workflow patterns file not found');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('isCacheValid()', () => {
|
|
82
|
+
it('should return false when cache is null', () => {
|
|
83
|
+
expect(registry.isCacheValid()).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should return true after loading workflows', () => {
|
|
87
|
+
registry.loadWorkflows();
|
|
88
|
+
expect(registry.isCacheValid()).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should return false after invalidation', () => {
|
|
92
|
+
registry.loadWorkflows();
|
|
93
|
+
registry.invalidateCache();
|
|
94
|
+
expect(registry.isCacheValid()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('getWorkflowNames()', () => {
|
|
99
|
+
it('should return array of workflow names', () => {
|
|
100
|
+
const names = registry.getWorkflowNames();
|
|
101
|
+
expect(Array.isArray(names)).toBe(true);
|
|
102
|
+
expect(names.length).toBe(10);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should include expected workflows', () => {
|
|
106
|
+
const names = registry.getWorkflowNames();
|
|
107
|
+
expect(names).toContain('story_development');
|
|
108
|
+
expect(names).toContain('epic_creation');
|
|
109
|
+
expect(names).toContain('backlog_management');
|
|
110
|
+
expect(names).toContain('architecture_review');
|
|
111
|
+
expect(names).toContain('git_workflow');
|
|
112
|
+
expect(names).toContain('database_workflow');
|
|
113
|
+
expect(names).toContain('code_quality_workflow');
|
|
114
|
+
expect(names).toContain('documentation_workflow');
|
|
115
|
+
expect(names).toContain('ux_workflow');
|
|
116
|
+
expect(names).toContain('research_workflow');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('getWorkflow()', () => {
|
|
121
|
+
it('should return workflow by name', () => {
|
|
122
|
+
const workflow = registry.getWorkflow('epic_creation');
|
|
123
|
+
expect(workflow).toBeDefined();
|
|
124
|
+
expect(workflow.description).toBeDefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should return null for unknown workflow', () => {
|
|
128
|
+
const workflow = registry.getWorkflow('nonexistent');
|
|
129
|
+
expect(workflow).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should include transitions in workflow', () => {
|
|
133
|
+
const workflow = registry.getWorkflow('epic_creation');
|
|
134
|
+
expect(workflow.transitions).toBeDefined();
|
|
135
|
+
expect(typeof workflow.transitions).toBe('object');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('matchWorkflow()', () => {
|
|
140
|
+
it('should match workflow from command history', () => {
|
|
141
|
+
const commands = ['create-epic', 'create-story'];
|
|
142
|
+
const match = registry.matchWorkflow(commands);
|
|
143
|
+
|
|
144
|
+
expect(match).not.toBeNull();
|
|
145
|
+
expect(match.name).toBe('epic_creation');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return best matching workflow', () => {
|
|
149
|
+
const commands = ['validate-story-draft', 'develop', 'review-qa'];
|
|
150
|
+
const match = registry.matchWorkflow(commands);
|
|
151
|
+
|
|
152
|
+
expect(match).not.toBeNull();
|
|
153
|
+
expect(match.name).toBe('story_development');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should return null for empty commands', () => {
|
|
157
|
+
const match = registry.matchWorkflow([]);
|
|
158
|
+
expect(match).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should return null for unmatched commands', () => {
|
|
162
|
+
const match = registry.matchWorkflow(['random-cmd', 'another-random']);
|
|
163
|
+
expect(match).toBeNull();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should include score in match result', () => {
|
|
167
|
+
const match = registry.matchWorkflow(['create-epic', 'create-story']);
|
|
168
|
+
expect(match.score).toBeGreaterThanOrEqual(2);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should include matched commands', () => {
|
|
172
|
+
const match = registry.matchWorkflow(['create-epic', 'create-story']);
|
|
173
|
+
expect(match.matchedCommands).toBeDefined();
|
|
174
|
+
expect(Array.isArray(match.matchedCommands)).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('getTransitions()', () => {
|
|
179
|
+
it('should return transitions for valid state', () => {
|
|
180
|
+
const transition = registry.getTransitions('epic_creation', 'epic_drafted');
|
|
181
|
+
|
|
182
|
+
expect(transition).not.toBeNull();
|
|
183
|
+
expect(transition.trigger).toBeDefined();
|
|
184
|
+
expect(transition.confidence).toBeDefined();
|
|
185
|
+
expect(transition.next_steps).toBeDefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should return null for unknown state', () => {
|
|
189
|
+
const transition = registry.getTransitions('epic_creation', 'unknown_state');
|
|
190
|
+
expect(transition).toBeNull();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should return null for unknown workflow', () => {
|
|
194
|
+
const transition = registry.getTransitions('unknown_workflow', 'any_state');
|
|
195
|
+
expect(transition).toBeNull();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should return empty array for workflow without transitions', () => {
|
|
199
|
+
const result = registry.getAllTransitions('nonexistent');
|
|
200
|
+
expect(result).toEqual({});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('getNextSteps()', () => {
|
|
205
|
+
it('should return sorted next steps', () => {
|
|
206
|
+
const steps = registry.getNextSteps('epic_creation', 'epic_drafted');
|
|
207
|
+
|
|
208
|
+
expect(Array.isArray(steps)).toBe(true);
|
|
209
|
+
expect(steps.length).toBeGreaterThan(0);
|
|
210
|
+
expect(steps[0].priority).toBeLessThanOrEqual(steps[1]?.priority || 99);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should include command and description', () => {
|
|
214
|
+
const steps = registry.getNextSteps('epic_creation', 'epic_drafted');
|
|
215
|
+
|
|
216
|
+
expect(steps[0].command).toBeDefined();
|
|
217
|
+
expect(steps[0].description).toBeDefined();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should return empty array for unknown state', () => {
|
|
221
|
+
const steps = registry.getNextSteps('epic_creation', 'unknown_state');
|
|
222
|
+
expect(steps).toEqual([]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('findCurrentState()', () => {
|
|
227
|
+
it('should find state from command', () => {
|
|
228
|
+
const state = registry.findCurrentState('epic_creation', 'create-epic completed');
|
|
229
|
+
expect(state).toBe('epic_drafted');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should find state without "completed" suffix', () => {
|
|
233
|
+
const state = registry.findCurrentState('epic_creation', 'create-epic');
|
|
234
|
+
expect(state).toBe('epic_drafted');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should return null for unknown command', () => {
|
|
238
|
+
const state = registry.findCurrentState('epic_creation', 'random-command');
|
|
239
|
+
expect(state).toBeNull();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should return null for unknown workflow', () => {
|
|
243
|
+
const state = registry.findCurrentState('unknown', 'create-epic');
|
|
244
|
+
expect(state).toBeNull();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('getWorkflowsByAgent()', () => {
|
|
249
|
+
it('should return workflows for agent', () => {
|
|
250
|
+
const workflows = registry.getWorkflowsByAgent('@po');
|
|
251
|
+
|
|
252
|
+
expect(Array.isArray(workflows)).toBe(true);
|
|
253
|
+
expect(workflows.length).toBeGreaterThan(0);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should handle agent without @ prefix', () => {
|
|
257
|
+
const workflows = registry.getWorkflowsByAgent('po');
|
|
258
|
+
expect(workflows.length).toBeGreaterThan(0);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should return empty array for unknown agent', () => {
|
|
262
|
+
const workflows = registry.getWorkflowsByAgent('@unknown');
|
|
263
|
+
expect(workflows).toEqual([]);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('getStats()', () => {
|
|
268
|
+
it('should return registry statistics', () => {
|
|
269
|
+
const stats = registry.getStats();
|
|
270
|
+
|
|
271
|
+
expect(stats.totalWorkflows).toBe(10);
|
|
272
|
+
expect(stats.workflowsWithTransitions).toBe(10);
|
|
273
|
+
expect(stats.totalTransitions).toBeGreaterThan(0);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should report cache status', () => {
|
|
277
|
+
registry.loadWorkflows();
|
|
278
|
+
const stats = registry.getStats();
|
|
279
|
+
|
|
280
|
+
expect(stats.cacheValid).toBe(true);
|
|
281
|
+
expect(stats.cacheAge).toBeGreaterThanOrEqual(0);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('normalizeCommand()', () => {
|
|
286
|
+
it('should normalize command strings', () => {
|
|
287
|
+
expect(registry.normalizeCommand('CREATE-EPIC')).toBe('create-epic');
|
|
288
|
+
expect(registry.normalizeCommand('*create-epic')).toBe('create-epic');
|
|
289
|
+
expect(registry.normalizeCommand('create-epic completed')).toBe('create-epic');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should handle null input', () => {
|
|
293
|
+
expect(registry.normalizeCommand(null)).toBe('');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('DEFAULT_CACHE_TTL', () => {
|
|
298
|
+
it('should be 5 minutes', () => {
|
|
299
|
+
expect(DEFAULT_CACHE_TTL).toBe(5 * 60 * 1000);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ConfidenceScorer
|
|
3
|
+
* @description Confidence scoring algorithm for workflow suggestions
|
|
4
|
+
* @story WIS-2 - Workflow Registry Enhancement
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scoring weights for confidence calculation
|
|
12
|
+
* @type {Object}
|
|
13
|
+
*/
|
|
14
|
+
const SCORING_WEIGHTS = {
|
|
15
|
+
COMMAND_MATCH: 0.40,
|
|
16
|
+
AGENT_MATCH: 0.25,
|
|
17
|
+
HISTORY_DEPTH: 0.20,
|
|
18
|
+
PROJECT_STATE: 0.15
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ConfidenceScorer class for calculating workflow suggestion confidence
|
|
23
|
+
*/
|
|
24
|
+
class ConfidenceScorer {
|
|
25
|
+
/**
|
|
26
|
+
* Create a ConfidenceScorer instance
|
|
27
|
+
* @param {Object} options - Configuration options
|
|
28
|
+
* @param {Object} options.weights - Custom scoring weights
|
|
29
|
+
*/
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.weights = { ...SCORING_WEIGHTS, ...options.weights };
|
|
32
|
+
this.validateWeights();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate that weights sum to 1.0
|
|
37
|
+
* @throws {Error} If weights don't sum to 1.0
|
|
38
|
+
*/
|
|
39
|
+
validateWeights() {
|
|
40
|
+
const sum = Object.values(this.weights).reduce((a, b) => a + b, 0);
|
|
41
|
+
if (Math.abs(sum - 1.0) > 0.001) {
|
|
42
|
+
throw new Error(`Scoring weights must sum to 1.0, got ${sum}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Calculate confidence score for a suggestion given context
|
|
48
|
+
* @param {Object} suggestion - The workflow suggestion
|
|
49
|
+
* @param {string} suggestion.trigger - Command or condition that triggers transition
|
|
50
|
+
* @param {string[]} suggestion.agentSequence - Expected agent sequence
|
|
51
|
+
* @param {string[]} suggestion.keyCommands - Key commands for this workflow
|
|
52
|
+
* @param {Object} context - Current session context
|
|
53
|
+
* @param {string} context.lastCommand - Last executed command
|
|
54
|
+
* @param {string[]} context.lastCommands - Recent command history
|
|
55
|
+
* @param {string} context.agentId - Current active agent
|
|
56
|
+
* @param {Object} context.projectState - Current project state
|
|
57
|
+
* @returns {number} Normalized score between 0.0 and 1.0
|
|
58
|
+
*/
|
|
59
|
+
score(suggestion, context) {
|
|
60
|
+
if (!suggestion || !context) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const commandMatch = this.matchCommand(suggestion.trigger, context.lastCommand);
|
|
65
|
+
const agentMatch = this.matchAgent(suggestion.agentSequence, context.agentId);
|
|
66
|
+
const historyDepth = this.matchHistory(suggestion.keyCommands, context.lastCommands);
|
|
67
|
+
const projectState = this.matchProjectState(suggestion, context.projectState);
|
|
68
|
+
|
|
69
|
+
const rawScore = (
|
|
70
|
+
commandMatch * this.weights.COMMAND_MATCH +
|
|
71
|
+
agentMatch * this.weights.AGENT_MATCH +
|
|
72
|
+
historyDepth * this.weights.HISTORY_DEPTH +
|
|
73
|
+
projectState * this.weights.PROJECT_STATE
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return this.normalize(rawScore);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Match command trigger against last executed command
|
|
81
|
+
* @param {string} trigger - Expected trigger command
|
|
82
|
+
* @param {string} lastCommand - Last executed command
|
|
83
|
+
* @returns {number} Match score 0.0-1.0
|
|
84
|
+
*/
|
|
85
|
+
matchCommand(trigger, lastCommand) {
|
|
86
|
+
if (!trigger || !lastCommand) {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const normalizedTrigger = this.normalizeCommand(trigger);
|
|
91
|
+
const normalizedLast = this.normalizeCommand(lastCommand);
|
|
92
|
+
|
|
93
|
+
// Exact match
|
|
94
|
+
if (normalizedTrigger === normalizedLast) {
|
|
95
|
+
return 1.0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Trigger contains the command
|
|
99
|
+
if (normalizedTrigger.includes(normalizedLast)) {
|
|
100
|
+
return 0.9;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Command contains trigger keyword
|
|
104
|
+
if (normalizedLast.includes(normalizedTrigger.split(' ')[0])) {
|
|
105
|
+
return 0.7;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Partial word match
|
|
109
|
+
const triggerWords = normalizedTrigger.split(/[\s-_]+/);
|
|
110
|
+
const lastWords = normalizedLast.split(/[\s-_]+/);
|
|
111
|
+
const commonWords = triggerWords.filter(w => lastWords.includes(w));
|
|
112
|
+
|
|
113
|
+
if (commonWords.length > 0) {
|
|
114
|
+
return 0.5 * (commonWords.length / Math.max(triggerWords.length, 1));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Match agent sequence against current agent
|
|
122
|
+
* @param {string[]} agentSequence - Expected agent sequence
|
|
123
|
+
* @param {string} currentAgent - Current active agent
|
|
124
|
+
* @returns {number} Match score 0.0-1.0
|
|
125
|
+
*/
|
|
126
|
+
matchAgent(agentSequence, currentAgent) {
|
|
127
|
+
if (!agentSequence || !currentAgent) {
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const normalizedAgent = currentAgent.replace('@', '').toLowerCase();
|
|
132
|
+
|
|
133
|
+
// Check if current agent is in the sequence
|
|
134
|
+
const agentIndex = agentSequence.findIndex(
|
|
135
|
+
agent => agent.toLowerCase() === normalizedAgent
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (agentIndex === -1) {
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Higher score for agents later in sequence (workflow is progressing)
|
|
143
|
+
const progressScore = (agentIndex + 1) / agentSequence.length;
|
|
144
|
+
|
|
145
|
+
// Bonus for being the expected next agent
|
|
146
|
+
if (agentIndex === 0) {
|
|
147
|
+
return 0.6 + (progressScore * 0.4);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return progressScore;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Match command history against key workflow commands
|
|
155
|
+
* @param {string[]} keyCommands - Key commands for the workflow
|
|
156
|
+
* @param {string[]} lastCommands - Recent command history
|
|
157
|
+
* @returns {number} Match score 0.0-1.0
|
|
158
|
+
*/
|
|
159
|
+
matchHistory(keyCommands, lastCommands) {
|
|
160
|
+
if (!keyCommands || !lastCommands || keyCommands.length === 0) {
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const normalizedKeys = keyCommands.map(cmd => this.normalizeCommand(cmd));
|
|
165
|
+
const normalizedHistory = lastCommands.map(cmd => this.normalizeCommand(cmd));
|
|
166
|
+
|
|
167
|
+
let matchCount = 0;
|
|
168
|
+
let recentBonus = 0;
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < normalizedHistory.length; i++) {
|
|
171
|
+
const historyCmd = normalizedHistory[i];
|
|
172
|
+
|
|
173
|
+
for (const keyCmd of normalizedKeys) {
|
|
174
|
+
if (historyCmd.includes(keyCmd) || keyCmd.includes(historyCmd)) {
|
|
175
|
+
matchCount++;
|
|
176
|
+
|
|
177
|
+
// Recent commands get higher weight (decay factor)
|
|
178
|
+
const recency = 1 - (i / normalizedHistory.length);
|
|
179
|
+
recentBonus += recency * 0.1;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const baseScore = Math.min(matchCount / keyCommands.length, 1.0);
|
|
186
|
+
return Math.min(baseScore + recentBonus, 1.0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Match suggestion against project state
|
|
191
|
+
* @param {Object} suggestion - The workflow suggestion
|
|
192
|
+
* @param {Object} projectState - Current project state
|
|
193
|
+
* @returns {number} Match score 0.0-1.0
|
|
194
|
+
*/
|
|
195
|
+
matchProjectState(suggestion, projectState) {
|
|
196
|
+
if (!suggestion || !projectState) {
|
|
197
|
+
return 0.5; // Neutral score when state unknown
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let score = 0.5;
|
|
201
|
+
const factors = [];
|
|
202
|
+
|
|
203
|
+
// Check if story is in progress
|
|
204
|
+
if (projectState.activeStory) {
|
|
205
|
+
factors.push(0.2);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check if uncommitted changes exist
|
|
209
|
+
if (projectState.hasUncommittedChanges) {
|
|
210
|
+
// Git-related suggestions get higher score
|
|
211
|
+
if (suggestion.trigger?.includes('git') ||
|
|
212
|
+
suggestion.trigger?.includes('commit') ||
|
|
213
|
+
suggestion.trigger?.includes('push')) {
|
|
214
|
+
factors.push(0.3);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if tests are failing
|
|
219
|
+
if (projectState.failingTests) {
|
|
220
|
+
// QA-related suggestions get higher score
|
|
221
|
+
if (suggestion.trigger?.includes('test') ||
|
|
222
|
+
suggestion.trigger?.includes('qa') ||
|
|
223
|
+
suggestion.trigger?.includes('fix')) {
|
|
224
|
+
factors.push(0.3);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check current workflow phase
|
|
229
|
+
if (projectState.workflowPhase && suggestion.phase) {
|
|
230
|
+
if (projectState.workflowPhase === suggestion.phase) {
|
|
231
|
+
factors.push(0.2);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return Math.min(score + factors.reduce((a, b) => a + b, 0), 1.0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Normalize a command string for comparison
|
|
240
|
+
* @param {string} command - Command to normalize
|
|
241
|
+
* @returns {string} Normalized command
|
|
242
|
+
*/
|
|
243
|
+
normalizeCommand(command) {
|
|
244
|
+
if (!command) return '';
|
|
245
|
+
|
|
246
|
+
return command
|
|
247
|
+
.toLowerCase()
|
|
248
|
+
.replace(/\s+completed\s*$/i, '')
|
|
249
|
+
.replace(/\s+successfully\s*$/i, '')
|
|
250
|
+
.replace(/^\*/, '')
|
|
251
|
+
.replace(/['"]/g, '')
|
|
252
|
+
.trim();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Normalize score to 0.0-1.0 range
|
|
257
|
+
* @param {number} score - Raw score
|
|
258
|
+
* @returns {number} Normalized score
|
|
259
|
+
*/
|
|
260
|
+
normalize(score) {
|
|
261
|
+
return Math.max(0, Math.min(1, score));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Calculate scores for multiple suggestions and rank them
|
|
266
|
+
* @param {Object[]} suggestions - Array of suggestions
|
|
267
|
+
* @param {Object} context - Current session context
|
|
268
|
+
* @returns {Object[]} Sorted suggestions with scores
|
|
269
|
+
*/
|
|
270
|
+
rankSuggestions(suggestions, context) {
|
|
271
|
+
if (!suggestions || suggestions.length === 0) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return suggestions
|
|
276
|
+
.map(suggestion => ({
|
|
277
|
+
...suggestion,
|
|
278
|
+
score: this.score(suggestion, context)
|
|
279
|
+
}))
|
|
280
|
+
.sort((a, b) => b.score - a.score);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get scoring weights
|
|
285
|
+
* @returns {Object} Current scoring weights
|
|
286
|
+
*/
|
|
287
|
+
getWeights() {
|
|
288
|
+
return { ...this.weights };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Create a new ConfidenceScorer instance
|
|
294
|
+
* @param {Object} options - Configuration options
|
|
295
|
+
* @returns {ConfidenceScorer} New scorer instance
|
|
296
|
+
*/
|
|
297
|
+
function createConfidenceScorer(options = {}) {
|
|
298
|
+
return new ConfidenceScorer(options);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = {
|
|
302
|
+
ConfidenceScorer,
|
|
303
|
+
createConfidenceScorer,
|
|
304
|
+
SCORING_WEIGHTS
|
|
305
|
+
};
|