skill-tree 0.1.5 → 0.1.7

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.
Files changed (40) hide show
  1. package/dist/cli/index.js +1827 -1410
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/cli/index.mjs +9538 -1657
  4. package/dist/cli/index.mjs.map +1 -0
  5. package/dist/index.d.mts +318 -7
  6. package/dist/index.d.ts +318 -7
  7. package/dist/index.js +1539 -92
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +9614 -70
  10. package/dist/index.mjs.map +1 -0
  11. package/package.json +3 -2
  12. package/dist/chunk-3SRB47JW.mjs +0 -8344
  13. package/dist/chunk-43YOKLZP.mjs +0 -6081
  14. package/dist/chunk-4AGZU52D.mjs +0 -7918
  15. package/dist/chunk-4OC5QFIF.mjs +0 -11267
  16. package/dist/chunk-55SMGVTP.mjs +0 -7126
  17. package/dist/chunk-6FX4IK4Z.mjs +0 -5368
  18. package/dist/chunk-7EGDKOHV.mjs +0 -9439
  19. package/dist/chunk-7LMOQW5H.mjs +0 -4893
  20. package/dist/chunk-7QIQJVNP.mjs +0 -14206
  21. package/dist/chunk-7VB4ZRZO.mjs +0 -7127
  22. package/dist/chunk-BPVRW25O.mjs +0 -6089
  23. package/dist/chunk-CI4476KM.mjs +0 -6607
  24. package/dist/chunk-DDXYQ74I.mjs +0 -13969
  25. package/dist/chunk-DQOFJXBX.mjs +0 -6595
  26. package/dist/chunk-E2CVK23F.mjs +0 -8751
  27. package/dist/chunk-F3YEUQAP.mjs +0 -654
  28. package/dist/chunk-FKJJ4RJG.mjs +0 -13874
  29. package/dist/chunk-II7DECZQ.mjs +0 -9111
  30. package/dist/chunk-INKVOZXK.mjs +0 -15898
  31. package/dist/chunk-K6NRCSAZ.mjs +0 -4355
  32. package/dist/chunk-OYHYXKXO.mjs +0 -7297
  33. package/dist/chunk-PDPN7FW7.mjs +0 -1045
  34. package/dist/chunk-TEUB6DZR.mjs +0 -6453
  35. package/dist/chunk-TWPEHDW4.mjs +0 -1067
  36. package/dist/chunk-Y54UK2J3.mjs +0 -13071
  37. package/dist/chunk-ZQVS7MQK.mjs +0 -6081
  38. package/dist/sqlite-OLU72GHB.mjs +0 -6
  39. package/dist/sqlite-XJRPMNAJ.mjs +0 -6
  40. package/dist/sync-BSWMMDA6.mjs +0 -14
