aios-core 3.6.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 +13 -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/create-service.md +391 -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/service-template/README.md.hbs +158 -0
- package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -0
- package/.aios-core/development/templates/service-template/client.ts.hbs +403 -0
- package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -0
- package/.aios-core/development/templates/service-template/index.ts.hbs +120 -0
- package/.aios-core/development/templates/service-template/jest.config.js +89 -0
- package/.aios-core/development/templates/service-template/package.json.hbs +87 -0
- package/.aios-core/development/templates/service-template/tsconfig.json +45 -0
- package/.aios-core/development/templates/service-template/types.ts.hbs +145 -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/infrastructure/scripts/ide-sync/agent-parser.js +45 -1
- package/.aios-core/infrastructure/scripts/ide-sync/transformers/antigravity.js +6 -6
- package/.aios-core/infrastructure/scripts/ide-sync/transformers/cursor.js +5 -4
- package/.aios-core/infrastructure/scripts/ide-sync/transformers/trae.js +3 -3
- package/.aios-core/infrastructure/scripts/ide-sync/transformers/windsurf.js +3 -3
- package/.aios-core/install-manifest.yaml +139 -35
- 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,498 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module PatternStore
|
|
3
|
+
* @description Stores and manages learned workflow patterns
|
|
4
|
+
* @story WIS-5 - Pattern Capture (Internal)
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const yaml = require('yaml');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default storage path for learned patterns
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_STORAGE_PATH = path.join(__dirname, '../../..', 'data', 'learned-patterns.yaml');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default maximum number of patterns
|
|
23
|
+
* @type {number}
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_MAX_PATTERNS = 100;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default auto-prune threshold (percentage of max)
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_PRUNE_THRESHOLD = 0.9;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pattern status values
|
|
35
|
+
* @type {Object}
|
|
36
|
+
*/
|
|
37
|
+
const PATTERN_STATUS = {
|
|
38
|
+
PENDING: 'pending',
|
|
39
|
+
ACTIVE: 'active',
|
|
40
|
+
PROMOTED: 'promoted',
|
|
41
|
+
DEPRECATED: 'deprecated'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* PatternStore class for persisting workflow patterns
|
|
46
|
+
*/
|
|
47
|
+
class PatternStore {
|
|
48
|
+
/**
|
|
49
|
+
* Create a PatternStore instance
|
|
50
|
+
* @param {Object} options - Configuration options
|
|
51
|
+
* @param {string} options.storagePath - Path to storage file
|
|
52
|
+
* @param {number} options.maxPatterns - Maximum patterns to store
|
|
53
|
+
* @param {number} options.pruneThreshold - Auto-prune threshold (0-1)
|
|
54
|
+
* @param {string} options.pruneStrategy - Prune strategy ('oldest_low_occurrence' | 'lowest_success_rate')
|
|
55
|
+
*/
|
|
56
|
+
constructor(options = {}) {
|
|
57
|
+
this.storagePath = options.storagePath || DEFAULT_STORAGE_PATH;
|
|
58
|
+
this.maxPatterns = options.maxPatterns || DEFAULT_MAX_PATTERNS;
|
|
59
|
+
this.pruneThreshold = options.pruneThreshold || DEFAULT_PRUNE_THRESHOLD;
|
|
60
|
+
this.pruneStrategy = options.pruneStrategy || 'oldest_low_occurrence';
|
|
61
|
+
this._cache = null;
|
|
62
|
+
this._cacheTime = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Save a pattern to storage
|
|
67
|
+
* @param {Object} pattern - Pattern to save
|
|
68
|
+
* @returns {Object} Save result
|
|
69
|
+
*/
|
|
70
|
+
save(pattern) {
|
|
71
|
+
const data = this._load();
|
|
72
|
+
|
|
73
|
+
// Ensure pattern has required fields
|
|
74
|
+
const normalizedPattern = this._normalizePattern(pattern);
|
|
75
|
+
|
|
76
|
+
// Check for existing pattern with same sequence
|
|
77
|
+
const existingIndex = data.patterns.findIndex(
|
|
78
|
+
p => this._sequenceEquals(p.sequence, normalizedPattern.sequence)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (existingIndex >= 0) {
|
|
82
|
+
// Update existing pattern
|
|
83
|
+
const existing = data.patterns[existingIndex];
|
|
84
|
+
existing.occurrences = (existing.occurrences || 1) + 1;
|
|
85
|
+
existing.lastSeen = new Date().toISOString();
|
|
86
|
+
|
|
87
|
+
// Update success rate (weighted average)
|
|
88
|
+
const totalOccurrences = existing.occurrences;
|
|
89
|
+
const newSuccessRate = normalizedPattern.successRate || 1.0;
|
|
90
|
+
existing.successRate = (
|
|
91
|
+
(existing.successRate * (totalOccurrences - 1) + newSuccessRate) / totalOccurrences
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
data.patterns[existingIndex] = existing;
|
|
95
|
+
|
|
96
|
+
this._save(data);
|
|
97
|
+
return { action: 'updated', pattern: existing };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Add new pattern
|
|
101
|
+
data.patterns.push(normalizedPattern);
|
|
102
|
+
|
|
103
|
+
// Check if auto-prune needed
|
|
104
|
+
if (data.patterns.length > this.maxPatterns * this.pruneThreshold) {
|
|
105
|
+
this._autoPrune(data);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this._save(data);
|
|
109
|
+
return { action: 'created', pattern: normalizedPattern };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Load all patterns from storage
|
|
114
|
+
* @returns {Object} Loaded data with patterns array
|
|
115
|
+
*/
|
|
116
|
+
load() {
|
|
117
|
+
return this._load();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Find patterns similar to a given sequence
|
|
122
|
+
* @param {string[]} sequence - Sequence to match
|
|
123
|
+
* @returns {Object[]} Matching patterns sorted by relevance
|
|
124
|
+
*/
|
|
125
|
+
findSimilar(sequence) {
|
|
126
|
+
const data = this._load();
|
|
127
|
+
|
|
128
|
+
if (!sequence || sequence.length === 0) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const matches = [];
|
|
133
|
+
|
|
134
|
+
for (const pattern of data.patterns) {
|
|
135
|
+
const similarity = this._calculateSimilarity(sequence, pattern.sequence);
|
|
136
|
+
|
|
137
|
+
if (similarity > 0.3) { // Minimum threshold for consideration
|
|
138
|
+
matches.push({
|
|
139
|
+
...pattern,
|
|
140
|
+
similarity: similarity
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Sort by similarity (descending), then by occurrences
|
|
146
|
+
return matches.sort((a, b) => {
|
|
147
|
+
if (Math.abs(a.similarity - b.similarity) > 0.1) {
|
|
148
|
+
return b.similarity - a.similarity;
|
|
149
|
+
}
|
|
150
|
+
return (b.occurrences || 1) - (a.occurrences || 1);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get storage statistics
|
|
156
|
+
* @returns {Object} Statistics object
|
|
157
|
+
*/
|
|
158
|
+
getStats() {
|
|
159
|
+
const data = this._load();
|
|
160
|
+
const patterns = data.patterns;
|
|
161
|
+
|
|
162
|
+
const statusCounts = {
|
|
163
|
+
pending: 0,
|
|
164
|
+
active: 0,
|
|
165
|
+
promoted: 0,
|
|
166
|
+
deprecated: 0
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
let totalSuccessRate = 0;
|
|
170
|
+
let totalOccurrences = 0;
|
|
171
|
+
|
|
172
|
+
for (const pattern of patterns) {
|
|
173
|
+
statusCounts[pattern.status || 'pending']++;
|
|
174
|
+
totalSuccessRate += pattern.successRate || 0;
|
|
175
|
+
totalOccurrences += pattern.occurrences || 1;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const avgSuccessRate = patterns.length > 0
|
|
179
|
+
? totalSuccessRate / patterns.length
|
|
180
|
+
: 0;
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
totalPatterns: patterns.length,
|
|
184
|
+
maxPatterns: this.maxPatterns,
|
|
185
|
+
utilizationPercent: Math.round((patterns.length / this.maxPatterns) * 100),
|
|
186
|
+
statusCounts: statusCounts,
|
|
187
|
+
avgSuccessRate: Math.round(avgSuccessRate * 100) / 100,
|
|
188
|
+
totalOccurrences: totalOccurrences,
|
|
189
|
+
storageFile: this.storagePath,
|
|
190
|
+
lastUpdated: data.lastUpdated || null
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Prune patterns based on configured strategy
|
|
196
|
+
* @param {Object} options - Prune options
|
|
197
|
+
* @param {number} options.keepCount - Number of patterns to keep
|
|
198
|
+
* @param {string} options.strategy - Prune strategy override
|
|
199
|
+
* @returns {Object} Prune result
|
|
200
|
+
*/
|
|
201
|
+
prune(options = {}) {
|
|
202
|
+
const data = this._load();
|
|
203
|
+
const originalCount = data.patterns.length;
|
|
204
|
+
|
|
205
|
+
const keepCount = options.keepCount || Math.floor(this.maxPatterns * 0.7);
|
|
206
|
+
const strategy = options.strategy || this.pruneStrategy;
|
|
207
|
+
|
|
208
|
+
if (data.patterns.length <= keepCount) {
|
|
209
|
+
return { pruned: 0, remaining: data.patterns.length };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Sort patterns for pruning
|
|
213
|
+
let sorted;
|
|
214
|
+
if (strategy === 'lowest_success_rate') {
|
|
215
|
+
sorted = [...data.patterns].sort((a, b) => {
|
|
216
|
+
// Keep promoted/active patterns
|
|
217
|
+
if (a.status === 'promoted' || a.status === 'active') return -1;
|
|
218
|
+
if (b.status === 'promoted' || b.status === 'active') return 1;
|
|
219
|
+
// Then by success rate
|
|
220
|
+
return (b.successRate || 0) - (a.successRate || 0);
|
|
221
|
+
});
|
|
222
|
+
} else { // oldest_low_occurrence (default)
|
|
223
|
+
sorted = [...data.patterns].sort((a, b) => {
|
|
224
|
+
// Keep promoted patterns
|
|
225
|
+
if (a.status === 'promoted') return -1;
|
|
226
|
+
if (b.status === 'promoted') return 1;
|
|
227
|
+
// Keep active patterns
|
|
228
|
+
if (a.status === 'active') return -1;
|
|
229
|
+
if (b.status === 'active') return 1;
|
|
230
|
+
// Then by occurrences (higher = keep)
|
|
231
|
+
if ((a.occurrences || 1) !== (b.occurrences || 1)) {
|
|
232
|
+
return (b.occurrences || 1) - (a.occurrences || 1);
|
|
233
|
+
}
|
|
234
|
+
// Then by last seen (newer = keep)
|
|
235
|
+
return new Date(b.lastSeen || 0) - new Date(a.lastSeen || 0);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Keep top patterns
|
|
240
|
+
data.patterns = sorted.slice(0, keepCount);
|
|
241
|
+
|
|
242
|
+
this._save(data);
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
pruned: originalCount - data.patterns.length,
|
|
246
|
+
remaining: data.patterns.length
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Update pattern status
|
|
252
|
+
* @param {string} patternId - Pattern ID
|
|
253
|
+
* @param {string} newStatus - New status value
|
|
254
|
+
* @returns {Object} Update result
|
|
255
|
+
*/
|
|
256
|
+
updateStatus(patternId, newStatus) {
|
|
257
|
+
const data = this._load();
|
|
258
|
+
const pattern = data.patterns.find(p => p.id === patternId);
|
|
259
|
+
|
|
260
|
+
if (!pattern) {
|
|
261
|
+
return { success: false, error: 'Pattern not found' };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const validStatuses = Object.values(PATTERN_STATUS);
|
|
265
|
+
if (!validStatuses.includes(newStatus)) {
|
|
266
|
+
return { success: false, error: `Invalid status: ${newStatus}` };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
pattern.status = newStatus;
|
|
270
|
+
pattern.lastUpdated = new Date().toISOString();
|
|
271
|
+
|
|
272
|
+
this._save(data);
|
|
273
|
+
|
|
274
|
+
return { success: true, pattern: pattern };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get patterns by status
|
|
279
|
+
* @param {string} status - Status to filter by
|
|
280
|
+
* @returns {Object[]} Patterns with given status
|
|
281
|
+
*/
|
|
282
|
+
getByStatus(status) {
|
|
283
|
+
const data = this._load();
|
|
284
|
+
return data.patterns.filter(p => p.status === status);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get active and promoted patterns (for SuggestionEngine)
|
|
289
|
+
* @returns {Object[]} Active patterns
|
|
290
|
+
*/
|
|
291
|
+
getActivePatterns() {
|
|
292
|
+
const data = this._load();
|
|
293
|
+
return data.patterns.filter(
|
|
294
|
+
p => p.status === PATTERN_STATUS.ACTIVE || p.status === PATTERN_STATUS.PROMOTED
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Delete a pattern by ID
|
|
300
|
+
* @param {string} patternId - Pattern ID
|
|
301
|
+
* @returns {Object} Delete result
|
|
302
|
+
*/
|
|
303
|
+
delete(patternId) {
|
|
304
|
+
const data = this._load();
|
|
305
|
+
const index = data.patterns.findIndex(p => p.id === patternId);
|
|
306
|
+
|
|
307
|
+
if (index < 0) {
|
|
308
|
+
return { success: false, error: 'Pattern not found' };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
data.patterns.splice(index, 1);
|
|
312
|
+
this._save(data);
|
|
313
|
+
|
|
314
|
+
return { success: true };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Load data from storage file
|
|
319
|
+
* @returns {Object} Loaded data
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
322
|
+
_load() {
|
|
323
|
+
// Use cache if valid (within 5 seconds)
|
|
324
|
+
if (this._cache && this._cacheTime && (Date.now() - this._cacheTime) < 5000) {
|
|
325
|
+
return this._cache;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
if (fs.existsSync(this.storagePath)) {
|
|
330
|
+
const content = fs.readFileSync(this.storagePath, 'utf8');
|
|
331
|
+
const data = yaml.parse(content) || {};
|
|
332
|
+
data.patterns = data.patterns || [];
|
|
333
|
+
this._cache = data;
|
|
334
|
+
this._cacheTime = Date.now();
|
|
335
|
+
return data;
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.warn('[PatternStore] Failed to load:', error.message);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Return empty structure
|
|
342
|
+
const empty = { version: '1.0', patterns: [] };
|
|
343
|
+
this._cache = empty;
|
|
344
|
+
this._cacheTime = Date.now();
|
|
345
|
+
return empty;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Save data to storage file
|
|
350
|
+
* @param {Object} data - Data to save
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
_save(data) {
|
|
354
|
+
try {
|
|
355
|
+
data.lastUpdated = new Date().toISOString();
|
|
356
|
+
|
|
357
|
+
// Sort patterns for git-friendly diffs
|
|
358
|
+
data.patterns.sort((a, b) => a.id.localeCompare(b.id));
|
|
359
|
+
|
|
360
|
+
// Ensure directory exists
|
|
361
|
+
const dir = path.dirname(this.storagePath);
|
|
362
|
+
if (!fs.existsSync(dir)) {
|
|
363
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const content = yaml.stringify(data, { indent: 2, lineWidth: 120 });
|
|
367
|
+
fs.writeFileSync(this.storagePath, content, 'utf8');
|
|
368
|
+
|
|
369
|
+
// Invalidate cache
|
|
370
|
+
this._cache = data;
|
|
371
|
+
this._cacheTime = Date.now();
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error('[PatternStore] Failed to save:', error.message);
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Normalize pattern object
|
|
380
|
+
* @param {Object} pattern - Pattern to normalize
|
|
381
|
+
* @returns {Object} Normalized pattern
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
_normalizePattern(pattern) {
|
|
385
|
+
return {
|
|
386
|
+
id: pattern.id || crypto.randomUUID(),
|
|
387
|
+
sequence: pattern.sequence || [],
|
|
388
|
+
agents: pattern.agents || [],
|
|
389
|
+
occurrences: pattern.occurrences || 1,
|
|
390
|
+
successRate: pattern.successRate || 1.0,
|
|
391
|
+
firstSeen: pattern.firstSeen || new Date().toISOString(),
|
|
392
|
+
lastSeen: pattern.lastSeen || new Date().toISOString(),
|
|
393
|
+
workflow: pattern.workflow || null,
|
|
394
|
+
status: pattern.status || PATTERN_STATUS.PENDING
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Check if two sequences are equal
|
|
400
|
+
* @param {string[]} seq1 - First sequence
|
|
401
|
+
* @param {string[]} seq2 - Second sequence
|
|
402
|
+
* @returns {boolean} True if equal
|
|
403
|
+
* @private
|
|
404
|
+
*/
|
|
405
|
+
_sequenceEquals(seq1, seq2) {
|
|
406
|
+
if (!seq1 || !seq2 || seq1.length !== seq2.length) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
return seq1.every((cmd, i) => cmd === seq2[i]);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Calculate similarity between sequences
|
|
414
|
+
* @param {string[]} seq1 - First sequence
|
|
415
|
+
* @param {string[]} seq2 - Second sequence
|
|
416
|
+
* @returns {number} Similarity score (0-1)
|
|
417
|
+
* @private
|
|
418
|
+
*/
|
|
419
|
+
_calculateSimilarity(seq1, seq2) {
|
|
420
|
+
if (!seq1 || !seq2) return 0;
|
|
421
|
+
|
|
422
|
+
// Check for subsequence match
|
|
423
|
+
const joined1 = seq1.join('|');
|
|
424
|
+
const joined2 = seq2.join('|');
|
|
425
|
+
|
|
426
|
+
if (joined2.includes(joined1) || joined1.includes(joined2)) {
|
|
427
|
+
return 0.9;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Check for partial overlap
|
|
431
|
+
let matches = 0;
|
|
432
|
+
for (const cmd of seq1) {
|
|
433
|
+
if (seq2.includes(cmd)) {
|
|
434
|
+
matches++;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return matches / Math.max(seq1.length, seq2.length);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Auto-prune when approaching limit
|
|
443
|
+
* @param {Object} data - Data object
|
|
444
|
+
* @private
|
|
445
|
+
*/
|
|
446
|
+
_autoPrune(data) {
|
|
447
|
+
const keepCount = Math.floor(this.maxPatterns * 0.8);
|
|
448
|
+
|
|
449
|
+
// Sort by priority (keep promoted > active > pending)
|
|
450
|
+
// Then by occurrences, then by last seen
|
|
451
|
+
data.patterns.sort((a, b) => {
|
|
452
|
+
// Status priority
|
|
453
|
+
const statusPriority = { promoted: 3, active: 2, pending: 1, deprecated: 0 };
|
|
454
|
+
const aPriority = statusPriority[a.status] || 0;
|
|
455
|
+
const bPriority = statusPriority[b.status] || 0;
|
|
456
|
+
|
|
457
|
+
if (aPriority !== bPriority) {
|
|
458
|
+
return bPriority - aPriority;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Then by occurrences
|
|
462
|
+
if ((a.occurrences || 1) !== (b.occurrences || 1)) {
|
|
463
|
+
return (b.occurrences || 1) - (a.occurrences || 1);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Then by last seen
|
|
467
|
+
return new Date(b.lastSeen || 0) - new Date(a.lastSeen || 0);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
data.patterns = data.patterns.slice(0, keepCount);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Invalidate cache
|
|
475
|
+
*/
|
|
476
|
+
invalidateCache() {
|
|
477
|
+
this._cache = null;
|
|
478
|
+
this._cacheTime = null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Create a new PatternStore instance
|
|
484
|
+
* @param {Object} options - Configuration options
|
|
485
|
+
* @returns {PatternStore} New instance
|
|
486
|
+
*/
|
|
487
|
+
function createPatternStore(options = {}) {
|
|
488
|
+
return new PatternStore(options);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
module.exports = {
|
|
492
|
+
PatternStore,
|
|
493
|
+
createPatternStore,
|
|
494
|
+
DEFAULT_STORAGE_PATH,
|
|
495
|
+
DEFAULT_MAX_PATTERNS,
|
|
496
|
+
DEFAULT_PRUNE_THRESHOLD,
|
|
497
|
+
PATTERN_STATUS
|
|
498
|
+
};
|