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,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module PatternValidator
|
|
3
|
+
* @description Validates workflow patterns before storage
|
|
4
|
+
* @story WIS-5 - Pattern Capture (Internal)
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default validation rules
|
|
12
|
+
* @type {Object}
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_VALIDATION_RULES = {
|
|
15
|
+
minSequenceLength: 3,
|
|
16
|
+
maxSequenceLength: 10,
|
|
17
|
+
minOccurrences: 2,
|
|
18
|
+
minSuccessRate: 0.8,
|
|
19
|
+
requiredKeyCommands: [
|
|
20
|
+
'develop',
|
|
21
|
+
'review-qa',
|
|
22
|
+
'create-story',
|
|
23
|
+
'validate-story-draft',
|
|
24
|
+
'apply-qa-fixes',
|
|
25
|
+
'run-tests'
|
|
26
|
+
],
|
|
27
|
+
similarityThreshold: 0.85 // For fuzzy duplicate detection
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Known AIOS task registry commands
|
|
32
|
+
* @type {Set<string>}
|
|
33
|
+
*/
|
|
34
|
+
const KNOWN_COMMANDS = new Set([
|
|
35
|
+
'develop',
|
|
36
|
+
'develop-yolo',
|
|
37
|
+
'develop-interactive',
|
|
38
|
+
'develop-preflight',
|
|
39
|
+
'review-qa',
|
|
40
|
+
'apply-qa-fixes',
|
|
41
|
+
'run-tests',
|
|
42
|
+
'create-story',
|
|
43
|
+
'create-next-story',
|
|
44
|
+
'validate-story-draft',
|
|
45
|
+
'create-pr',
|
|
46
|
+
'push',
|
|
47
|
+
'backlog-debt',
|
|
48
|
+
'help',
|
|
49
|
+
'exit',
|
|
50
|
+
'guide',
|
|
51
|
+
'explain',
|
|
52
|
+
'session-info',
|
|
53
|
+
'load-full',
|
|
54
|
+
'clear-cache',
|
|
55
|
+
'patterns',
|
|
56
|
+
'next'
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* PatternValidator class for validating pattern quality
|
|
61
|
+
*/
|
|
62
|
+
class PatternValidator {
|
|
63
|
+
/**
|
|
64
|
+
* Create a PatternValidator instance
|
|
65
|
+
* @param {Object} options - Configuration options
|
|
66
|
+
* @param {Object} options.rules - Custom validation rules
|
|
67
|
+
*/
|
|
68
|
+
constructor(options = {}) {
|
|
69
|
+
this.rules = { ...DEFAULT_VALIDATION_RULES, ...options.rules };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate a pattern against all rules
|
|
74
|
+
* @param {Object} pattern - Pattern to validate
|
|
75
|
+
* @returns {Object} Validation result with valid flag and reasons
|
|
76
|
+
*/
|
|
77
|
+
validate(pattern) {
|
|
78
|
+
const errors = [];
|
|
79
|
+
const warnings = [];
|
|
80
|
+
|
|
81
|
+
// Check required fields
|
|
82
|
+
if (!pattern) {
|
|
83
|
+
return { valid: false, errors: ['Pattern is null or undefined'], warnings: [] };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!pattern.sequence || !Array.isArray(pattern.sequence)) {
|
|
87
|
+
return { valid: false, errors: ['Missing or invalid sequence'], warnings: [] };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Rule 1: Minimum sequence length
|
|
91
|
+
if (pattern.sequence.length < this.rules.minSequenceLength) {
|
|
92
|
+
errors.push(
|
|
93
|
+
`Sequence too short: ${pattern.sequence.length} < minimum ${this.rules.minSequenceLength}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Rule 2: Maximum sequence length
|
|
98
|
+
if (pattern.sequence.length > this.rules.maxSequenceLength) {
|
|
99
|
+
warnings.push(
|
|
100
|
+
`Sequence unusually long: ${pattern.sequence.length} > ${this.rules.maxSequenceLength}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Rule 3: Minimum occurrences for promotion
|
|
105
|
+
if (pattern.occurrences !== undefined && pattern.occurrences < this.rules.minOccurrences) {
|
|
106
|
+
warnings.push(
|
|
107
|
+
`Low occurrences: ${pattern.occurrences} < minimum ${this.rules.minOccurrences} for promotion`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Rule 4: Minimum success rate
|
|
112
|
+
if (pattern.successRate !== undefined && pattern.successRate < this.rules.minSuccessRate) {
|
|
113
|
+
errors.push(
|
|
114
|
+
`Success rate too low: ${(pattern.successRate * 100).toFixed(1)}% < ${(this.rules.minSuccessRate * 100).toFixed(1)}%`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Rule 5: Must contain at least one key workflow command
|
|
119
|
+
const hasKeyCommand = pattern.sequence.some(cmd =>
|
|
120
|
+
this.rules.requiredKeyCommands.some(key => cmd.includes(key))
|
|
121
|
+
);
|
|
122
|
+
if (!hasKeyCommand) {
|
|
123
|
+
errors.push('Sequence must contain at least one key workflow command');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Rule 6: All commands should exist in AIOS task registry
|
|
127
|
+
const unknownCommands = pattern.sequence.filter(cmd => !this._isKnownCommand(cmd));
|
|
128
|
+
if (unknownCommands.length > 0) {
|
|
129
|
+
warnings.push(`Unknown commands: ${unknownCommands.join(', ')}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Rule 7: No duplicate consecutive commands
|
|
133
|
+
const hasDuplicateConsecutive = pattern.sequence.some(
|
|
134
|
+
(cmd, i) => i > 0 && cmd === pattern.sequence[i - 1]
|
|
135
|
+
);
|
|
136
|
+
if (hasDuplicateConsecutive) {
|
|
137
|
+
warnings.push('Pattern contains duplicate consecutive commands');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
valid: errors.length === 0,
|
|
142
|
+
errors: errors,
|
|
143
|
+
warnings: warnings,
|
|
144
|
+
reason: errors.length > 0 ? errors.join('; ') : null
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if a pattern is a duplicate of existing patterns
|
|
150
|
+
* @param {Object} pattern - Pattern to check
|
|
151
|
+
* @param {Object[]} existingPatterns - Existing patterns to compare against
|
|
152
|
+
* @returns {Object} Duplicate check result
|
|
153
|
+
*/
|
|
154
|
+
isDuplicate(pattern, existingPatterns) {
|
|
155
|
+
if (!existingPatterns || existingPatterns.length === 0) {
|
|
156
|
+
return { isDuplicate: false };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const existing of existingPatterns) {
|
|
160
|
+
// Exact match check first
|
|
161
|
+
if (this._isExactMatch(pattern.sequence, existing.sequence)) {
|
|
162
|
+
return {
|
|
163
|
+
isDuplicate: true,
|
|
164
|
+
duplicateOf: existing.id,
|
|
165
|
+
similarity: 1.0,
|
|
166
|
+
exact: true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Then similarity check
|
|
171
|
+
const similarity = this._calculateSimilarity(pattern.sequence, existing.sequence);
|
|
172
|
+
|
|
173
|
+
if (similarity >= this.rules.similarityThreshold) {
|
|
174
|
+
return {
|
|
175
|
+
isDuplicate: true,
|
|
176
|
+
duplicateOf: existing.id,
|
|
177
|
+
similarity: similarity
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { isDuplicate: false };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if pattern meets minimum threshold for promotion
|
|
187
|
+
* @param {Object} pattern - Pattern to check
|
|
188
|
+
* @returns {Object} Threshold check result
|
|
189
|
+
*/
|
|
190
|
+
meetsMinimumThreshold(pattern) {
|
|
191
|
+
const meetsOccurrences = pattern.occurrences >= this.rules.minOccurrences;
|
|
192
|
+
const meetsSuccessRate = pattern.successRate >= this.rules.minSuccessRate;
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
meetsThreshold: meetsOccurrences && meetsSuccessRate,
|
|
196
|
+
meetsOccurrences: meetsOccurrences,
|
|
197
|
+
meetsSuccessRate: meetsSuccessRate,
|
|
198
|
+
currentOccurrences: pattern.occurrences,
|
|
199
|
+
requiredOccurrences: this.rules.minOccurrences,
|
|
200
|
+
currentSuccessRate: pattern.successRate,
|
|
201
|
+
requiredSuccessRate: this.rules.minSuccessRate
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get current validation rules
|
|
207
|
+
* @returns {Object} Current validation rules
|
|
208
|
+
*/
|
|
209
|
+
getValidationRules() {
|
|
210
|
+
return { ...this.rules };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Update validation rules
|
|
215
|
+
* @param {Object} newRules - New rules to merge
|
|
216
|
+
*/
|
|
217
|
+
updateRules(newRules) {
|
|
218
|
+
this.rules = { ...this.rules, ...newRules };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Check if a command is known in the AIOS task registry
|
|
223
|
+
* @param {string} command - Command to check
|
|
224
|
+
* @returns {boolean} True if command is known
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
_isKnownCommand(command) {
|
|
228
|
+
const normalized = command.toLowerCase().replace(/^\*/, '').trim();
|
|
229
|
+
|
|
230
|
+
// Direct match
|
|
231
|
+
if (KNOWN_COMMANDS.has(normalized)) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Prefix match (e.g., "develop-yolo" starts with known "develop")
|
|
236
|
+
for (const known of KNOWN_COMMANDS) {
|
|
237
|
+
if (normalized.startsWith(known) || known.startsWith(normalized)) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Calculate similarity between two sequences using Jaccard similarity
|
|
247
|
+
* @param {string[]} seq1 - First sequence
|
|
248
|
+
* @param {string[]} seq2 - Second sequence
|
|
249
|
+
* @returns {number} Similarity score (0-1)
|
|
250
|
+
* @private
|
|
251
|
+
*/
|
|
252
|
+
_calculateSimilarity(seq1, seq2) {
|
|
253
|
+
if (!seq1 || !seq2 || seq1.length === 0 || seq2.length === 0) {
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const set1 = new Set(seq1);
|
|
258
|
+
const set2 = new Set(seq2);
|
|
259
|
+
|
|
260
|
+
// Jaccard similarity
|
|
261
|
+
const intersection = new Set([...set1].filter(x => set2.has(x)));
|
|
262
|
+
const union = new Set([...set1, ...set2]);
|
|
263
|
+
|
|
264
|
+
const jaccardSimilarity = intersection.size / union.size;
|
|
265
|
+
|
|
266
|
+
// Order similarity (for sequence patterns, order matters)
|
|
267
|
+
let orderScore = 0;
|
|
268
|
+
const minLen = Math.min(seq1.length, seq2.length);
|
|
269
|
+
for (let i = 0; i < minLen; i++) {
|
|
270
|
+
if (seq1[i] === seq2[i]) {
|
|
271
|
+
orderScore++;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const orderSimilarity = orderScore / Math.max(seq1.length, seq2.length);
|
|
275
|
+
|
|
276
|
+
// Combined score (weighted average)
|
|
277
|
+
return (jaccardSimilarity * 0.4) + (orderSimilarity * 0.6);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if two sequences are exactly the same
|
|
282
|
+
* @param {string[]} seq1 - First sequence
|
|
283
|
+
* @param {string[]} seq2 - Second sequence
|
|
284
|
+
* @returns {boolean} True if exact match
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
_isExactMatch(seq1, seq2) {
|
|
288
|
+
if (seq1.length !== seq2.length) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
return seq1.every((cmd, i) => cmd === seq2[i]);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Create a new PatternValidator instance
|
|
297
|
+
* @param {Object} options - Configuration options
|
|
298
|
+
* @returns {PatternValidator} New instance
|
|
299
|
+
*/
|
|
300
|
+
function createPatternValidator(options = {}) {
|
|
301
|
+
return new PatternValidator(options);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = {
|
|
305
|
+
PatternValidator,
|
|
306
|
+
createPatternValidator,
|
|
307
|
+
DEFAULT_VALIDATION_RULES,
|
|
308
|
+
KNOWN_COMMANDS
|
|
309
|
+
};
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module WorkflowRegistry
|
|
3
|
+
* @description Registry for loading and managing workflow patterns
|
|
4
|
+
* @story WIS-2 - Workflow Registry Enhancement
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default cache TTL in milliseconds (5 minutes)
|
|
16
|
+
* @type {number}
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_CACHE_TTL = 5 * 60 * 1000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default path to workflow patterns file
|
|
22
|
+
* @type {string}
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_PATTERNS_PATH = path.join(
|
|
25
|
+
__dirname,
|
|
26
|
+
'../../data/workflow-patterns.yaml'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* WorkflowRegistry class for managing workflow patterns
|
|
31
|
+
*/
|
|
32
|
+
class WorkflowRegistry {
|
|
33
|
+
/**
|
|
34
|
+
* Create a WorkflowRegistry instance
|
|
35
|
+
* @param {Object} options - Configuration options
|
|
36
|
+
* @param {string} options.patternsPath - Path to workflow-patterns.yaml
|
|
37
|
+
* @param {number} options.cacheTTL - Cache time-to-live in milliseconds
|
|
38
|
+
*/
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.patternsPath = options.patternsPath || DEFAULT_PATTERNS_PATH;
|
|
41
|
+
this.cacheTTL = options.cacheTTL || DEFAULT_CACHE_TTL;
|
|
42
|
+
this.cache = null;
|
|
43
|
+
this.cacheTimestamp = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load workflows from YAML file with caching
|
|
48
|
+
* @returns {Object} Loaded workflow patterns
|
|
49
|
+
* @throws {Error} If file cannot be read or parsed
|
|
50
|
+
*/
|
|
51
|
+
loadWorkflows() {
|
|
52
|
+
// Check if cache is valid
|
|
53
|
+
if (this.isCacheValid()) {
|
|
54
|
+
return this.cache;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(this.patternsPath, 'utf8');
|
|
59
|
+
const parsed = yaml.load(content);
|
|
60
|
+
|
|
61
|
+
if (!parsed || !parsed.workflows) {
|
|
62
|
+
throw new Error('Invalid workflow patterns file: missing workflows key');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.cache = parsed.workflows;
|
|
66
|
+
this.cacheTimestamp = Date.now();
|
|
67
|
+
|
|
68
|
+
return this.cache;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error.code === 'ENOENT') {
|
|
71
|
+
throw new Error(`Workflow patterns file not found: ${this.patternsPath}`);
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`Failed to load workflow patterns: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if cache is still valid
|
|
79
|
+
* @returns {boolean} True if cache is valid
|
|
80
|
+
*/
|
|
81
|
+
isCacheValid() {
|
|
82
|
+
if (!this.cache || !this.cacheTimestamp) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return (Date.now() - this.cacheTimestamp) < this.cacheTTL;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Invalidate the cache
|
|
90
|
+
*/
|
|
91
|
+
invalidateCache() {
|
|
92
|
+
this.cache = null;
|
|
93
|
+
this.cacheTimestamp = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all workflow names
|
|
98
|
+
* @returns {string[]} Array of workflow names
|
|
99
|
+
*/
|
|
100
|
+
getWorkflowNames() {
|
|
101
|
+
const workflows = this.loadWorkflows();
|
|
102
|
+
return Object.keys(workflows);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a specific workflow by name
|
|
107
|
+
* @param {string} name - Workflow name
|
|
108
|
+
* @returns {Object|null} Workflow object or null if not found
|
|
109
|
+
*/
|
|
110
|
+
getWorkflow(name) {
|
|
111
|
+
const workflows = this.loadWorkflows();
|
|
112
|
+
return workflows[name] || null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Match command history to find the best matching workflow
|
|
117
|
+
* @param {string[]} commands - Recent command history
|
|
118
|
+
* @returns {Object|null} Best matching workflow with score, or null
|
|
119
|
+
*/
|
|
120
|
+
matchWorkflow(commands) {
|
|
121
|
+
if (!commands || commands.length === 0) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const workflows = this.loadWorkflows();
|
|
126
|
+
const normalizedCommands = commands.map(cmd => this.normalizeCommand(cmd));
|
|
127
|
+
|
|
128
|
+
let bestMatch = null;
|
|
129
|
+
let bestScore = 0;
|
|
130
|
+
|
|
131
|
+
for (const [name, workflow] of Object.entries(workflows)) {
|
|
132
|
+
const score = this.calculateWorkflowMatch(workflow, normalizedCommands);
|
|
133
|
+
|
|
134
|
+
if (score > bestScore && score >= (workflow.trigger_threshold ?? 2)) {
|
|
135
|
+
bestScore = score;
|
|
136
|
+
bestMatch = {
|
|
137
|
+
name,
|
|
138
|
+
workflow,
|
|
139
|
+
score,
|
|
140
|
+
matchedCommands: this.getMatchedCommands(workflow, normalizedCommands)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return bestMatch;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Calculate match score between workflow and command history
|
|
150
|
+
* @param {Object} workflow - Workflow definition
|
|
151
|
+
* @param {string[]} commands - Normalized command history
|
|
152
|
+
* @returns {number} Match score
|
|
153
|
+
*/
|
|
154
|
+
calculateWorkflowMatch(workflow, commands) {
|
|
155
|
+
if (!workflow.key_commands) {
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const keyCommands = workflow.key_commands.map(cmd => this.normalizeCommand(cmd));
|
|
160
|
+
let matches = 0;
|
|
161
|
+
|
|
162
|
+
for (const cmd of commands) {
|
|
163
|
+
for (const keyCmd of keyCommands) {
|
|
164
|
+
if (cmd.includes(keyCmd) || keyCmd.includes(cmd)) {
|
|
165
|
+
matches++;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return matches;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get commands that matched from history
|
|
176
|
+
* @param {Object} workflow - Workflow definition
|
|
177
|
+
* @param {string[]} commands - Normalized command history
|
|
178
|
+
* @returns {string[]} Matched commands
|
|
179
|
+
*/
|
|
180
|
+
getMatchedCommands(workflow, commands) {
|
|
181
|
+
if (!workflow.key_commands) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const keyCommands = workflow.key_commands.map(cmd => this.normalizeCommand(cmd));
|
|
186
|
+
const matched = [];
|
|
187
|
+
|
|
188
|
+
for (const cmd of commands) {
|
|
189
|
+
for (const keyCmd of keyCommands) {
|
|
190
|
+
if (cmd.includes(keyCmd) || keyCmd.includes(cmd)) {
|
|
191
|
+
matched.push(cmd);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return matched;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get transitions for a workflow state
|
|
202
|
+
* @param {string} workflowName - Name of the workflow
|
|
203
|
+
* @param {string} state - Current state in the workflow
|
|
204
|
+
* @returns {Object|null} Transition object or null if not found
|
|
205
|
+
*/
|
|
206
|
+
getTransitions(workflowName, state) {
|
|
207
|
+
const workflow = this.getWorkflow(workflowName);
|
|
208
|
+
|
|
209
|
+
if (!workflow || !workflow.transitions) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return workflow.transitions[state] || null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get all available transitions for a workflow
|
|
218
|
+
* @param {string} workflowName - Name of the workflow
|
|
219
|
+
* @returns {Object} All transitions for the workflow
|
|
220
|
+
*/
|
|
221
|
+
getAllTransitions(workflowName) {
|
|
222
|
+
const workflow = this.getWorkflow(workflowName);
|
|
223
|
+
|
|
224
|
+
if (!workflow || !workflow.transitions) {
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return workflow.transitions;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get next steps for a workflow state
|
|
233
|
+
* @param {string} workflowName - Name of the workflow
|
|
234
|
+
* @param {string} state - Current state
|
|
235
|
+
* @returns {Object[]} Array of next step suggestions
|
|
236
|
+
*/
|
|
237
|
+
getNextSteps(workflowName, state) {
|
|
238
|
+
const transition = this.getTransitions(workflowName, state);
|
|
239
|
+
|
|
240
|
+
if (!transition || !transition.next_steps) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return transition.next_steps.sort((a, b) => (a.priority || 99) - (b.priority || 99));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Find current state based on last command
|
|
249
|
+
* @param {string} workflowName - Name of the workflow
|
|
250
|
+
* @param {string} lastCommand - Last executed command
|
|
251
|
+
* @returns {string|null} Current state or null if not found
|
|
252
|
+
*/
|
|
253
|
+
findCurrentState(workflowName, lastCommand) {
|
|
254
|
+
const workflow = this.getWorkflow(workflowName);
|
|
255
|
+
|
|
256
|
+
if (!workflow || !workflow.transitions) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const normalizedCmd = this.normalizeCommand(lastCommand);
|
|
261
|
+
|
|
262
|
+
for (const [state, transition] of Object.entries(workflow.transitions)) {
|
|
263
|
+
if (transition.trigger) {
|
|
264
|
+
const normalizedTrigger = this.normalizeCommand(transition.trigger);
|
|
265
|
+
if (normalizedCmd.includes(normalizedTrigger) ||
|
|
266
|
+
normalizedTrigger.includes(normalizedCmd)) {
|
|
267
|
+
return state;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get workflows by agent
|
|
277
|
+
* @param {string} agentId - Agent identifier
|
|
278
|
+
* @returns {Object[]} Workflows involving this agent
|
|
279
|
+
*/
|
|
280
|
+
getWorkflowsByAgent(agentId) {
|
|
281
|
+
const workflows = this.loadWorkflows();
|
|
282
|
+
const normalizedAgent = agentId.replace('@', '').toLowerCase();
|
|
283
|
+
const results = [];
|
|
284
|
+
|
|
285
|
+
for (const [name, workflow] of Object.entries(workflows)) {
|
|
286
|
+
if (workflow.agent_sequence) {
|
|
287
|
+
const hasAgent = workflow.agent_sequence.some(
|
|
288
|
+
agent => agent.toLowerCase() === normalizedAgent
|
|
289
|
+
);
|
|
290
|
+
if (hasAgent) {
|
|
291
|
+
results.push({ name, workflow });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return results;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Normalize a command string for matching
|
|
301
|
+
* @param {string} command - Command to normalize
|
|
302
|
+
* @returns {string} Normalized command
|
|
303
|
+
*/
|
|
304
|
+
normalizeCommand(command) {
|
|
305
|
+
if (!command) return '';
|
|
306
|
+
|
|
307
|
+
return command
|
|
308
|
+
.toLowerCase()
|
|
309
|
+
.replace(/\s+completed\s*$/i, '')
|
|
310
|
+
.replace(/\s+successfully\s*$/i, '')
|
|
311
|
+
.replace(/^\*/, '')
|
|
312
|
+
.replace(/['"]/g, '')
|
|
313
|
+
.trim();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get registry statistics
|
|
318
|
+
* @returns {Object} Statistics about the registry
|
|
319
|
+
*/
|
|
320
|
+
getStats() {
|
|
321
|
+
const workflows = this.loadWorkflows();
|
|
322
|
+
const workflowNames = Object.keys(workflows);
|
|
323
|
+
|
|
324
|
+
let totalTransitions = 0;
|
|
325
|
+
let workflowsWithTransitions = 0;
|
|
326
|
+
|
|
327
|
+
for (const workflow of Object.values(workflows)) {
|
|
328
|
+
if (workflow.transitions) {
|
|
329
|
+
workflowsWithTransitions++;
|
|
330
|
+
totalTransitions += Object.keys(workflow.transitions).length;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
totalWorkflows: workflowNames.length,
|
|
336
|
+
workflowsWithTransitions,
|
|
337
|
+
totalTransitions,
|
|
338
|
+
cacheValid: this.isCacheValid(),
|
|
339
|
+
cacheAge: this.cacheTimestamp ? Date.now() - this.cacheTimestamp : null
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Create a new WorkflowRegistry instance
|
|
346
|
+
* @param {Object} options - Configuration options
|
|
347
|
+
* @returns {WorkflowRegistry} New registry instance
|
|
348
|
+
*/
|
|
349
|
+
function createWorkflowRegistry(options = {}) {
|
|
350
|
+
return new WorkflowRegistry(options);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = {
|
|
354
|
+
WorkflowRegistry,
|
|
355
|
+
createWorkflowRegistry,
|
|
356
|
+
DEFAULT_CACHE_TTL,
|
|
357
|
+
DEFAULT_PATTERNS_PATH
|
|
358
|
+
};
|