skill-tree 0.1.0 → 0.1.2

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