@@ -1,4355 +0,0 @@
1
- // src/adapters/base.ts
2
- var BaseSessionAdapter = class {
3
- /**
4
- * Parse multiple sessions from a directory or concatenated file
5
- */
6
- async parseMany(input) {
7
- const trajectory = await this.parse(input);
8
- return [trajectory];
9
- }
10
- /**
11
- * Generate a unique session ID if none provided
12
- */
13
- generateSessionId() {
14
- return `session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
15
- }
16
- /**
17
- * Safely parse JSON with error handling
18
- */
19
- safeJsonParse(input) {
20
- try {
21
- return JSON.parse(input);
22
- } catch {
23
- return null;
24
- }
25
- }
26
- };
27
- var AdapterRegistry = class {
28
- constructor() {
29
- this.adapters = /* @__PURE__ */ new Map();
30
- }
31
- register(adapter) {
32
- this.adapters.set(adapter.name, adapter);
33
- }
34
- unregister(name) {
35
- return this.adapters.delete(name);
36
- }
37
- get(name) {
38
- return this.adapters.get(name);
39
- }
40
- /**
41
- * Find an adapter that can handle the given input
42
- */
43
- findAdapter(input) {
44
- for (const adapter of this.adapters.values()) {
45
- if (adapter.canHandle(input)) {
46
- return adapter;
47
- }
48
- }
49
- return void 0;
50
- }
51
- /**
52
- * Find adapter by file extension
53
- */
54
- findByExtension(extension) {
55
- const ext = extension.startsWith(".") ? extension : `.${extension}`;
56
- for (const adapter of this.adapters.values()) {
57
- if (adapter.supportedExtensions.includes(ext)) {
58
- return adapter;
59
- }
60
- }
61
- return void 0;
62
- }
63
- listAdapters() {
64
- return Array.from(this.adapters.values());
65
- }
66
- };
67
- var adapterRegistry = new AdapterRegistry();
68
-
69
- // src/adapters/claude-code.ts
70
- var ClaudeCodeAdapter = class extends BaseSessionAdapter {
71
- constructor() {
72
- super(...arguments);
73
- this.name = "claude-code";
74
- this.supportedExtensions = [".jsonl", ".json"];
75
- }
76
- canHandle(input) {
77
- const str = typeof input === "string" ? input : input.toString("utf-8");
78
- const firstLine = str.split("\n")[0]?.trim();
79
- if (!firstLine) return false;
80
- const parsed = this.safeJsonParse(firstLine);
81
- if (!parsed) return false;
82
- return parsed.type !== void 0 || parsed.message?.role !== void 0 || parsed.sessionId !== void 0;
83
- }
84
- async parse(input) {
85
- const str = typeof input === "string" ? input : input.toString("utf-8");
86
- const lines = str.split("\n").filter((line) => line.trim());
87
- const messages = [];
88
- for (const line of lines) {
89
- const parsed = this.safeJsonParse(line);
90
- if (parsed) {
91
- messages.push(parsed);
92
- }
93
- }
94
- return this.convertToTrajectory(messages);
95
- }
96
- async parseMany(input) {
97
- const trajectory = await this.parse(input);
98
- return [trajectory];
99
- }
100
- convertToTrajectory(messages) {
101
- const turns = [];
102
- let sessionId = this.generateSessionId();
103
- let startedAt = /* @__PURE__ */ new Date();
104
- let endedAt;
105
- const metadata = {
106
- agentType: "claude-code",
107
- custom: {}
108
- };
109
- const pendingToolCalls = /* @__PURE__ */ new Map();
110
- for (let i = 0; i < messages.length; i++) {
111
- const msg = messages[i];
112
- if (msg.sessionId) sessionId = msg.sessionId;
113
- if (msg.timestamp) {
114
- const ts = new Date(msg.timestamp);
115
- if (i === 0) startedAt = ts;
116
- endedAt = ts;
117
- }
118
- if (msg.cwd) metadata.workingDirectory = msg.cwd;
119
- if (msg.model) metadata.model = msg.model;
120
- if (msg.summary) continue;
121
- if (msg.type === "result" && msg.toolUseId) {
122
- const lastAssistantTurn = [...turns].reverse().find((t) => t.role === "assistant");
123
- if (lastAssistantTurn) {
124
- if (!lastAssistantTurn.toolResults) {
125
- lastAssistantTurn.toolResults = [];
126
- }
127
- lastAssistantTurn.toolResults.push({
128
- callId: msg.toolUseId,
129
- name: msg.toolName || "unknown",
130
- output: msg.result || "",
131
- success: true
132
- // Claude Code doesn't always indicate errors explicitly
133
- });
134
- }
135
- continue;
136
- }
137
- if (msg.message) {
138
- const turn = this.convertMessageToTurn(msg, turns.length, pendingToolCalls);
139
- if (turn) {
140
- turns.push(turn);
141
- }
142
- }
143
- }
144
- const outcome = this.inferOutcome(turns);
145
- return {
146
- sessionId,
147
- startedAt,
148
- endedAt,
149
- turns,
150
- metadata,
151
- outcome
152
- };
153
- }
154
- convertMessageToTurn(msg, index, pendingToolCalls) {
155
- if (!msg.message) return null;
156
- const { role, content } = msg.message;
157
- const turn = {
158
- index,
159
- role,
160
- content: "",
161
- timestamp: msg.timestamp ? new Date(msg.timestamp) : void 0
162
- };
163
- if (typeof content === "string") {
164
- turn.content = content;
165
- } else if (Array.isArray(content)) {
166
- const textParts = [];
167
- const toolCalls = [];
168
- const toolResults = [];
169
- for (const block of content) {
170
- if (block.type === "text" && block.text) {
171
- textParts.push(block.text);
172
- } else if (block.type === "tool_use" && block.name) {
173
- const toolCall = {
174
- name: block.name,
175
- input: block.input || {},
176
- callId: block.id
177
- };
178
- toolCalls.push(toolCall);
179
- if (block.id) {
180
- pendingToolCalls.set(block.id, toolCall);
181
- }
182
- } else if (block.type === "tool_result") {
183
- const resultContent = this.extractToolResultContent(block.content);
184
- toolResults.push({
185
- callId: block.tool_use_id,
186
- name: pendingToolCalls.get(block.tool_use_id || "")?.name || "unknown",
187
- output: resultContent,
188
- success: !block.is_error,
189
- error: block.is_error ? resultContent : void 0
190
- });
191
- }
192
- }
193
- turn.content = textParts.join("\n");
194
- if (toolCalls.length > 0) turn.toolCalls = toolCalls;
195
- if (toolResults.length > 0) turn.toolResults = toolResults;
196
- }
197
- return turn;
198
- }
199
- extractToolResultContent(content) {
200
- if (!content) return "";
201
- if (typeof content === "string") return content;
202
- if (Array.isArray(content)) {
203
- return content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
204
- }
205
- return "";
206
- }
207
- inferOutcome(turns) {
208
- const outcome = {
209
- success: true,
210
- // Default to success, look for failure signals
211
- filesModified: [],
212
- errors: []
213
- };
214
- for (const turn of turns) {
215
- if (turn.content) {
216
- const lowerContent = turn.content.toLowerCase();
217
- if (lowerContent.includes("error") || lowerContent.includes("failed") || lowerContent.includes("couldn't") || lowerContent.includes("can't complete")) {
218
- outcome.errors?.push(turn.content.substring(0, 200));
219
- }
220
- }
221
- if (turn.toolResults) {
222
- for (const result of turn.toolResults) {
223
- if (!result.success && result.error) {
224
- outcome.errors?.push(result.error.substring(0, 200));
225
- outcome.success = false;
226
- }
227
- }
228
- }
229
- if (turn.toolCalls) {
230
- for (const call of turn.toolCalls) {
231
- if (["Write", "Edit", "MultiEdit"].includes(call.name)) {
232
- const filePath = call.input.file_path || call.input.filePath;
233
- if (typeof filePath === "string") {
234
- outcome.filesModified?.push(filePath);
235
- }
236
- }
237
- }
238
- }
239
- }
240
- outcome.filesModified = [...new Set(outcome.filesModified)];
241
- if (outcome.errors?.length === 0) {
242
- outcome.success = true;
243
- }
244
- return outcome;
245
- }
246
- };
247
- var claudeCodeAdapter = new ClaudeCodeAdapter();
248
-
249
- // src/extraction/quality-gates.ts
250
- var DEFAULT_QUALITY_GATES = [
251
- {
252
- name: "reusability",
253
- type: "reusability",
254
- description: "Skill should be applicable across multiple future tasks, not one-off",
255
- required: true
256
- },
257
- {
258
- name: "non-trivial",
259
- type: "non-trivial",
260
- description: "Skill should involve discovery beyond basic documentation lookup",
261
- required: true
262
- },
263
- {
264
- name: "specific-triggers",
265
- type: "specific",
266
- description: "Skill should have clear, specific trigger conditions",
267
- required: true
268
- },
269
- {
270
- name: "verified",
271
- type: "verified",
272
- description: "Skill solution should be verified to actually work",
273
- required: false
274
- }
275
- ];
276
- var QualityGateEvaluator = class {
277
- constructor(gates = DEFAULT_QUALITY_GATES) {
278
- this.gates = gates;
279
- }
280
- /**
281
- * Evaluate all quality gates for a skill candidate
282
- */
283
- async evaluate(skill, trajectory, turnRange) {
284
- const results = [];
285
- for (const gate of this.gates) {
286
- const result = await this.evaluateGate(gate, skill, trajectory, turnRange);
287
- results.push(result);
288
- }
289
- const passed = results.every((r) => {
290
- const gate = this.gates.find((g) => g.name === r.gateName);
291
- return !gate?.required || r.passed;
292
- });
293
- return { passed, results };
294
- }
295
- async evaluateGate(gate, skill, trajectory, turnRange) {
296
- const turns = trajectory.turns.slice(turnRange[0], turnRange[1] + 1);
297
- switch (gate.type) {
298
- case "reusability":
299
- return this.evaluateReusability(gate, skill, turns);
300
- case "non-trivial":
301
- return this.evaluateNonTrivial(gate, skill, turns);
302
- case "specific":
303
- return this.evaluateSpecificTriggers(gate, skill);
304
- case "verified":
305
- return this.evaluateVerified(gate, trajectory);
306
- case "custom":
307
- return this.evaluateCustom(gate, skill, trajectory, turns);
308
- default:
309
- return {
310
- gateName: gate.name,
311
- passed: true,
312
- score: 1,
313
- explanation: "Unknown gate type, passing by default"
314
- };
315
- }
316
- }
317
- evaluateReusability(gate, skill, turns) {
318
- let score = 0;
319
- const reasons = [];
320
- const description = skill.description || "";
321
- const problem = skill.problem || "";
322
- const combined = `${description} ${problem}`.toLowerCase();
323
- const reusabilitySignals = [
324
- "error",
325
- "bug",
326
- "fix",
327
- "pattern",
328
- "when",
329
- "if",
330
- "always",
331
- "common",
332
- "typical",
333
- "usually"
334
- ];
335
- const signalCount = reusabilitySignals.filter((s) => combined.includes(s)).length;
336
- score += Math.min(signalCount * 0.15, 0.45);
337
- const specificSignals = [
338
- "this project",
339
- "this repo",
340
- "our codebase",
341
- "specific to",
342
- "only in"
343
- ];
344
- const specificCount = specificSignals.filter((s) => combined.includes(s)).length;
345
- score -= specificCount * 0.2;
346
- if (skill.triggerConditions && skill.triggerConditions.length > 0) {
347
- const hasGenericTrigger = skill.triggerConditions.some(
348
- (t) => t.type === "error" || t.type === "pattern"
349
- );
350
- if (hasGenericTrigger) {
351
- score += 0.25;
352
- reasons.push("Has generic trigger conditions");
353
- }
354
- }
355
- const toolNames = /* @__PURE__ */ new Set();
356
- for (const turn of turns) {
357
- turn.toolCalls?.forEach((tc) => toolNames.add(tc.name));
358
- }
359
- if (toolNames.size > 2) {
360
- score += 0.15;
361
- reasons.push(`Uses ${toolNames.size} different tools`);
362
- }
363
- score = Math.max(0, Math.min(1, score + 0.3));
364
- return {
365
- gateName: gate.name,
366
- passed: score >= 0.5,
367
- score,
368
- explanation: reasons.length > 0 ? reasons.join("; ") : score >= 0.5 ? "Skill appears reusable" : "Skill may be too project-specific"
369
- };
370
- }
371
- evaluateNonTrivial(gate, skill, turns) {
372
- let score = 0;
373
- const reasons = [];
374
- if (turns.length >= 5) {
375
- score += 0.2;
376
- reasons.push(`${turns.length} turns of investigation`);
377
- } else if (turns.length >= 3) {
378
- score += 0.1;
379
- }
380
- const hasErrors = turns.some(
381
- (t) => t.toolResults?.some((r) => !r.success) || t.content?.toLowerCase().includes("error")
382
- );
383
- if (hasErrors) {
384
- score += 0.25;
385
- reasons.push("Involves error diagnosis/recovery");
386
- }
387
- const toolCallCounts = /* @__PURE__ */ new Map();
388
- for (const turn of turns) {
389
- turn.toolCalls?.forEach((tc) => {
390
- toolCallCounts.set(tc.name, (toolCallCounts.get(tc.name) || 0) + 1);
391
- });
392
- }
393
- const hasIteration = Array.from(toolCallCounts.values()).some((count) => count >= 3);
394
- if (hasIteration) {
395
- score += 0.2;
396
- reasons.push("Shows iterative refinement");
397
- }
398
- const solutionLength = (skill.solution || "").length;
399
- if (solutionLength > 500) {
400
- score += 0.2;
401
- reasons.push("Detailed solution");
402
- } else if (solutionLength > 200) {
403
- score += 0.1;
404
- }
405
- const nonObviousSignals = [
406
- "workaround",
407
- "trick",
408
- "gotcha",
409
- "subtle",
410
- "hidden",
411
- "unexpected",
412
- "actually",
413
- "turns out",
414
- "discovered",
415
- "realized"
416
- ];
417
- const content = `${skill.problem || ""} ${skill.solution || ""}`.toLowerCase();
418
- const nonObviousCount = nonObviousSignals.filter((s) => content.includes(s)).length;
419
- score += Math.min(nonObviousCount * 0.1, 0.25);
420
- if (nonObviousCount > 0) {
421
- reasons.push("Contains non-obvious discoveries");
422
- }
423
- score = Math.max(0, Math.min(1, score));
424
- return {
425
- gateName: gate.name,
426
- passed: score >= 0.4,
427
- score,
428
- explanation: reasons.length > 0 ? reasons.join("; ") : score >= 0.4 ? "Skill involves non-trivial discovery" : "Skill may be too trivial (basic documentation lookup)"
429
- };
430
- }
431
- evaluateSpecificTriggers(gate, skill) {
432
- const triggers = skill.triggerConditions || [];
433
- if (triggers.length === 0) {
434
- return {
435
- gateName: gate.name,
436
- passed: false,
437
- score: 0,
438
- explanation: "No trigger conditions defined"
439
- };
440
- }
441
- let score = 0;
442
- const reasons = [];
443
- for (const trigger of triggers) {
444
- if (trigger.value && trigger.value.length > 10) {
445
- score += 0.2;
446
- }
447
- if (trigger.description) {
448
- score += 0.1;
449
- }
450
- if (trigger.type === "error" && trigger.value.length > 20) {
451
- score += 0.15;
452
- reasons.push("Has specific error trigger");
453
- }
454
- if (trigger.type === "pattern") {
455
- score += 0.15;
456
- reasons.push("Has pattern-based trigger");
457
- }
458
- }
459
- score = Math.min(1, score);
460
- return {
461
- gateName: gate.name,
462
- passed: score >= 0.4,
463
- score,
464
- explanation: reasons.length > 0 ? reasons.join("; ") : score >= 0.4 ? "Has specific trigger conditions" : "Trigger conditions need more specificity"
465
- };
466
- }
467
- evaluateVerified(gate, trajectory) {
468
- const success = trajectory.outcome?.success ?? false;
469
- const hasVerification = trajectory.turns.some((t) => {
470
- const content = t.content?.toLowerCase() || "";
471
- return content.includes("works") || content.includes("fixed") || content.includes("solved") || content.includes("success");
472
- });
473
- const score = success ? 0.7 : hasVerification ? 0.5 : 0.2;
474
- return {
475
- gateName: gate.name,
476
- passed: score >= 0.5,
477
- score,
478
- explanation: success ? "Session completed successfully" : hasVerification ? "Solution appears verified in conversation" : "Solution verification unclear"
479
- };
480
- }
481
- evaluateCustom(gate, _skill, _trajectory, _turns) {
482
- return {
483
- gateName: gate.name,
484
- passed: true,
485
- score: 1,
486
- explanation: "Custom gate - no implementation provided"
487
- };
488
- }
489
- /**
490
- * Add a custom quality gate
491
- */
492
- addGate(gate) {
493
- this.gates.push(gate);
494
- }
495
- /**
496
- * Remove a quality gate by name
497
- */
498
- removeGate(name) {
499
- const index = this.gates.findIndex((g) => g.name === name);
500
- if (index >= 0) {
501
- this.gates.splice(index, 1);
502
- return true;
503
- }
504
- return false;
505
- }
506
- /**
507
- * Get all configured gates
508
- */
509
- getGates() {
510
- return [...this.gates];
511
- }
512
- };
513
-
514
- // src/extraction/base.ts
515
- var BaseExtractor = class {
516
- constructor(config) {
517
- this.config = {
518
- mode: config?.mode || "manual",
519
- qualityGates: config?.qualityGates || DEFAULT_QUALITY_GATES,
520
- minConfidence: config?.minConfidence ?? 0.6,
521
- llmProvider: config?.llmProvider
522
- };
523
- this.gateEvaluator = new QualityGateEvaluator(this.config.qualityGates);
524
- }
525
- /**
526
- * Create initial skill metrics
527
- */
528
- createInitialMetrics() {
529
- return {
530
- usageCount: 0,
531
- successRate: 0,
532
- feedbackScores: []
533
- };
534
- }
535
- /**
536
- * Create skill source info for extracted skills
537
- */
538
- createSource(trajectory, type) {
539
- return {
540
- type,
541
- sessionId: trajectory.sessionId,
542
- importedAt: /* @__PURE__ */ new Date()
543
- };
544
- }
545
- /**
546
- * Generate a skill ID from name
547
- */
548
- generateSkillId(name) {
549
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
550
- }
551
- /**
552
- * Validate and run quality gates on extracted skill
553
- */
554
- async validateWithGates(skill, trajectory, turnRange) {
555
- return this.gateEvaluator.evaluate(skill, trajectory, turnRange);
556
- }
557
- /**
558
- * Add or update quality gates
559
- */
560
- setQualityGates(gates) {
561
- this.config.qualityGates = gates;
562
- this.gateEvaluator = new QualityGateEvaluator(gates);
563
- }
564
- /**
565
- * Get current configuration
566
- */
567
- getConfig() {
568
- return { ...this.config };
569
- }
570
- };
571
-
572
- // src/extraction/manual.ts
573
- var ManualExtractor = class extends BaseExtractor {
574
- constructor(config) {
575
- super({ mode: "manual", qualityGates: config });
576
- }
577
- /**
578
- * Extract a skill from a trajectory with manual guidance
579
- */
580
- async extract(trajectory, options) {
581
- const turnRange = options?.turnRange || [
582
- 0,
583
- trajectory.turns.length - 1
584
- ];
585
- const turns = trajectory.turns.slice(turnRange[0], turnRange[1] + 1);
586
- const skill = this.buildSkillFromTurns(trajectory, turns, options);
587
- if (!options?.skipValidation) {
588
- const validation = await this.validateWithGates(skill, trajectory, turnRange);
589
- if (!validation.passed) {
590
- return [
591
- {
592
- success: false,
593
- confidence: this.calculateConfidence(validation.results),
594
- gateResults: validation.results,
595
- failureReason: "Quality gates not passed",
596
- sourceTrajectory: {
597
- sessionId: trajectory.sessionId,
598
- turnRange
599
- }
600
- }
601
- ];
602
- }
603
- return [
604
- {
605
- success: true,
606
- skill,
607
- confidence: this.calculateConfidence(validation.results),
608
- gateResults: validation.results,
609
- sourceTrajectory: {
610
- sessionId: trajectory.sessionId,
611
- turnRange
612
- }
613
- }
614
- ];
615
- }
616
- return [
617
- {
618
- success: true,
619
- skill,
620
- confidence: 0.8,
621
- gateResults: [],
622
- sourceTrajectory: {
623
- sessionId: trajectory.sessionId,
624
- turnRange
625
- }
626
- }
627
- ];
628
- }
629
- /**
630
- * Build a skill object from trajectory turns
631
- */
632
- buildSkillFromTurns(trajectory, turns, options) {
633
- const firstUserTurn = turns.find((t) => t.role === "user");
634
- const problem = firstUserTurn?.content || "Problem not specified";
635
- const assistantTurns = turns.filter((t) => t.role === "assistant");
636
- const solution = this.buildSolutionFromTurns(assistantTurns);
637
- const triggerConditions = this.inferTriggerConditions(turns);
638
- const example = this.buildExampleFromTurns(turns);
639
- const name = options?.suggestedName || this.inferSkillName(problem, solution);
640
- const id = this.generateSkillId(name);
641
- const description = options?.description || this.generateDescription(problem, triggerConditions);
642
- return {
643
- id,
644
- name,
645
- version: "1.0.0",
646
- description,
647
- problem: this.truncate(problem, 1e3),
648
- triggerConditions,
649
- solution,
650
- verification: this.inferVerification(trajectory),
651
- examples: example ? [example] : [],
652
- notes: this.extractNotes(turns),
653
- author: "extracted",
654
- tags: options?.tags || this.inferTags(problem, solution),
655
- createdAt: /* @__PURE__ */ new Date(),
656
- updatedAt: /* @__PURE__ */ new Date(),
657
- status: "draft",
658
- metrics: this.createInitialMetrics(),
659
- source: this.createSource(trajectory, "extracted")
660
- };
661
- }
662
- /**
663
- * Build solution text from assistant turns
664
- */
665
- buildSolutionFromTurns(assistantTurns) {
666
- const steps = [];
667
- for (let i = 0; i < assistantTurns.length; i++) {
668
- const turn = assistantTurns[i];
669
- if (turn.content && turn.content.trim()) {
670
- steps.push(turn.content.trim());
671
- }
672
- if (turn.toolCalls) {
673
- for (const call of turn.toolCalls) {
674
- const toolStep = this.formatToolCallAsStep(call);
675
- if (toolStep) {
676
- steps.push(toolStep);
677
- }
678
- }
679
- }
680
- }
681
- return steps.filter((s) => s.length > 10).slice(0, 10).map((step, i) => `${i + 1}. ${step}`).join("\n\n");
682
- }
683
- /**
684
- * Format a tool call as a solution step
685
- */
686
- formatToolCallAsStep(call) {
687
- const { name, input } = call;
688
- switch (name) {
689
- case "Read":
690
- return `Read file: \`${input.file_path || input.path}\``;
691
- case "Write":
692
- return `Create/write file: \`${input.file_path || input.path}\``;
693
- case "Edit":
694
- return `Edit file: \`${input.file_path || input.path}\``;
695
- case "Bash":
696
- return `Run command: \`${this.truncate(String(input.command || ""), 100)}\``;
697
- case "Grep":
698
- return `Search for: \`${input.pattern}\``;
699
- case "Glob":
700
- return `Find files matching: \`${input.pattern}\``;
701
- default:
702
- return null;
703
- }
704
- }
705
- /**
706
- * Infer trigger conditions from turns
707
- */
708
- inferTriggerConditions(turns) {
709
- const conditions = [];
710
- for (const turn of turns) {
711
- if (turn.toolResults) {
712
- for (const result of turn.toolResults) {
713
- if (!result.success && result.error) {
714
- conditions.push({
715
- type: "error",
716
- value: this.extractErrorSignature(result.error),
717
- description: `Error from ${result.name}`
718
- });
719
- }
720
- }
721
- }
722
- const content = turn.content || "";
723
- const errorMatch = content.match(/error[:\s]+([^\n]+)/i);
724
- if (errorMatch) {
725
- conditions.push({
726
- type: "error",
727
- value: errorMatch[1].trim()
728
- });
729
- }
730
- }
731
- const firstUserTurn = turns.find((t) => t.role === "user");
732
- if (firstUserTurn?.content) {
733
- const keywords = this.extractKeywords(firstUserTurn.content);
734
- if (keywords.length > 0) {
735
- conditions.push({
736
- type: "keyword",
737
- value: keywords.join(", "),
738
- description: "Keywords from original problem"
739
- });
740
- }
741
- }
742
- return this.deduplicateTriggers(conditions);
743
- }
744
- /**
745
- * Extract a representative error signature
746
- */
747
- extractErrorSignature(error) {
748
- const lines = error.split("\n");
749
- const errorLine = lines.find(
750
- (l) => l.toLowerCase().includes("error") || l.toLowerCase().includes("exception") || l.toLowerCase().includes("failed")
751
- );
752
- return this.truncate(errorLine || lines[0] || error, 200);
753
- }
754
- /**
755
- * Extract meaningful keywords from text
756
- */
757
- extractKeywords(text) {
758
- const words = text.toLowerCase().split(/\W+/);
759
- const stopWords = /* @__PURE__ */ new Set([
760
- "the",
761
- "a",
762
- "an",
763
- "is",
764
- "are",
765
- "was",
766
- "were",
767
- "be",
768
- "been",
769
- "being",
770
- "have",
771
- "has",
772
- "had",
773
- "do",
774
- "does",
775
- "did",
776
- "will",
777
- "would",
778
- "could",
779
- "should",
780
- "may",
781
- "might",
782
- "can",
783
- "this",
784
- "that",
785
- "these",
786
- "those",
787
- "i",
788
- "you",
789
- "he",
790
- "she",
791
- "it",
792
- "we",
793
- "they",
794
- "what",
795
- "which",
796
- "who",
797
- "when",
798
- "where",
799
- "how",
800
- "to",
801
- "from",
802
- "in",
803
- "on",
804
- "at",
805
- "by",
806
- "for",
807
- "with",
808
- "about",
809
- "into",
810
- "through",
811
- "during",
812
- "before",
813
- "after",
814
- "above",
815
- "below",
816
- "and",
817
- "or",
818
- "but",
819
- "if",
820
- "then",
821
- "else",
822
- "so",
823
- "because",
824
- "as",
825
- "until",
826
- "while",
827
- "of",
828
- "not",
829
- "no",
830
- "just",
831
- "only"
832
- ]);
833
- const keywords = words.filter((w) => w.length > 3 && !stopWords.has(w)).slice(0, 5);
834
- return [...new Set(keywords)];
835
- }
836
- /**
837
- * Deduplicate trigger conditions
838
- */
839
- deduplicateTriggers(triggers) {
840
- const seen = /* @__PURE__ */ new Set();
841
- return triggers.filter((t) => {
842
- const key = `${t.type}:${t.value}`;
843
- if (seen.has(key)) return false;
844
- seen.add(key);
845
- return true;
846
- });
847
- }
848
- /**
849
- * Build an example from the trajectory
850
- */
851
- buildExampleFromTurns(turns) {
852
- const firstUserTurn = turns.find((t) => t.role === "user");
853
- const lastAssistantTurn = [...turns].reverse().find((t) => t.role === "assistant");
854
- if (!firstUserTurn || !lastAssistantTurn) return null;
855
- return {
856
- scenario: "Original extraction context",
857
- before: this.truncate(firstUserTurn.content || "", 500),
858
- after: this.truncate(lastAssistantTurn.content || "", 500)
859
- };
860
- }
861
- /**
862
- * Infer skill name from problem and solution
863
- */
864
- inferSkillName(problem, solution) {
865
- const combined = `${problem} ${solution}`.toLowerCase();
866
- const patterns = [
867
- /fix(?:ing|ed)?\s+(\w+(?:\s+\w+)?)/i,
868
- /solv(?:e|ing|ed)\s+(\w+(?:\s+\w+)?)/i,
869
- /handl(?:e|ing|ed)\s+(\w+(?:\s+\w+)?)/i,
870
- /(\w+)\s+error/i,
871
- /(\w+)\s+issue/i,
872
- /(\w+)\s+problem/i
873
- ];
874
- for (const pattern of patterns) {
875
- const match = combined.match(pattern);
876
- if (match && match[1]) {
877
- return `${match[1].trim()}-fix`;
878
- }
879
- }
880
- const words = problem.split(/\s+/).slice(0, 4).join("-");
881
- return words.toLowerCase().replace(/[^a-z0-9-]/g, "") || "extracted-skill";
882
- }
883
- /**
884
- * Generate a description optimized for semantic matching
885
- */
886
- generateDescription(problem, triggers) {
887
- let desc = this.truncate(problem, 150);
888
- const errorTriggers = triggers.filter((t) => t.type === "error");
889
- if (errorTriggers.length > 0) {
890
- desc += ` Triggered by: ${errorTriggers[0].value}`;
891
- }
892
- return desc;
893
- }
894
- /**
895
- * Infer verification steps
896
- */
897
- inferVerification(trajectory) {
898
- if (trajectory.outcome?.success) {
899
- return "Verified: Session completed successfully.";
900
- }
901
- return "Verify by checking that the original error no longer occurs.";
902
- }
903
- /**
904
- * Extract notes from turns (warnings, caveats, etc.)
905
- */
906
- extractNotes(turns) {
907
- const notes = [];
908
- for (const turn of turns) {
909
- const content = turn.content || "";
910
- if (content.toLowerCase().includes("note:") || content.toLowerCase().includes("warning:") || content.toLowerCase().includes("caveat:") || content.toLowerCase().includes("important:")) {
911
- const match = content.match(/(note|warning|caveat|important):\s*([^\n]+)/i);
912
- if (match) {
913
- notes.push(match[0]);
914
- }
915
- }
916
- }
917
- return notes.length > 0 ? notes.join("\n") : void 0;
918
- }
919
- /**
920
- * Infer tags from content
921
- */
922
- inferTags(problem, solution) {
923
- const tags = [];
924
- const content = `${problem} ${solution}`.toLowerCase();
925
- const techPatterns = [
926
- [/typescript|\.ts\b/i, "typescript"],
927
- [/javascript|\.js\b/i, "javascript"],
928
- [/python|\.py\b/i, "python"],
929
- [/react/i, "react"],
930
- [/node\.?js/i, "nodejs"],
931
- [/docker/i, "docker"],
932
- [/git\b/i, "git"],
933
- [/npm|yarn|pnpm/i, "package-manager"],
934
- [/test|jest|vitest|mocha/i, "testing"],
935
- [/database|sql|postgres|mysql|mongo/i, "database"],
936
- [/api|rest|graphql/i, "api"]
937
- ];
938
- for (const [pattern, tag] of techPatterns) {
939
- if (pattern.test(content)) {
940
- tags.push(tag);
941
- }
942
- }
943
- if (content.includes("error") || content.includes("fix")) {
944
- tags.push("debugging");
945
- }
946
- if (content.includes("performance") || content.includes("slow")) {
947
- tags.push("performance");
948
- }
949
- if (content.includes("security") || content.includes("auth")) {
950
- tags.push("security");
951
- }
952
- return [...new Set(tags)].slice(0, 5);
953
- }
954
- /**
955
- * Calculate confidence from gate results
956
- */
957
- calculateConfidence(gateResults) {
958
- if (gateResults.length === 0) return 0.5;
959
- const avgScore = gateResults.reduce((sum, r) => sum + r.score, 0) / gateResults.length;
960
- return Math.round(avgScore * 100) / 100;
961
- }
962
- /**
963
- * Truncate text to max length
964
- */
965
- truncate(text, maxLength) {
966
- if (text.length <= maxLength) return text;
967
- return text.substring(0, maxLength - 3) + "...";
968
- }
969
- };
970
-
971
- // src/extraction/automatic.ts
972
- var AutomaticExtractor = class extends BaseExtractor {
973
- constructor(config) {
974
- super({
975
- mode: "automatic",
976
- qualityGates: config?.qualityGates,
977
- minConfidence: config?.minConfidence,
978
- llmProvider: config?.llmProvider
979
- });
980
- this.llmProvider = config?.llmProvider || null;
981
- }
982
- /**
983
- * Set the LLM provider for automatic extraction
984
- */
985
- setLLMProvider(provider) {
986
- this.llmProvider = provider;
987
- this.config.llmProvider = provider;
988
- }
989
- /**
990
- * Extract skills automatically from a trajectory
991
- */
992
- async extract(trajectory, options) {
993
- if (!this.llmProvider) {
994
- return [
995
- {
996
- success: false,
997
- confidence: 0,
998
- gateResults: [],
999
- failureReason: "No LLM provider configured for automatic extraction",
1000
- sourceTrajectory: {
1001
- sessionId: trajectory.sessionId,
1002
- turnRange: options?.turnRange || [0, trajectory.turns.length - 1]
1003
- }
1004
- }
1005
- ];
1006
- }
1007
- const candidates = await this.identifySkillCandidates(trajectory, options);
1008
- if (candidates.length === 0) {
1009
- return [
1010
- {
1011
- success: false,
1012
- confidence: 0,
1013
- gateResults: [],
1014
- failureReason: "No extractable skills identified in trajectory",
1015
- sourceTrajectory: {
1016
- sessionId: trajectory.sessionId,
1017
- turnRange: options?.turnRange || [0, trajectory.turns.length - 1]
1018
- }
1019
- }
1020
- ];
1021
- }
1022
- const results = [];
1023
- const minConfidence = options?.minConfidence ?? this.config.minConfidence ?? 0.6;
1024
- for (const candidate of candidates) {
1025
- if (candidate.confidence < minConfidence) {
1026
- results.push({
1027
- success: false,
1028
- confidence: candidate.confidence,
1029
- gateResults: [],
1030
- failureReason: `Confidence ${candidate.confidence} below threshold ${minConfidence}`,
1031
- sourceTrajectory: {
1032
- sessionId: trajectory.sessionId,
1033
- turnRange: candidate.turnRange
1034
- }
1035
- });
1036
- continue;
1037
- }
1038
- const skill = this.buildSkillFromCandidate(candidate, trajectory);
1039
- if (!options?.skipValidation) {
1040
- const validation = await this.validateWithGates(
1041
- skill,
1042
- trajectory,
1043
- candidate.turnRange
1044
- );
1045
- if (!validation.passed) {
1046
- results.push({
1047
- success: false,
1048
- skill,
1049
- confidence: candidate.confidence,
1050
- gateResults: validation.results,
1051
- failureReason: "Quality gates not passed",
1052
- sourceTrajectory: {
1053
- sessionId: trajectory.sessionId,
1054
- turnRange: candidate.turnRange
1055
- }
1056
- });
1057
- continue;
1058
- }
1059
- results.push({
1060
- success: true,
1061
- skill,
1062
- confidence: candidate.confidence,
1063
- gateResults: validation.results,
1064
- sourceTrajectory: {
1065
- sessionId: trajectory.sessionId,
1066
- turnRange: candidate.turnRange
1067
- }
1068
- });
1069
- } else {
1070
- results.push({
1071
- success: true,
1072
- skill,
1073
- confidence: candidate.confidence,
1074
- gateResults: [],
1075
- sourceTrajectory: {
1076
- sessionId: trajectory.sessionId,
1077
- turnRange: candidate.turnRange
1078
- }
1079
- });
1080
- }
1081
- }
1082
- return results;
1083
- }
1084
- /**
1085
- * Identify skill candidates using LLM analysis
1086
- */
1087
- async identifySkillCandidates(trajectory, options) {
1088
- const prompt = this.buildAnalysisPrompt(trajectory, options);
1089
- try {
1090
- const response = await this.llmProvider.completeStructured(prompt, SKILL_CANDIDATE_SCHEMA, { temperature: 0.3 });
1091
- return response.candidates || [];
1092
- } catch (error) {
1093
- try {
1094
- const textResponse = await this.llmProvider.complete(prompt, {
1095
- temperature: 0.3
1096
- });
1097
- return this.parseTextResponse(textResponse);
1098
- } catch {
1099
- return [];
1100
- }
1101
- }
1102
- }
1103
- /**
1104
- * Build the analysis prompt for skill identification
1105
- */
1106
- buildAnalysisPrompt(trajectory, options) {
1107
- const turns = options?.turnRange ? trajectory.turns.slice(options.turnRange[0], options.turnRange[1] + 1) : trajectory.turns;
1108
- const trajectoryText = this.formatTrajectoryForPrompt(turns);
1109
- return `Analyze the following agent conversation trajectory and identify any reusable skills that could be extracted.
1110
-
1111
- A skill is extractable if it meets these criteria:
1112
- 1. **Reusability**: The solution applies to multiple future tasks, not just this specific case
1113
- 2. **Non-trivial**: It involves discovery beyond basic documentation lookup
1114
- 3. **Specific triggers**: Has clear conditions for when to apply (error messages, patterns, contexts)
1115
- 4. **Verified**: The solution demonstrably worked in this trajectory
1116
-
1117
- For each skill you identify, provide:
1118
- - name: A descriptive kebab-case name
1119
- - description: 1-2 sentences optimized for semantic search/matching
1120
- - problem: What problem does this skill solve?
1121
- - solution: Step-by-step instructions to apply this skill
1122
- - triggerConditions: Array of {type, value, description} for when to use this skill
1123
- - verification: How to verify the skill worked
1124
- - turnRange: [startTurn, endTurn] indices in the trajectory
1125
- - confidence: 0-1 score for how extractable this skill is
1126
- - reasoning: Why this is or isn't a good skill candidate
1127
-
1128
- If no skills are extractable, return an empty candidates array.
1129
-
1130
- TRAJECTORY:
1131
- ${trajectoryText}
1132
-
1133
- ${options?.context ? `ADDITIONAL CONTEXT: ${options.context}` : ""}
1134
-
1135
- Respond with a JSON object containing a "candidates" array.`;
1136
- }
1137
- /**
1138
- * Format trajectory turns for the prompt
1139
- */
1140
- formatTrajectoryForPrompt(turns) {
1141
- return turns.map((turn, i) => {
1142
- let text = `[Turn ${i}] ${turn.role.toUpperCase()}:
1143
- ${turn.content || "(no content)"}`;
1144
- if (turn.toolCalls && turn.toolCalls.length > 0) {
1145
- text += "\nTool calls:";
1146
- for (const call of turn.toolCalls) {
1147
- text += `
1148
- - ${call.name}: ${JSON.stringify(call.input).substring(0, 200)}`;
1149
- }
1150
- }
1151
- if (turn.toolResults && turn.toolResults.length > 0) {
1152
- text += "\nTool results:";
1153
- for (const result of turn.toolResults) {
1154
- const status = result.success ? "SUCCESS" : "ERROR";
1155
- text += `
1156
- - ${result.name} [${status}]: ${result.output.substring(0, 200)}`;
1157
- }
1158
- }
1159
- return text;
1160
- }).join("\n\n---\n\n");
1161
- }
1162
- /**
1163
- * Parse text response into skill candidates (fallback)
1164
- */
1165
- parseTextResponse(response) {
1166
- try {
1167
- const jsonMatch = response.match(/\{[\s\S]*"candidates"[\s\S]*\}/);
1168
- if (jsonMatch) {
1169
- const parsed = JSON.parse(jsonMatch[0]);
1170
- return parsed.candidates || [];
1171
- }
1172
- } catch {
1173
- }
1174
- return [];
1175
- }
1176
- /**
1177
- * Build a Skill from a candidate
1178
- */
1179
- buildSkillFromCandidate(candidate, trajectory) {
1180
- const id = candidate.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1181
- const turns = trajectory.turns.slice(candidate.turnRange[0], candidate.turnRange[1] + 1);
1182
- const example = this.buildExample(turns);
1183
- return {
1184
- id,
1185
- name: candidate.name,
1186
- version: "1.0.0",
1187
- description: candidate.description,
1188
- problem: candidate.problem,
1189
- triggerConditions: candidate.triggerConditions,
1190
- solution: candidate.solution,
1191
- verification: candidate.verification,
1192
- examples: example ? [example] : [],
1193
- author: "auto-extracted",
1194
- tags: this.inferTags(candidate),
1195
- createdAt: /* @__PURE__ */ new Date(),
1196
- updatedAt: /* @__PURE__ */ new Date(),
1197
- status: "draft",
1198
- metrics: this.createInitialMetrics(),
1199
- source: {
1200
- type: "extracted",
1201
- sessionId: trajectory.sessionId,
1202
- importedAt: /* @__PURE__ */ new Date()
1203
- }
1204
- };
1205
- }
1206
- /**
1207
- * Build an example from turns
1208
- */
1209
- buildExample(turns) {
1210
- const firstUser = turns.find((t) => t.role === "user");
1211
- const lastAssistant = [...turns].reverse().find((t) => t.role === "assistant");
1212
- if (!firstUser || !lastAssistant) return null;
1213
- return {
1214
- scenario: "Extracted from conversation",
1215
- before: (firstUser.content || "").substring(0, 500),
1216
- after: (lastAssistant.content || "").substring(0, 500)
1217
- };
1218
- }
1219
- /**
1220
- * Infer tags from candidate
1221
- */
1222
- inferTags(candidate) {
1223
- const tags = [];
1224
- const content = `${candidate.problem} ${candidate.solution}`.toLowerCase();
1225
- const techPatterns = [
1226
- [/typescript/i, "typescript"],
1227
- [/javascript/i, "javascript"],
1228
- [/python/i, "python"],
1229
- [/react/i, "react"],
1230
- [/node/i, "nodejs"],
1231
- [/docker/i, "docker"],
1232
- [/git\b/i, "git"],
1233
- [/test/i, "testing"],
1234
- [/error/i, "debugging"]
1235
- ];
1236
- for (const [pattern, tag] of techPatterns) {
1237
- if (pattern.test(content)) {
1238
- tags.push(tag);
1239
- }
1240
- }
1241
- return [...new Set(tags)];
1242
- }
1243
- };
1244
- var SKILL_CANDIDATE_SCHEMA = {
1245
- type: "object",
1246
- properties: {
1247
- candidates: {
1248
- type: "array",
1249
- items: {
1250
- type: "object",
1251
- properties: {
1252
- name: { type: "string" },
1253
- description: { type: "string" },
1254
- problem: { type: "string" },
1255
- solution: { type: "string" },
1256
- triggerConditions: {
1257
- type: "array",
1258
- items: {
1259
- type: "object",
1260
- properties: {
1261
- type: { type: "string", enum: ["error", "pattern", "context", "keyword", "custom"] },
1262
- value: { type: "string" },
1263
- description: { type: "string" }
1264
- },
1265
- required: ["type", "value"]
1266
- }
1267
- },
1268
- verification: { type: "string" },
1269
- turnRange: {
1270
- type: "array",
1271
- items: { type: "number" },
1272
- minItems: 2,
1273
- maxItems: 2
1274
- },
1275
- confidence: { type: "number", minimum: 0, maximum: 1 },
1276
- reasoning: { type: "string" }
1277
- },
1278
- required: ["name", "description", "problem", "solution", "turnRange", "confidence"]
1279
- }
1280
- }
1281
- },
1282
- required: ["candidates"]
1283
- };
1284
-
1285
- // src/storage/base.ts
1286
- var BaseStorageAdapter = class {
1287
- constructor() {
1288
- this.initialized = false;
1289
- }
1290
- /**
1291
- * Ensure storage is initialized before operations
1292
- */
1293
- ensureInitialized() {
1294
- if (!this.initialized) {
1295
- throw new Error("Storage not initialized. Call initialize() first.");
1296
- }
1297
- }
1298
- /**
1299
- * Apply filters to a list of skills
1300
- */
1301
- applyFilter(skills, filter) {
1302
- if (!filter) return skills;
1303
- return skills.filter((skill) => {
1304
- if (filter.status && filter.status.length > 0) {
1305
- if (!filter.status.includes(skill.status)) return false;
1306
- }
1307
- if (filter.tags && filter.tags.length > 0) {
1308
- const hasTag = filter.tags.some((tag) => skill.tags.includes(tag));
1309
- if (!hasTag) return false;
1310
- }
1311
- if (filter.author && skill.author !== filter.author) {
1312
- return false;
1313
- }
1314
- if (filter.minSuccessRate !== void 0 && skill.metrics.successRate < filter.minSuccessRate) {
1315
- return false;
1316
- }
1317
- if (filter.createdAfter && skill.createdAt < filter.createdAfter) {
1318
- return false;
1319
- }
1320
- if (filter.createdBefore && skill.createdAt > filter.createdBefore) {
1321
- return false;
1322
- }
1323
- return true;
1324
- });
1325
- }
1326
- /**
1327
- * Simple text search across skill fields
1328
- */
1329
- textSearch(skills, query) {
1330
- const lowerQuery = query.toLowerCase();
1331
- const terms = lowerQuery.split(/\s+/).filter((t) => t.length > 0);
1332
- return skills.filter((skill) => {
1333
- const searchText = [
1334
- skill.name,
1335
- skill.description,
1336
- skill.problem,
1337
- skill.solution,
1338
- ...skill.tags,
1339
- skill.triggerConditions.map((t) => t.value).join(" ")
1340
- ].join(" ").toLowerCase();
1341
- return terms.every((term) => searchText.includes(term));
1342
- });
1343
- }
1344
- };
1345
- var MemoryStorageAdapter = class extends BaseStorageAdapter {
1346
- constructor() {
1347
- super(...arguments);
1348
- this.skills = /* @__PURE__ */ new Map();
1349
- // skillId -> version -> skill
1350
- this.lineages = /* @__PURE__ */ new Map();
1351
- }
1352
- async initialize() {
1353
- this.initialized = true;
1354
- }
1355
- async saveSkill(skill) {
1356
- this.ensureInitialized();
1357
- if (!this.skills.has(skill.id)) {
1358
- this.skills.set(skill.id, /* @__PURE__ */ new Map());
1359
- }
1360
- const versionMap = this.skills.get(skill.id);
1361
- versionMap.set(skill.version, { ...skill });
1362
- this.updateLineage(skill);
1363
- }
1364
- async getSkill(id, version) {
1365
- this.ensureInitialized();
1366
- const versionMap = this.skills.get(id);
1367
- if (!versionMap) return null;
1368
- if (version) {
1369
- return versionMap.get(version) || null;
1370
- }
1371
- const versions = Array.from(versionMap.keys()).sort(this.compareVersions);
1372
- const latestVersion = versions[versions.length - 1];
1373
- return latestVersion ? versionMap.get(latestVersion) || null : null;
1374
- }
1375
- async listSkills(filter) {
1376
- this.ensureInitialized();
1377
- const skills = [];
1378
- for (const versionMap of this.skills.values()) {
1379
- const versions = Array.from(versionMap.keys()).sort(this.compareVersions);
1380
- const latestVersion = versions[versions.length - 1];
1381
- if (latestVersion) {
1382
- const skill = versionMap.get(latestVersion);
1383
- if (skill) skills.push(skill);
1384
- }
1385
- }
1386
- return this.applyFilter(skills, filter);
1387
- }
1388
- async deleteSkill(id, version) {
1389
- this.ensureInitialized();
1390
- if (version) {
1391
- const versionMap = this.skills.get(id);
1392
- if (!versionMap) return false;
1393
- return versionMap.delete(version);
1394
- }
1395
- const existed = this.skills.has(id);
1396
- this.skills.delete(id);
1397
- this.lineages.delete(id);
1398
- return existed;
1399
- }
1400
- async getVersionHistory(skillId) {
1401
- this.ensureInitialized();
1402
- const versionMap = this.skills.get(skillId);
1403
- if (!versionMap) return [];
1404
- const versions = [];
1405
- for (const [version, skill] of versionMap) {
1406
- versions.push({
1407
- skillId,
1408
- version,
1409
- skill,
1410
- changelog: "",
1411
- // Not tracked in memory
1412
- createdAt: skill.createdAt,
1413
- contentHash: this.hashSkill(skill)
1414
- });
1415
- }
1416
- return versions.sort((a, b) => this.compareVersions(a.version, b.version));
1417
- }
1418
- async getLineage(skillId) {
1419
- this.ensureInitialized();
1420
- return this.lineages.get(skillId) || null;
1421
- }
1422
- async searchSkills(query) {
1423
- this.ensureInitialized();
1424
- const allSkills = await this.listSkills();
1425
- return this.textSearch(allSkills, query);
1426
- }
1427
- updateLineage(skill) {
1428
- if (!this.lineages.has(skill.id)) {
1429
- this.lineages.set(skill.id, {
1430
- rootId: skill.id,
1431
- versions: [],
1432
- forks: []
1433
- });
1434
- }
1435
- const lineage = this.lineages.get(skill.id);
1436
- const existingIndex = lineage.versions.findIndex((v) => v.version === skill.version);
1437
- const versionEntry = {
1438
- skillId: skill.id,
1439
- version: skill.version,
1440
- skill,
1441
- changelog: "",
1442
- createdAt: skill.createdAt,
1443
- contentHash: this.hashSkill(skill)
1444
- };
1445
- if (existingIndex >= 0) {
1446
- lineage.versions[existingIndex] = versionEntry;
1447
- } else {
1448
- lineage.versions.push(versionEntry);
1449
- lineage.versions.sort((a, b) => this.compareVersions(a.version, b.version));
1450
- }
1451
- }
1452
- compareVersions(a, b) {
1453
- const partsA = a.split(".").map(Number);
1454
- const partsB = b.split(".").map(Number);
1455
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
1456
- const numA = partsA[i] || 0;
1457
- const numB = partsB[i] || 0;
1458
- if (numA !== numB) return numA - numB;
1459
- }
1460
- return 0;
1461
- }
1462
- hashSkill(skill) {
1463
- const content = JSON.stringify({
1464
- problem: skill.problem,
1465
- solution: skill.solution,
1466
- triggerConditions: skill.triggerConditions
1467
- });
1468
- let hash = 0;
1469
- for (let i = 0; i < content.length; i++) {
1470
- const char = content.charCodeAt(i);
1471
- hash = (hash << 5) - hash + char;
1472
- hash = hash & hash;
1473
- }
1474
- return Math.abs(hash).toString(16);
1475
- }
1476
- /**
1477
- * Clear all stored skills (for testing)
1478
- */
1479
- clear() {
1480
- this.skills.clear();
1481
- this.lineages.clear();
1482
- }
1483
- };
1484
-
1485
- // src/storage/filesystem.ts
1486
- import * as fs from "fs/promises";
1487
- import * as path from "path";
1488
- var FilesystemStorageAdapter = class extends BaseStorageAdapter {
1489
- constructor(config) {
1490
- super();
1491
- this.config = {
1492
- basePath: config.basePath,
1493
- openSkillsCompatible: config.openSkillsCompatible ?? true,
1494
- trackVersions: config.trackVersions ?? true
1495
- };
1496
- this.versionsDir = path.join(this.config.basePath, ".versions");
1497
- }
1498
- async initialize() {
1499
- await fs.mkdir(this.config.basePath, { recursive: true });
1500
- if (this.config.trackVersions) {
1501
- await fs.mkdir(this.versionsDir, { recursive: true });
1502
- }
1503
- this.initialized = true;
1504
- }
1505
- async saveSkill(skill) {
1506
- this.ensureInitialized();
1507
- const skillDir = path.join(this.config.basePath, skill.id);
1508
- await fs.mkdir(skillDir, { recursive: true });
1509
- const skillContent = this.serializeSkill(skill);
1510
- const skillFileName = this.config.openSkillsCompatible ? "SKILL.md" : "skill.md";
1511
- await fs.writeFile(path.join(skillDir, skillFileName), skillContent, "utf-8");
1512
- const metadata = this.createMetadata(skill);
1513
- await fs.writeFile(
1514
- path.join(skillDir, ".skilltree.json"),
1515
- JSON.stringify(metadata, null, 2),
1516
- "utf-8"
1517
- );
1518
- if (this.config.trackVersions) {
1519
- await this.saveVersionSnapshot(skill);
1520
- }
1521
- }
1522
- async getSkill(id, version) {
1523
- this.ensureInitialized();
1524
- if (version && this.config.trackVersions) {
1525
- return this.getVersionedSkill(id, version);
1526
- }
1527
- const skillDir = path.join(this.config.basePath, id);
1528
- const skillFileName = this.config.openSkillsCompatible ? "SKILL.md" : "skill.md";
1529
- const skillPath = path.join(skillDir, skillFileName);
1530
- try {
1531
- const content = await fs.readFile(skillPath, "utf-8");
1532
- const metadata = await this.loadMetadata(skillDir);
1533
- return this.parseSkill(id, content, metadata);
1534
- } catch (error) {
1535
- if (error.code === "ENOENT") {
1536
- return null;
1537
- }
1538
- throw error;
1539
- }
1540
- }
1541
- async listSkills(filter) {
1542
- this.ensureInitialized();
1543
- const skills = [];
1544
- try {
1545
- const entries = await fs.readdir(this.config.basePath, { withFileTypes: true });
1546
- for (const entry of entries) {
1547
- if (!entry.isDirectory() || entry.name.startsWith(".")) {
1548
- continue;
1549
- }
1550
- const skill = await this.getSkill(entry.name);
1551
- if (skill) {
1552
- skills.push(skill);
1553
- }
1554
- }
1555
- } catch (error) {
1556
- if (error.code !== "ENOENT") {
1557
- throw error;
1558
- }
1559
- }
1560
- return this.applyFilter(skills, filter);
1561
- }
1562
- async deleteSkill(id, version) {
1563
- this.ensureInitialized();
1564
- if (version && this.config.trackVersions) {
1565
- return this.deleteVersion(id, version);
1566
- }
1567
- const skillDir = path.join(this.config.basePath, id);
1568
- try {
1569
- await fs.rm(skillDir, { recursive: true });
1570
- if (this.config.trackVersions) {
1571
- const versionDir = path.join(this.versionsDir, id);
1572
- await fs.rm(versionDir, { recursive: true }).catch(() => {
1573
- });
1574
- }
1575
- return true;
1576
- } catch (error) {
1577
- if (error.code === "ENOENT") {
1578
- return false;
1579
- }
1580
- throw error;
1581
- }
1582
- }
1583
- async getVersionHistory(skillId) {
1584
- this.ensureInitialized();
1585
- if (!this.config.trackVersions) {
1586
- const skill = await this.getSkill(skillId);
1587
- if (!skill) return [];
1588
- return [
1589
- {
1590
- skillId,
1591
- version: skill.version,
1592
- skill,
1593
- changelog: "",
1594
- createdAt: skill.createdAt,
1595
- contentHash: this.hashContent(skill)
1596
- }
1597
- ];
1598
- }
1599
- const versionDir = path.join(this.versionsDir, skillId);
1600
- const versions = [];
1601
- try {
1602
- const entries = await fs.readdir(versionDir);
1603
- for (const entry of entries) {
1604
- if (!entry.endsWith(".json")) continue;
1605
- const versionPath = path.join(versionDir, entry);
1606
- const content = await fs.readFile(versionPath, "utf-8");
1607
- const versionData = JSON.parse(content);
1608
- versionData.createdAt = new Date(versionData.createdAt);
1609
- if (versionData.skill) {
1610
- const skill = versionData.skill;
1611
- skill.createdAt = new Date(skill.createdAt);
1612
- skill.updatedAt = new Date(skill.updatedAt);
1613
- }
1614
- versions.push(versionData);
1615
- }
1616
- } catch (error) {
1617
- if (error.code !== "ENOENT") {
1618
- throw error;
1619
- }
1620
- }
1621
- return versions.sort((a, b) => this.compareVersions(a.version, b.version));
1622
- }
1623
- async getLineage(skillId) {
1624
- this.ensureInitialized();
1625
- const versions = await this.getVersionHistory(skillId);
1626
- if (versions.length === 0) return null;
1627
- const skillDir = path.join(this.config.basePath, skillId);
1628
- const metadata = await this.loadMetadata(skillDir);
1629
- return {
1630
- rootId: metadata?.lineage?.rootId || skillId,
1631
- versions,
1632
- forks: metadata?.lineage?.forks || []
1633
- };
1634
- }
1635
- async searchSkills(query) {
1636
- this.ensureInitialized();
1637
- const allSkills = await this.listSkills();
1638
- return this.textSearch(allSkills, query);
1639
- }
1640
- // ==========================================================================
1641
- // Serialization
1642
- // ==========================================================================
1643
- /**
1644
- * Serialize skill to YAML frontmatter + Markdown format
1645
- */
1646
- serializeSkill(skill) {
1647
- const frontmatter = this.buildFrontmatter(skill);
1648
- const body = this.buildBody(skill);
1649
- return `---
1650
- ${frontmatter}---
1651
-
1652
- ${body}`;
1653
- }
1654
- /**
1655
- * Build YAML frontmatter
1656
- */
1657
- buildFrontmatter(skill) {
1658
- const lines = [];
1659
- lines.push(`name: ${skill.name}`);
1660
- lines.push(`description: |`);
1661
- lines.push(` ${skill.description}`);
1662
- lines.push(`version: ${skill.version}`);
1663
- lines.push(`author: ${skill.author}`);
1664
- lines.push(`status: ${skill.status}`);
1665
- lines.push(`date: ${skill.updatedAt.toISOString().split("T")[0]}`);
1666
- if (skill.tags.length > 0) {
1667
- lines.push(`tags:`);
1668
- for (const tag of skill.tags) {
1669
- lines.push(` - ${tag}`);
1670
- }
1671
- }
1672
- if (skill.parentVersion) {
1673
- lines.push(`parentVersion: ${skill.parentVersion}`);
1674
- }
1675
- if (skill.derivedFrom && skill.derivedFrom.length > 0) {
1676
- lines.push(`derivedFrom:`);
1677
- for (const id of skill.derivedFrom) {
1678
- lines.push(` - ${id}`);
1679
- }
1680
- }
1681
- return lines.join("\n") + "\n";
1682
- }
1683
- /**
1684
- * Build Markdown body (Claudeception-inspired structure)
1685
- */
1686
- buildBody(skill) {
1687
- const sections = [];
1688
- sections.push("## Problem\n");
1689
- sections.push(skill.problem);
1690
- sections.push("");
1691
- if (skill.triggerConditions.length > 0) {
1692
- sections.push("## Trigger Conditions\n");
1693
- for (const trigger of skill.triggerConditions) {
1694
- const desc = trigger.description ? ` - ${trigger.description}` : "";
1695
- sections.push(`- **${trigger.type}**: \`${trigger.value}\`${desc}`);
1696
- }
1697
- sections.push("");
1698
- }
1699
- sections.push("## Solution\n");
1700
- sections.push(skill.solution);
1701
- sections.push("");
1702
- sections.push("## Verification\n");
1703
- sections.push(skill.verification);
1704
- sections.push("");
1705
- if (skill.examples.length > 0) {
1706
- sections.push("## Examples\n");
1707
- for (const example of skill.examples) {
1708
- sections.push(`### ${example.scenario}
1709
- `);
1710
- sections.push("**Before:**");
1711
- sections.push("```");
1712
- sections.push(example.before);
1713
- sections.push("```\n");
1714
- sections.push("**After:**");
1715
- sections.push("```");
1716
- sections.push(example.after);
1717
- sections.push("```");
1718
- sections.push("");
1719
- }
1720
- }
1721
- if (skill.notes) {
1722
- sections.push("## Notes\n");
1723
- sections.push(skill.notes);
1724
- sections.push("");
1725
- }
1726
- return sections.join("\n");
1727
- }
1728
- /**
1729
- * Parse skill from YAML frontmatter + Markdown content
1730
- */
1731
- parseSkill(id, content, metadata) {
1732
- const { frontmatter, body } = this.parseFrontmatterAndBody(content);
1733
- const sections = this.parseBodySections(body);
1734
- const name = this.extractYamlField(frontmatter, "name") || id;
1735
- const description = this.extractYamlMultiline(frontmatter, "description") || "";
1736
- const version = this.extractYamlField(frontmatter, "version") || "1.0.0";
1737
- const author = this.extractYamlField(frontmatter, "author") || "unknown";
1738
- const status = this.extractYamlField(frontmatter, "status") || "active";
1739
- const dateStr = this.extractYamlField(frontmatter, "date");
1740
- const tags = this.extractYamlList(frontmatter, "tags");
1741
- const parentVersion = this.extractYamlField(frontmatter, "parentVersion");
1742
- const derivedFrom = this.extractYamlList(frontmatter, "derivedFrom");
1743
- const problem = sections["problem"] || "";
1744
- const solution = sections["solution"] || "";
1745
- const verification = sections["verification"] || "";
1746
- const notes = sections["notes"];
1747
- const triggerConditions = this.parseTriggerConditions(sections["trigger conditions"] || "");
1748
- const examples = this.parseExamples(sections["examples"] || "");
1749
- const date = dateStr ? new Date(dateStr) : /* @__PURE__ */ new Date();
1750
- return {
1751
- id,
1752
- name,
1753
- version,
1754
- description,
1755
- problem,
1756
- triggerConditions,
1757
- solution,
1758
- verification,
1759
- examples,
1760
- notes,
1761
- author,
1762
- tags,
1763
- createdAt: date,
1764
- updatedAt: date,
1765
- status,
1766
- parentVersion: parentVersion || void 0,
1767
- derivedFrom: derivedFrom.length > 0 ? derivedFrom : void 0,
1768
- metrics: {
1769
- usageCount: 0,
1770
- successRate: 0,
1771
- feedbackScores: []
1772
- },
1773
- source: metadata?.source
1774
- };
1775
- }
1776
- /**
1777
- * Split content into frontmatter and body
1778
- */
1779
- parseFrontmatterAndBody(content) {
1780
- const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
1781
- if (match) {
1782
- return { frontmatter: match[1], body: match[2] };
1783
- }
1784
- return { frontmatter: "", body: content };
1785
- }
1786
- /**
1787
- * Parse body into sections by ## headers
1788
- */
1789
- parseBodySections(body) {
1790
- const sections = {};
1791
- const parts = body.split(/^## /m);
1792
- for (const part of parts) {
1793
- if (!part.trim()) continue;
1794
- const lines = part.split("\n");
1795
- const header = lines[0]?.trim().toLowerCase();
1796
- const content = lines.slice(1).join("\n").trim();
1797
- if (header) {
1798
- sections[header] = content;
1799
- }
1800
- }
1801
- return sections;
1802
- }
1803
- /**
1804
- * Extract a single-line YAML field
1805
- */
1806
- extractYamlField(yaml, field) {
1807
- const match = yaml.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
1808
- return match ? match[1].trim() : null;
1809
- }
1810
- /**
1811
- * Extract a multiline YAML field (using |)
1812
- */
1813
- extractYamlMultiline(yaml, field) {
1814
- const match = yaml.match(new RegExp(`^${field}:\\s*\\|\\n((?:\\s{2}.+\\n?)+)`, "m"));
1815
- if (match) {
1816
- return match[1].split("\n").map((line) => line.replace(/^\s{2}/, "")).join("\n").trim();
1817
- }
1818
- return this.extractYamlField(yaml, field);
1819
- }
1820
- /**
1821
- * Extract a YAML list
1822
- */
1823
- extractYamlList(yaml, field) {
1824
- const match = yaml.match(new RegExp(`^${field}:\\n((?:\\s+-\\s+.+\\n?)+)`, "m"));
1825
- if (match) {
1826
- return match[1].split("\n").map((line) => line.replace(/^\s+-\s+/, "").trim()).filter((line) => line.length > 0);
1827
- }
1828
- return [];
1829
- }
1830
- /**
1831
- * Parse trigger conditions from markdown
1832
- */
1833
- parseTriggerConditions(content) {
1834
- const conditions = [];
1835
- const lines = content.split("\n");
1836
- for (const line of lines) {
1837
- const match = line.match(/^-\s+\*\*(\w+)\*\*:\s*`([^`]+)`(?:\s*-\s*(.+))?/);
1838
- if (match) {
1839
- conditions.push({
1840
- type: match[1],
1841
- value: match[2],
1842
- description: match[3]?.trim()
1843
- });
1844
- }
1845
- }
1846
- return conditions;
1847
- }
1848
- /**
1849
- * Parse examples from markdown
1850
- */
1851
- parseExamples(content) {
1852
- const examples = [];
1853
- const parts = content.split(/^### /m);
1854
- for (const part of parts) {
1855
- if (!part.trim()) continue;
1856
- const lines = part.split("\n");
1857
- const scenario = lines[0]?.trim() || "Example";
1858
- const rest = lines.slice(1).join("\n");
1859
- const beforeMatch = rest.match(/\*\*Before:\*\*\n```\n?([\s\S]*?)```/);
1860
- const afterMatch = rest.match(/\*\*After:\*\*\n```\n?([\s\S]*?)```/);
1861
- if (beforeMatch || afterMatch) {
1862
- examples.push({
1863
- scenario,
1864
- before: beforeMatch?.[1]?.trim() || "",
1865
- after: afterMatch?.[1]?.trim() || ""
1866
- });
1867
- }
1868
- }
1869
- return examples;
1870
- }
1871
- // ==========================================================================
1872
- // Version Management
1873
- // ==========================================================================
1874
- /**
1875
- * Save a version snapshot
1876
- */
1877
- async saveVersionSnapshot(skill) {
1878
- const versionDir = path.join(this.versionsDir, skill.id);
1879
- await fs.mkdir(versionDir, { recursive: true });
1880
- const versionFile = path.join(versionDir, `${skill.version}.json`);
1881
- const versionData = {
1882
- skillId: skill.id,
1883
- version: skill.version,
1884
- skill,
1885
- changelog: "",
1886
- // Could be enhanced to track changes
1887
- createdAt: /* @__PURE__ */ new Date(),
1888
- contentHash: this.hashContent(skill)
1889
- };
1890
- await fs.writeFile(versionFile, JSON.stringify(versionData, null, 2), "utf-8");
1891
- }
1892
- /**
1893
- * Get a specific version of a skill
1894
- */
1895
- async getVersionedSkill(id, version) {
1896
- const versionFile = path.join(this.versionsDir, id, `${version}.json`);
1897
- try {
1898
- const content = await fs.readFile(versionFile, "utf-8");
1899
- const versionData = JSON.parse(content);
1900
- const skill = versionData.skill;
1901
- skill.createdAt = new Date(skill.createdAt);
1902
- skill.updatedAt = new Date(skill.updatedAt);
1903
- return skill;
1904
- } catch (error) {
1905
- if (error.code === "ENOENT") {
1906
- return null;
1907
- }
1908
- throw error;
1909
- }
1910
- }
1911
- /**
1912
- * Delete a specific version
1913
- */
1914
- async deleteVersion(id, version) {
1915
- const versionFile = path.join(this.versionsDir, id, `${version}.json`);
1916
- try {
1917
- await fs.unlink(versionFile);
1918
- return true;
1919
- } catch (error) {
1920
- if (error.code === "ENOENT") {
1921
- return false;
1922
- }
1923
- throw error;
1924
- }
1925
- }
1926
- // ==========================================================================
1927
- // Metadata
1928
- // ==========================================================================
1929
- /**
1930
- * Create metadata for a skill
1931
- */
1932
- createMetadata(skill) {
1933
- return {
1934
- source: skill.source,
1935
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1936
- lineage: {
1937
- rootId: skill.id,
1938
- parentVersion: skill.parentVersion,
1939
- derivedFrom: skill.derivedFrom
1940
- }
1941
- };
1942
- }
1943
- /**
1944
- * Load metadata for a skill directory
1945
- */
1946
- async loadMetadata(skillDir) {
1947
- const metadataPath = path.join(skillDir, ".skilltree.json");
1948
- try {
1949
- const content = await fs.readFile(metadataPath, "utf-8");
1950
- return JSON.parse(content);
1951
- } catch {
1952
- return null;
1953
- }
1954
- }
1955
- // ==========================================================================
1956
- // Utilities
1957
- // ==========================================================================
1958
- /**
1959
- * Compare semantic versions
1960
- */
1961
- compareVersions(a, b) {
1962
- const partsA = a.split(".").map(Number);
1963
- const partsB = b.split(".").map(Number);
1964
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
1965
- const numA = partsA[i] || 0;
1966
- const numB = partsB[i] || 0;
1967
- if (numA !== numB) return numA - numB;
1968
- }
1969
- return 0;
1970
- }
1971
- /**
1972
- * Generate content hash
1973
- */
1974
- hashContent(skill) {
1975
- const content = JSON.stringify({
1976
- problem: skill.problem,
1977
- solution: skill.solution,
1978
- triggerConditions: skill.triggerConditions
1979
- });
1980
- let hash = 0;
1981
- for (let i = 0; i < content.length; i++) {
1982
- const char = content.charCodeAt(i);
1983
- hash = (hash << 5) - hash + char;
1984
- hash = hash & hash;
1985
- }
1986
- return Math.abs(hash).toString(16);
1987
- }
1988
- };
1989
-
1990
- // src/versioning/semver.ts
1991
- function parseVersion(version) {
1992
- const match = version.match(
1993
- /^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/
1994
- );
1995
- if (!match) {
1996
- throw new Error(`Invalid version format: ${version}`);
1997
- }
1998
- return {
1999
- major: parseInt(match[1], 10),
2000
- minor: parseInt(match[2], 10),
2001
- patch: parseInt(match[3], 10),
2002
- prerelease: match[4],
2003
- build: match[5]
2004
- };
2005
- }
2006
- function formatVersion(parsed) {
2007
- let version = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
2008
- if (parsed.prerelease) {
2009
- version += `-${parsed.prerelease}`;
2010
- }
2011
- if (parsed.build) {
2012
- version += `+${parsed.build}`;
2013
- }
2014
- return version;
2015
- }
2016
- function isValidVersion(version) {
2017
- try {
2018
- parseVersion(version);
2019
- return true;
2020
- } catch {
2021
- return false;
2022
- }
2023
- }
2024
- function compareVersions(a, b) {
2025
- const parsedA = parseVersion(a);
2026
- const parsedB = parseVersion(b);
2027
- if (parsedA.major !== parsedB.major) {
2028
- return parsedA.major < parsedB.major ? -1 : 1;
2029
- }
2030
- if (parsedA.minor !== parsedB.minor) {
2031
- return parsedA.minor < parsedB.minor ? -1 : 1;
2032
- }
2033
- if (parsedA.patch !== parsedB.patch) {
2034
- return parsedA.patch < parsedB.patch ? -1 : 1;
2035
- }
2036
- if (!parsedA.prerelease && parsedB.prerelease) return 1;
2037
- if (parsedA.prerelease && !parsedB.prerelease) return -1;
2038
- if (parsedA.prerelease && parsedB.prerelease) {
2039
- return parsedA.prerelease < parsedB.prerelease ? -1 : parsedA.prerelease > parsedB.prerelease ? 1 : 0;
2040
- }
2041
- return 0;
2042
- }
2043
- function bumpVersion(version, type) {
2044
- const parsed = parseVersion(version);
2045
- switch (type) {
2046
- case "major":
2047
- return formatVersion({
2048
- major: parsed.major + 1,
2049
- minor: 0,
2050
- patch: 0
2051
- });
2052
- case "minor":
2053
- return formatVersion({
2054
- major: parsed.major,
2055
- minor: parsed.minor + 1,
2056
- patch: 0
2057
- });
2058
- case "patch":
2059
- return formatVersion({
2060
- major: parsed.major,
2061
- minor: parsed.minor,
2062
- patch: parsed.patch + 1
2063
- });
2064
- case "prerelease":
2065
- if (parsed.prerelease) {
2066
- const match = parsed.prerelease.match(/^(.+?)(\d+)$/);
2067
- if (match) {
2068
- return formatVersion({
2069
- ...parsed,
2070
- prerelease: `${match[1]}${parseInt(match[2], 10) + 1}`
2071
- });
2072
- }
2073
- return formatVersion({
2074
- ...parsed,
2075
- prerelease: `${parsed.prerelease}.1`
2076
- });
2077
- }
2078
- return formatVersion({
2079
- ...parsed,
2080
- prerelease: "alpha.1"
2081
- });
2082
- default:
2083
- throw new Error(`Unknown bump type: ${type}`);
2084
- }
2085
- }
2086
- function satisfiesRange(version, range) {
2087
- const parsed = parseVersion(version);
2088
- if (isValidVersion(range)) {
2089
- return compareVersions(version, range) === 0;
2090
- }
2091
- if (range.startsWith("^")) {
2092
- const rangeVersion = parseVersion(range.slice(1));
2093
- if (parsed.major !== rangeVersion.major) return false;
2094
- if (rangeVersion.major === 0) {
2095
- if (parsed.minor !== rangeVersion.minor) return false;
2096
- if (rangeVersion.minor === 0) {
2097
- return parsed.patch >= rangeVersion.patch;
2098
- }
2099
- return parsed.patch >= rangeVersion.patch;
2100
- }
2101
- return compareVersions(version, range.slice(1)) >= 0;
2102
- }
2103
- if (range.startsWith("~")) {
2104
- const rangeVersion = parseVersion(range.slice(1));
2105
- return parsed.major === rangeVersion.major && parsed.minor === rangeVersion.minor && parsed.patch >= rangeVersion.patch;
2106
- }
2107
- if (range.startsWith(">=")) {
2108
- return compareVersions(version, range.slice(2)) >= 0;
2109
- }
2110
- if (range.startsWith(">")) {
2111
- return compareVersions(version, range.slice(1)) > 0;
2112
- }
2113
- if (range.startsWith("<=")) {
2114
- return compareVersions(version, range.slice(2)) <= 0;
2115
- }
2116
- if (range.startsWith("<")) {
2117
- return compareVersions(version, range.slice(1)) < 0;
2118
- }
2119
- if (range.includes("x") || range.includes("*")) {
2120
- const parts = range.split(".");
2121
- if (parts[0] === "x" || parts[0] === "*") return true;
2122
- if (parsed.major !== parseInt(parts[0], 10)) return false;
2123
- if (parts[1] === "x" || parts[1] === "*" || parts.length === 1) return true;
2124
- if (parsed.minor !== parseInt(parts[1], 10)) return false;
2125
- if (parts[2] === "x" || parts[2] === "*" || parts.length === 2) return true;
2126
- return parsed.patch === parseInt(parts[2], 10);
2127
- }
2128
- return false;
2129
- }
2130
- function getLatestVersion(versions) {
2131
- if (versions.length === 0) return null;
2132
- return versions.reduce((latest, current) => {
2133
- return compareVersions(current, latest) > 0 ? current : latest;
2134
- });
2135
- }
2136
- function sortVersions(versions) {
2137
- return [...versions].sort((a, b) => compareVersions(a, b));
2138
- }
2139
- function inferBumpType(changes) {
2140
- if (changes.breakingChanges && changes.breakingChanges.length > 0) {
2141
- return "major";
2142
- }
2143
- if (changes.newFeatures && changes.newFeatures.length > 0) {
2144
- return "minor";
2145
- }
2146
- return "patch";
2147
- }
2148
-
2149
- // src/versioning/lineage.ts
2150
- var LineageTracker = class {
2151
- constructor(storage) {
2152
- this.storage = storage;
2153
- }
2154
- /**
2155
- * Create a new version of a skill
2156
- */
2157
- async createVersion(skillId, updates, options = {}) {
2158
- const currentSkill = await this.storage.getSkill(skillId);
2159
- if (!currentSkill) {
2160
- throw new Error(`Skill not found: ${skillId}`);
2161
- }
2162
- const bumpType = options.bumpType || inferBumpType(options.changes || {});
2163
- const newVersion = bumpVersion(currentSkill.version, bumpType);
2164
- const updatedSkill = {
2165
- ...currentSkill,
2166
- ...updates,
2167
- id: skillId,
2168
- version: newVersion,
2169
- parentVersion: currentSkill.version,
2170
- updatedAt: /* @__PURE__ */ new Date()
2171
- };
2172
- await this.storage.saveSkill(updatedSkill);
2173
- return updatedSkill;
2174
- }
2175
- /**
2176
- * Fork a skill to create a new, related skill
2177
- */
2178
- async forkSkill(skillId, options) {
2179
- const sourceSkill = await this.storage.getSkill(skillId, options.fromVersion);
2180
- if (!sourceSkill) {
2181
- throw new Error(`Skill not found: ${skillId}${options.fromVersion ? `@${options.fromVersion}` : ""}`);
2182
- }
2183
- const forkedSkill = {
2184
- ...sourceSkill,
2185
- id: options.newId,
2186
- name: options.newName || `${sourceSkill.name}-fork`,
2187
- version: "1.0.0",
2188
- parentVersion: void 0,
2189
- derivedFrom: [skillId],
2190
- createdAt: /* @__PURE__ */ new Date(),
2191
- updatedAt: /* @__PURE__ */ new Date(),
2192
- status: "draft",
2193
- metrics: {
2194
- usageCount: 0,
2195
- successRate: 0,
2196
- feedbackScores: []
2197
- },
2198
- source: {
2199
- type: "composed",
2200
- importedAt: /* @__PURE__ */ new Date()
2201
- }
2202
- };
2203
- await this.storage.saveSkill(forkedSkill);
2204
- return forkedSkill;
2205
- }
2206
- /**
2207
- * Merge changes from one skill into another
2208
- */
2209
- async mergeSkill(targetId, sourceId, options = {}) {
2210
- const targetSkill = await this.storage.getSkill(targetId);
2211
- const sourceSkill = await this.storage.getSkill(sourceId);
2212
- if (!targetSkill) throw new Error(`Target skill not found: ${targetId}`);
2213
- if (!sourceSkill) throw new Error(`Source skill not found: ${sourceId}`);
2214
- const fields = options.fields || [
2215
- "solution",
2216
- "triggerConditions",
2217
- "examples",
2218
- "notes"
2219
- ];
2220
- const strategy = options.conflictStrategy || "combine";
2221
- const updates = {};
2222
- for (const field of fields) {
2223
- if (field === "triggerConditions") {
2224
- updates.triggerConditions = this.mergeTriggerConditions(
2225
- targetSkill.triggerConditions,
2226
- sourceSkill.triggerConditions
2227
- );
2228
- } else if (field === "examples") {
2229
- updates.examples = this.mergeExamples(
2230
- targetSkill.examples,
2231
- sourceSkill.examples
2232
- );
2233
- } else if (field === "tags") {
2234
- updates.tags = [.../* @__PURE__ */ new Set([...targetSkill.tags, ...sourceSkill.tags])];
2235
- } else if (field === "solution" || field === "notes") {
2236
- updates[field] = this.mergeText(
2237
- targetSkill[field] || "",
2238
- sourceSkill[field] || "",
2239
- strategy
2240
- );
2241
- }
2242
- }
2243
- updates.derivedFrom = [
2244
- ...targetSkill.derivedFrom || [],
2245
- sourceId
2246
- ].filter((id, i, arr) => arr.indexOf(id) === i);
2247
- return this.createVersion(targetId, updates, {
2248
- bumpType: "minor",
2249
- changelog: options.changelog || `Merged from ${sourceId}`,
2250
- changes: { newFeatures: [`Merged content from ${sourceId}`] }
2251
- });
2252
- }
2253
- /**
2254
- * Get the full lineage tree for a skill
2255
- */
2256
- async getLineageTree(skillId) {
2257
- const lineage = await this.storage.getLineage(skillId);
2258
- if (!lineage) return null;
2259
- const tree = {
2260
- skillId,
2261
- versions: lineage.versions,
2262
- forks: [],
2263
- ancestors: []
2264
- };
2265
- for (const fork of lineage.forks) {
2266
- const forkedSkill = await this.storage.getSkill(fork.forkedSkillId);
2267
- if (forkedSkill) {
2268
- tree.forks.push({
2269
- skill: forkedSkill,
2270
- fork
2271
- });
2272
- }
2273
- }
2274
- const currentSkill = await this.storage.getSkill(skillId);
2275
- if (currentSkill?.derivedFrom) {
2276
- for (const ancestorId of currentSkill.derivedFrom) {
2277
- const ancestor = await this.storage.getSkill(ancestorId);
2278
- if (ancestor) {
2279
- tree.ancestors.push(ancestor);
2280
- }
2281
- }
2282
- }
2283
- return tree;
2284
- }
2285
- /**
2286
- * Rollback a skill to a previous version
2287
- */
2288
- async rollback(skillId, toVersion) {
2289
- const targetSkill = await this.storage.getSkill(skillId, toVersion);
2290
- if (!targetSkill) {
2291
- throw new Error(`Version not found: ${skillId}@${toVersion}`);
2292
- }
2293
- const currentSkill = await this.storage.getSkill(skillId);
2294
- if (!currentSkill) {
2295
- throw new Error(`Skill not found: ${skillId}`);
2296
- }
2297
- const newVersion = bumpVersion(currentSkill.version, "patch");
2298
- const rolledBackSkill = {
2299
- ...targetSkill,
2300
- version: newVersion,
2301
- parentVersion: currentSkill.version,
2302
- updatedAt: /* @__PURE__ */ new Date()
2303
- };
2304
- await this.storage.saveSkill(rolledBackSkill);
2305
- return rolledBackSkill;
2306
- }
2307
- /**
2308
- * Compare two versions of a skill
2309
- */
2310
- async compareVersions(skillId, versionA, versionB) {
2311
- const skillA = await this.storage.getSkill(skillId, versionA);
2312
- const skillB = await this.storage.getSkill(skillId, versionB);
2313
- if (!skillA) throw new Error(`Version not found: ${skillId}@${versionA}`);
2314
- if (!skillB) throw new Error(`Version not found: ${skillId}@${versionB}`);
2315
- return {
2316
- versionA,
2317
- versionB,
2318
- changes: this.diffSkills(skillA, skillB)
2319
- };
2320
- }
2321
- /**
2322
- * Get suggested next version based on changes
2323
- */
2324
- async suggestNextVersion(skillId, changes) {
2325
- const skill = await this.storage.getSkill(skillId);
2326
- if (!skill) throw new Error(`Skill not found: ${skillId}`);
2327
- const bumpType = inferBumpType(changes);
2328
- return bumpVersion(skill.version, bumpType);
2329
- }
2330
- // ==========================================================================
2331
- // Private helpers
2332
- // ==========================================================================
2333
- mergeTriggerConditions(target, source) {
2334
- const seen = new Set(target.map((t) => `${t.type}:${t.value}`));
2335
- const merged = [...target];
2336
- for (const trigger of source) {
2337
- const key = `${trigger.type}:${trigger.value}`;
2338
- if (!seen.has(key)) {
2339
- merged.push(trigger);
2340
- seen.add(key);
2341
- }
2342
- }
2343
- return merged;
2344
- }
2345
- mergeExamples(target, source) {
2346
- const seen = new Set(target.map((e) => e.scenario));
2347
- const merged = [...target];
2348
- for (const example of source) {
2349
- if (!seen.has(example.scenario)) {
2350
- merged.push(example);
2351
- seen.add(example.scenario);
2352
- }
2353
- }
2354
- return merged;
2355
- }
2356
- mergeText(target, source, strategy) {
2357
- switch (strategy) {
2358
- case "source":
2359
- return source;
2360
- case "target":
2361
- return target;
2362
- case "combine":
2363
- if (!target) return source;
2364
- if (!source) return target;
2365
- return `${target}
2366
-
2367
- ---
2368
-
2369
- ${source}`;
2370
- }
2371
- }
2372
- diffSkills(skillA, skillB) {
2373
- const changes = {
2374
- modified: [],
2375
- added: [],
2376
- removed: []
2377
- };
2378
- const textFields = [
2379
- "name",
2380
- "description",
2381
- "problem",
2382
- "solution",
2383
- "verification",
2384
- "notes"
2385
- ];
2386
- for (const field of textFields) {
2387
- const valueA = skillA[field];
2388
- const valueB = skillB[field];
2389
- if (valueA !== valueB) {
2390
- changes.modified.push({
2391
- field,
2392
- oldValue: valueA,
2393
- newValue: valueB
2394
- });
2395
- }
2396
- }
2397
- const triggersA = new Set(skillA.triggerConditions.map((t) => `${t.type}:${t.value}`));
2398
- const triggersB = new Set(skillB.triggerConditions.map((t) => `${t.type}:${t.value}`));
2399
- for (const trigger of triggersB) {
2400
- if (!triggersA.has(trigger)) {
2401
- changes.added.push({ type: "triggerCondition", value: trigger });
2402
- }
2403
- }
2404
- for (const trigger of triggersA) {
2405
- if (!triggersB.has(trigger)) {
2406
- changes.removed.push({ type: "triggerCondition", value: trigger });
2407
- }
2408
- }
2409
- const tagsA = new Set(skillA.tags);
2410
- const tagsB = new Set(skillB.tags);
2411
- for (const tag of tagsB) {
2412
- if (!tagsA.has(tag)) {
2413
- changes.added.push({ type: "tag", value: tag });
2414
- }
2415
- }
2416
- for (const tag of tagsA) {
2417
- if (!tagsB.has(tag)) {
2418
- changes.removed.push({ type: "tag", value: tag });
2419
- }
2420
- }
2421
- return changes;
2422
- }
2423
- };
2424
-
2425
- // src/matching/embeddings.ts
2426
- var SimpleHashEmbedding = class {
2427
- constructor() {
2428
- this.name = "simple-hash";
2429
- this.dimensions = 64;
2430
- }
2431
- async embed(text) {
2432
- return this.hashToVector(text);
2433
- }
2434
- async embedBatch(texts) {
2435
- return texts.map((text) => this.hashToVector(text));
2436
- }
2437
- hashToVector(text) {
2438
- const normalized = text.toLowerCase().trim();
2439
- const vector = new Array(this.dimensions).fill(0);
2440
- for (let i = 0; i < normalized.length; i++) {
2441
- const charCode = normalized.charCodeAt(i);
2442
- const idx = (charCode + i) % this.dimensions;
2443
- vector[idx] += Math.sin(charCode * (i + 1)) * 0.1;
2444
- }
2445
- const words = normalized.split(/\s+/);
2446
- for (let i = 0; i < words.length; i++) {
2447
- const word = words[i];
2448
- const hash = this.simpleHash(word);
2449
- const idx = hash % this.dimensions;
2450
- vector[idx] += 0.5;
2451
- }
2452
- return this.normalize(vector);
2453
- }
2454
- simpleHash(str) {
2455
- let hash = 0;
2456
- for (let i = 0; i < str.length; i++) {
2457
- const char = str.charCodeAt(i);
2458
- hash = (hash << 5) - hash + char;
2459
- hash = hash & hash;
2460
- }
2461
- return Math.abs(hash);
2462
- }
2463
- normalize(vector) {
2464
- const magnitude = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
2465
- if (magnitude === 0) return vector;
2466
- return vector.map((v) => v / magnitude);
2467
- }
2468
- };
2469
- var OpenAIEmbedding = class {
2470
- constructor(options) {
2471
- this.name = "openai";
2472
- this.dimensions = 1536;
2473
- if (!options.apiKey) {
2474
- throw new Error("OpenAI API key is required");
2475
- }
2476
- this.apiKey = options.apiKey;
2477
- this.model = options.model || "text-embedding-3-small";
2478
- this.baseUrl = options.baseUrl || "https://api.openai.com/v1";
2479
- if (this.model === "text-embedding-3-large") {
2480
- this.dimensions = 3072;
2481
- } else if (this.model === "text-embedding-ada-002") {
2482
- this.dimensions = 1536;
2483
- }
2484
- }
2485
- async embed(text) {
2486
- const embeddings = await this.embedBatch([text]);
2487
- return embeddings[0];
2488
- }
2489
- async embedBatch(texts) {
2490
- const response = await fetch(`${this.baseUrl}/embeddings`, {
2491
- method: "POST",
2492
- headers: {
2493
- "Content-Type": "application/json",
2494
- Authorization: `Bearer ${this.apiKey}`
2495
- },
2496
- body: JSON.stringify({
2497
- model: this.model,
2498
- input: texts
2499
- })
2500
- });
2501
- if (!response.ok) {
2502
- const error = await response.text();
2503
- throw new Error(`OpenAI API error: ${error}`);
2504
- }
2505
- const data = await response.json();
2506
- return data.data.sort((a, b) => a.index - b.index).map((item) => item.embedding);
2507
- }
2508
- };
2509
- var VoyageEmbedding = class {
2510
- constructor(options) {
2511
- this.name = "voyage";
2512
- this.dimensions = 1024;
2513
- if (!options.apiKey) {
2514
- throw new Error("Voyage API key is required");
2515
- }
2516
- this.apiKey = options.apiKey;
2517
- this.model = options.model || "voyage-2";
2518
- }
2519
- async embed(text) {
2520
- const embeddings = await this.embedBatch([text]);
2521
- return embeddings[0];
2522
- }
2523
- async embedBatch(texts) {
2524
- const response = await fetch("https://api.voyageai.com/v1/embeddings", {
2525
- method: "POST",
2526
- headers: {
2527
- "Content-Type": "application/json",
2528
- Authorization: `Bearer ${this.apiKey}`
2529
- },
2530
- body: JSON.stringify({
2531
- model: this.model,
2532
- input: texts
2533
- })
2534
- });
2535
- if (!response.ok) {
2536
- const error = await response.text();
2537
- throw new Error(`Voyage API error: ${error}`);
2538
- }
2539
- const data = await response.json();
2540
- return data.data.map((item) => item.embedding);
2541
- }
2542
- };
2543
- function cosineSimilarity(a, b) {
2544
- if (a.length !== b.length) {
2545
- throw new Error("Vectors must have the same length");
2546
- }
2547
- let dotProduct = 0;
2548
- let normA = 0;
2549
- let normB = 0;
2550
- for (let i = 0; i < a.length; i++) {
2551
- dotProduct += a[i] * b[i];
2552
- normA += a[i] * a[i];
2553
- normB += b[i] * b[i];
2554
- }
2555
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
2556
- if (magnitude === 0) return 0;
2557
- return dotProduct / magnitude;
2558
- }
2559
- function euclideanDistance(a, b) {
2560
- if (a.length !== b.length) {
2561
- throw new Error("Vectors must have the same length");
2562
- }
2563
- let sum = 0;
2564
- for (let i = 0; i < a.length; i++) {
2565
- const diff = a[i] - b[i];
2566
- sum += diff * diff;
2567
- }
2568
- return Math.sqrt(sum);
2569
- }
2570
-
2571
- // src/matching/matcher.ts
2572
- var SemanticMatcher = class {
2573
- constructor(config = {}) {
2574
- this.cache = /* @__PURE__ */ new Map();
2575
- this.provider = config.embeddingProvider || new SimpleHashEmbedding();
2576
- this.config = {
2577
- embeddingProvider: this.provider,
2578
- similarityThreshold: config.similarityThreshold ?? 0.5,
2579
- maxResults: config.maxResults ?? 10,
2580
- cacheEmbeddings: config.cacheEmbeddings ?? true,
2581
- embeddingFields: config.embeddingFields ?? [
2582
- "name",
2583
- "description",
2584
- "problem",
2585
- "triggerConditions",
2586
- "tags"
2587
- ]
2588
- };
2589
- }
2590
- /**
2591
- * Find skills matching a query string
2592
- */
2593
- async findMatches(query, skills, options) {
2594
- const threshold = options?.threshold ?? this.config.similarityThreshold;
2595
- const maxResults = options?.maxResults ?? this.config.maxResults;
2596
- const queryEmbedding = await this.provider.embed(query);
2597
- const results = [];
2598
- for (const skill of skills) {
2599
- const skillEmbedding = await this.getSkillEmbedding(skill);
2600
- const similarity = cosineSimilarity(queryEmbedding, skillEmbedding);
2601
- if (similarity >= threshold) {
2602
- results.push({
2603
- skill,
2604
- similarity,
2605
- matchedFields: this.identifyMatchedFields(query, skill)
2606
- });
2607
- }
2608
- }
2609
- return results.sort((a, b) => b.similarity - a.similarity).slice(0, maxResults);
2610
- }
2611
- /**
2612
- * Find skills matching a context (problem description, error message, etc.)
2613
- */
2614
- async findByContext(context, skills, options) {
2615
- const queryParts = [];
2616
- if (context.problemDescription) {
2617
- queryParts.push(context.problemDescription);
2618
- }
2619
- if (context.errorMessage) {
2620
- queryParts.push(`error: ${context.errorMessage}`);
2621
- }
2622
- if (context.keywords) {
2623
- queryParts.push(context.keywords.join(" "));
2624
- }
2625
- if (context.technologies) {
2626
- queryParts.push(context.technologies.join(" "));
2627
- }
2628
- if (context.codeSnippet) {
2629
- const identifiers = this.extractIdentifiers(context.codeSnippet);
2630
- queryParts.push(identifiers.join(" "));
2631
- }
2632
- const query = queryParts.join(" ");
2633
- return this.findMatches(query, skills, options);
2634
- }
2635
- /**
2636
- * Find skills similar to a given skill
2637
- */
2638
- async findSimilar(skill, allSkills, options) {
2639
- const skillText = this.skillToText(skill);
2640
- const excludeSelf = options?.excludeSelf ?? true;
2641
- const candidates = excludeSelf ? allSkills.filter((s) => s.id !== skill.id) : allSkills;
2642
- return this.findMatches(skillText, candidates, options);
2643
- }
2644
- /**
2645
- * Find skills that might apply to a trajectory
2646
- */
2647
- async findForTrajectory(trajectory, skills, options) {
2648
- const contextParts = [];
2649
- for (const turn of trajectory.turns) {
2650
- if (turn.content) {
2651
- const errorMatch = turn.content.match(/error[:\s]+([^\n]+)/i);
2652
- if (errorMatch) {
2653
- contextParts.push(errorMatch[1]);
2654
- }
2655
- if (turn.role === "user") {
2656
- contextParts.push(turn.content.substring(0, 500));
2657
- }
2658
- }
2659
- }
2660
- const query = contextParts.join(" ");
2661
- return this.findMatches(query, skills, options);
2662
- }
2663
- /**
2664
- * Pre-compute and cache embeddings for all skills
2665
- */
2666
- async indexSkills(skills) {
2667
- const texts = skills.map((s) => this.skillToText(s));
2668
- const embeddings = await this.provider.embedBatch(texts);
2669
- for (let i = 0; i < skills.length; i++) {
2670
- const skill = skills[i];
2671
- const cacheKey = this.getCacheKey(skill);
2672
- this.cache.set(cacheKey, {
2673
- skillId: skill.id,
2674
- version: skill.version,
2675
- embedding: embeddings[i],
2676
- updatedAt: /* @__PURE__ */ new Date()
2677
- });
2678
- }
2679
- }
2680
- /**
2681
- * Index skills from a storage adapter
2682
- */
2683
- async indexFromStorage(storage) {
2684
- const skills = await storage.listSkills();
2685
- await this.indexSkills(skills);
2686
- return skills.length;
2687
- }
2688
- /**
2689
- * Clear the embedding cache
2690
- */
2691
- clearCache() {
2692
- this.cache.clear();
2693
- }
2694
- /**
2695
- * Get cache statistics
2696
- */
2697
- getCacheStats() {
2698
- return {
2699
- size: this.cache.size,
2700
- skills: Array.from(this.cache.values()).map((e) => `${e.skillId}@${e.version}`)
2701
- };
2702
- }
2703
- /**
2704
- * Update configuration
2705
- */
2706
- setConfig(config) {
2707
- if (config.embeddingProvider) {
2708
- this.provider = config.embeddingProvider;
2709
- this.config.embeddingProvider = config.embeddingProvider;
2710
- this.clearCache();
2711
- }
2712
- if (config.similarityThreshold !== void 0) {
2713
- this.config.similarityThreshold = config.similarityThreshold;
2714
- }
2715
- if (config.maxResults !== void 0) {
2716
- this.config.maxResults = config.maxResults;
2717
- }
2718
- if (config.cacheEmbeddings !== void 0) {
2719
- this.config.cacheEmbeddings = config.cacheEmbeddings;
2720
- }
2721
- if (config.embeddingFields) {
2722
- this.config.embeddingFields = config.embeddingFields;
2723
- this.clearCache();
2724
- }
2725
- }
2726
- // ===========================================================================
2727
- // Private helpers
2728
- // ===========================================================================
2729
- /**
2730
- * Get or compute embedding for a skill
2731
- */
2732
- async getSkillEmbedding(skill) {
2733
- if (this.config.cacheEmbeddings) {
2734
- const cacheKey = this.getCacheKey(skill);
2735
- const cached = this.cache.get(cacheKey);
2736
- if (cached) {
2737
- return cached.embedding;
2738
- }
2739
- const embedding = await this.provider.embed(this.skillToText(skill));
2740
- this.cache.set(cacheKey, {
2741
- skillId: skill.id,
2742
- version: skill.version,
2743
- embedding,
2744
- updatedAt: /* @__PURE__ */ new Date()
2745
- });
2746
- return embedding;
2747
- }
2748
- return this.provider.embed(this.skillToText(skill));
2749
- }
2750
- /**
2751
- * Convert skill to text for embedding
2752
- */
2753
- skillToText(skill) {
2754
- const parts = [];
2755
- for (const field of this.config.embeddingFields) {
2756
- switch (field) {
2757
- case "name":
2758
- parts.push(skill.name);
2759
- break;
2760
- case "description":
2761
- parts.push(skill.description);
2762
- break;
2763
- case "problem":
2764
- parts.push(skill.problem);
2765
- break;
2766
- case "solution":
2767
- parts.push(skill.solution);
2768
- break;
2769
- case "triggerConditions":
2770
- parts.push(
2771
- skill.triggerConditions.map((t) => `${t.type}: ${t.value}`).join(" ")
2772
- );
2773
- break;
2774
- case "tags":
2775
- parts.push(skill.tags.join(" "));
2776
- break;
2777
- }
2778
- }
2779
- return parts.filter(Boolean).join(" ");
2780
- }
2781
- /**
2782
- * Get cache key for a skill
2783
- */
2784
- getCacheKey(skill) {
2785
- return `${skill.id}:${skill.version}`;
2786
- }
2787
- /**
2788
- * Identify which fields in the skill matched the query
2789
- */
2790
- identifyMatchedFields(query, skill) {
2791
- const queryTerms = query.toLowerCase().split(/\s+/);
2792
- const matchedFields = [];
2793
- const checkField = (fieldName, value) => {
2794
- const lowerValue = value.toLowerCase();
2795
- if (queryTerms.some((term) => lowerValue.includes(term))) {
2796
- matchedFields.push(fieldName);
2797
- }
2798
- };
2799
- checkField("name", skill.name);
2800
- checkField("description", skill.description);
2801
- checkField("problem", skill.problem);
2802
- checkField("solution", skill.solution);
2803
- checkField("tags", skill.tags.join(" "));
2804
- checkField(
2805
- "triggerConditions",
2806
- skill.triggerConditions.map((t) => t.value).join(" ")
2807
- );
2808
- return matchedFields;
2809
- }
2810
- /**
2811
- * Extract identifiers from code snippet
2812
- */
2813
- extractIdentifiers(code) {
2814
- const identifiers = code.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || [];
2815
- return [...new Set(identifiers)].filter((id) => id.length > 2);
2816
- }
2817
- };
2818
-
2819
- // src/skill-bank.ts
2820
- var SkillBank = class {
2821
- constructor(config = {}) {
2822
- this.eventHandlers = [];
2823
- this.initialized = false;
2824
- this.autoIndexOnInit = false;
2825
- if (config.storage?.type === "filesystem") {
2826
- this.storage = new FilesystemStorageAdapter({
2827
- basePath: config.storage.basePath,
2828
- openSkillsCompatible: config.storage.openSkillsCompatible ?? true
2829
- });
2830
- } else {
2831
- this.storage = new MemoryStorageAdapter();
2832
- }
2833
- this.adapterRegistry = new AdapterRegistry();
2834
- this.adapterRegistry.register(claudeCodeAdapter);
2835
- if (config.adapters) {
2836
- for (const adapter of config.adapters) {
2837
- this.adapterRegistry.register(adapter);
2838
- }
2839
- }
2840
- this.manualExtractor = new ManualExtractor();
2841
- this.automaticExtractor = new AutomaticExtractor({
2842
- llmProvider: config.llmProvider,
2843
- minConfidence: config.minExtractionConfidence
2844
- });
2845
- this.lineageTracker = new LineageTracker(this.storage);
2846
- this.semanticMatcher = new SemanticMatcher({
2847
- embeddingProvider: config.matching?.embeddingProvider,
2848
- similarityThreshold: config.matching?.similarityThreshold
2849
- });
2850
- this.autoIndexOnInit = config.matching?.autoIndex ?? false;
2851
- }
2852
- /**
2853
- * Initialize the skill bank (required before use)
2854
- */
2855
- async initialize() {
2856
- await this.storage.initialize();
2857
- this.initialized = true;
2858
- if (this.autoIndexOnInit) {
2859
- await this.indexSkills();
2860
- }
2861
- }
2862
- /**
2863
- * Ensure initialized before operations
2864
- */
2865
- ensureInitialized() {
2866
- if (!this.initialized) {
2867
- throw new Error("SkillBank not initialized. Call initialize() first.");
2868
- }
2869
- }
2870
- // ==========================================================================
2871
- // Session Parsing
2872
- // ==========================================================================
2873
- /**
2874
- * Parse a session from raw content
2875
- */
2876
- async parseSession(content, adapterName) {
2877
- let adapter;
2878
- if (adapterName) {
2879
- adapter = this.adapterRegistry.get(adapterName);
2880
- if (!adapter) {
2881
- throw new Error(`Adapter not found: ${adapterName}`);
2882
- }
2883
- } else {
2884
- adapter = this.adapterRegistry.findAdapter(content);
2885
- if (!adapter) {
2886
- throw new Error("No suitable adapter found for this content");
2887
- }
2888
- }
2889
- return adapter.parse(content);
2890
- }
2891
- /**
2892
- * Parse multiple sessions
2893
- */
2894
- async parseSessions(input, adapterName) {
2895
- let adapter;
2896
- if (adapterName) {
2897
- adapter = this.adapterRegistry.get(adapterName);
2898
- } else {
2899
- adapter = this.adapterRegistry.findAdapter(input);
2900
- }
2901
- if (!adapter) {
2902
- throw new Error("No suitable adapter found");
2903
- }
2904
- return adapter.parseMany(input);
2905
- }
2906
- /**
2907
- * Register a custom session adapter
2908
- */
2909
- registerAdapter(adapter) {
2910
- this.adapterRegistry.register(adapter);
2911
- }
2912
- // ==========================================================================
2913
- // Skill Extraction
2914
- // ==========================================================================
2915
- /**
2916
- * Extract a skill manually from a trajectory
2917
- */
2918
- async extractManual(trajectory, request) {
2919
- this.ensureInitialized();
2920
- this.emit({ type: "extraction:started", sessionId: trajectory.sessionId });
2921
- const results = await this.manualExtractor.extract(trajectory, {
2922
- turnRange: request?.turnRange,
2923
- suggestedName: request?.suggestedName,
2924
- skipValidation: request?.skipValidation
2925
- });
2926
- const result = results[0];
2927
- if (!result) {
2928
- const failedResult = {
2929
- success: false,
2930
- confidence: 0,
2931
- gateResults: [],
2932
- failureReason: "No extraction result",
2933
- sourceTrajectory: {
2934
- sessionId: trajectory.sessionId,
2935
- turnRange: request?.turnRange || [0, trajectory.turns.length - 1]
2936
- }
2937
- };
2938
- this.emit({ type: "extraction:failed", error: "No extraction result" });
2939
- return failedResult;
2940
- }
2941
- if (result.success && result.skill) {
2942
- await this.storage.saveSkill(result.skill);
2943
- this.emit({ type: "skill:created", skill: result.skill });
2944
- }
2945
- this.emit({ type: "extraction:completed", result });
2946
- return result;
2947
- }
2948
- /**
2949
- * Extract skills automatically from a trajectory
2950
- */
2951
- async extractAutomatic(trajectory, options) {
2952
- this.ensureInitialized();
2953
- this.emit({ type: "extraction:started", sessionId: trajectory.sessionId });
2954
- const results = await this.automaticExtractor.extract(trajectory, options);
2955
- for (const result of results) {
2956
- if (result.success && result.skill) {
2957
- await this.storage.saveSkill(result.skill);
2958
- this.emit({ type: "skill:created", skill: result.skill });
2959
- }
2960
- this.emit({ type: "extraction:completed", result });
2961
- }
2962
- return results;
2963
- }
2964
- /**
2965
- * Extract from raw session content (convenience method)
2966
- */
2967
- async extractFromSession(content, options) {
2968
- const trajectory = await this.parseSession(content, options?.adapterName);
2969
- if (options?.mode === "automatic") {
2970
- return this.extractAutomatic(trajectory, options);
2971
- } else {
2972
- const result = await this.extractManual(trajectory, options);
2973
- return [result];
2974
- }
2975
- }
2976
- /**
2977
- * Set LLM provider for automatic extraction
2978
- */
2979
- setLLMProvider(provider) {
2980
- this.automaticExtractor.setLLMProvider(provider);
2981
- }
2982
- // ==========================================================================
2983
- // Semantic Matching
2984
- // ==========================================================================
2985
- /**
2986
- * Find skills matching a query using semantic similarity
2987
- */
2988
- async findMatchingSkills(query, options) {
2989
- this.ensureInitialized();
2990
- const skills = await this.storage.listSkills({ status: ["active"] });
2991
- return this.semanticMatcher.findMatches(query, skills, options);
2992
- }
2993
- /**
2994
- * Find skills matching a context (problem, error, keywords)
2995
- */
2996
- async findSkillsByContext(context, options) {
2997
- this.ensureInitialized();
2998
- const skills = await this.storage.listSkills({ status: ["active"] });
2999
- return this.semanticMatcher.findByContext(context, skills, options);
3000
- }
3001
- /**
3002
- * Find skills similar to a given skill
3003
- */
3004
- async findSimilarSkills(skillId, options) {
3005
- this.ensureInitialized();
3006
- const skill = await this.storage.getSkill(skillId);
3007
- if (!skill) {
3008
- throw new Error(`Skill not found: ${skillId}`);
3009
- }
3010
- const allSkills = await this.storage.listSkills({ status: ["active"] });
3011
- return this.semanticMatcher.findSimilar(skill, allSkills, {
3012
- ...options,
3013
- excludeSelf: true
3014
- });
3015
- }
3016
- /**
3017
- * Find skills that might apply to a trajectory
3018
- */
3019
- async findSkillsForTrajectory(trajectory, options) {
3020
- this.ensureInitialized();
3021
- const skills = await this.storage.listSkills({ status: ["active"] });
3022
- return this.semanticMatcher.findForTrajectory(trajectory, skills, options);
3023
- }
3024
- /**
3025
- * Index all skills for faster semantic matching
3026
- */
3027
- async indexSkills() {
3028
- this.ensureInitialized();
3029
- return this.semanticMatcher.indexFromStorage(this.storage);
3030
- }
3031
- /**
3032
- * Set embedding provider for semantic matching
3033
- */
3034
- setEmbeddingProvider(provider) {
3035
- this.semanticMatcher.setConfig({ embeddingProvider: provider });
3036
- }
3037
- // ==========================================================================
3038
- // Skill CRUD Operations
3039
- // ==========================================================================
3040
- /**
3041
- * Get a skill by ID
3042
- */
3043
- async getSkill(id, version) {
3044
- this.ensureInitialized();
3045
- return this.storage.getSkill(id, version);
3046
- }
3047
- /**
3048
- * List all skills with optional filtering
3049
- */
3050
- async listSkills(filter) {
3051
- this.ensureInitialized();
3052
- return this.storage.listSkills(filter);
3053
- }
3054
- /**
3055
- * Search skills by query
3056
- */
3057
- async searchSkills(query) {
3058
- this.ensureInitialized();
3059
- return this.storage.searchSkills(query);
3060
- }
3061
- /**
3062
- * Save or update a skill
3063
- */
3064
- async saveSkill(skill) {
3065
- this.ensureInitialized();
3066
- const existing = await this.storage.getSkill(skill.id);
3067
- await this.storage.saveSkill(skill);
3068
- if (existing) {
3069
- this.emit({ type: "skill:updated", skill, previousVersion: existing.version });
3070
- } else {
3071
- this.emit({ type: "skill:created", skill });
3072
- }
3073
- }
3074
- /**
3075
- * Delete a skill
3076
- */
3077
- async deleteSkill(id, version) {
3078
- this.ensureInitialized();
3079
- const deleted = await this.storage.deleteSkill(id, version);
3080
- if (deleted) {
3081
- this.emit({ type: "skill:deleted", skillId: id });
3082
- }
3083
- return deleted;
3084
- }
3085
- /**
3086
- * Deprecate a skill
3087
- */
3088
- async deprecateSkill(id) {
3089
- this.ensureInitialized();
3090
- const skill = await this.storage.getSkill(id);
3091
- if (!skill) {
3092
- throw new Error(`Skill not found: ${id}`);
3093
- }
3094
- const deprecated = {
3095
- ...skill,
3096
- status: "deprecated",
3097
- updatedAt: /* @__PURE__ */ new Date()
3098
- };
3099
- await this.storage.saveSkill(deprecated);
3100
- this.emit({ type: "skill:deprecated", skillId: id });
3101
- return deprecated;
3102
- }
3103
- // ==========================================================================
3104
- // Version Management
3105
- // ==========================================================================
3106
- /**
3107
- * Create a new version of a skill
3108
- */
3109
- async createVersion(skillId, updates, options) {
3110
- this.ensureInitialized();
3111
- const newSkill = await this.lineageTracker.createVersion(skillId, updates, options);
3112
- this.emit({ type: "skill:updated", skill: newSkill, previousVersion: newSkill.parentVersion || "" });
3113
- return newSkill;
3114
- }
3115
- /**
3116
- * Fork a skill to create a new variant
3117
- */
3118
- async forkSkill(skillId, options) {
3119
- this.ensureInitialized();
3120
- const forked = await this.lineageTracker.forkSkill(skillId, options);
3121
- this.emit({ type: "skill:created", skill: forked });
3122
- return forked;
3123
- }
3124
- /**
3125
- * Get version history for a skill
3126
- */
3127
- async getVersionHistory(skillId) {
3128
- this.ensureInitialized();
3129
- return this.storage.getVersionHistory(skillId);
3130
- }
3131
- /**
3132
- * Get full lineage for a skill
3133
- */
3134
- async getLineage(skillId) {
3135
- this.ensureInitialized();
3136
- return this.storage.getLineage(skillId);
3137
- }
3138
- /**
3139
- * Rollback a skill to a previous version
3140
- */
3141
- async rollbackSkill(skillId, toVersion) {
3142
- this.ensureInitialized();
3143
- return this.lineageTracker.rollback(skillId, toVersion);
3144
- }
3145
- /**
3146
- * Compare two versions of a skill
3147
- */
3148
- async compareVersions(skillId, versionA, versionB) {
3149
- this.ensureInitialized();
3150
- return this.lineageTracker.compareVersions(skillId, versionA, versionB);
3151
- }
3152
- // ==========================================================================
3153
- // Events
3154
- // ==========================================================================
3155
- /**
3156
- * Subscribe to skill bank events
3157
- */
3158
- on(handler) {
3159
- this.eventHandlers.push(handler);
3160
- return () => {
3161
- const index = this.eventHandlers.indexOf(handler);
3162
- if (index >= 0) {
3163
- this.eventHandlers.splice(index, 1);
3164
- }
3165
- };
3166
- }
3167
- /**
3168
- * Emit an event to all handlers
3169
- */
3170
- emit(event) {
3171
- for (const handler of this.eventHandlers) {
3172
- try {
3173
- handler(event);
3174
- } catch (error) {
3175
- console.error("Event handler error:", error);
3176
- }
3177
- }
3178
- }
3179
- // ==========================================================================
3180
- // Utilities
3181
- // ==========================================================================
3182
- /**
3183
- * Get statistics about the skill bank
3184
- */
3185
- async getStats() {
3186
- this.ensureInitialized();
3187
- const skills = await this.storage.listSkills();
3188
- const stats = {
3189
- totalSkills: skills.length,
3190
- byStatus: {
3191
- draft: 0,
3192
- active: 0,
3193
- deprecated: 0,
3194
- experimental: 0
3195
- },
3196
- byTag: {},
3197
- avgSuccessRate: 0,
3198
- totalUsage: 0
3199
- };
3200
- let successRateSum = 0;
3201
- let successRateCount = 0;
3202
- for (const skill of skills) {
3203
- stats.byStatus[skill.status]++;
3204
- for (const tag of skill.tags) {
3205
- stats.byTag[tag] = (stats.byTag[tag] || 0) + 1;
3206
- }
3207
- stats.totalUsage += skill.metrics.usageCount;
3208
- if (skill.metrics.successRate > 0) {
3209
- successRateSum += skill.metrics.successRate;
3210
- successRateCount++;
3211
- }
3212
- }
3213
- stats.avgSuccessRate = successRateCount > 0 ? successRateSum / successRateCount : 0;
3214
- return stats;
3215
- }
3216
- /**
3217
- * Export all skills (for backup or migration)
3218
- */
3219
- async exportAll() {
3220
- this.ensureInitialized();
3221
- return this.storage.listSkills();
3222
- }
3223
- /**
3224
- * Import skills (for restore or migration)
3225
- */
3226
- async importSkills(skills) {
3227
- this.ensureInitialized();
3228
- let imported = 0;
3229
- let failed = 0;
3230
- for (const skill of skills) {
3231
- try {
3232
- await this.storage.saveSkill(skill);
3233
- this.emit({ type: "skill:created", skill });
3234
- imported++;
3235
- } catch {
3236
- failed++;
3237
- }
3238
- }
3239
- return { imported, failed };
3240
- }
3241
- };
3242
- function createSkillBank(config) {
3243
- return new SkillBank(config);
3244
- }
3245
-
3246
- // src/batch/processor.ts
3247
- var BatchProcessor = class {
3248
- constructor(config = {}) {
3249
- this.config = {
3250
- concurrency: config.concurrency ?? 5,
3251
- minConfidence: config.minConfidence ?? 0.6,
3252
- deduplication: config.deduplication ?? "semantic",
3253
- deduplicationThreshold: config.deduplicationThreshold ?? 0.85,
3254
- skipQualityGates: config.skipQualityGates ?? false,
3255
- extractionMode: config.extractionMode ?? "manual",
3256
- llmProvider: config.llmProvider,
3257
- embeddingProvider: config.embeddingProvider,
3258
- onProgress: config.onProgress
3259
- };
3260
- this.manualExtractor = new ManualExtractor();
3261
- this.automaticExtractor = new AutomaticExtractor({
3262
- minConfidence: this.config.minConfidence
3263
- });
3264
- if (this.config.llmProvider) {
3265
- this.automaticExtractor.setLLMProvider(this.config.llmProvider);
3266
- }
3267
- this.matcher = new SemanticMatcher({
3268
- embeddingProvider: this.config.embeddingProvider || new SimpleHashEmbedding(),
3269
- similarityThreshold: this.config.deduplicationThreshold
3270
- });
3271
- }
3272
- /**
3273
- * Process multiple sessions and extract skills
3274
- */
3275
- async process(sessions, parseSession) {
3276
- const startTime = Date.now();
3277
- const allExtractions = [];
3278
- const failedSessions = [];
3279
- const progress = {
3280
- total: sessions.length,
3281
- processed: 0,
3282
- skillsExtracted: 0,
3283
- duplicatesFound: 0,
3284
- phase: "parsing",
3285
- percentage: 0
3286
- };
3287
- this.reportProgress(progress);
3288
- const batches = this.createBatches(sessions, this.config.concurrency);
3289
- for (const batch of batches) {
3290
- progress.phase = "extracting";
3291
- const batchPromises = batch.map(async (session) => {
3292
- progress.currentSession = session.source;
3293
- this.reportProgress(progress);
3294
- try {
3295
- const trajectory = session.trajectory || await parseSession(session.content);
3296
- let results = [];
3297
- if (this.config.extractionMode === "automatic" && this.config.llmProvider) {
3298
- results = await this.automaticExtractor.extract(trajectory);
3299
- } else if (this.config.extractionMode === "manual") {
3300
- results = await this.manualExtractor.extract(trajectory, {
3301
- suggestedName: this.generateSkillName(trajectory, session.source),
3302
- skipValidation: this.config.skipQualityGates
3303
- });
3304
- } else if (this.config.extractionMode === "candidates-only") {
3305
- results = [this.createCandidateExtraction(trajectory, session.source)];
3306
- }
3307
- const successful = results.filter((r) => r.success && r.skill);
3308
- allExtractions.push(...successful);
3309
- progress.skillsExtracted += successful.length;
3310
- } catch (error) {
3311
- failedSessions.push({
3312
- source: session.source,
3313
- error: error instanceof Error ? error.message : String(error)
3314
- });
3315
- }
3316
- progress.processed++;
3317
- progress.percentage = Math.round(progress.processed / progress.total * 100);
3318
- this.reportProgress(progress);
3319
- });
3320
- await Promise.all(batchPromises);
3321
- }
3322
- progress.phase = "deduplicating";
3323
- this.reportProgress(progress);
3324
- const { unique, duplicates } = await this.deduplicate(allExtractions);
3325
- progress.duplicatesFound = duplicates.length;
3326
- progress.phase = "complete";
3327
- progress.percentage = 100;
3328
- this.reportProgress(progress);
3329
- const avgConfidence = unique.length > 0 ? unique.reduce((sum, e) => sum + e.confidence, 0) / unique.length : 0;
3330
- return {
3331
- skills: unique,
3332
- totalSessions: sessions.length,
3333
- failedSessions,
3334
- duplicates,
3335
- stats: {
3336
- totalExtractions: allExtractions.length,
3337
- uniqueSkills: unique.length,
3338
- duplicatesRemoved: duplicates.length,
3339
- averageConfidence: avgConfidence,
3340
- processingTimeMs: Date.now() - startTime
3341
- }
3342
- };
3343
- }
3344
- /**
3345
- * Process session files from file paths
3346
- */
3347
- async processFiles(filePaths, readFile2, parseSession) {
3348
- const sessions = [];
3349
- for (const path2 of filePaths) {
3350
- const content = await readFile2(path2);
3351
- sessions.push({
3352
- content,
3353
- source: path2
3354
- });
3355
- }
3356
- return this.process(sessions, parseSession);
3357
- }
3358
- /**
3359
- * Set LLM provider for automatic extraction
3360
- */
3361
- setLLMProvider(provider) {
3362
- this.config.llmProvider = provider;
3363
- this.automaticExtractor.setLLMProvider(provider);
3364
- }
3365
- /**
3366
- * Set embedding provider for semantic deduplication
3367
- */
3368
- setEmbeddingProvider(provider) {
3369
- this.config.embeddingProvider = provider;
3370
- this.matcher = new SemanticMatcher({
3371
- embeddingProvider: provider,
3372
- similarityThreshold: this.config.deduplicationThreshold
3373
- });
3374
- }
3375
- /**
3376
- * Update configuration
3377
- */
3378
- setConfig(config) {
3379
- if (config.concurrency !== void 0) {
3380
- this.config.concurrency = config.concurrency;
3381
- }
3382
- if (config.minConfidence !== void 0) {
3383
- this.config.minConfidence = config.minConfidence;
3384
- this.automaticExtractor = new AutomaticExtractor({
3385
- minConfidence: config.minConfidence
3386
- });
3387
- if (this.config.llmProvider) {
3388
- this.automaticExtractor.setLLMProvider(this.config.llmProvider);
3389
- }
3390
- }
3391
- if (config.deduplication !== void 0) {
3392
- this.config.deduplication = config.deduplication;
3393
- }
3394
- if (config.deduplicationThreshold !== void 0) {
3395
- this.config.deduplicationThreshold = config.deduplicationThreshold;
3396
- this.matcher.setConfig({ similarityThreshold: config.deduplicationThreshold });
3397
- }
3398
- if (config.skipQualityGates !== void 0) {
3399
- this.config.skipQualityGates = config.skipQualityGates;
3400
- }
3401
- if (config.extractionMode !== void 0) {
3402
- this.config.extractionMode = config.extractionMode;
3403
- }
3404
- if (config.onProgress !== void 0) {
3405
- this.config.onProgress = config.onProgress;
3406
- }
3407
- if (config.llmProvider !== void 0) {
3408
- this.setLLMProvider(config.llmProvider);
3409
- }
3410
- if (config.embeddingProvider !== void 0) {
3411
- this.setEmbeddingProvider(config.embeddingProvider);
3412
- }
3413
- }
3414
- // ===========================================================================
3415
- // Private helpers
3416
- // ===========================================================================
3417
- /**
3418
- * Create batches for parallel processing
3419
- */
3420
- createBatches(items, batchSize) {
3421
- const batches = [];
3422
- for (let i = 0; i < items.length; i += batchSize) {
3423
- batches.push(items.slice(i, i + batchSize));
3424
- }
3425
- return batches;
3426
- }
3427
- /**
3428
- * Deduplicate extracted skills
3429
- */
3430
- async deduplicate(extractions) {
3431
- if (this.config.deduplication === "none" || extractions.length === 0) {
3432
- return { unique: extractions, duplicates: [] };
3433
- }
3434
- const unique = [];
3435
- const duplicates = [];
3436
- for (const extraction of extractions) {
3437
- if (unique.length === 0) {
3438
- unique.push(extraction);
3439
- continue;
3440
- }
3441
- const isDuplicate = await this.checkDuplicate(extraction, unique);
3442
- if (isDuplicate) {
3443
- duplicates.push(isDuplicate);
3444
- } else {
3445
- unique.push(extraction);
3446
- }
3447
- }
3448
- return { unique, duplicates };
3449
- }
3450
- /**
3451
- * Check if an extraction is a duplicate of existing ones
3452
- */
3453
- async checkDuplicate(extraction, existing) {
3454
- const extractionSkill = extraction.skill;
3455
- if (!extractionSkill) return null;
3456
- if (this.config.deduplication === "exact") {
3457
- for (const e of existing) {
3458
- const existingSkill = e.skill;
3459
- if (!existingSkill) continue;
3460
- if (existingSkill.name === extractionSkill.name || existingSkill.problem === extractionSkill.problem) {
3461
- return {
3462
- skill: extraction,
3463
- duplicateOf: existingSkill.id
3464
- };
3465
- }
3466
- }
3467
- } else if (this.config.deduplication === "semantic") {
3468
- const existingSkills = existing.filter((e) => e.skill !== void 0).map((e) => e.skill);
3469
- const matches = await this.matcher.findMatches(
3470
- `${extractionSkill.name} ${extractionSkill.problem} ${extractionSkill.description}`,
3471
- existingSkills,
3472
- { threshold: this.config.deduplicationThreshold, maxResults: 1 }
3473
- );
3474
- if (matches.length > 0) {
3475
- return {
3476
- skill: extraction,
3477
- duplicateOf: matches[0].skill.id,
3478
- similarity: matches[0].similarity
3479
- };
3480
- }
3481
- }
3482
- return null;
3483
- }
3484
- /**
3485
- * Generate skill name from trajectory
3486
- */
3487
- generateSkillName(trajectory, source) {
3488
- const firstUserTurn = trajectory.turns.find((t) => t.role === "user");
3489
- if (firstUserTurn?.content) {
3490
- const firstLine = firstUserTurn.content.split(/[.\n]/)[0].trim();
3491
- if (firstLine.length > 0 && firstLine.length < 60) {
3492
- return this.slugify(firstLine);
3493
- }
3494
- }
3495
- if (source) {
3496
- const baseName = source.split("/").pop()?.replace(/\.[^.]+$/, "");
3497
- if (baseName) {
3498
- return `skill-from-${this.slugify(baseName)}`;
3499
- }
3500
- }
3501
- return `skill-${Date.now().toString(36)}`;
3502
- }
3503
- /**
3504
- * Convert string to slug
3505
- */
3506
- slugify(text) {
3507
- return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
3508
- }
3509
- /**
3510
- * Create a candidate extraction without full processing
3511
- */
3512
- createCandidateExtraction(trajectory, source) {
3513
- const name = this.generateSkillName(trajectory, source);
3514
- const firstUserTurn = trajectory.turns.find((t) => t.role === "user");
3515
- return {
3516
- success: true,
3517
- skill: {
3518
- id: name,
3519
- name: name.replace(/-/g, " "),
3520
- description: "Candidate skill - needs review",
3521
- problem: firstUserTurn?.content?.substring(0, 500) || "Unknown problem",
3522
- solution: "Solution pending extraction",
3523
- verification: "Verification pending",
3524
- triggerConditions: [],
3525
- tags: [],
3526
- version: "0.1.0",
3527
- status: "draft",
3528
- examples: [],
3529
- author: "batch-processor",
3530
- metrics: {
3531
- usageCount: 0,
3532
- successRate: 0,
3533
- feedbackScores: []
3534
- },
3535
- source: {
3536
- type: "extracted",
3537
- sessionId: trajectory.sessionId,
3538
- importedAt: /* @__PURE__ */ new Date()
3539
- },
3540
- createdAt: /* @__PURE__ */ new Date(),
3541
- updatedAt: /* @__PURE__ */ new Date()
3542
- },
3543
- confidence: 0.3,
3544
- gateResults: [],
3545
- sourceTrajectory: {
3546
- sessionId: trajectory.sessionId,
3547
- turnRange: [0, trajectory.turns.length - 1]
3548
- }
3549
- };
3550
- }
3551
- /**
3552
- * Report progress to callback
3553
- */
3554
- reportProgress(progress) {
3555
- if (this.config.onProgress) {
3556
- this.config.onProgress({ ...progress });
3557
- }
3558
- }
3559
- };
3560
-
3561
- // src/composer/dependency-graph.ts
3562
- var DependencyGraph = class {
3563
- constructor() {
3564
- this.nodes = /* @__PURE__ */ new Map();
3565
- this.edges = [];
3566
- this.adjacencyList = /* @__PURE__ */ new Map();
3567
- this.reverseAdjacencyList = /* @__PURE__ */ new Map();
3568
- }
3569
- /**
3570
- * Add a skill to the graph
3571
- */
3572
- addSkill(skill) {
3573
- const key = this.getKey(skill.id, skill.version);
3574
- this.nodes.set(key, {
3575
- skillId: skill.id,
3576
- version: skill.version,
3577
- skill
3578
- });
3579
- if (!this.adjacencyList.has(key)) {
3580
- this.adjacencyList.set(key, /* @__PURE__ */ new Set());
3581
- }
3582
- if (!this.reverseAdjacencyList.has(key)) {
3583
- this.reverseAdjacencyList.set(key, /* @__PURE__ */ new Set());
3584
- }
3585
- }
3586
- /**
3587
- * Remove a skill from the graph
3588
- */
3589
- removeSkill(skillId, version) {
3590
- const keysToRemove = version ? [this.getKey(skillId, version)] : Array.from(this.nodes.keys()).filter((k) => k.startsWith(`${skillId}@`));
3591
- for (const key of keysToRemove) {
3592
- this.nodes.delete(key);
3593
- this.adjacencyList.delete(key);
3594
- this.reverseAdjacencyList.delete(key);
3595
- this.edges = this.edges.filter(
3596
- (e) => this.getKey(e.from) !== key && this.getKey(e.to) !== key
3597
- );
3598
- for (const adj of this.adjacencyList.values()) {
3599
- adj.delete(key);
3600
- }
3601
- for (const adj of this.reverseAdjacencyList.values()) {
3602
- adj.delete(key);
3603
- }
3604
- }
3605
- }
3606
- /**
3607
- * Add a dependency edge
3608
- */
3609
- addDependency(fromId, toId, type, options) {
3610
- const edge = {
3611
- from: fromId,
3612
- to: toId,
3613
- type,
3614
- required: options?.required ?? true,
3615
- description: options?.description
3616
- };
3617
- this.edges.push(edge);
3618
- const fromKey = options?.fromVersion ? this.getKey(fromId, options.fromVersion) : fromId;
3619
- const toKey = options?.toVersion ? this.getKey(toId, options.toVersion) : toId;
3620
- if (!this.adjacencyList.has(fromKey)) {
3621
- this.adjacencyList.set(fromKey, /* @__PURE__ */ new Set());
3622
- }
3623
- this.adjacencyList.get(fromKey).add(toKey);
3624
- if (!this.reverseAdjacencyList.has(toKey)) {
3625
- this.reverseAdjacencyList.set(toKey, /* @__PURE__ */ new Set());
3626
- }
3627
- this.reverseAdjacencyList.get(toKey).add(fromKey);
3628
- }
3629
- /**
3630
- * Remove a dependency edge
3631
- */
3632
- removeDependency(fromId, toId, type) {
3633
- this.edges = this.edges.filter(
3634
- (e) => !(e.from === fromId && e.to === toId && (type === void 0 || e.type === type))
3635
- );
3636
- const remainingEdges = this.edges.filter(
3637
- (e) => e.from === fromId && e.to === toId
3638
- );
3639
- if (remainingEdges.length === 0) {
3640
- this.adjacencyList.get(fromId)?.delete(toId);
3641
- this.reverseAdjacencyList.get(toId)?.delete(fromId);
3642
- }
3643
- }
3644
- /**
3645
- * Get all dependencies of a skill
3646
- */
3647
- getDependencies(skillId) {
3648
- return this.edges.filter((e) => e.from === skillId);
3649
- }
3650
- /**
3651
- * Get all skills that depend on this skill
3652
- */
3653
- getDependents(skillId) {
3654
- return this.edges.filter((e) => e.to === skillId);
3655
- }
3656
- /**
3657
- * Get conflicts for a skill
3658
- */
3659
- getConflicts(skillId) {
3660
- return this.edges.filter(
3661
- (e) => e.type === "conflicts" && (e.from === skillId || e.to === skillId)
3662
- );
3663
- }
3664
- /**
3665
- * Check if two skills conflict
3666
- */
3667
- hasConflict(skillId1, skillId2) {
3668
- return this.edges.some(
3669
- (e) => e.type === "conflicts" && (e.from === skillId1 && e.to === skillId2 || e.from === skillId2 && e.to === skillId1)
3670
- );
3671
- }
3672
- /**
3673
- * Get topological sort of skills (for execution order)
3674
- */
3675
- getTopologicalOrder(skillIds) {
3676
- const targetIds = skillIds || Array.from(new Set(this.edges.flatMap((e) => [e.from, e.to])));
3677
- const visited = /* @__PURE__ */ new Set();
3678
- const result = [];
3679
- const visit = (id) => {
3680
- if (visited.has(id)) return;
3681
- visited.add(id);
3682
- const deps = this.edges.filter(
3683
- (e) => e.from === id && (e.type === "requires" || e.type === "precedes" || e.type === "extends")
3684
- );
3685
- for (const dep of deps) {
3686
- visit(dep.to);
3687
- }
3688
- result.push(id);
3689
- };
3690
- for (const id of targetIds) {
3691
- visit(id);
3692
- }
3693
- return result;
3694
- }
3695
- /**
3696
- * Detect cycles in the graph
3697
- */
3698
- detectCycle() {
3699
- const visited = /* @__PURE__ */ new Set();
3700
- const recStack = /* @__PURE__ */ new Set();
3701
- const path2 = [];
3702
- const dfs = (node) => {
3703
- visited.add(node);
3704
- recStack.add(node);
3705
- path2.push(node);
3706
- const neighbors = this.adjacencyList.get(node) || /* @__PURE__ */ new Set();
3707
- for (const neighbor of neighbors) {
3708
- if (!visited.has(neighbor)) {
3709
- const cycle = dfs(neighbor);
3710
- if (cycle) return cycle;
3711
- } else if (recStack.has(neighbor)) {
3712
- const cycleStart = path2.indexOf(neighbor);
3713
- return path2.slice(cycleStart).concat(neighbor);
3714
- }
3715
- }
3716
- path2.pop();
3717
- recStack.delete(node);
3718
- return null;
3719
- };
3720
- const allNodes = /* @__PURE__ */ new Set([
3721
- ...this.adjacencyList.keys(),
3722
- ...this.reverseAdjacencyList.keys()
3723
- ]);
3724
- for (const node of allNodes) {
3725
- if (!visited.has(node)) {
3726
- const cycle = dfs(node);
3727
- if (cycle) {
3728
- return { hasCycle: true, cycle };
3729
- }
3730
- }
3731
- }
3732
- return { hasCycle: false };
3733
- }
3734
- /**
3735
- * Get all required dependencies (transitive)
3736
- */
3737
- getTransitiveDependencies(skillId) {
3738
- const result = /* @__PURE__ */ new Set();
3739
- const queue = [skillId];
3740
- while (queue.length > 0) {
3741
- const current = queue.shift();
3742
- const deps = this.edges.filter(
3743
- (e) => e.from === current && e.type === "requires" && e.required
3744
- );
3745
- for (const dep of deps) {
3746
- if (!result.has(dep.to)) {
3747
- result.add(dep.to);
3748
- queue.push(dep.to);
3749
- }
3750
- }
3751
- }
3752
- return Array.from(result);
3753
- }
3754
- /**
3755
- * Check if all required dependencies are satisfied
3756
- */
3757
- checkDependenciesSatisfied(skillIds) {
3758
- const available = new Set(skillIds);
3759
- const missing = [];
3760
- for (const id of skillIds) {
3761
- const requiredDeps = this.edges.filter(
3762
- (e) => e.from === id && e.type === "requires" && e.required
3763
- );
3764
- for (const dep of requiredDeps) {
3765
- if (!available.has(dep.to)) {
3766
- missing.push(dep.to);
3767
- }
3768
- }
3769
- }
3770
- return {
3771
- satisfied: missing.length === 0,
3772
- missing: [...new Set(missing)]
3773
- };
3774
- }
3775
- /**
3776
- * Get all skills in the graph
3777
- */
3778
- getSkills() {
3779
- return Array.from(this.nodes.values()).map((n) => n.skill);
3780
- }
3781
- /**
3782
- * Get all edges in the graph
3783
- */
3784
- getEdges() {
3785
- return [...this.edges];
3786
- }
3787
- /**
3788
- * Get graph statistics
3789
- */
3790
- getStats() {
3791
- const edgesByType = {
3792
- requires: 0,
3793
- extends: 0,
3794
- conflicts: 0,
3795
- enhances: 0,
3796
- precedes: 0,
3797
- follows: 0
3798
- };
3799
- for (const edge of this.edges) {
3800
- edgesByType[edge.type]++;
3801
- }
3802
- return {
3803
- nodeCount: this.nodes.size,
3804
- edgeCount: this.edges.length,
3805
- edgesByType
3806
- };
3807
- }
3808
- /**
3809
- * Clear the graph
3810
- */
3811
- clear() {
3812
- this.nodes.clear();
3813
- this.edges = [];
3814
- this.adjacencyList.clear();
3815
- this.reverseAdjacencyList.clear();
3816
- }
3817
- /**
3818
- * Export graph to JSON
3819
- */
3820
- toJSON() {
3821
- return {
3822
- nodes: Array.from(this.nodes.values()).map((n) => ({
3823
- skillId: n.skillId,
3824
- version: n.version
3825
- })),
3826
- edges: this.edges
3827
- };
3828
- }
3829
- /**
3830
- * Import graph from JSON (requires skills to be added separately)
3831
- */
3832
- fromJSON(data) {
3833
- for (const edge of data.edges) {
3834
- this.addDependency(edge.from, edge.to, edge.type, {
3835
- required: edge.required,
3836
- description: edge.description
3837
- });
3838
- }
3839
- }
3840
- // ===========================================================================
3841
- // Private helpers
3842
- // ===========================================================================
3843
- getKey(skillId, version) {
3844
- return version ? `${skillId}@${version}` : skillId;
3845
- }
3846
- };
3847
-
3848
- // src/composer/composer.ts
3849
- var SkillComposer = class {
3850
- constructor(config = {}) {
3851
- this.skillCache = /* @__PURE__ */ new Map();
3852
- this.config = {
3853
- storage: config.storage,
3854
- embeddingProvider: config.embeddingProvider || new SimpleHashEmbedding(),
3855
- suggestionThreshold: config.suggestionThreshold ?? 0.6,
3856
- maxDependencyDepth: config.maxDependencyDepth ?? 10
3857
- };
3858
- this.graph = new DependencyGraph();
3859
- this.matcher = new SemanticMatcher({
3860
- embeddingProvider: this.config.embeddingProvider,
3861
- similarityThreshold: this.config.suggestionThreshold
3862
- });
3863
- }
3864
- /**
3865
- * Compose multiple skills into a compound skill
3866
- */
3867
- async compose(skillIds, options) {
3868
- const conflicts = [];
3869
- const warnings = [];
3870
- const missingDependencies = [];
3871
- const skills = [];
3872
- for (const id of skillIds) {
3873
- const skill = await this.getSkill(id);
3874
- if (!skill) {
3875
- missingDependencies.push(id);
3876
- continue;
3877
- }
3878
- skills.push(skill);
3879
- }
3880
- if (missingDependencies.length > 0) {
3881
- return {
3882
- success: false,
3883
- conflicts,
3884
- warnings,
3885
- missingDependencies
3886
- };
3887
- }
3888
- const detectedConflicts = this.detectConflicts(skills);
3889
- conflicts.push(...detectedConflicts);
3890
- const blockingConflicts = conflicts.filter((c) => c.severity === "error");
3891
- if (blockingConflicts.length > 0) {
3892
- return {
3893
- success: false,
3894
- conflicts,
3895
- warnings,
3896
- missingDependencies
3897
- };
3898
- }
3899
- for (const conflict of conflicts.filter((c) => c.severity === "warning")) {
3900
- warnings.push(conflict.description);
3901
- }
3902
- const depCheck = this.graph.checkDependenciesSatisfied(skillIds);
3903
- if (!depCheck.satisfied) {
3904
- warnings.push(
3905
- `Missing optional dependencies: ${depCheck.missing.join(", ")}`
3906
- );
3907
- }
3908
- const orderedIds = options.mode === "sequential" ? this.getExecutionOrder(skillIds, options.executionOrder) : skillIds;
3909
- const components = orderedIds.map((id, index) => ({
3910
- skillId: id,
3911
- priority: index
3912
- }));
3913
- const compoundSkill = {
3914
- id: this.generateId(options.name),
3915
- name: options.name,
3916
- description: options.description || this.generateDescription(skills),
3917
- problem: this.mergeProblemStatements(skills),
3918
- triggerConditions: this.mergeTriggerConditions(skills),
3919
- solution: this.generateSolutionSteps(skills, options.mode || "sequential"),
3920
- verification: this.mergeVerifications(skills),
3921
- examples: [],
3922
- notes: `Composed from: ${skillIds.join(", ")}`,
3923
- author: "skill-composer",
3924
- tags: this.mergeTags(skills),
3925
- version: "1.0.0",
3926
- status: "draft",
3927
- metrics: {
3928
- usageCount: 0,
3929
- successRate: 0,
3930
- feedbackScores: []
3931
- },
3932
- source: {
3933
- type: "composed",
3934
- importedAt: /* @__PURE__ */ new Date()
3935
- },
3936
- createdAt: /* @__PURE__ */ new Date(),
3937
- updatedAt: /* @__PURE__ */ new Date(),
3938
- derivedFrom: skillIds,
3939
- isCompound: true,
3940
- composition: {
3941
- mode: options.mode || "sequential",
3942
- executionOrder: options.executionOrder,
3943
- components,
3944
- stopOnFailure: options.stopOnFailure ?? true
3945
- }
3946
- };
3947
- return {
3948
- success: true,
3949
- skill: compoundSkill,
3950
- conflicts,
3951
- warnings,
3952
- missingDependencies
3953
- };
3954
- }
3955
- /**
3956
- * Decompose a compound skill into its components
3957
- */
3958
- async decompose(skill) {
3959
- const skills = [];
3960
- for (const component of skill.composition.components) {
3961
- const componentSkill = await this.getSkill(
3962
- component.skillId,
3963
- component.version
3964
- );
3965
- if (componentSkill) {
3966
- skills.push(componentSkill);
3967
- }
3968
- }
3969
- return skills;
3970
- }
3971
- /**
3972
- * Add a dependency relationship between skills
3973
- */
3974
- addDependency(fromId, toId, type, options) {
3975
- this.graph.addDependency(fromId, toId, type, options);
3976
- }
3977
- /**
3978
- * Remove a dependency relationship
3979
- */
3980
- removeDependency(fromId, toId, type) {
3981
- this.graph.removeDependency(fromId, toId, type);
3982
- }
3983
- /**
3984
- * Get dependencies for a skill
3985
- */
3986
- getDependencies(skillId) {
3987
- return this.graph.getDependencies(skillId);
3988
- }
3989
- /**
3990
- * Get skills that depend on this skill
3991
- */
3992
- getDependents(skillId) {
3993
- return this.graph.getDependents(skillId);
3994
- }
3995
- /**
3996
- * Detect conflicts between skills
3997
- */
3998
- detectConflicts(skills) {
3999
- const conflicts = [];
4000
- for (let i = 0; i < skills.length; i++) {
4001
- for (let j = i + 1; j < skills.length; j++) {
4002
- const skill1 = skills[i];
4003
- const skill2 = skills[j];
4004
- if (this.graph.hasConflict(skill1.id, skill2.id)) {
4005
- conflicts.push({
4006
- type: "direct",
4007
- skillId1: skill1.id,
4008
- skillId2: skill2.id,
4009
- description: `Skills "${skill1.name}" and "${skill2.name}" have declared conflicts`,
4010
- severity: "error"
4011
- });
4012
- }
4013
- const triggerOverlap = this.checkTriggerOverlap(skill1, skill2);
4014
- if (triggerOverlap) {
4015
- conflicts.push({
4016
- type: "trigger-overlap",
4017
- skillId1: skill1.id,
4018
- skillId2: skill2.id,
4019
- description: `Skills have overlapping trigger conditions: ${triggerOverlap}`,
4020
- severity: "warning"
4021
- });
4022
- }
4023
- }
4024
- }
4025
- return conflicts;
4026
- }
4027
- /**
4028
- * Validate a composition without creating it
4029
- */
4030
- async validateComposition(skillIds) {
4031
- const skills = [];
4032
- const missingDependencies = [];
4033
- const warnings = [];
4034
- for (const id of skillIds) {
4035
- const skill = await this.getSkill(id);
4036
- if (!skill) {
4037
- missingDependencies.push(id);
4038
- } else {
4039
- skills.push(skill);
4040
- }
4041
- }
4042
- const conflicts = this.detectConflicts(skills);
4043
- const blockingConflicts = conflicts.filter((c) => c.severity === "error");
4044
- const cycleResult = this.graph.detectCycle();
4045
- if (cycleResult.hasCycle) {
4046
- warnings.push(
4047
- `Circular dependency detected: ${cycleResult.cycle?.join(" -> ")}`
4048
- );
4049
- }
4050
- return {
4051
- valid: blockingConflicts.length === 0 && missingDependencies.length === 0,
4052
- conflicts,
4053
- missingDependencies,
4054
- warnings
4055
- };
4056
- }
4057
- /**
4058
- * Suggest skill compositions based on usage patterns and similarity
4059
- */
4060
- async suggestCompositions(skills, options) {
4061
- const suggestions = [];
4062
- const maxSuggestions = options?.maxSuggestions ?? 5;
4063
- const minConfidence = options?.minConfidence ?? this.config.suggestionThreshold;
4064
- for (let i = 0; i < skills.length; i++) {
4065
- const similar = await this.matcher.findSimilar(skills[i], skills, {
4066
- threshold: minConfidence,
4067
- maxResults: 3
4068
- });
4069
- for (const match of similar) {
4070
- if (match.skill.id === skills[i].id) continue;
4071
- const existing = suggestions.find(
4072
- (s) => s.skillIds.includes(skills[i].id) && s.skillIds.includes(match.skill.id)
4073
- );
4074
- if (existing) continue;
4075
- const mode = this.suggestCompositionMode(skills[i], match.skill);
4076
- suggestions.push({
4077
- skillIds: [skills[i].id, match.skill.id],
4078
- mode,
4079
- confidence: match.similarity,
4080
- reason: `Similar ${this.getSimilarityReason(skills[i], match.skill)}`,
4081
- benefit: this.estimateBenefit(skills[i], match.skill, mode)
4082
- });
4083
- }
4084
- }
4085
- const dependencyPairs = this.findCommonDependencyPairs(skills);
4086
- for (const pair of dependencyPairs) {
4087
- const existing = suggestions.find(
4088
- (s) => s.skillIds.includes(pair[0]) && s.skillIds.includes(pair[1])
4089
- );
4090
- if (!existing) {
4091
- suggestions.push({
4092
- skillIds: pair,
4093
- mode: "sequential",
4094
- confidence: 0.7,
4095
- reason: "Frequently used together based on dependency patterns",
4096
- benefit: "Streamlined workflow"
4097
- });
4098
- }
4099
- }
4100
- return suggestions.sort((a, b) => b.confidence - a.confidence).slice(0, maxSuggestions);
4101
- }
4102
- /**
4103
- * Index skills from storage for composition suggestions
4104
- */
4105
- async indexFromStorage(storage) {
4106
- const skills = await storage.listSkills();
4107
- for (const skill of skills) {
4108
- this.graph.addSkill(skill);
4109
- this.skillCache.set(skill.id, skill);
4110
- if (skill.derivedFrom) {
4111
- for (const parentId of skill.derivedFrom) {
4112
- this.graph.addDependency(skill.id, parentId, "extends");
4113
- }
4114
- }
4115
- }
4116
- await this.matcher.indexSkills(skills);
4117
- return skills.length;
4118
- }
4119
- /**
4120
- * Get dependency graph
4121
- */
4122
- getGraph() {
4123
- return this.graph;
4124
- }
4125
- /**
4126
- * Clear caches and graph
4127
- */
4128
- clear() {
4129
- this.graph.clear();
4130
- this.skillCache.clear();
4131
- this.matcher.clearCache();
4132
- }
4133
- // ===========================================================================
4134
- // Private helpers
4135
- // ===========================================================================
4136
- async getSkill(id, version) {
4137
- const cacheKey = version ? `${id}@${version}` : id;
4138
- if (this.skillCache.has(cacheKey)) {
4139
- return this.skillCache.get(cacheKey);
4140
- }
4141
- if (this.config.storage) {
4142
- const skill = await this.config.storage.getSkill(id, version);
4143
- if (skill) {
4144
- this.skillCache.set(cacheKey, skill);
4145
- return skill;
4146
- }
4147
- }
4148
- return null;
4149
- }
4150
- generateId(name) {
4151
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4152
- }
4153
- generateDescription(skills) {
4154
- return `Combines: ${skills.map((s) => s.name).join(", ")}`;
4155
- }
4156
- mergeProblemStatements(skills) {
4157
- const problems = skills.map((s) => `- ${s.problem}`);
4158
- return `This compound skill addresses:
4159
- ${problems.join("\n")}`;
4160
- }
4161
- mergeTriggerConditions(skills) {
4162
- const seen = /* @__PURE__ */ new Set();
4163
- const triggers = [];
4164
- for (const skill of skills) {
4165
- for (const trigger of skill.triggerConditions) {
4166
- const key = `${trigger.type}:${trigger.value}`;
4167
- if (!seen.has(key)) {
4168
- seen.add(key);
4169
- triggers.push(trigger);
4170
- }
4171
- }
4172
- }
4173
- return triggers;
4174
- }
4175
- generateSolutionSteps(skills, mode) {
4176
- const steps = [];
4177
- switch (mode) {
4178
- case "sequential":
4179
- steps.push("Execute the following skills in order:");
4180
- skills.forEach((s, i) => {
4181
- steps.push(`
4182
- ## Step ${i + 1}: ${s.name}
4183
- ${s.solution}`);
4184
- });
4185
- break;
4186
- case "parallel":
4187
- steps.push("Execute the following skills (can be done in parallel):");
4188
- skills.forEach((s) => {
4189
- steps.push(`
4190
- ## ${s.name}
4191
- ${s.solution}`);
4192
- });
4193
- break;
4194
- case "conditional":
4195
- steps.push("Execute skills based on conditions:");
4196
- skills.forEach((s) => {
4197
- const triggers = s.triggerConditions.map((t) => t.value).join(" OR ");
4198
- steps.push(`
4199
- ## If ${triggers}:
4200
- ${s.solution}`);
4201
- });
4202
- break;
4203
- case "fallback":
4204
- steps.push("Try skills in order until one succeeds:");
4205
- skills.forEach((s, i) => {
4206
- steps.push(`
4207
- ## Option ${i + 1}: ${s.name}
4208
- ${s.solution}`);
4209
- });
4210
- break;
4211
- }
4212
- return steps.join("\n");
4213
- }
4214
- mergeVerifications(skills) {
4215
- const verifications = skills.map(
4216
- (s) => `- ${s.name}: ${s.verification}`
4217
- );
4218
- return `Verify all components:
4219
- ${verifications.join("\n")}`;
4220
- }
4221
- mergeTags(skills) {
4222
- const tags = /* @__PURE__ */ new Set();
4223
- for (const skill of skills) {
4224
- for (const tag of skill.tags) {
4225
- tags.add(tag);
4226
- }
4227
- }
4228
- tags.add("compound");
4229
- return Array.from(tags);
4230
- }
4231
- getExecutionOrder(skillIds, order) {
4232
- switch (order) {
4233
- case "dependency":
4234
- return this.graph.getTopologicalOrder(skillIds);
4235
- case "priority":
4236
- return skillIds;
4237
- case "fixed":
4238
- default:
4239
- return skillIds;
4240
- }
4241
- }
4242
- checkTriggerOverlap(skill1, skill2) {
4243
- const triggers1 = skill1.triggerConditions.map((t) => t.value.toLowerCase());
4244
- const triggers2 = skill2.triggerConditions.map((t) => t.value.toLowerCase());
4245
- for (const t1 of triggers1) {
4246
- for (const t2 of triggers2) {
4247
- if (t1 === t2 || t1.includes(t2) || t2.includes(t1)) {
4248
- return t1;
4249
- }
4250
- }
4251
- }
4252
- return null;
4253
- }
4254
- suggestCompositionMode(skill1, skill2) {
4255
- if (skill1.solution.toLowerCase().includes(skill2.problem.toLowerCase().slice(0, 20)) || skill2.solution.toLowerCase().includes(skill1.problem.toLowerCase().slice(0, 20))) {
4256
- return "sequential";
4257
- }
4258
- const overlap = this.checkTriggerOverlap(skill1, skill2);
4259
- if (!overlap) {
4260
- return "conditional";
4261
- }
4262
- return "fallback";
4263
- }
4264
- getSimilarityReason(skill1, skill2) {
4265
- const reasons = [];
4266
- const problem1Words = new Set(skill1.problem.toLowerCase().split(/\s+/));
4267
- const problem2Words = new Set(skill2.problem.toLowerCase().split(/\s+/));
4268
- const problemOverlap = [...problem1Words].filter((w) => problem2Words.has(w));
4269
- if (problemOverlap.length > 3) {
4270
- reasons.push("problem statements");
4271
- }
4272
- const tagOverlap = skill1.tags.filter((t) => skill2.tags.includes(t));
4273
- if (tagOverlap.length > 0) {
4274
- reasons.push(`tags (${tagOverlap.join(", ")})`);
4275
- }
4276
- if (this.checkTriggerOverlap(skill1, skill2)) {
4277
- reasons.push("trigger conditions");
4278
- }
4279
- return reasons.length > 0 ? reasons.join(", ") : "content";
4280
- }
4281
- estimateBenefit(skill1, skill2, mode) {
4282
- switch (mode) {
4283
- case "sequential":
4284
- return "Combined workflow reduces manual steps";
4285
- case "parallel":
4286
- return "Parallel execution saves time";
4287
- case "conditional":
4288
- return "Smart routing to appropriate skill";
4289
- case "fallback":
4290
- return "Increased reliability with backup options";
4291
- default:
4292
- return "Unified skill management";
4293
- }
4294
- }
4295
- findCommonDependencyPairs(skills) {
4296
- const pairs = [];
4297
- const skillIds = new Set(skills.map((s) => s.id));
4298
- for (const skill of skills) {
4299
- const deps = this.graph.getDependencies(skill.id);
4300
- for (const dep of deps) {
4301
- if (skillIds.has(dep.to) && dep.type !== "conflicts") {
4302
- pairs.push([skill.id, dep.to]);
4303
- }
4304
- }
4305
- }
4306
- return pairs;
4307
- }
4308
- };
4309
-
4310
- // src/composer/types.ts
4311
- function isCompoundSkill(skill) {
4312
- return "isCompound" in skill && skill.isCompound === true;
4313
- }
4314
-
4315
- // src/index.ts
4316
- var VERSION = "0.1.0";
4317
-
4318
- export {
4319
- BaseSessionAdapter,
4320
- AdapterRegistry,
4321
- adapterRegistry,
4322
- ClaudeCodeAdapter,
4323
- claudeCodeAdapter,
4324
- DEFAULT_QUALITY_GATES,
4325
- QualityGateEvaluator,
4326
- BaseExtractor,
4327
- ManualExtractor,
4328
- AutomaticExtractor,
4329
- BaseStorageAdapter,
4330
- MemoryStorageAdapter,
4331
- FilesystemStorageAdapter,
4332
- parseVersion,
4333
- formatVersion,
4334
- isValidVersion,
4335
- compareVersions,
4336
- bumpVersion,
4337
- satisfiesRange,
4338
- getLatestVersion,
4339
- sortVersions,
4340
- inferBumpType,
4341
- LineageTracker,
4342
- SimpleHashEmbedding,
4343
- OpenAIEmbedding,
4344
- VoyageEmbedding,
4345
- cosineSimilarity,
4346
- euclideanDistance,
4347
- SemanticMatcher,
4348
- SkillBank,
4349
- createSkillBank,
4350
- BatchProcessor,
4351
- DependencyGraph,
4352
- SkillComposer,
4353
- isCompoundSkill,
4354
- VERSION
4355
- };