musubi-sdd 3.10.0 → 5.1.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/README.md +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Registry - Centralized skill registration and discovery
|
|
3
|
+
* Sprint 3.1: Skill System Architecture
|
|
4
|
+
*
|
|
5
|
+
* Inspired by OpenAI Agents SDK skill management patterns
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require('events');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Skill metadata schema
|
|
12
|
+
*/
|
|
13
|
+
class SkillMetadata {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.id = options.id || '';
|
|
16
|
+
this.name = options.name || '';
|
|
17
|
+
this.description = options.description || '';
|
|
18
|
+
this.version = options.version || '1.0.0';
|
|
19
|
+
this.category = options.category || 'general';
|
|
20
|
+
this.tags = options.tags || [];
|
|
21
|
+
this.author = options.author || '';
|
|
22
|
+
this.inputs = options.inputs || [];
|
|
23
|
+
this.outputs = options.outputs || [];
|
|
24
|
+
this.dependencies = options.dependencies || [];
|
|
25
|
+
this.timeout = options.timeout || 30000;
|
|
26
|
+
this.retryPolicy = options.retryPolicy || { maxRetries: 3, backoffMs: 1000 };
|
|
27
|
+
this.priority = options.priority || 'P2';
|
|
28
|
+
this.permissions = options.permissions || [];
|
|
29
|
+
this.createdAt = options.createdAt || new Date().toISOString();
|
|
30
|
+
this.updatedAt = options.updatedAt || new Date().toISOString();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
validate() {
|
|
34
|
+
const errors = [];
|
|
35
|
+
|
|
36
|
+
if (!this.id) {
|
|
37
|
+
errors.push('Skill ID is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!this.name) {
|
|
41
|
+
errors.push('Skill name is required');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!/^[a-z0-9-]+$/.test(this.id)) {
|
|
45
|
+
errors.push('Skill ID must be lowercase alphanumeric with hyphens');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!['P0', 'P1', 'P2', 'P3'].includes(this.priority)) {
|
|
49
|
+
errors.push('Priority must be P0, P1, P2, or P3');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate inputs
|
|
53
|
+
for (const input of this.inputs) {
|
|
54
|
+
if (!input.name) {
|
|
55
|
+
errors.push('Input name is required');
|
|
56
|
+
}
|
|
57
|
+
if (!input.type) {
|
|
58
|
+
errors.push(`Input ${input.name || 'unknown'} must have a type`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate outputs
|
|
63
|
+
for (const output of this.outputs) {
|
|
64
|
+
if (!output.name) {
|
|
65
|
+
errors.push('Output name is required');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
valid: errors.length === 0,
|
|
71
|
+
errors
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toJSON() {
|
|
76
|
+
return {
|
|
77
|
+
id: this.id,
|
|
78
|
+
name: this.name,
|
|
79
|
+
description: this.description,
|
|
80
|
+
version: this.version,
|
|
81
|
+
category: this.category,
|
|
82
|
+
tags: this.tags,
|
|
83
|
+
author: this.author,
|
|
84
|
+
inputs: this.inputs,
|
|
85
|
+
outputs: this.outputs,
|
|
86
|
+
dependencies: this.dependencies,
|
|
87
|
+
timeout: this.timeout,
|
|
88
|
+
retryPolicy: this.retryPolicy,
|
|
89
|
+
priority: this.priority,
|
|
90
|
+
permissions: this.permissions,
|
|
91
|
+
createdAt: this.createdAt,
|
|
92
|
+
updatedAt: this.updatedAt
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Skill categories
|
|
99
|
+
*/
|
|
100
|
+
const SkillCategory = {
|
|
101
|
+
ANALYSIS: 'analysis',
|
|
102
|
+
DESIGN: 'design',
|
|
103
|
+
IMPLEMENTATION: 'implementation',
|
|
104
|
+
TESTING: 'testing',
|
|
105
|
+
DOCUMENTATION: 'documentation',
|
|
106
|
+
ORCHESTRATION: 'orchestration',
|
|
107
|
+
VALIDATION: 'validation',
|
|
108
|
+
INTEGRATION: 'integration',
|
|
109
|
+
MONITORING: 'monitoring',
|
|
110
|
+
GENERAL: 'general'
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Priority levels for skill execution
|
|
115
|
+
*/
|
|
116
|
+
const SkillPriority = {
|
|
117
|
+
P0: 'P0', // Critical - blocks all other work
|
|
118
|
+
P1: 'P1', // High - should run soon
|
|
119
|
+
P2: 'P2', // Medium - normal priority
|
|
120
|
+
P3: 'P3' // Low - background/optional
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Skill health status
|
|
125
|
+
*/
|
|
126
|
+
const SkillHealth = {
|
|
127
|
+
HEALTHY: 'healthy',
|
|
128
|
+
DEGRADED: 'degraded',
|
|
129
|
+
UNHEALTHY: 'unhealthy',
|
|
130
|
+
UNKNOWN: 'unknown'
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Main Skill Registry class
|
|
135
|
+
*/
|
|
136
|
+
class SkillRegistry extends EventEmitter {
|
|
137
|
+
constructor(options = {}) {
|
|
138
|
+
super();
|
|
139
|
+
this.skills = new Map();
|
|
140
|
+
this.categoryIndex = new Map();
|
|
141
|
+
this.tagIndex = new Map();
|
|
142
|
+
this.healthStatus = new Map();
|
|
143
|
+
this.executionStats = new Map();
|
|
144
|
+
this.validators = [];
|
|
145
|
+
this.hooks = {
|
|
146
|
+
beforeRegister: [],
|
|
147
|
+
afterRegister: [],
|
|
148
|
+
beforeUnregister: [],
|
|
149
|
+
afterUnregister: []
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Options
|
|
153
|
+
this.options = {
|
|
154
|
+
enableHealthMonitoring: options.enableHealthMonitoring !== false,
|
|
155
|
+
healthCheckInterval: options.healthCheckInterval || 60000,
|
|
156
|
+
maxSkills: options.maxSkills || 1000,
|
|
157
|
+
enableStats: options.enableStats !== false
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Start health monitoring if enabled
|
|
161
|
+
if (this.options.enableHealthMonitoring) {
|
|
162
|
+
this._startHealthMonitoring();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Register a new skill
|
|
168
|
+
*/
|
|
169
|
+
registerSkill(skillDef, handler = null) {
|
|
170
|
+
const metadata = skillDef instanceof SkillMetadata
|
|
171
|
+
? skillDef
|
|
172
|
+
: new SkillMetadata(skillDef);
|
|
173
|
+
|
|
174
|
+
// Validate metadata
|
|
175
|
+
const validation = metadata.validate();
|
|
176
|
+
if (!validation.valid) {
|
|
177
|
+
throw new Error(`Invalid skill metadata: ${validation.errors.join(', ')}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check max skills limit
|
|
181
|
+
if (this.skills.size >= this.options.maxSkills) {
|
|
182
|
+
throw new Error(`Maximum skill limit (${this.options.maxSkills}) reached`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Run custom validators
|
|
186
|
+
for (const validator of this.validators) {
|
|
187
|
+
const result = validator(metadata);
|
|
188
|
+
if (!result.valid) {
|
|
189
|
+
throw new Error(`Skill validation failed: ${result.error}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Run beforeRegister hooks
|
|
194
|
+
for (const hook of this.hooks.beforeRegister) {
|
|
195
|
+
hook(metadata);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check for duplicate
|
|
199
|
+
if (this.skills.has(metadata.id)) {
|
|
200
|
+
throw new Error(`Skill with ID '${metadata.id}' already exists`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Validate dependencies exist
|
|
204
|
+
for (const dep of metadata.dependencies) {
|
|
205
|
+
if (!this.skills.has(dep)) {
|
|
206
|
+
throw new Error(`Dependency '${dep}' not found for skill '${metadata.id}'`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Store skill
|
|
211
|
+
const skillEntry = {
|
|
212
|
+
metadata,
|
|
213
|
+
handler,
|
|
214
|
+
registeredAt: new Date().toISOString()
|
|
215
|
+
};
|
|
216
|
+
this.skills.set(metadata.id, skillEntry);
|
|
217
|
+
|
|
218
|
+
// Update indexes
|
|
219
|
+
this._addToIndex(this.categoryIndex, metadata.category, metadata.id);
|
|
220
|
+
for (const tag of metadata.tags) {
|
|
221
|
+
this._addToIndex(this.tagIndex, tag, metadata.id);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Initialize health and stats
|
|
225
|
+
this.healthStatus.set(metadata.id, SkillHealth.HEALTHY);
|
|
226
|
+
if (this.options.enableStats) {
|
|
227
|
+
this.executionStats.set(metadata.id, {
|
|
228
|
+
totalExecutions: 0,
|
|
229
|
+
successCount: 0,
|
|
230
|
+
failureCount: 0,
|
|
231
|
+
averageExecutionTime: 0,
|
|
232
|
+
lastExecutedAt: null
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Run afterRegister hooks
|
|
237
|
+
for (const hook of this.hooks.afterRegister) {
|
|
238
|
+
hook(metadata);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.emit('skill-registered', { skillId: metadata.id, metadata });
|
|
242
|
+
|
|
243
|
+
return metadata;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Unregister a skill
|
|
248
|
+
*/
|
|
249
|
+
unregisterSkill(skillId) {
|
|
250
|
+
const skillEntry = this.skills.get(skillId);
|
|
251
|
+
if (!skillEntry) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check if other skills depend on this one
|
|
256
|
+
const dependents = this._findDependents(skillId);
|
|
257
|
+
if (dependents.length > 0) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Cannot unregister skill '${skillId}': required by ${dependents.join(', ')}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Run beforeUnregister hooks
|
|
264
|
+
for (const hook of this.hooks.beforeUnregister) {
|
|
265
|
+
hook(skillEntry.metadata);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Remove from indexes
|
|
269
|
+
const metadata = skillEntry.metadata;
|
|
270
|
+
this._removeFromIndex(this.categoryIndex, metadata.category, skillId);
|
|
271
|
+
for (const tag of metadata.tags) {
|
|
272
|
+
this._removeFromIndex(this.tagIndex, tag, skillId);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Remove skill
|
|
276
|
+
this.skills.delete(skillId);
|
|
277
|
+
this.healthStatus.delete(skillId);
|
|
278
|
+
this.executionStats.delete(skillId);
|
|
279
|
+
|
|
280
|
+
// Run afterUnregister hooks
|
|
281
|
+
for (const hook of this.hooks.afterUnregister) {
|
|
282
|
+
hook(metadata);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.emit('skill-unregistered', { skillId, metadata });
|
|
286
|
+
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get a skill by ID
|
|
292
|
+
*/
|
|
293
|
+
getSkill(skillId) {
|
|
294
|
+
const entry = this.skills.get(skillId);
|
|
295
|
+
return entry ? entry.metadata : null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get skill with handler
|
|
300
|
+
*/
|
|
301
|
+
getSkillEntry(skillId) {
|
|
302
|
+
return this.skills.get(skillId) || null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check if skill exists
|
|
307
|
+
*/
|
|
308
|
+
hasSkill(skillId) {
|
|
309
|
+
return this.skills.has(skillId);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get all skills
|
|
314
|
+
*/
|
|
315
|
+
getAllSkills() {
|
|
316
|
+
return Array.from(this.skills.values()).map(entry => entry.metadata);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Find skills by category
|
|
321
|
+
*/
|
|
322
|
+
findByCategory(category) {
|
|
323
|
+
const skillIds = this.categoryIndex.get(category) || new Set();
|
|
324
|
+
return Array.from(skillIds).map(id => this.getSkill(id)).filter(Boolean);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Find skills by tags (OR matching)
|
|
329
|
+
*/
|
|
330
|
+
findByTags(tags, matchAll = false) {
|
|
331
|
+
if (matchAll) {
|
|
332
|
+
// AND matching - skill must have all tags
|
|
333
|
+
return this.getAllSkills().filter(skill =>
|
|
334
|
+
tags.every(tag => skill.tags.includes(tag))
|
|
335
|
+
);
|
|
336
|
+
} else {
|
|
337
|
+
// OR matching - skill must have at least one tag
|
|
338
|
+
const matchedIds = new Set();
|
|
339
|
+
for (const tag of tags) {
|
|
340
|
+
const skillIds = this.tagIndex.get(tag) || new Set();
|
|
341
|
+
for (const id of skillIds) {
|
|
342
|
+
matchedIds.add(id);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return Array.from(matchedIds).map(id => this.getSkill(id)).filter(Boolean);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Search skills by query
|
|
351
|
+
*/
|
|
352
|
+
search(query) {
|
|
353
|
+
const lowerQuery = query.toLowerCase();
|
|
354
|
+
return this.getAllSkills().filter(skill =>
|
|
355
|
+
skill.id.toLowerCase().includes(lowerQuery) ||
|
|
356
|
+
skill.name.toLowerCase().includes(lowerQuery) ||
|
|
357
|
+
skill.description.toLowerCase().includes(lowerQuery) ||
|
|
358
|
+
skill.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Get skills by priority
|
|
364
|
+
*/
|
|
365
|
+
findByPriority(priority) {
|
|
366
|
+
return this.getAllSkills().filter(skill => skill.priority === priority);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Get skill dependencies (resolved order)
|
|
371
|
+
*/
|
|
372
|
+
resolveDependencies(skillId, visited = new Set()) {
|
|
373
|
+
const skill = this.getSkill(skillId);
|
|
374
|
+
if (!skill) {
|
|
375
|
+
throw new Error(`Skill '${skillId}' not found`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Circular dependency check
|
|
379
|
+
if (visited.has(skillId)) {
|
|
380
|
+
throw new Error(`Circular dependency detected: ${skillId}`);
|
|
381
|
+
}
|
|
382
|
+
visited.add(skillId);
|
|
383
|
+
|
|
384
|
+
const resolved = [];
|
|
385
|
+
for (const depId of skill.dependencies) {
|
|
386
|
+
const depResolved = this.resolveDependencies(depId, new Set(visited));
|
|
387
|
+
for (const r of depResolved) {
|
|
388
|
+
if (!resolved.includes(r)) {
|
|
389
|
+
resolved.push(r);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
resolved.push(skillId);
|
|
394
|
+
|
|
395
|
+
return resolved;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Update skill health status
|
|
400
|
+
*/
|
|
401
|
+
updateHealth(skillId, status, reason = null) {
|
|
402
|
+
if (!this.skills.has(skillId)) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const previousStatus = this.healthStatus.get(skillId);
|
|
407
|
+
this.healthStatus.set(skillId, status);
|
|
408
|
+
|
|
409
|
+
if (previousStatus !== status) {
|
|
410
|
+
this.emit('health-changed', {
|
|
411
|
+
skillId,
|
|
412
|
+
previousStatus,
|
|
413
|
+
newStatus: status,
|
|
414
|
+
reason
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get skill health
|
|
423
|
+
*/
|
|
424
|
+
getHealth(skillId) {
|
|
425
|
+
return this.healthStatus.get(skillId) || SkillHealth.UNKNOWN;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get all healthy skills
|
|
430
|
+
*/
|
|
431
|
+
getHealthySkills() {
|
|
432
|
+
return this.getAllSkills().filter(
|
|
433
|
+
skill => this.getHealth(skill.id) === SkillHealth.HEALTHY
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Record execution stats
|
|
439
|
+
*/
|
|
440
|
+
recordExecution(skillId, success, executionTime) {
|
|
441
|
+
if (!this.options.enableStats) return;
|
|
442
|
+
|
|
443
|
+
const stats = this.executionStats.get(skillId);
|
|
444
|
+
if (!stats) return;
|
|
445
|
+
|
|
446
|
+
stats.totalExecutions++;
|
|
447
|
+
if (success) {
|
|
448
|
+
stats.successCount++;
|
|
449
|
+
} else {
|
|
450
|
+
stats.failureCount++;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Update average execution time
|
|
454
|
+
stats.averageExecutionTime = (
|
|
455
|
+
(stats.averageExecutionTime * (stats.totalExecutions - 1) + executionTime) /
|
|
456
|
+
stats.totalExecutions
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
stats.lastExecutedAt = new Date().toISOString();
|
|
460
|
+
|
|
461
|
+
this.emit('execution-recorded', { skillId, success, executionTime, stats });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get execution stats
|
|
466
|
+
*/
|
|
467
|
+
getStats(skillId) {
|
|
468
|
+
return this.executionStats.get(skillId) || null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get all stats
|
|
473
|
+
*/
|
|
474
|
+
getAllStats() {
|
|
475
|
+
return Object.fromEntries(this.executionStats);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Add custom validator
|
|
480
|
+
*/
|
|
481
|
+
addValidator(validator) {
|
|
482
|
+
this.validators.push(validator);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Add hook
|
|
487
|
+
*/
|
|
488
|
+
addHook(event, handler) {
|
|
489
|
+
if (this.hooks[event]) {
|
|
490
|
+
this.hooks[event].push(handler);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Get registry summary
|
|
496
|
+
*/
|
|
497
|
+
getSummary() {
|
|
498
|
+
const categories = {};
|
|
499
|
+
for (const [category, ids] of this.categoryIndex) {
|
|
500
|
+
categories[category] = ids.size;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const healthCounts = {
|
|
504
|
+
[SkillHealth.HEALTHY]: 0,
|
|
505
|
+
[SkillHealth.DEGRADED]: 0,
|
|
506
|
+
[SkillHealth.UNHEALTHY]: 0,
|
|
507
|
+
[SkillHealth.UNKNOWN]: 0
|
|
508
|
+
};
|
|
509
|
+
for (const status of this.healthStatus.values()) {
|
|
510
|
+
healthCounts[status]++;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
totalSkills: this.skills.size,
|
|
515
|
+
categories,
|
|
516
|
+
healthStatus: healthCounts,
|
|
517
|
+
tags: Array.from(this.tagIndex.keys())
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Export registry to JSON
|
|
523
|
+
*/
|
|
524
|
+
exportToJSON() {
|
|
525
|
+
const skills = this.getAllSkills().map(skill => skill.toJSON());
|
|
526
|
+
return {
|
|
527
|
+
version: '1.0.0',
|
|
528
|
+
exportedAt: new Date().toISOString(),
|
|
529
|
+
skills,
|
|
530
|
+
summary: this.getSummary()
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Import skills from JSON
|
|
536
|
+
*/
|
|
537
|
+
importFromJSON(data) {
|
|
538
|
+
if (!data.skills || !Array.isArray(data.skills)) {
|
|
539
|
+
throw new Error('Invalid import data: skills array required');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const imported = [];
|
|
543
|
+
const errors = [];
|
|
544
|
+
|
|
545
|
+
for (const skillDef of data.skills) {
|
|
546
|
+
try {
|
|
547
|
+
const metadata = this.registerSkill(skillDef);
|
|
548
|
+
imported.push(metadata.id);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
errors.push({ skillId: skillDef.id, error: error.message });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return { imported, errors };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Clear all skills
|
|
559
|
+
*/
|
|
560
|
+
clear() {
|
|
561
|
+
this.skills.clear();
|
|
562
|
+
this.categoryIndex.clear();
|
|
563
|
+
this.tagIndex.clear();
|
|
564
|
+
this.healthStatus.clear();
|
|
565
|
+
this.executionStats.clear();
|
|
566
|
+
this.emit('registry-cleared');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Private methods
|
|
570
|
+
|
|
571
|
+
_addToIndex(index, key, value) {
|
|
572
|
+
if (!index.has(key)) {
|
|
573
|
+
index.set(key, new Set());
|
|
574
|
+
}
|
|
575
|
+
index.get(key).add(value);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
_removeFromIndex(index, key, value) {
|
|
579
|
+
const set = index.get(key);
|
|
580
|
+
if (set) {
|
|
581
|
+
set.delete(value);
|
|
582
|
+
if (set.size === 0) {
|
|
583
|
+
index.delete(key);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
_findDependents(skillId) {
|
|
589
|
+
const dependents = [];
|
|
590
|
+
for (const [id, entry] of this.skills) {
|
|
591
|
+
if (entry.metadata.dependencies.includes(skillId)) {
|
|
592
|
+
dependents.push(id);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return dependents;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
_startHealthMonitoring() {
|
|
599
|
+
this._healthCheckInterval = setInterval(() => {
|
|
600
|
+
this._performHealthCheck();
|
|
601
|
+
}, this.options.healthCheckInterval);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
_performHealthCheck() {
|
|
605
|
+
for (const [skillId, entry] of this.skills) {
|
|
606
|
+
const stats = this.executionStats.get(skillId);
|
|
607
|
+
if (!stats || stats.totalExecutions === 0) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const failureRate = stats.failureCount / stats.totalExecutions;
|
|
612
|
+
|
|
613
|
+
let newStatus = SkillHealth.HEALTHY;
|
|
614
|
+
if (failureRate > 0.5) {
|
|
615
|
+
newStatus = SkillHealth.UNHEALTHY;
|
|
616
|
+
} else if (failureRate > 0.2) {
|
|
617
|
+
newStatus = SkillHealth.DEGRADED;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
this.updateHealth(skillId, newStatus, `Failure rate: ${(failureRate * 100).toFixed(1)}%`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Stop health monitoring
|
|
626
|
+
*/
|
|
627
|
+
stopHealthMonitoring() {
|
|
628
|
+
if (this._healthCheckInterval) {
|
|
629
|
+
clearInterval(this._healthCheckInterval);
|
|
630
|
+
this._healthCheckInterval = null;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Destroy registry
|
|
636
|
+
*/
|
|
637
|
+
destroy() {
|
|
638
|
+
this.stopHealthMonitoring();
|
|
639
|
+
this.clear();
|
|
640
|
+
this.removeAllListeners();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
module.exports = {
|
|
645
|
+
SkillRegistry,
|
|
646
|
+
SkillMetadata,
|
|
647
|
+
SkillCategory,
|
|
648
|
+
SkillPriority,
|
|
649
|
+
SkillHealth
|
|
650
|
+
};
|