skill-tree 0.1.4 → 0.1.6

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.
@@ -1,1045 +0,0 @@
1
- // src/storage/sqlite.ts
2
- import Database from "better-sqlite3";
3
- import * as path from "path";
4
- import * as fs from "fs";
5
-
6
- // src/storage/base.ts
7
- var BaseStorageAdapter = class {
8
- constructor() {
9
- this.initialized = false;
10
- }
11
- /**
12
- * Ensure storage is initialized before operations
13
- */
14
- ensureInitialized() {
15
- if (!this.initialized) {
16
- throw new Error("Storage not initialized. Call initialize() first.");
17
- }
18
- }
19
- /**
20
- * Apply filters to a list of skills
21
- */
22
- applyFilter(skills, filter) {
23
- if (!filter) return skills;
24
- return skills.filter((skill) => {
25
- if (filter.status && filter.status.length > 0) {
26
- if (!filter.status.includes(skill.status)) return false;
27
- }
28
- if (filter.tags && filter.tags.length > 0) {
29
- const hasTag = filter.tags.some((tag) => skill.tags.includes(tag));
30
- if (!hasTag) return false;
31
- }
32
- if (filter.author && skill.author !== filter.author) {
33
- return false;
34
- }
35
- if (filter.minSuccessRate !== void 0 && skill.metrics.successRate < filter.minSuccessRate) {
36
- return false;
37
- }
38
- if (filter.createdAfter && skill.createdAt < filter.createdAfter) {
39
- return false;
40
- }
41
- if (filter.createdBefore && skill.createdAt > filter.createdBefore) {
42
- return false;
43
- }
44
- if (!this.applyNamespaceFilter(skill, filter)) {
45
- return false;
46
- }
47
- return true;
48
- });
49
- }
50
- /**
51
- * Apply namespace-related filters to a skill
52
- */
53
- applyNamespaceFilter(skill, filter) {
54
- const namespace = skill.namespace;
55
- if (filter.scope) {
56
- const scopes = Array.isArray(filter.scope) ? filter.scope : [filter.scope];
57
- const skillScope = namespace?.scope || "personal";
58
- if (!scopes.includes(skillScope)) return false;
59
- }
60
- if (filter.owner) {
61
- const skillOwner = namespace?.owner;
62
- if (skillOwner !== filter.owner) return false;
63
- }
64
- if (filter.team) {
65
- const skillTeam = namespace?.team;
66
- if (skillTeam !== filter.team) return false;
67
- }
68
- if (filter.visibility) {
69
- const visibilities = Array.isArray(filter.visibility) ? filter.visibility : [filter.visibility];
70
- const skillVisibility = namespace?.visibility || "private";
71
- if (!visibilities.includes(skillVisibility)) return false;
72
- }
73
- if (filter.accessibleBy) {
74
- if (!this.canAgentAccessSkill(skill, filter.accessibleBy, filter.accessibleByTeam)) {
75
- return false;
76
- }
77
- }
78
- return true;
79
- }
80
- /**
81
- * Check if an agent can access a skill based on namespace rules
82
- */
83
- canAgentAccessSkill(skill, agentId, agentTeam) {
84
- const namespace = skill.namespace;
85
- if (!namespace) return true;
86
- if (namespace.owner === agentId) return true;
87
- switch (namespace.visibility) {
88
- case "public":
89
- return true;
90
- case "team-only":
91
- return agentTeam !== void 0 && agentTeam === namespace.team;
92
- case "private":
93
- return namespace.owner === agentId;
94
- default:
95
- return false;
96
- }
97
- }
98
- /**
99
- * Simple text search across skill fields
100
- */
101
- textSearch(skills, query) {
102
- const lowerQuery = query.toLowerCase();
103
- const terms = lowerQuery.split(/\s+/).filter((t) => t.length > 0);
104
- return skills.filter((skill) => {
105
- const searchText = [
106
- skill.name,
107
- skill.description,
108
- skill.problem,
109
- skill.solution,
110
- ...skill.tags,
111
- skill.triggerConditions.map((t) => t.value).join(" ")
112
- ].join(" ").toLowerCase();
113
- return terms.every((term) => searchText.includes(term));
114
- });
115
- }
116
- };
117
- var MemoryStorageAdapter = class extends BaseStorageAdapter {
118
- constructor() {
119
- super(...arguments);
120
- this.skills = /* @__PURE__ */ new Map();
121
- // skillId -> version -> skill
122
- this.lineages = /* @__PURE__ */ new Map();
123
- }
124
- async initialize() {
125
- this.initialized = true;
126
- }
127
- async saveSkill(skill) {
128
- this.ensureInitialized();
129
- if (!this.skills.has(skill.id)) {
130
- this.skills.set(skill.id, /* @__PURE__ */ new Map());
131
- }
132
- const versionMap = this.skills.get(skill.id);
133
- versionMap.set(skill.version, { ...skill });
134
- this.updateLineage(skill);
135
- }
136
- async getSkill(id, version) {
137
- this.ensureInitialized();
138
- const versionMap = this.skills.get(id);
139
- if (!versionMap) return null;
140
- if (version) {
141
- return versionMap.get(version) || null;
142
- }
143
- const versions = Array.from(versionMap.keys()).sort(this.compareVersions);
144
- const latestVersion = versions[versions.length - 1];
145
- return latestVersion ? versionMap.get(latestVersion) || null : null;
146
- }
147
- async listSkills(filter) {
148
- this.ensureInitialized();
149
- const skills = [];
150
- for (const versionMap of this.skills.values()) {
151
- const versions = Array.from(versionMap.keys()).sort(this.compareVersions);
152
- const latestVersion = versions[versions.length - 1];
153
- if (latestVersion) {
154
- const skill = versionMap.get(latestVersion);
155
- if (skill) skills.push(skill);
156
- }
157
- }
158
- return this.applyFilter(skills, filter);
159
- }
160
- async deleteSkill(id, version) {
161
- this.ensureInitialized();
162
- if (version) {
163
- const versionMap = this.skills.get(id);
164
- if (!versionMap) return false;
165
- return versionMap.delete(version);
166
- }
167
- const existed = this.skills.has(id);
168
- this.skills.delete(id);
169
- this.lineages.delete(id);
170
- return existed;
171
- }
172
- async getVersionHistory(skillId) {
173
- this.ensureInitialized();
174
- const versionMap = this.skills.get(skillId);
175
- if (!versionMap) return [];
176
- const versions = [];
177
- for (const [version, skill] of versionMap) {
178
- versions.push({
179
- skillId,
180
- version,
181
- skill,
182
- changelog: "",
183
- // Not tracked in memory
184
- createdAt: skill.createdAt,
185
- contentHash: this.hashSkill(skill)
186
- });
187
- }
188
- return versions.sort((a, b) => this.compareVersions(a.version, b.version));
189
- }
190
- async getLineage(skillId) {
191
- this.ensureInitialized();
192
- return this.lineages.get(skillId) || null;
193
- }
194
- async searchSkills(query) {
195
- this.ensureInitialized();
196
- const allSkills = await this.listSkills();
197
- return this.textSearch(allSkills, query);
198
- }
199
- updateLineage(skill) {
200
- if (!this.lineages.has(skill.id)) {
201
- this.lineages.set(skill.id, {
202
- rootId: skill.id,
203
- versions: [],
204
- forks: []
205
- });
206
- }
207
- const lineage = this.lineages.get(skill.id);
208
- const existingIndex = lineage.versions.findIndex((v) => v.version === skill.version);
209
- const versionEntry = {
210
- skillId: skill.id,
211
- version: skill.version,
212
- skill,
213
- changelog: "",
214
- createdAt: skill.createdAt,
215
- contentHash: this.hashSkill(skill)
216
- };
217
- if (existingIndex >= 0) {
218
- lineage.versions[existingIndex] = versionEntry;
219
- } else {
220
- lineage.versions.push(versionEntry);
221
- lineage.versions.sort((a, b) => this.compareVersions(a.version, b.version));
222
- }
223
- }
224
- compareVersions(a, b) {
225
- const partsA = a.split(".").map(Number);
226
- const partsB = b.split(".").map(Number);
227
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
228
- const numA = partsA[i] || 0;
229
- const numB = partsB[i] || 0;
230
- if (numA !== numB) return numA - numB;
231
- }
232
- return 0;
233
- }
234
- hashSkill(skill) {
235
- const content = JSON.stringify({
236
- problem: skill.problem,
237
- solution: skill.solution,
238
- triggerConditions: skill.triggerConditions
239
- });
240
- let hash = 0;
241
- for (let i = 0; i < content.length; i++) {
242
- const char = content.charCodeAt(i);
243
- hash = (hash << 5) - hash + char;
244
- hash = hash & hash;
245
- }
246
- return Math.abs(hash).toString(16);
247
- }
248
- /**
249
- * Clear all stored skills (for testing)
250
- */
251
- clear() {
252
- this.skills.clear();
253
- this.lineages.clear();
254
- }
255
- // ==========================================================================
256
- // Fork Tracking
257
- // ==========================================================================
258
- async recordFork(sourceSkillId, fork) {
259
- this.ensureInitialized();
260
- const lineage = this.lineages.get(sourceSkillId);
261
- if (lineage) {
262
- const exists = lineage.forks.some(
263
- (f) => f.forkedSkillId === fork.forkedSkillId
264
- );
265
- if (!exists) {
266
- lineage.forks.push(fork);
267
- }
268
- }
269
- }
270
- };
271
-
272
- // src/storage/sqlite.ts
273
- var SCHEMA_VERSION = 2;
274
- var SQLiteStorageAdapter = class extends BaseStorageAdapter {
275
- constructor(config) {
276
- super();
277
- this.db = null;
278
- this.config = {
279
- walMode: true,
280
- enableFTS: true,
281
- ...config
282
- };
283
- }
284
- async initialize() {
285
- const dir = path.dirname(this.config.dbPath);
286
- if (dir && !fs.existsSync(dir)) {
287
- fs.mkdirSync(dir, { recursive: true });
288
- }
289
- this.db = new Database(this.config.dbPath);
290
- if (this.config.walMode) {
291
- this.db.pragma("journal_mode = WAL");
292
- }
293
- this.db.pragma("foreign_keys = ON");
294
- this.createSchema();
295
- this.runMigrations();
296
- this.initialized = true;
297
- }
298
- createSchema() {
299
- const db = this.getDb();
300
- db.exec(`
301
- CREATE TABLE IF NOT EXISTS schema_version (
302
- version INTEGER PRIMARY KEY
303
- )
304
- `);
305
- db.exec(`
306
- CREATE TABLE IF NOT EXISTS skills (
307
- id TEXT PRIMARY KEY,
308
- version TEXT NOT NULL,
309
- name TEXT NOT NULL,
310
- description TEXT NOT NULL,
311
- problem TEXT NOT NULL,
312
- trigger_conditions TEXT NOT NULL,
313
- solution TEXT NOT NULL,
314
- verification TEXT NOT NULL,
315
- examples TEXT NOT NULL,
316
- notes TEXT,
317
- author TEXT NOT NULL,
318
- tags TEXT NOT NULL,
319
- created_at TEXT NOT NULL,
320
- updated_at TEXT NOT NULL,
321
- status TEXT NOT NULL,
322
- parent_version TEXT,
323
- derived_from TEXT,
324
- metrics TEXT NOT NULL,
325
- source TEXT,
326
- taxonomy TEXT,
327
- external_source TEXT
328
- )
329
- `);
330
- db.exec(`
331
- CREATE TABLE IF NOT EXISTS skill_versions (
332
- id INTEGER PRIMARY KEY AUTOINCREMENT,
333
- skill_id TEXT NOT NULL,
334
- version TEXT NOT NULL,
335
- skill_data TEXT NOT NULL,
336
- changelog TEXT NOT NULL,
337
- created_at TEXT NOT NULL,
338
- content_hash TEXT NOT NULL,
339
- UNIQUE(skill_id, version)
340
- )
341
- `);
342
- db.exec(`
343
- CREATE TABLE IF NOT EXISTS skill_lineage (
344
- skill_id TEXT PRIMARY KEY,
345
- root_id TEXT NOT NULL
346
- )
347
- `);
348
- db.exec(`
349
- CREATE TABLE IF NOT EXISTS skill_forks (
350
- id INTEGER PRIMARY KEY AUTOINCREMENT,
351
- root_skill_id TEXT NOT NULL,
352
- forked_skill_id TEXT NOT NULL,
353
- from_version TEXT NOT NULL,
354
- reason TEXT NOT NULL,
355
- forked_at TEXT NOT NULL,
356
- FOREIGN KEY (root_skill_id) REFERENCES skill_lineage(skill_id)
357
- )
358
- `);
359
- db.exec(`
360
- CREATE INDEX IF NOT EXISTS idx_skills_status ON skills(status);
361
- CREATE INDEX IF NOT EXISTS idx_skills_author ON skills(author);
362
- CREATE INDEX IF NOT EXISTS idx_skills_updated ON skills(updated_at);
363
- CREATE INDEX IF NOT EXISTS idx_versions_skill ON skill_versions(skill_id);
364
- `);
365
- if (this.config.enableFTS) {
366
- db.exec(`
367
- CREATE VIRTUAL TABLE IF NOT EXISTS skills_fts USING fts5(
368
- skill_id,
369
- name,
370
- description,
371
- problem,
372
- solution,
373
- tags,
374
- trigger_values
375
- )
376
- `);
377
- }
378
- db.exec(`
379
- CREATE TABLE IF NOT EXISTS taxonomy_nodes (
380
- id TEXT PRIMARY KEY,
381
- name TEXT NOT NULL,
382
- description TEXT,
383
- parent_id TEXT REFERENCES taxonomy_nodes(id),
384
- path TEXT NOT NULL,
385
- skill_count INTEGER DEFAULT 0,
386
- created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
387
- )
388
- `);
389
- db.exec(`
390
- CREATE TABLE IF NOT EXISTS skill_taxonomy_placements (
391
- skill_id TEXT NOT NULL,
392
- node_id TEXT NOT NULL,
393
- is_primary INTEGER DEFAULT 0,
394
- confidence REAL,
395
- reasoning TEXT,
396
- created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
397
- PRIMARY KEY (skill_id, node_id)
398
- )
399
- `);
400
- db.exec(`
401
- CREATE TABLE IF NOT EXISTS skill_relationships (
402
- source_skill_id TEXT NOT NULL,
403
- target_skill_id TEXT NOT NULL,
404
- type TEXT NOT NULL,
405
- confidence REAL NOT NULL,
406
- reasoning TEXT,
407
- created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
408
- PRIMARY KEY (source_skill_id, target_skill_id, type)
409
- )
410
- `);
411
- db.exec(`
412
- CREATE TABLE IF NOT EXISTS skill_sources (
413
- id TEXT PRIMARY KEY,
414
- type TEXT NOT NULL,
415
- url TEXT,
416
- last_scraped TEXT,
417
- etag TEXT,
418
- skill_count INTEGER DEFAULT 0
419
- )
420
- `);
421
- db.exec(`
422
- CREATE INDEX IF NOT EXISTS idx_taxonomy_parent ON taxonomy_nodes(parent_id);
423
- CREATE INDEX IF NOT EXISTS idx_taxonomy_path ON taxonomy_nodes(path);
424
- CREATE INDEX IF NOT EXISTS idx_placements_skill ON skill_taxonomy_placements(skill_id);
425
- CREATE INDEX IF NOT EXISTS idx_placements_node ON skill_taxonomy_placements(node_id);
426
- CREATE INDEX IF NOT EXISTS idx_relationships_source ON skill_relationships(source_skill_id);
427
- CREATE INDEX IF NOT EXISTS idx_relationships_target ON skill_relationships(target_skill_id);
428
- `);
429
- const version = db.prepare("SELECT version FROM schema_version").get();
430
- if (!version) {
431
- db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
432
- }
433
- }
434
- runMigrations() {
435
- const db = this.getDb();
436
- const row = db.prepare("SELECT version FROM schema_version").get();
437
- const currentVersion = row?.version || 0;
438
- if (currentVersion < SCHEMA_VERSION) {
439
- if (currentVersion < 2) {
440
- try {
441
- db.exec("ALTER TABLE skills ADD COLUMN taxonomy TEXT");
442
- } catch {
443
- }
444
- try {
445
- db.exec("ALTER TABLE skills ADD COLUMN external_source TEXT");
446
- } catch {
447
- }
448
- }
449
- db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
450
- }
451
- }
452
- getDb() {
453
- if (!this.db) {
454
- throw new Error("Database not initialized. Call initialize() first.");
455
- }
456
- return this.db;
457
- }
458
- async saveSkill(skill) {
459
- this.ensureInitialized();
460
- const db = this.getDb();
461
- const stmt = db.prepare(`
462
- INSERT OR REPLACE INTO skills (
463
- id, version, name, description, problem, trigger_conditions, solution,
464
- verification, examples, notes, author, tags, created_at, updated_at,
465
- status, parent_version, derived_from, metrics, source, taxonomy, external_source
466
- ) VALUES (
467
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
468
- )
469
- `);
470
- stmt.run(
471
- skill.id,
472
- skill.version,
473
- skill.name,
474
- skill.description,
475
- skill.problem,
476
- JSON.stringify(skill.triggerConditions),
477
- skill.solution,
478
- skill.verification,
479
- JSON.stringify(skill.examples),
480
- skill.notes || null,
481
- skill.author,
482
- JSON.stringify(skill.tags),
483
- skill.createdAt.toISOString(),
484
- skill.updatedAt.toISOString(),
485
- skill.status,
486
- skill.parentVersion || null,
487
- skill.derivedFrom ? JSON.stringify(skill.derivedFrom) : null,
488
- JSON.stringify(skill.metrics),
489
- skill.source ? JSON.stringify(skill.source) : null,
490
- skill.taxonomy ? JSON.stringify(skill.taxonomy) : null,
491
- skill.externalSource ? JSON.stringify({
492
- ...skill.externalSource,
493
- scrapedAt: skill.externalSource.scrapedAt.toISOString()
494
- }) : null
495
- );
496
- if (skill.relationships && skill.relationships.length > 0) {
497
- await this.saveSkillRelationships(skill.id, skill.relationships);
498
- }
499
- if (this.config.enableFTS) {
500
- this.updateFTSIndex(skill);
501
- }
502
- await this.saveVersionSnapshot(skill);
503
- this.updateLineage(skill);
504
- }
505
- updateFTSIndex(skill) {
506
- const db = this.getDb();
507
- db.prepare("DELETE FROM skills_fts WHERE skill_id = ?").run(skill.id);
508
- const triggerValues = skill.triggerConditions.map((t) => t.value).join(" ");
509
- const tags = skill.tags.join(" ");
510
- db.prepare(`
511
- INSERT INTO skills_fts (skill_id, name, description, problem, solution, tags, trigger_values)
512
- VALUES (?, ?, ?, ?, ?, ?, ?)
513
- `).run(skill.id, skill.name, skill.description, skill.problem, skill.solution, tags, triggerValues);
514
- }
515
- async saveVersionSnapshot(skill) {
516
- const db = this.getDb();
517
- const existing = db.prepare(
518
- "SELECT id FROM skill_versions WHERE skill_id = ? AND version = ?"
519
- ).get(skill.id, skill.version);
520
- if (existing) {
521
- db.prepare(`
522
- UPDATE skill_versions
523
- SET skill_data = ?, content_hash = ?
524
- WHERE skill_id = ? AND version = ?
525
- `).run(
526
- JSON.stringify(this.serializeSkill(skill)),
527
- this.hashSkill(skill),
528
- skill.id,
529
- skill.version
530
- );
531
- } else {
532
- db.prepare(`
533
- INSERT INTO skill_versions (skill_id, version, skill_data, changelog, created_at, content_hash)
534
- VALUES (?, ?, ?, ?, ?, ?)
535
- `).run(
536
- skill.id,
537
- skill.version,
538
- JSON.stringify(this.serializeSkill(skill)),
539
- "",
540
- // Changelog can be added via separate method
541
- skill.createdAt.toISOString(),
542
- this.hashSkill(skill)
543
- );
544
- }
545
- }
546
- updateLineage(skill) {
547
- const db = this.getDb();
548
- const existing = db.prepare(
549
- "SELECT skill_id FROM skill_lineage WHERE skill_id = ?"
550
- ).get(skill.id);
551
- if (!existing) {
552
- db.prepare(
553
- "INSERT INTO skill_lineage (skill_id, root_id) VALUES (?, ?)"
554
- ).run(skill.id, skill.id);
555
- }
556
- }
557
- async getSkill(id, version) {
558
- this.ensureInitialized();
559
- const db = this.getDb();
560
- if (version) {
561
- const row2 = db.prepare(
562
- "SELECT skill_data FROM skill_versions WHERE skill_id = ? AND version = ?"
563
- ).get(id, version);
564
- if (!row2) return null;
565
- return this.deserializeSkill(JSON.parse(row2.skill_data));
566
- }
567
- const row = db.prepare("SELECT * FROM skills WHERE id = ?").get(id);
568
- if (!row) return null;
569
- return this.rowToSkill(row);
570
- }
571
- async listSkills(filter) {
572
- this.ensureInitialized();
573
- const db = this.getDb();
574
- let sql = "SELECT * FROM skills WHERE 1=1";
575
- const params = [];
576
- if (filter?.status && filter.status.length > 0) {
577
- sql += ` AND status IN (${filter.status.map(() => "?").join(",")})`;
578
- params.push(...filter.status);
579
- }
580
- if (filter?.author) {
581
- sql += " AND author = ?";
582
- params.push(filter.author);
583
- }
584
- if (filter?.minSuccessRate !== void 0) {
585
- sql += " AND json_extract(metrics, '$.successRate') >= ?";
586
- params.push(filter.minSuccessRate);
587
- }
588
- if (filter?.createdAfter) {
589
- sql += " AND created_at >= ?";
590
- params.push(filter.createdAfter.toISOString());
591
- }
592
- if (filter?.createdBefore) {
593
- sql += " AND created_at <= ?";
594
- params.push(filter.createdBefore.toISOString());
595
- }
596
- sql += " ORDER BY updated_at DESC";
597
- const rows = db.prepare(sql).all(...params);
598
- let skills = rows.map((row) => this.rowToSkill(row));
599
- if (filter?.tags && filter.tags.length > 0) {
600
- skills = skills.filter(
601
- (skill) => filter.tags.some((tag) => skill.tags.includes(tag))
602
- );
603
- }
604
- return skills;
605
- }
606
- async deleteSkill(id, version) {
607
- this.ensureInitialized();
608
- const db = this.getDb();
609
- if (version) {
610
- const result = db.prepare(
611
- "DELETE FROM skill_versions WHERE skill_id = ? AND version = ?"
612
- ).run(id, version);
613
- return result.changes > 0;
614
- }
615
- const transaction = db.transaction(() => {
616
- db.prepare("DELETE FROM skill_versions WHERE skill_id = ?").run(id);
617
- db.prepare("DELETE FROM skill_forks WHERE root_skill_id = ? OR forked_skill_id = ?").run(id, id);
618
- db.prepare("DELETE FROM skill_lineage WHERE skill_id = ?").run(id);
619
- if (this.config.enableFTS) {
620
- db.prepare("DELETE FROM skills_fts WHERE skill_id = ?").run(id);
621
- }
622
- db.prepare("DELETE FROM skill_taxonomy_placements WHERE skill_id = ?").run(id);
623
- db.prepare("DELETE FROM skill_relationships WHERE source_skill_id = ? OR target_skill_id = ?").run(id, id);
624
- const result = db.prepare("DELETE FROM skills WHERE id = ?").run(id);
625
- return result.changes > 0;
626
- });
627
- return transaction();
628
- }
629
- async getVersionHistory(skillId) {
630
- this.ensureInitialized();
631
- const db = this.getDb();
632
- const rows = db.prepare(`
633
- SELECT skill_id, version, skill_data, changelog, created_at, content_hash
634
- FROM skill_versions
635
- WHERE skill_id = ?
636
- ORDER BY created_at ASC
637
- `).all(skillId);
638
- return rows.map((row) => ({
639
- skillId: row.skill_id,
640
- version: row.version,
641
- skill: this.deserializeSkill(JSON.parse(row.skill_data)),
642
- changelog: row.changelog,
643
- createdAt: new Date(row.created_at),
644
- contentHash: row.content_hash
645
- }));
646
- }
647
- async getLineage(skillId) {
648
- this.ensureInitialized();
649
- const db = this.getDb();
650
- const lineageRow = db.prepare(
651
- "SELECT * FROM skill_lineage WHERE skill_id = ?"
652
- ).get(skillId);
653
- if (!lineageRow) return null;
654
- const versions = await this.getVersionHistory(skillId);
655
- const forkRows = db.prepare(`
656
- SELECT forked_skill_id, from_version, reason, forked_at
657
- FROM skill_forks
658
- WHERE root_skill_id = ?
659
- `).all(skillId);
660
- const forks = forkRows.map((row) => ({
661
- forkedSkillId: row.forked_skill_id,
662
- fromVersion: row.from_version,
663
- reason: row.reason,
664
- forkedAt: new Date(row.forked_at)
665
- }));
666
- return {
667
- rootId: lineageRow.root_id,
668
- versions,
669
- forks
670
- };
671
- }
672
- async searchSkills(query) {
673
- this.ensureInitialized();
674
- const db = this.getDb();
675
- if (this.config.enableFTS) {
676
- const rows = db.prepare(`
677
- SELECT s.* FROM skills s
678
- JOIN skills_fts fts ON s.id = fts.skill_id
679
- WHERE skills_fts MATCH ?
680
- ORDER BY rank
681
- `).all(query);
682
- return rows.map((row) => this.rowToSkill(row));
683
- }
684
- const allSkills = await this.listSkills();
685
- return this.textSearch(allSkills, query);
686
- }
687
- /**
688
- * Add a fork record
689
- */
690
- async addFork(rootSkillId, forkedSkillId, fromVersion, reason) {
691
- this.ensureInitialized();
692
- const db = this.getDb();
693
- db.prepare(`
694
- INSERT INTO skill_forks (root_skill_id, forked_skill_id, from_version, reason, forked_at)
695
- VALUES (?, ?, ?, ?, ?)
696
- `).run(rootSkillId, forkedSkillId, fromVersion, reason, (/* @__PURE__ */ new Date()).toISOString());
697
- }
698
- /**
699
- * Update changelog for a version
700
- */
701
- async updateChangelog(skillId, version, changelog) {
702
- this.ensureInitialized();
703
- const db = this.getDb();
704
- db.prepare(`
705
- UPDATE skill_versions SET changelog = ? WHERE skill_id = ? AND version = ?
706
- `).run(changelog, skillId, version);
707
- }
708
- /**
709
- * Get skills by tag
710
- */
711
- async getSkillsByTag(tag) {
712
- return this.listSkills({ tags: [tag] });
713
- }
714
- /**
715
- * Get all tags with counts
716
- */
717
- async getTagCounts() {
718
- this.ensureInitialized();
719
- const db = this.getDb();
720
- const rows = db.prepare("SELECT tags FROM skills").all();
721
- const counts = /* @__PURE__ */ new Map();
722
- for (const row of rows) {
723
- const tags = JSON.parse(row.tags);
724
- for (const tag of tags) {
725
- counts.set(tag, (counts.get(tag) || 0) + 1);
726
- }
727
- }
728
- return counts;
729
- }
730
- /**
731
- * Get skill count by status
732
- */
733
- async getStatusCounts() {
734
- this.ensureInitialized();
735
- const db = this.getDb();
736
- const rows = db.prepare(`
737
- SELECT status, COUNT(*) as count FROM skills GROUP BY status
738
- `).all();
739
- const counts = /* @__PURE__ */ new Map();
740
- for (const row of rows) {
741
- counts.set(row.status, row.count);
742
- }
743
- return counts;
744
- }
745
- /**
746
- * Close the database connection
747
- */
748
- close() {
749
- if (this.db) {
750
- this.db.close();
751
- this.db = null;
752
- this.initialized = false;
753
- }
754
- }
755
- /**
756
- * Export all skills for backup
757
- */
758
- async exportAll() {
759
- return this.listSkills();
760
- }
761
- /**
762
- * Import skills from backup
763
- */
764
- async importSkills(skills) {
765
- let imported = 0;
766
- let failed = 0;
767
- for (const skill of skills) {
768
- try {
769
- await this.saveSkill(skill);
770
- imported++;
771
- } catch {
772
- failed++;
773
- }
774
- }
775
- return { imported, failed };
776
- }
777
- // =========================================================================
778
- // TAXONOMY METHODS
779
- // =========================================================================
780
- /**
781
- * Get or create a taxonomy node
782
- */
783
- async ensureTaxonomyNode(path2) {
784
- this.ensureInitialized();
785
- const db = this.getDb();
786
- const pathStr = path2.join("/");
787
- const existing = db.prepare("SELECT id FROM taxonomy_nodes WHERE path = ?").get(pathStr);
788
- if (existing) return existing.id;
789
- const id = `node-${pathStr.replace(/\//g, "-").toLowerCase()}`;
790
- const name = path2[path2.length - 1] || "Root";
791
- const parentPath = path2.slice(0, -1);
792
- let parentId = null;
793
- if (parentPath.length > 0) {
794
- parentId = await this.ensureTaxonomyNode(parentPath);
795
- }
796
- db.prepare(`
797
- INSERT INTO taxonomy_nodes (id, name, parent_id, path, created_at)
798
- VALUES (?, ?, ?, ?, ?)
799
- `).run(id, name, parentId, pathStr, (/* @__PURE__ */ new Date()).toISOString());
800
- return id;
801
- }
802
- /**
803
- * Place a skill in the taxonomy
804
- */
805
- async placeInTaxonomy(skillId, taxonomy) {
806
- this.ensureInitialized();
807
- const db = this.getDb();
808
- const primaryNodeId = await this.ensureTaxonomyNode(taxonomy.primaryPath);
809
- db.prepare(`
810
- INSERT OR REPLACE INTO skill_taxonomy_placements
811
- (skill_id, node_id, is_primary, confidence, created_at)
812
- VALUES (?, ?, 1, ?, ?)
813
- `).run(skillId, primaryNodeId, taxonomy.confidence || null, (/* @__PURE__ */ new Date()).toISOString());
814
- db.prepare("UPDATE taxonomy_nodes SET skill_count = skill_count + 1 WHERE id = ?").run(primaryNodeId);
815
- if (taxonomy.secondaryPaths) {
816
- for (const secondaryPath of taxonomy.secondaryPaths) {
817
- const secondaryNodeId = await this.ensureTaxonomyNode(secondaryPath);
818
- db.prepare(`
819
- INSERT OR IGNORE INTO skill_taxonomy_placements
820
- (skill_id, node_id, is_primary, created_at)
821
- VALUES (?, ?, 0, ?)
822
- `).run(skillId, secondaryNodeId, (/* @__PURE__ */ new Date()).toISOString());
823
- }
824
- }
825
- }
826
- /**
827
- * Get taxonomy tree
828
- */
829
- async getTaxonomyTree(rootPath) {
830
- this.ensureInitialized();
831
- const db = this.getDb();
832
- let sql = "SELECT * FROM taxonomy_nodes";
833
- const params = [];
834
- if (rootPath && rootPath.length > 0) {
835
- const pathPrefix = rootPath.join("/");
836
- sql += " WHERE path LIKE ? OR path = ?";
837
- params.push(`${pathPrefix}/%`, pathPrefix);
838
- }
839
- sql += " ORDER BY path";
840
- const rows = db.prepare(sql).all(...params);
841
- const nodeMap = /* @__PURE__ */ new Map();
842
- const roots = [];
843
- for (const row of rows) {
844
- const node = {
845
- id: row.id,
846
- name: row.name,
847
- path: row.path.split("/"),
848
- skillCount: row.skill_count,
849
- children: []
850
- };
851
- nodeMap.set(row.id, node);
852
- if (row.parent_id && nodeMap.has(row.parent_id)) {
853
- nodeMap.get(row.parent_id).children.push(node);
854
- } else {
855
- roots.push(node);
856
- }
857
- }
858
- return roots;
859
- }
860
- /**
861
- * Get skills in a taxonomy node
862
- */
863
- async getSkillsInTaxonomyNode(nodeId) {
864
- this.ensureInitialized();
865
- const db = this.getDb();
866
- const rows = db.prepare(`
867
- SELECT s.* FROM skills s
868
- JOIN skill_taxonomy_placements p ON s.id = p.skill_id
869
- WHERE p.node_id = ?
870
- `).all(nodeId);
871
- return rows.map((row) => this.rowToSkill(row));
872
- }
873
- // =========================================================================
874
- // RELATIONSHIP METHODS
875
- // =========================================================================
876
- /**
877
- * Save skill relationships
878
- */
879
- async saveSkillRelationships(skillId, relationships) {
880
- const db = this.getDb();
881
- db.prepare("DELETE FROM skill_relationships WHERE source_skill_id = ?").run(skillId);
882
- const stmt = db.prepare(`
883
- INSERT OR REPLACE INTO skill_relationships
884
- (source_skill_id, target_skill_id, type, confidence, reasoning, created_at)
885
- VALUES (?, ?, ?, ?, ?, ?)
886
- `);
887
- for (const rel of relationships) {
888
- stmt.run(
889
- skillId,
890
- rel.targetSkillId,
891
- rel.type,
892
- rel.confidence,
893
- rel.reasoning || null,
894
- (/* @__PURE__ */ new Date()).toISOString()
895
- );
896
- }
897
- }
898
- /**
899
- * Add a relationship between skills
900
- */
901
- async addRelationship(sourceSkillId, targetSkillId, type, confidence, reasoning) {
902
- this.ensureInitialized();
903
- const db = this.getDb();
904
- db.prepare(`
905
- INSERT OR REPLACE INTO skill_relationships
906
- (source_skill_id, target_skill_id, type, confidence, reasoning, created_at)
907
- VALUES (?, ?, ?, ?, ?, ?)
908
- `).run(sourceSkillId, targetSkillId, type, confidence, reasoning || null, (/* @__PURE__ */ new Date()).toISOString());
909
- }
910
- /**
911
- * Get relationships for a skill
912
- */
913
- async getRelationships(skillId) {
914
- this.ensureInitialized();
915
- const db = this.getDb();
916
- const rows = db.prepare(`
917
- SELECT target_skill_id, type, confidence, reasoning
918
- FROM skill_relationships
919
- WHERE source_skill_id = ?
920
- `).all(skillId);
921
- return rows.map((row) => ({
922
- targetSkillId: row.target_skill_id,
923
- type: row.type,
924
- confidence: row.confidence,
925
- reasoning: row.reasoning || void 0
926
- }));
927
- }
928
- /**
929
- * Get skills that depend on a given skill
930
- */
931
- async getDependentSkills(skillId) {
932
- this.ensureInitialized();
933
- const db = this.getDb();
934
- const rows = db.prepare(`
935
- SELECT s.* FROM skills s
936
- JOIN skill_relationships r ON s.id = r.source_skill_id
937
- WHERE r.target_skill_id = ? AND r.type = 'depends_on'
938
- `).all(skillId);
939
- return rows.map((row) => this.rowToSkill(row));
940
- }
941
- /**
942
- * Get related skills (any relationship type)
943
- */
944
- async getRelatedSkills(skillId) {
945
- this.ensureInitialized();
946
- const db = this.getDb();
947
- const rows = db.prepare(`
948
- SELECT s.*, r.type, r.confidence, r.reasoning
949
- FROM skills s
950
- JOIN skill_relationships r ON s.id = r.target_skill_id
951
- WHERE r.source_skill_id = ?
952
- `).all(skillId);
953
- return rows.map((row) => ({
954
- skill: this.rowToSkill(row),
955
- relationship: {
956
- targetSkillId: row.id,
957
- type: row.type,
958
- confidence: row.confidence,
959
- reasoning: row.reasoning || void 0
960
- }
961
- }));
962
- }
963
- // =========================================================================
964
- // HELPER METHODS
965
- // =========================================================================
966
- rowToSkill(row) {
967
- const externalSource = row.external_source ? JSON.parse(row.external_source) : void 0;
968
- return {
969
- id: row.id,
970
- version: row.version,
971
- name: row.name,
972
- description: row.description,
973
- problem: row.problem,
974
- triggerConditions: JSON.parse(row.trigger_conditions),
975
- solution: row.solution,
976
- verification: row.verification,
977
- examples: JSON.parse(row.examples),
978
- notes: row.notes || void 0,
979
- author: row.author,
980
- tags: JSON.parse(row.tags),
981
- createdAt: new Date(row.created_at),
982
- updatedAt: new Date(row.updated_at),
983
- status: row.status,
984
- parentVersion: row.parent_version || void 0,
985
- derivedFrom: row.derived_from ? JSON.parse(row.derived_from) : void 0,
986
- metrics: JSON.parse(row.metrics),
987
- source: row.source ? JSON.parse(row.source) : void 0,
988
- taxonomy: row.taxonomy ? JSON.parse(row.taxonomy) : void 0,
989
- externalSource: externalSource ? {
990
- ...externalSource,
991
- scrapedAt: new Date(externalSource.scrapedAt)
992
- } : void 0
993
- };
994
- }
995
- serializeSkill(skill) {
996
- return {
997
- ...skill,
998
- createdAt: skill.createdAt.toISOString(),
999
- updatedAt: skill.updatedAt.toISOString(),
1000
- source: skill.source ? {
1001
- ...skill.source,
1002
- importedAt: skill.source.importedAt.toISOString()
1003
- } : void 0,
1004
- metrics: {
1005
- ...skill.metrics,
1006
- lastUsed: skill.metrics.lastUsed?.toISOString()
1007
- }
1008
- };
1009
- }
1010
- deserializeSkill(data) {
1011
- return {
1012
- ...data,
1013
- createdAt: new Date(data.createdAt),
1014
- updatedAt: new Date(data.updatedAt),
1015
- source: data.source ? {
1016
- ...data.source,
1017
- importedAt: new Date(data.source.importedAt)
1018
- } : void 0,
1019
- metrics: {
1020
- ...data.metrics,
1021
- lastUsed: data.metrics.lastUsed ? new Date(data.metrics.lastUsed) : void 0
1022
- }
1023
- };
1024
- }
1025
- hashSkill(skill) {
1026
- const content = JSON.stringify({
1027
- problem: skill.problem,
1028
- solution: skill.solution,
1029
- triggerConditions: skill.triggerConditions
1030
- });
1031
- let hash = 0;
1032
- for (let i = 0; i < content.length; i++) {
1033
- const char = content.charCodeAt(i);
1034
- hash = (hash << 5) - hash + char;
1035
- hash = hash & hash;
1036
- }
1037
- return Math.abs(hash).toString(16);
1038
- }
1039
- };
1040
-
1041
- export {
1042
- BaseStorageAdapter,
1043
- MemoryStorageAdapter,
1044
- SQLiteStorageAdapter
1045
- };