skill-tree 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +102 -2
  2. package/dist/bowser-CQI7RKRA.mjs +2821 -0
  3. package/dist/chunk-2NL4MXNX.mjs +3156 -0
  4. package/dist/chunk-2STDJU5Y.mjs +1174 -0
  5. package/dist/chunk-3BCRI4CA.mjs +101 -0
  6. package/dist/chunk-3SRB47JW.mjs +8344 -0
  7. package/dist/chunk-43YOKLZP.mjs +6081 -0
  8. package/dist/chunk-4AGZU52D.mjs +7918 -0
  9. package/dist/chunk-4HXHCEFH.mjs +9157 -0
  10. package/dist/chunk-4OC5QFIF.mjs +11267 -0
  11. package/dist/chunk-4QGSDVGH.mjs +580 -0
  12. package/dist/chunk-4TFMKAVC.mjs +1225 -0
  13. package/dist/chunk-55SMGVTP.mjs +7126 -0
  14. package/dist/chunk-5C4MEQMR.mjs +125 -0
  15. package/dist/chunk-6FX4IK4Z.mjs +5368 -0
  16. package/dist/chunk-6UPDN5QM.mjs +163 -0
  17. package/dist/chunk-7EGDKOHV.mjs +9439 -0
  18. package/dist/chunk-7LMOQW5H.mjs +4893 -0
  19. package/dist/chunk-7QIQJVNP.mjs +14206 -0
  20. package/dist/chunk-7VB4ZRZO.mjs +7127 -0
  21. package/dist/chunk-A3SILZYX.mjs +8360 -0
  22. package/dist/chunk-BPVRW25O.mjs +6089 -0
  23. package/dist/chunk-BZ2JKJ54.mjs +1057 -0
  24. package/dist/chunk-CI4476KM.mjs +6607 -0
  25. package/dist/chunk-DCRKELD5.mjs +46 -0
  26. package/dist/chunk-DDXYQ74I.mjs +13969 -0
  27. package/dist/chunk-DQOFJXBX.mjs +6595 -0
  28. package/dist/chunk-E2CVK23F.mjs +8751 -0
  29. package/dist/chunk-F3YEUQAP.mjs +654 -0
  30. package/dist/chunk-FKJJ4RJG.mjs +13874 -0
  31. package/dist/chunk-II7DECZQ.mjs +9111 -0
  32. package/dist/chunk-INKVOZXK.mjs +15898 -0
  33. package/dist/chunk-J2JM7HAK.mjs +8787 -0
  34. package/dist/chunk-K6NRCSAZ.mjs +4355 -0
  35. package/dist/chunk-LACI6YL4.mjs +1379 -0
  36. package/dist/chunk-MBIGW6KU.mjs +644 -0
  37. package/dist/chunk-OYHYXKXO.mjs +7297 -0
  38. package/dist/chunk-P5GJJ4JB.mjs +9237 -0
  39. package/dist/chunk-PDPN7FW7.mjs +1045 -0
  40. package/dist/chunk-QNK3WYNA.mjs +8971 -0
  41. package/dist/chunk-QZ7TP4HQ.mjs +7 -0
  42. package/dist/chunk-RJYJGJO3.mjs +349 -0
  43. package/dist/chunk-T4PVQW5O.mjs +124 -0
  44. package/dist/chunk-TEUB6DZR.mjs +6453 -0
  45. package/dist/chunk-TWPEHDW4.mjs +1067 -0
  46. package/dist/chunk-VHFTX33A.mjs +6724 -0
  47. package/dist/chunk-Y54UK2J3.mjs +13071 -0
  48. package/dist/chunk-YDVZIFIU.mjs +2102 -0
  49. package/dist/chunk-ZQVS7MQK.mjs +6081 -0
  50. package/dist/chunk-ZYKRDDFO.mjs +163 -0
  51. package/dist/cli/index.js +1324 -386
  52. package/dist/cli/index.mjs +212 -9074
  53. package/dist/dist-es-2JG6ZWFR.mjs +69 -0
  54. package/dist/dist-es-2JGXQKUP.mjs +6077 -0
  55. package/dist/dist-es-644EP2LP.mjs +317 -0
  56. package/dist/dist-es-DSNCHWLJ.mjs +170 -0
  57. package/dist/dist-es-FIVW7BUZ.mjs +317 -0
  58. package/dist/dist-es-GXJAFBE5.mjs +22 -0
  59. package/dist/dist-es-HRBPKDMR.mjs +935 -0
  60. package/dist/dist-es-LHPJ63IO.mjs +4437 -0
  61. package/dist/dist-es-LT2AQAG7.mjs +4437 -0
  62. package/dist/dist-es-ORE4PQTL.mjs +87 -0
  63. package/dist/dist-es-TLCYJJ25.mjs +495 -0
  64. package/dist/dist-es-V4LHTSRG.mjs +69 -0
  65. package/dist/dist-es-XHTU3ZU2.mjs +935 -0
  66. package/dist/dist-es-Y2MPJ6IO.mjs +378 -0
  67. package/dist/dist-es-ZYHLY2E6.mjs +487 -0
  68. package/dist/event-streams-KIAAAC7Z.mjs +42 -0
  69. package/dist/index.d.mts +1143 -56
  70. package/dist/index.d.ts +1143 -56
  71. package/dist/index.js +38701 -499
  72. package/dist/index.mjs +129 -9612
  73. package/dist/loadSso-NPRY7QRT.mjs +579 -0
  74. package/dist/loadSso-OYKG6ZRE.mjs +579 -0
  75. package/dist/signin-LMFNL434.mjs +665 -0
  76. package/dist/signin-LUKXFXSI.mjs +743 -0
  77. package/dist/sqlite-MG45OOTV.mjs +6 -0
  78. package/dist/sqlite-OLU72GHB.mjs +6 -0
  79. package/dist/sqlite-RR2SJ3SR.mjs +7 -0
  80. package/dist/sqlite-XJRPMNAJ.mjs +6 -0
  81. package/dist/sso-oidc-NNH6SQIH.mjs +832 -0
  82. package/dist/sso-oidc-STZH2XK2.mjs +832 -0
  83. package/dist/sts-EF755UBF.mjs +6290 -0
  84. package/dist/sts-ZIS4G6FQ.mjs +6290 -0
  85. package/dist/sync-BSWMMDA6.mjs +14 -0
  86. package/dist/sync-WHIIDHML.mjs +14 -0
  87. package/dist/sync-XRWFQYBY.mjs +15 -0
  88. package/package.json +9 -2
  89. package/dist/cli/index.js.map +0 -1
  90. package/dist/cli/index.mjs.map +0 -1
  91. package/dist/index.js.map +0 -1
  92. package/dist/index.mjs.map +0 -1
package/dist/cli/index.js CHANGED
@@ -31,11 +31,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  ));
32
32
 
33
33
  // src/storage/base.ts
34
- var BaseStorageAdapter, MemoryStorageAdapter;
34
+ var _BaseStorageAdapter, BaseStorageAdapter, MemoryStorageAdapter;
35
35
  var init_base = __esm({
36
36
  "src/storage/base.ts"() {
37
37
  "use strict";
38
- BaseStorageAdapter = class {
38
+ _BaseStorageAdapter = class _BaseStorageAdapter {
39
39
  constructor() {
40
40
  this.initialized = false;
41
41
  }
@@ -63,9 +63,6 @@ var init_base = __esm({
63
63
  if (filter.author && skill.author !== filter.author) {
64
64
  return false;
65
65
  }
66
- if (filter.minSuccessRate !== void 0 && skill.metrics.successRate < filter.minSuccessRate) {
67
- return false;
68
- }
69
66
  if (filter.createdAfter && skill.createdAt < filter.createdAfter) {
70
67
  return false;
71
68
  }
@@ -130,19 +127,118 @@ var init_base = __esm({
130
127
  * Simple text search across skill fields
131
128
  */
132
129
  textSearch(skills, query) {
133
- const lowerQuery = query.toLowerCase();
134
- const terms = lowerQuery.split(/\s+/).filter((t) => t.length > 0);
135
- return skills.filter((skill) => {
136
- const searchText = [
137
- skill.name,
138
- skill.description,
139
- skill.instructions,
140
- ...skill.tags
141
- ].join(" ").toLowerCase();
142
- return terms.every((term) => searchText.includes(term));
130
+ const queryTerms = this.tokenize(query);
131
+ if (queryTerms.length === 0) return [];
132
+ const N = skills.length;
133
+ const k1 = 1.5;
134
+ const b = 0.75;
135
+ const docs = skills.map((skill) => ({
136
+ skill,
137
+ terms: this.tokenize(
138
+ [skill.name, skill.description, ...skill.tags].join(" ")
139
+ )
140
+ }));
141
+ const avgDl = docs.reduce((sum, d) => sum + d.terms.length, 0) / (N || 1);
142
+ const df = /* @__PURE__ */ new Map();
143
+ for (const doc of docs) {
144
+ const unique = new Set(doc.terms);
145
+ for (const t of unique) {
146
+ df.set(t, (df.get(t) ?? 0) + 1);
147
+ }
148
+ }
149
+ const scored = docs.map((doc) => {
150
+ let score = 0;
151
+ const tf = /* @__PURE__ */ new Map();
152
+ for (const t of doc.terms) {
153
+ tf.set(t, (tf.get(t) ?? 0) + 1);
154
+ }
155
+ for (const qt of queryTerms) {
156
+ const termDf = df.get(qt) ?? 0;
157
+ if (termDf === 0) continue;
158
+ const idf = Math.log((N - termDf + 0.5) / (termDf + 0.5) + 1);
159
+ const termTf = tf.get(qt) ?? 0;
160
+ const tfNorm = termTf * (k1 + 1) / (termTf + k1 * (1 - b + b * doc.terms.length / avgDl));
161
+ score += idf * tfNorm;
162
+ }
163
+ return { skill: doc.skill, score };
143
164
  });
165
+ return scored.filter((s) => s.score > 0).sort((a, b2) => b2.score - a.score).map((s) => s.skill);
166
+ }
167
+ tokenize(text) {
168
+ return text.toLowerCase().split(/\W+/).filter((t) => t.length > 1 && !_BaseStorageAdapter.STOP_WORDS.has(t)).map((t) => _BaseStorageAdapter.stem(t));
169
+ }
170
+ static stem(word) {
171
+ if (word.length <= 3) return word;
172
+ let w = word;
173
+ w = w.replace(/ies$/, "y");
174
+ w = w.replace(/(ation|tion)$/, "t");
175
+ w = w.replace(/sion$/, "s");
176
+ w = w.replace(/(ing|ment|ness|able|ible|ous|ive|ful|less|ize|ise|ance|ence)$/, "");
177
+ w = w.replace(/([^aeiou])ed$/, "$1");
178
+ w = w.replace(/es$/, "");
179
+ w = w.replace(/([^s])s$/, "$1");
180
+ w = w.replace(/(.)\1$/, "$1");
181
+ if (w.length <= 1) return word;
182
+ return w;
144
183
  }
145
184
  };
185
+ _BaseStorageAdapter.STOP_WORDS = /* @__PURE__ */ new Set([
186
+ "the",
187
+ "a",
188
+ "an",
189
+ "is",
190
+ "are",
191
+ "was",
192
+ "were",
193
+ "be",
194
+ "been",
195
+ "being",
196
+ "have",
197
+ "has",
198
+ "had",
199
+ "do",
200
+ "does",
201
+ "did",
202
+ "will",
203
+ "would",
204
+ "could",
205
+ "should",
206
+ "may",
207
+ "might",
208
+ "shall",
209
+ "can",
210
+ "to",
211
+ "of",
212
+ "in",
213
+ "for",
214
+ "on",
215
+ "with",
216
+ "at",
217
+ "by",
218
+ "from",
219
+ "as",
220
+ "into",
221
+ "through",
222
+ "and",
223
+ "but",
224
+ "or",
225
+ "not",
226
+ "so",
227
+ "yet",
228
+ "if",
229
+ "when",
230
+ "that",
231
+ "this",
232
+ "it",
233
+ "its",
234
+ "also",
235
+ "which",
236
+ "what",
237
+ "how",
238
+ "why",
239
+ "where"
240
+ ]);
241
+ BaseStorageAdapter = _BaseStorageAdapter;
146
242
  MemoryStorageAdapter = class extends BaseStorageAdapter {
147
243
  constructor() {
148
244
  super(...arguments);
@@ -358,12 +454,19 @@ var init_sqlite = __esm({
358
454
  status TEXT NOT NULL,
359
455
  parent_version TEXT,
360
456
  derived_from TEXT,
361
- metrics TEXT NOT NULL,
362
457
  source TEXT,
363
458
  taxonomy TEXT,
364
459
  external_source TEXT
365
460
  )
366
461
  `);
462
+ try {
463
+ db.exec("ALTER TABLE skills DROP COLUMN metrics");
464
+ } catch {
465
+ const cols = db.prepare("PRAGMA table_info(skills)").all();
466
+ if (cols.some((c) => c.name === "metrics")) {
467
+ this.rebuildSkillsTableWithoutMetrics(db, cols);
468
+ }
469
+ }
367
470
  db.exec(`
368
471
  CREATE TABLE IF NOT EXISTS skill_versions (
369
472
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -527,6 +630,75 @@ var init_sqlite = __esm({
527
630
  db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
528
631
  }
529
632
  }
633
+ /**
634
+ * Rebuild the `skills` table without the legacy `metrics` column.
635
+ *
636
+ * Used as the fallback path on SQLite < 3.35 where ALTER TABLE DROP
637
+ * COLUMN isn't supported. Uses the standard "table dance" pattern:
638
+ * CREATE NEW → INSERT FROM OLD → DROP OLD → RENAME, inside a single
639
+ * transaction so a partial state can't outlive a crash.
640
+ *
641
+ * The `existingCols` arg is the full PRAGMA table_info output for the
642
+ * legacy table — we use it to figure out which columns to copy. Some
643
+ * legacy installs may have a subset of the current schema's columns
644
+ * (e.g., taxonomy/external_source were added in v2, related in v3),
645
+ * so we only copy columns that exist on both sides.
646
+ */
647
+ rebuildSkillsTableWithoutMetrics(db, existingCols) {
648
+ const NEW_SCHEMA_COLUMNS = [
649
+ "id",
650
+ "version",
651
+ "name",
652
+ "description",
653
+ "instructions",
654
+ "related",
655
+ "author",
656
+ "tags",
657
+ "created_at",
658
+ "updated_at",
659
+ "status",
660
+ "parent_version",
661
+ "derived_from",
662
+ "source",
663
+ "taxonomy",
664
+ "external_source"
665
+ ];
666
+ const sourceNames = new Set(existingCols.map((c) => c.name));
667
+ const copyable = NEW_SCHEMA_COLUMNS.filter((c) => sourceNames.has(c));
668
+ const copyList = copyable.join(", ");
669
+ db.exec("BEGIN");
670
+ try {
671
+ db.exec(`
672
+ CREATE TABLE skills_new (
673
+ id TEXT PRIMARY KEY,
674
+ version TEXT NOT NULL,
675
+ name TEXT NOT NULL,
676
+ description TEXT,
677
+ instructions TEXT NOT NULL DEFAULT '',
678
+ related TEXT,
679
+ author TEXT NOT NULL,
680
+ tags TEXT NOT NULL,
681
+ created_at TEXT NOT NULL,
682
+ updated_at TEXT NOT NULL,
683
+ status TEXT NOT NULL,
684
+ parent_version TEXT,
685
+ derived_from TEXT,
686
+ source TEXT,
687
+ taxonomy TEXT,
688
+ external_source TEXT
689
+ )
690
+ `);
691
+ db.exec(
692
+ `INSERT INTO skills_new (${copyList}) SELECT ${copyList} FROM skills`
693
+ );
694
+ db.exec("DROP TABLE skills");
695
+ db.exec("ALTER TABLE skills_new RENAME TO skills");
696
+ db.exec("COMMIT");
697
+ } catch (err) {
698
+ db.exec("ROLLBACK");
699
+ throw err;
700
+ }
701
+ }
530
702
  getDb() {
531
703
  if (!this.db) {
532
704
  throw new Error("Database not initialized. Call initialize() first.");
@@ -539,10 +711,10 @@ var init_sqlite = __esm({
539
711
  const stmt = db.prepare(`
540
712
  INSERT OR REPLACE INTO skills (
541
713
  id, version, name, description, instructions, related, author, tags,
542
- created_at, updated_at, status, parent_version, derived_from, metrics,
714
+ created_at, updated_at, status, parent_version, derived_from,
543
715
  source, taxonomy, external_source
544
716
  ) VALUES (
545
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
717
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
546
718
  )
547
719
  `);
548
720
  stmt.run(
@@ -559,7 +731,6 @@ var init_sqlite = __esm({
559
731
  skill.status,
560
732
  skill.parentVersion || null,
561
733
  skill.derivedFrom ? JSON.stringify(skill.derivedFrom) : null,
562
- JSON.stringify(skill.metrics),
563
734
  skill.source ? JSON.stringify(skill.source) : null,
564
735
  skill.taxonomy ? JSON.stringify(skill.taxonomy) : null,
565
736
  skill.externalSource ? JSON.stringify({
@@ -654,10 +825,6 @@ var init_sqlite = __esm({
654
825
  sql += " AND author = ?";
655
826
  params.push(filter.author);
656
827
  }
657
- if (filter?.minSuccessRate !== void 0) {
658
- sql += " AND json_extract(metrics, '$.successRate') >= ?";
659
- params.push(filter.minSuccessRate);
660
- }
661
828
  if (filter?.createdAfter) {
662
829
  sql += " AND created_at >= ?";
663
830
  params.push(filter.createdAfter.toISOString());
@@ -853,15 +1020,15 @@ var init_sqlite = __esm({
853
1020
  /**
854
1021
  * Get or create a taxonomy node
855
1022
  */
856
- async ensureTaxonomyNode(path18) {
1023
+ async ensureTaxonomyNode(path19) {
857
1024
  this.ensureInitialized();
858
1025
  const db = this.getDb();
859
- const pathStr = path18.join("/");
1026
+ const pathStr = path19.join("/");
860
1027
  const existing = db.prepare("SELECT id FROM taxonomy_nodes WHERE path = ?").get(pathStr);
861
1028
  if (existing) return existing.id;
862
1029
  const id = `node-${pathStr.replace(/\//g, "-").toLowerCase()}`;
863
- const name = path18[path18.length - 1] || "Root";
864
- const parentPath = path18.slice(0, -1);
1030
+ const name = path19[path19.length - 1] || "Root";
1031
+ const parentPath = path19.slice(0, -1);
865
1032
  let parentId = null;
866
1033
  if (parentPath.length > 0) {
867
1034
  parentId = await this.ensureTaxonomyNode(parentPath);
@@ -1052,7 +1219,6 @@ var init_sqlite = __esm({
1052
1219
  status: row.status,
1053
1220
  parentVersion: row.parent_version || void 0,
1054
1221
  derivedFrom: row.derived_from ? JSON.parse(row.derived_from) : void 0,
1055
- metrics: JSON.parse(row.metrics),
1056
1222
  source: row.source ? JSON.parse(row.source) : void 0,
1057
1223
  taxonomy: row.taxonomy ? JSON.parse(row.taxonomy) : void 0,
1058
1224
  externalSource: externalSource ? {
@@ -1069,11 +1235,7 @@ var init_sqlite = __esm({
1069
1235
  source: skill.source ? {
1070
1236
  ...skill.source,
1071
1237
  importedAt: skill.source.importedAt.toISOString()
1072
- } : void 0,
1073
- metrics: {
1074
- ...skill.metrics,
1075
- lastUsed: skill.metrics.lastUsed?.toISOString()
1076
- }
1238
+ } : void 0
1077
1239
  };
1078
1240
  }
1079
1241
  deserializeSkill(data) {
@@ -1084,11 +1246,7 @@ var init_sqlite = __esm({
1084
1246
  source: data.source ? {
1085
1247
  ...data.source,
1086
1248
  importedAt: new Date(data.source.importedAt)
1087
- } : void 0,
1088
- metrics: {
1089
- ...data.metrics,
1090
- lastUsed: data.metrics.lastUsed ? new Date(data.metrics.lastUsed) : void 0
1091
- }
1249
+ } : void 0
1092
1250
  };
1093
1251
  }
1094
1252
  hashSkill(skill) {
@@ -1294,9 +1452,6 @@ ${this.indentContent(skill.instructions, 4)}
1294
1452
  if (filter.tags && filter.tags.length > 0) {
1295
1453
  result = result.filter((s) => s.tags.some((t) => filter.tags.includes(t)));
1296
1454
  }
1297
- if (filter.minSuccessRate !== void 0) {
1298
- result = result.filter((s) => s.metrics.successRate >= filter.minSuccessRate);
1299
- }
1300
1455
  if (filter.limit) {
1301
1456
  result = result.slice(0, filter.limit);
1302
1457
  }
@@ -1393,11 +1548,6 @@ var init_parser = __esm({
1393
1548
  createdAt: defaults?.createdAt || now,
1394
1549
  updatedAt: now,
1395
1550
  status: "active",
1396
- metrics: defaults?.metrics || {
1397
- usageCount: 0,
1398
- successRate: 0,
1399
- feedbackScores: []
1400
- },
1401
1551
  source: {
1402
1552
  type: "imported",
1403
1553
  location: "AGENTS.md",
@@ -1765,8 +1915,6 @@ var init_sync = __esm({
1765
1915
  // Keep existing ID
1766
1916
  createdAt: existing.createdAt,
1767
1917
  // Preserve creation date
1768
- metrics: existing.metrics,
1769
- // Preserve usage metrics
1770
1918
  source: incoming.source || existing.source,
1771
1919
  parentVersion: existing.version
1772
1920
  // Track update lineage
@@ -2007,10 +2155,6 @@ var ConflictStore = class {
2007
2155
  ...skill,
2008
2156
  createdAt: skill.createdAt instanceof Date ? skill.createdAt.toISOString() : skill.createdAt,
2009
2157
  updatedAt: skill.updatedAt instanceof Date ? skill.updatedAt.toISOString() : skill.updatedAt,
2010
- metrics: {
2011
- ...skill.metrics,
2012
- lastUsed: skill.metrics?.lastUsed instanceof Date ? skill.metrics.lastUsed.toISOString() : skill.metrics?.lastUsed
2013
- },
2014
2158
  source: skill.source ? {
2015
2159
  ...skill.source,
2016
2160
  importedAt: skill.source.importedAt instanceof Date ? skill.source.importedAt.toISOString() : skill.source.importedAt
@@ -2022,10 +2166,6 @@ var ConflictStore = class {
2022
2166
  ...data,
2023
2167
  createdAt: new Date(data.createdAt),
2024
2168
  updatedAt: new Date(data.updatedAt),
2025
- metrics: {
2026
- ...data.metrics,
2027
- lastUsed: data.metrics?.lastUsed ? new Date(data.metrics.lastUsed) : void 0
2028
- },
2029
2169
  source: data.source ? {
2030
2170
  ...data.source,
2031
2171
  importedAt: new Date(data.source.importedAt)
@@ -2592,12 +2732,7 @@ ${remoteValue}`;
2592
2732
  tags: metadata.tags ? metadata.tags.split(",").map((t) => t.trim()) : [],
2593
2733
  createdAt: metadata.created ? new Date(metadata.created) : /* @__PURE__ */ new Date(),
2594
2734
  updatedAt: metadata.updated ? new Date(metadata.updated) : /* @__PURE__ */ new Date(),
2595
- status: isValidStatus(metadata.status) ? metadata.status : "active",
2596
- metrics: {
2597
- usageCount: 0,
2598
- successRate: 0,
2599
- feedbackScores: []
2600
- }
2735
+ status: isValidStatus(metadata.status) ? metadata.status : "active"
2601
2736
  };
2602
2737
  }
2603
2738
  async writeSkill(skill) {
@@ -2884,15 +3019,12 @@ var DEFAULT_CONFIG = {
2884
3019
  maxSummaryLength: 80,
2885
3020
  format: "xml"
2886
3021
  };
2887
- var CatalogRenderer = class _CatalogRenderer {
3022
+ var _CatalogRenderer = class _CatalogRenderer {
2888
3023
  constructor(storage, config2) {
2889
3024
  this.storage = storage;
2890
3025
  this.overviewCache = null;
2891
3026
  this.config = { ...DEFAULT_CONFIG, ...config2 };
2892
3027
  }
2893
- static {
2894
- this.CACHE_TTL_MS = 6e4;
2895
- }
2896
3028
  /**
2897
3029
  * Render level-0 catalog overview for system prompt injection.
2898
3030
  * Shows top-level categories with counts. ~200 tokens.
@@ -2920,11 +3052,11 @@ var CatalogRenderer = class _CatalogRenderer {
2920
3052
  * Render a specific category path for browse drill-down.
2921
3053
  * Shows subcategories at intermediate nodes, or skill summaries at leaf nodes.
2922
3054
  */
2923
- async renderCategory(path18) {
3055
+ async renderCategory(path19) {
2924
3056
  if (hasCatalogSupport(this.storage)) {
2925
- return this.renderCategoryFromTaxonomy(this.storage, path18);
3057
+ return this.renderCategoryFromTaxonomy(this.storage, path19);
2926
3058
  }
2927
- return this.renderCategoryFromTags(path18);
3059
+ return this.renderCategoryFromTags(path19);
2928
3060
  }
2929
3061
  /**
2930
3062
  * Invalidate the overview cache (e.g., after skill changes).
@@ -2962,9 +3094,9 @@ var CatalogRenderer = class _CatalogRenderer {
2962
3094
  const categories = counted.sort((a, b) => b.count - a.count).slice(0, this.config.maxCategoriesPerLevel).map((c) => ({ name: c.node.name, count: c.count }));
2963
3095
  return this.renderOverviewXml(totalSkills, categories);
2964
3096
  }
2965
- async renderCategoryFromTaxonomy(storage, path18) {
2966
- const tree = await storage.getTaxonomyTree(path18);
2967
- const pathStr = path18.join("/");
3097
+ async renderCategoryFromTaxonomy(storage, path19) {
3098
+ const tree = await storage.getTaxonomyTree(path19);
3099
+ const pathStr = path19.join("/");
2968
3100
  if (tree.length > 0 && tree.some((n) => n.children.length > 0)) {
2969
3101
  const root = tree[0];
2970
3102
  const rootCount = this.countNodeSkills(root);
@@ -2974,7 +3106,7 @@ var CatalogRenderer = class _CatalogRenderer {
2974
3106
  lines.push(`<catalog_browse path="${escapeXml(pathStr)}" count="${rootCount}">`);
2975
3107
  lines.push(" <subcategories>");
2976
3108
  for (const { node: child, count } of children) {
2977
- const childPath = [...path18, child.name].join("/");
3109
+ const childPath = [...path19, child.name].join("/");
2978
3110
  lines.push(` <category path="${escapeXml(childPath)}" count="${count}" />`);
2979
3111
  }
2980
3112
  lines.push(" </subcategories>");
@@ -2998,13 +3130,13 @@ var CatalogRenderer = class _CatalogRenderer {
2998
3130
  const categories = Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, this.config.maxCategoriesPerLevel).map(([name, count]) => ({ name, count }));
2999
3131
  return this.renderOverviewXml(skills.length, categories);
3000
3132
  }
3001
- async renderCategoryFromTags(path18) {
3002
- if (path18.length === 0) {
3133
+ async renderCategoryFromTags(path19) {
3134
+ if (path19.length === 0) {
3003
3135
  return this.renderOverviewFromTags();
3004
3136
  }
3005
- const tag = path18[0];
3137
+ const tag = path19[0];
3006
3138
  const matching = await this.storage.listSkills({ status: ["active"], tags: [tag] });
3007
- const pathStr = path18.join("/");
3139
+ const pathStr = path19.join("/");
3008
3140
  return this.renderLeafSkills(matching, pathStr, matching.length);
3009
3141
  }
3010
3142
  // ===========================================================================
@@ -3057,12 +3189,276 @@ var CatalogRenderer = class _CatalogRenderer {
3057
3189
  return count;
3058
3190
  }
3059
3191
  };
3192
+ _CatalogRenderer.CACHE_TTL_MS = 6e4;
3193
+ var CatalogRenderer = _CatalogRenderer;
3194
+
3195
+ // src/serving/term-similarity.ts
3196
+ var STOP_WORDS = /* @__PURE__ */ new Set([
3197
+ "the",
3198
+ "a",
3199
+ "an",
3200
+ "is",
3201
+ "are",
3202
+ "was",
3203
+ "were",
3204
+ "be",
3205
+ "been",
3206
+ "being",
3207
+ "have",
3208
+ "has",
3209
+ "had",
3210
+ "do",
3211
+ "does",
3212
+ "did",
3213
+ "will",
3214
+ "would",
3215
+ "could",
3216
+ "should",
3217
+ "may",
3218
+ "might",
3219
+ "shall",
3220
+ "can",
3221
+ "to",
3222
+ "of",
3223
+ "in",
3224
+ "for",
3225
+ "on",
3226
+ "with",
3227
+ "at",
3228
+ "by",
3229
+ "from",
3230
+ "as",
3231
+ "into",
3232
+ "through",
3233
+ "during",
3234
+ "before",
3235
+ "after",
3236
+ "and",
3237
+ "but",
3238
+ "or",
3239
+ "nor",
3240
+ "not",
3241
+ "so",
3242
+ "yet",
3243
+ "both",
3244
+ "either",
3245
+ "neither",
3246
+ "each",
3247
+ "every",
3248
+ "all",
3249
+ "any",
3250
+ "few",
3251
+ "more",
3252
+ "most",
3253
+ "other",
3254
+ "some",
3255
+ "such",
3256
+ "no",
3257
+ "only",
3258
+ "own",
3259
+ "same",
3260
+ "than",
3261
+ "too",
3262
+ "very",
3263
+ "just",
3264
+ "because",
3265
+ "if",
3266
+ "when",
3267
+ "that",
3268
+ "this",
3269
+ "it",
3270
+ "its",
3271
+ "also",
3272
+ "which",
3273
+ "what",
3274
+ "how",
3275
+ "why",
3276
+ "where"
3277
+ ]);
3278
+ function tokenizeList(text) {
3279
+ return text.toLowerCase().split(/\W+/).filter((t) => t.length > 2 && !STOP_WORDS.has(t));
3280
+ }
3281
+ function tokenize(text) {
3282
+ return new Set(tokenizeList(text));
3283
+ }
3284
+
3285
+ // src/serving/hybrid-retrieval.ts
3286
+ var DEFAULT_FIELD_WEIGHTS = {
3287
+ name: 10,
3288
+ description: 5,
3289
+ body: 5,
3290
+ tags: 3
3291
+ };
3292
+ var DEFAULTS = {
3293
+ rrfK: 60,
3294
+ bm25K1: 1.2,
3295
+ bm25B: 0.75,
3296
+ bm25Saturation: 8,
3297
+ signalWeights: { lexical: 0.5, dense: 0.5 }
3298
+ };
3299
+ function buildFieldDoc(skill, fw) {
3300
+ const fields = [
3301
+ [fw.name, tokenizeList(skill.name)],
3302
+ [fw.description, tokenizeList(skill.description)],
3303
+ [fw.body, tokenizeList(skill.instructions ?? "")],
3304
+ [fw.tags, skill.tags.flatMap((t) => tokenizeList(t))]
3305
+ ];
3306
+ const wtf = /* @__PURE__ */ new Map();
3307
+ let dl = 0;
3308
+ for (const [weight, toks] of fields) {
3309
+ if (weight <= 0) continue;
3310
+ dl += weight * toks.length;
3311
+ for (const t of toks) wtf.set(t, (wtf.get(t) ?? 0) + weight);
3312
+ }
3313
+ return { id: skill.id, wtf, dl };
3314
+ }
3315
+ function bm25Scores(query, skills, fieldWeights = DEFAULT_FIELD_WEIGHTS, k1 = DEFAULTS.bm25K1, b = DEFAULTS.bm25B) {
3316
+ const scores = /* @__PURE__ */ new Map();
3317
+ if (skills.length === 0) return scores;
3318
+ const queryTerms = [...tokenize(query)];
3319
+ const docs = skills.map((s) => buildFieldDoc(s, fieldWeights));
3320
+ const N = docs.length;
3321
+ const avgdl = docs.reduce((sum, d) => sum + d.dl, 0) / N || 1;
3322
+ const df = /* @__PURE__ */ new Map();
3323
+ for (const t of queryTerms) {
3324
+ let count = 0;
3325
+ for (const d of docs) if (d.wtf.has(t)) count++;
3326
+ df.set(t, count);
3327
+ }
3328
+ for (const d of docs) {
3329
+ let score = 0;
3330
+ for (const t of queryTerms) {
3331
+ const f = d.wtf.get(t);
3332
+ if (!f) continue;
3333
+ const n = df.get(t);
3334
+ const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
3335
+ const denom = f + k1 * (1 - b + b * (d.dl / avgdl));
3336
+ score += idf * (f * (k1 + 1) / (denom || 1));
3337
+ }
3338
+ scores.set(d.id, score);
3339
+ }
3340
+ return scores;
3341
+ }
3342
+ function cosineSimilarity(a, b) {
3343
+ const len = Math.min(a.length, b.length);
3344
+ let dot = 0;
3345
+ let na = 0;
3346
+ let nb = 0;
3347
+ for (let i = 0; i < len; i++) {
3348
+ dot += a[i] * b[i];
3349
+ na += a[i] * a[i];
3350
+ nb += b[i] * b[i];
3351
+ }
3352
+ if (na === 0 || nb === 0) return 0;
3353
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
3354
+ }
3355
+ function reciprocalRankFusion(rankings, k = DEFAULTS.rrfK) {
3356
+ const fused = /* @__PURE__ */ new Map();
3357
+ for (const ranking of rankings) {
3358
+ ranking.forEach((id, idx) => {
3359
+ fused.set(id, (fused.get(id) ?? 0) + 1 / (k + idx + 1));
3360
+ });
3361
+ }
3362
+ return fused;
3363
+ }
3364
+ function skillEmbedText(skill) {
3365
+ return [
3366
+ skill.name,
3367
+ skill.description,
3368
+ skill.tags.join(" "),
3369
+ skill.instructions ?? ""
3370
+ ].join("\n");
3371
+ }
3372
+ function rankByScore(scores) {
3373
+ return [...scores.entries()].sort((a, b) => b[1] - a[1]).map(([id]) => id);
3374
+ }
3375
+ async function scoreSkillsHybrid(query, skills, options = {}) {
3376
+ if (skills.length === 0) return [];
3377
+ const fieldWeights = options.fieldWeights ?? DEFAULT_FIELD_WEIGHTS;
3378
+ const sat = options.bm25Saturation ?? DEFAULTS.bm25Saturation;
3379
+ const raw = bm25Scores(
3380
+ query,
3381
+ skills,
3382
+ fieldWeights,
3383
+ options.bm25K1 ?? DEFAULTS.bm25K1,
3384
+ options.bm25B ?? DEFAULTS.bm25B
3385
+ );
3386
+ const lexAbs = /* @__PURE__ */ new Map();
3387
+ for (const s of skills) {
3388
+ const r = raw.get(s.id) ?? 0;
3389
+ lexAbs.set(s.id, r / (r + sat));
3390
+ }
3391
+ let denseAbs = null;
3392
+ if (options.embedder) {
3393
+ try {
3394
+ const vectors = await options.embedder.embed([
3395
+ query,
3396
+ ...skills.map(skillEmbedText)
3397
+ ]);
3398
+ const queryVec = vectors[0];
3399
+ if (queryVec && vectors.length === skills.length + 1) {
3400
+ denseAbs = /* @__PURE__ */ new Map();
3401
+ skills.forEach((s, i) => {
3402
+ denseAbs.set(s.id, Math.max(0, cosineSimilarity(queryVec, vectors[i + 1])));
3403
+ });
3404
+ }
3405
+ } catch {
3406
+ denseAbs = null;
3407
+ }
3408
+ }
3409
+ const sw = options.signalWeights ?? DEFAULTS.signalWeights;
3410
+ const wLex = sw.lexical;
3411
+ const wDense = denseAbs ? sw.dense : 0;
3412
+ const wSum = wLex + wDense || 1;
3413
+ const useUtility = !!options.utilityScorer?.trained;
3414
+ const scored = skills.map((s) => {
3415
+ const l = lexAbs.get(s.id) ?? 0;
3416
+ const d = denseAbs?.get(s.id) ?? 0;
3417
+ const relevanceScore = useUtility ? options.utilityScorer.score({ skillId: s.id, lexAbs: l, denseAbs: d }) : (wLex * l + wDense * d) / wSum;
3418
+ return { skill: s, relevanceScore };
3419
+ });
3420
+ if (!useUtility && (options.fusion ?? "weighted") === "rrf" && denseAbs) {
3421
+ const rrf = reciprocalRankFusion(
3422
+ [rankByScore(lexAbs), rankByScore(denseAbs)],
3423
+ options.rrfK ?? DEFAULTS.rrfK
3424
+ );
3425
+ scored.sort(
3426
+ (a, b) => (rrf.get(b.skill.id) ?? 0) - (rrf.get(a.skill.id) ?? 0) || b.relevanceScore - a.relevanceScore
3427
+ );
3428
+ } else {
3429
+ scored.sort((a, b) => b.relevanceScore - a.relevanceScore);
3430
+ }
3431
+ if (options.reranker) {
3432
+ const topN = options.rerankTopN ?? 20;
3433
+ const head = scored.slice(0, topN);
3434
+ if (head.length > 1) {
3435
+ const ranked = await options.reranker.rerank(
3436
+ query,
3437
+ head.map((h) => ({
3438
+ id: h.skill.id,
3439
+ text: `${h.skill.name}
3440
+ ${h.skill.description}
3441
+ ${h.skill.instructions ?? ""}`
3442
+ }))
3443
+ );
3444
+ const byId = new Map(head.map((h) => [h.skill.id, h.skill]));
3445
+ const fusedDesc = head.map((h) => h.relevanceScore);
3446
+ const reordered = ranked.filter((r) => byId.has(r.id)).map((r, i) => ({ skill: byId.get(r.id), relevanceScore: fusedDesc[i] ?? 0 }));
3447
+ const headIds = new Set(reordered.map((s) => s.skill.id));
3448
+ const tail = scored.filter((s) => !headIds.has(s.skill.id));
3449
+ return [...reordered, ...tail];
3450
+ }
3451
+ }
3452
+ return scored;
3453
+ }
3060
3454
 
3061
3455
  // src/serving/loadout-compiler.ts
3062
3456
  var DEFAULT_CONFIG2 = {
3063
3457
  defaultMaxSkills: 15,
3064
3458
  defaultStatus: ["active"],
3065
- semanticThreshold: 0.6
3459
+ semanticThreshold: 0.6,
3460
+ retrieval: {},
3461
+ scoringPoolSize: 200
3066
3462
  };
3067
3463
  var LoadoutCompiler = class {
3068
3464
  constructor(storage, config2) {
@@ -3073,7 +3469,24 @@ var LoadoutCompiler = class {
3073
3469
  };
3074
3470
  }
3075
3471
  /**
3076
- * Main entry point - compile skills from criteria
3472
+ * Main entry point - compile skills from criteria.
3473
+ *
3474
+ * Filter pipeline order:
3475
+ * 1. status (initial query)
3476
+ * 2. exclude (drop matching IDs)
3477
+ * 3. tags / tagsAll
3478
+ * 4. author
3479
+ * 5. semantic (currently no-op)
3480
+ * 6. relationships (rootSkills traversal)
3481
+ * 7. **include** — presence guarantee: ensures every ID in the
3482
+ * include list is in the result regardless of the filters above,
3483
+ * fetching missing ones from storage as needed. `exclude` still
3484
+ * wins (excluded IDs are removed from the include list before
3485
+ * this step).
3486
+ * 8. limits (maxSkills, maxTokens)
3487
+ *
3488
+ * For "restrict to exactly these skills" semantics, combine
3489
+ * `include: [...]` with `maxSkills: include.length`.
3077
3490
  */
3078
3491
  async compile(criteria) {
3079
3492
  const status = criteria.status ?? this.config.defaultStatus;
@@ -3083,6 +3496,7 @@ var LoadoutCompiler = class {
3083
3496
  candidates = this.applyQualityFilters(candidates, criteria);
3084
3497
  candidates = await this.applySemanticFilters(candidates, criteria);
3085
3498
  candidates = await this.applyRelationshipFilters(candidates, criteria);
3499
+ candidates = await this.ensureIncludedPresent(candidates, criteria);
3086
3500
  candidates = this.applyLimits(candidates, criteria);
3087
3501
  return candidates;
3088
3502
  }
@@ -3096,6 +3510,66 @@ var LoadoutCompiler = class {
3096
3510
  maxSkills: this.config.defaultMaxSkills
3097
3511
  });
3098
3512
  }
3513
+ /**
3514
+ * Compile with hybrid-retrieval scoring against a task description
3515
+ * (Tier 1). Returns skills annotated with absolute relevance scores in
3516
+ * [0,1], sorted by descending relevance. Used by the hybrid loadout
3517
+ * strategy to determine which skills should be auto-expanded vs shown
3518
+ * as summaries vs excluded.
3519
+ *
3520
+ * Scoring uses field-weighted BM25 over the skill name/description/body/
3521
+ * tags (the body matters most), optionally fused with dense embeddings
3522
+ * (when an `embedder` is
3523
+ * configured via `retrieval`). The candidate pool is the (filtered) set
3524
+ * up to `scoringPoolSize` — larger than the final loadout — so the ranker
3525
+ * re-ranks a real corpus rather than only a lexically pre-truncated top-N.
3526
+ */
3527
+ async compileWithScoring(taskDescription, criteria) {
3528
+ const baseCriteria = {
3529
+ ...criteria,
3530
+ taskDescription,
3531
+ // Score a broad pool, not the final loadout size. Callers narrow the
3532
+ // result via partitionByConfidence + maxExpanded downstream.
3533
+ maxSkills: criteria?.maxSkills ?? this.config.scoringPoolSize
3534
+ };
3535
+ const skills = await this.compile(baseCriteria);
3536
+ return scoreSkillsHybrid(taskDescription, skills, this.config.retrieval);
3537
+ }
3538
+ /**
3539
+ * Partition scored skills into confidence tiers.
3540
+ * - High confidence (>= expandAbove): should be auto-expanded
3541
+ * - Medium confidence (>= includeAbove): included as summaries
3542
+ * - Below includeAbove: excluded
3543
+ *
3544
+ * Abstain floor (Tier 1, T1.3): if `thresholds.minConfidence` is set and
3545
+ * even the single best-scoring skill is below it, the whole loadout
3546
+ * abstains — every skill is excluded and **nothing** is injected. This
3547
+ * makes "no sufficiently relevant skill" a first-class outcome (B=0),
3548
+ * which prevents irrelevant skills from dragging task success below the
3549
+ * no-skill baseline. `scored` is expected to be sorted descending, but we
3550
+ * defensively take the max rather than assume order.
3551
+ */
3552
+ partitionByConfidence(scored, thresholds) {
3553
+ const expand = [];
3554
+ const summarize = [];
3555
+ const excluded = [];
3556
+ if (thresholds.minConfidence !== void 0) {
3557
+ const topScore = scored.reduce((m, s) => Math.max(m, s.relevanceScore), 0);
3558
+ if (scored.length === 0 || topScore < thresholds.minConfidence) {
3559
+ return { expand, summarize, excluded: [...scored] };
3560
+ }
3561
+ }
3562
+ for (const item of scored) {
3563
+ if (item.relevanceScore >= thresholds.expandAbove) {
3564
+ expand.push(item);
3565
+ } else if (item.relevanceScore >= thresholds.includeAbove) {
3566
+ summarize.push(item);
3567
+ } else {
3568
+ excluded.push(item);
3569
+ }
3570
+ }
3571
+ return { expand, summarize, excluded };
3572
+ }
3099
3573
  /**
3100
3574
  * Compile from a named profile
3101
3575
  */
@@ -3128,7 +3602,9 @@ var LoadoutCompiler = class {
3128
3602
  // Filter Methods
3129
3603
  // ===========================================================================
3130
3604
  /**
3131
- * Apply explicit include/exclude filters
3605
+ * Apply explicit exclude filter. Include is handled separately at the
3606
+ * compile level (see `ensureIncludedPresent`) so it can guarantee
3607
+ * presence regardless of the other filters in this method or below.
3132
3608
  */
3133
3609
  applyExplicitFilters(skills, criteria) {
3134
3610
  let result = skills;
@@ -3136,15 +3612,6 @@ var LoadoutCompiler = class {
3136
3612
  const excludeSet = new Set(criteria.exclude);
3137
3613
  result = result.filter((s) => !excludeSet.has(s.id));
3138
3614
  }
3139
- if (criteria.include && criteria.include.length > 0) {
3140
- const includeSet = new Set(criteria.include);
3141
- const currentIds = new Set(result.map((s) => s.id));
3142
- const includedSkills = result.filter((s) => includeSet.has(s.id));
3143
- const otherSkills = result.filter((s) => !includeSet.has(s.id));
3144
- if (includedSkills.length > 0) {
3145
- result = [...includedSkills, ...otherSkills];
3146
- }
3147
- }
3148
3615
  return result;
3149
3616
  }
3150
3617
  /**
@@ -3168,24 +3635,37 @@ var LoadoutCompiler = class {
3168
3635
  */
3169
3636
  applyQualityFilters(skills, criteria) {
3170
3637
  let result = skills;
3171
- if (criteria.minSuccessRate !== void 0) {
3172
- result = result.filter(
3173
- (s) => s.metrics.successRate >= criteria.minSuccessRate
3174
- );
3175
- }
3176
3638
  if (criteria.author) {
3177
3639
  result = result.filter((s) => s.author === criteria.author);
3178
3640
  }
3179
3641
  return result;
3180
3642
  }
3181
3643
  /**
3182
- * Apply semantic filters (task description, problem context, etc.)
3644
+ * Apply semantic filters (task description matching).
3183
3645
  *
3184
- * Currently returns skills unchanged. Semantic matching was removed;
3185
- * use SQLite FTS via storage.searchSkills() for keyword-based search.
3646
+ * When `taskDescription` is provided, uses storage.searchSkills()
3647
+ * to find matching skills and boosts them to the front. Skills not
3648
+ * matching the search are retained at lower priority so that tag
3649
+ * filters and explicit includes still work.
3186
3650
  */
3187
- async applySemanticFilters(skills, _criteria) {
3188
- return skills;
3651
+ async applySemanticFilters(skills, criteria) {
3652
+ if (!criteria.taskDescription) return skills;
3653
+ const searchResults = await this.storage.searchSkills(criteria.taskDescription);
3654
+ const searchIds = new Set(searchResults.map((s) => s.id));
3655
+ const candidateIds = new Set(skills.map((s) => s.id));
3656
+ const boosted = [];
3657
+ const rest = [];
3658
+ for (const s of searchResults) {
3659
+ if (candidateIds.has(s.id)) {
3660
+ boosted.push(s);
3661
+ }
3662
+ }
3663
+ for (const s of skills) {
3664
+ if (!searchIds.has(s.id)) {
3665
+ rest.push(s);
3666
+ }
3667
+ }
3668
+ return [...boosted, ...rest];
3189
3669
  }
3190
3670
  /**
3191
3671
  * Apply relationship-based filters (root skills, dependencies)
@@ -3230,28 +3710,47 @@ var LoadoutCompiler = class {
3230
3710
  }
3231
3711
  return skills.filter((s) => result.has(s.id));
3232
3712
  }
3713
+ /**
3714
+ * Ensure every ID in `criteria.include` is present in the result,
3715
+ * regardless of which earlier filter would have dropped it. Missing
3716
+ * skills are fetched directly from storage.
3717
+ *
3718
+ * `criteria.exclude` still wins: an ID listed in both `include` and
3719
+ * `exclude` is treated as excluded (consistent with openteams' "deny
3720
+ * wins" inheritance rule on permissions).
3721
+ *
3722
+ * Included skills are placed at the front of the result, preserving
3723
+ * the order of `criteria.include`. Other skills retain their relative
3724
+ * order behind them.
3725
+ */
3726
+ async ensureIncludedPresent(current, criteria) {
3727
+ if (!criteria.include?.length) return current;
3728
+ const excludeSet = new Set(criteria.exclude ?? []);
3729
+ const effectiveInclude = criteria.include.filter(
3730
+ (id) => !excludeSet.has(id)
3731
+ );
3732
+ if (effectiveInclude.length === 0) return current;
3733
+ const currentById = new Map(current.map((s) => [s.id, s]));
3734
+ const ordered = [];
3735
+ for (const id of effectiveInclude) {
3736
+ const existing = currentById.get(id);
3737
+ if (existing) {
3738
+ ordered.push(existing);
3739
+ currentById.delete(id);
3740
+ continue;
3741
+ }
3742
+ const fetched = await this.storage.getSkill(id);
3743
+ if (fetched) {
3744
+ ordered.push(fetched);
3745
+ }
3746
+ }
3747
+ return [...ordered, ...currentById.values()];
3748
+ }
3233
3749
  /**
3234
3750
  * Apply limits and sorting
3235
3751
  */
3236
3752
  applyLimits(skills, criteria) {
3237
3753
  let result = skills;
3238
- if (criteria.priorityOrder) {
3239
- result = [...result].sort((a, b) => {
3240
- switch (criteria.priorityOrder) {
3241
- case "usage":
3242
- return b.metrics.usageCount - a.metrics.usageCount;
3243
- case "successRate":
3244
- return b.metrics.successRate - a.metrics.successRate;
3245
- case "recent":
3246
- const aDate = a.metrics.lastUsed?.getTime() ?? 0;
3247
- const bDate = b.metrics.lastUsed?.getTime() ?? 0;
3248
- return bDate - aDate;
3249
- case "relevance":
3250
- default:
3251
- return 0;
3252
- }
3253
- });
3254
- }
3255
3754
  const maxSkills = criteria.maxSkills ?? this.config.defaultMaxSkills;
3256
3755
  if (result.length > maxSkills) {
3257
3756
  result = result.slice(0, maxSkills);
@@ -3289,76 +3788,11 @@ var LoadoutCompiler = class {
3289
3788
  // src/serving/project-detector.ts
3290
3789
  var import_fs = require("fs");
3291
3790
  var import_path = require("path");
3292
- var ProjectDetector = class _ProjectDetector {
3791
+ var _ProjectDetector = class _ProjectDetector {
3293
3792
  constructor() {
3294
3793
  /** Cache for project context */
3295
3794
  this.cache = /* @__PURE__ */ new Map();
3296
3795
  }
3297
- static {
3298
- /** Project type patterns */
3299
- this.PROJECT_TYPES = [
3300
- { manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
3301
- { manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
3302
- { manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
3303
- { manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
3304
- { manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
3305
- { manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
3306
- { manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
3307
- { manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
3308
- ];
3309
- }
3310
- static {
3311
- /** TypeScript detection */
3312
- this.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
3313
- }
3314
- static {
3315
- /** Node.js framework patterns */
3316
- this.NODE_FRAMEWORKS = [
3317
- { name: "react", packageName: "react", tags: ["react", "frontend"] },
3318
- { name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
3319
- { name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
3320
- { name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
3321
- { name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
3322
- { name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
3323
- { name: "express", packageName: "express", tags: ["express", "backend", "api"] },
3324
- { name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
3325
- { name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
3326
- { name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
3327
- { name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
3328
- { name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
3329
- { name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
3330
- { name: "jest", packageName: "jest", tags: ["testing", "jest"] },
3331
- { name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
3332
- { name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
3333
- { name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
3334
- ];
3335
- }
3336
- static {
3337
- /** Python framework patterns (from pyproject.toml or requirements.txt) */
3338
- this.PYTHON_FRAMEWORKS = [
3339
- { name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
3340
- { name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
3341
- { name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
3342
- { name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
3343
- { name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
3344
- { name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
3345
- ];
3346
- }
3347
- static {
3348
- /** Directory patterns */
3349
- this.DIRECTORY_PATTERNS = [
3350
- { pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
3351
- { pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
3352
- { pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
3353
- { pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
3354
- { pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
3355
- { pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
3356
- { pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3357
- { pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3358
- { pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
3359
- { pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
3360
- ];
3361
- }
3362
3796
  /**
3363
3797
  * Detect project context from a directory
3364
3798
  */
@@ -3520,6 +3954,62 @@ var ProjectDetector = class _ProjectDetector {
3520
3954
  }
3521
3955
  }
3522
3956
  };
3957
+ /** Project type patterns */
3958
+ _ProjectDetector.PROJECT_TYPES = [
3959
+ { manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
3960
+ { manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
3961
+ { manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
3962
+ { manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
3963
+ { manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
3964
+ { manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
3965
+ { manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
3966
+ { manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
3967
+ ];
3968
+ /** TypeScript detection */
3969
+ _ProjectDetector.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
3970
+ /** Node.js framework patterns */
3971
+ _ProjectDetector.NODE_FRAMEWORKS = [
3972
+ { name: "react", packageName: "react", tags: ["react", "frontend"] },
3973
+ { name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
3974
+ { name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
3975
+ { name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
3976
+ { name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
3977
+ { name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
3978
+ { name: "express", packageName: "express", tags: ["express", "backend", "api"] },
3979
+ { name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
3980
+ { name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
3981
+ { name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
3982
+ { name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
3983
+ { name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
3984
+ { name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
3985
+ { name: "jest", packageName: "jest", tags: ["testing", "jest"] },
3986
+ { name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
3987
+ { name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
3988
+ { name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
3989
+ ];
3990
+ /** Python framework patterns (from pyproject.toml or requirements.txt) */
3991
+ _ProjectDetector.PYTHON_FRAMEWORKS = [
3992
+ { name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
3993
+ { name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
3994
+ { name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
3995
+ { name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
3996
+ { name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
3997
+ { name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
3998
+ ];
3999
+ /** Directory patterns */
4000
+ _ProjectDetector.DIRECTORY_PATTERNS = [
4001
+ { pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
4002
+ { pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
4003
+ { pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
4004
+ { pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
4005
+ { pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
4006
+ { pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
4007
+ { pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
4008
+ { pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
4009
+ { pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
4010
+ { pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
4011
+ ];
4012
+ var ProjectDetector = _ProjectDetector;
3523
4013
 
3524
4014
  // src/serving/view-renderer.ts
3525
4015
  var DEFAULT_CONFIG3 = {
@@ -3573,6 +4063,9 @@ var ViewRenderer = class {
3573
4063
  lines.push(`<skill id="${this.escapeXml(id)}" state="available">`);
3574
4064
  lines.push(` <name>${this.escapeXml(skill.name)}</name>`);
3575
4065
  lines.push(` <description>${this.escapeXml(summary)}</description>`);
4066
+ if (skill.serving?.instructionPreview) {
4067
+ lines.push(` <key_insight>${this.escapeXml(skill.serving.instructionPreview)}</key_insight>`);
4068
+ }
3576
4069
  if (skill.tags.length > 0) {
3577
4070
  lines.push(` <tags>${skill.tags.map((t) => this.escapeXml(t)).join(", ")}</tags>`);
3578
4071
  }
@@ -3711,8 +4204,11 @@ var ViewRenderer = class {
3711
4204
  */
3712
4205
  estimateSummaryTokens(skill) {
3713
4206
  const summary = this.getSummary(skill);
3714
- const content = [skill.name, summary, skill.tags.join(" ")].join(" ");
3715
- return Math.ceil(content.length / 4);
4207
+ const parts = [skill.name, summary, skill.tags.join(" ")];
4208
+ if (skill.serving?.instructionPreview) {
4209
+ parts.push(skill.serving.instructionPreview);
4210
+ }
4211
+ return Math.ceil(parts.join(" ").length / 4);
3716
4212
  }
3717
4213
  };
3718
4214
 
@@ -3741,7 +4237,6 @@ var securityProfile = {
3741
4237
  tagsAll: ["security"],
3742
4238
  taskDescription: "security review, vulnerability detection, secure coding",
3743
4239
  maxSkills: 8,
3744
- minSuccessRate: 0.7,
3745
4240
  priorityOrder: "relevance"
3746
4241
  };
3747
4242
  var testingProfile = {
@@ -3780,6 +4275,10 @@ var builtInProfiles = {
3780
4275
  };
3781
4276
 
3782
4277
  // src/serving/graph-server.ts
4278
+ var DEFAULT_CONFIDENCE_THRESHOLDS = {
4279
+ expandAbove: 0.3,
4280
+ includeAbove: 0.15
4281
+ };
3783
4282
  var DEFAULT_CONFIG4 = {
3784
4283
  agentCanModify: true,
3785
4284
  agentCanSetLoadout: false,
@@ -3787,8 +4286,16 @@ var DEFAULT_CONFIG4 = {
3787
4286
  requireApproval: false,
3788
4287
  autoExpandOnUse: true,
3789
4288
  autoExpandRelated: true,
3790
- maxExpanded: 5,
4289
+ // Default cap on simultaneously-expanded (full-body) skills. Set to 3:
4290
+ // on the SkillsBench selection benchmark the true-positive skill lands in
4291
+ // the top 3 every time, so 3 (vs 5) lifts loadout precision ~23%→38% with
4292
+ // zero recall loss — fewer irrelevant bodies injected into context.
4293
+ maxExpanded: 3,
3791
4294
  evictionStrategy: "lru",
4295
+ confidenceThresholds: DEFAULT_CONFIDENCE_THRESHOLDS,
4296
+ retrieval: {},
4297
+ scoringPoolSize: 200,
4298
+ deferExpansion: false,
3792
4299
  persistState: false,
3793
4300
  outputFormat: "xml",
3794
4301
  includeTokenEstimates: false,
@@ -3796,18 +4303,22 @@ var DEFAULT_CONFIG4 = {
3796
4303
  profiles: {}
3797
4304
  };
3798
4305
  var SkillGraphServer = class {
3799
- // Track LRU for eviction
3800
4306
  constructor(storage, config2) {
3801
4307
  this.storage = storage;
3802
4308
  this.catalogRenderer = null;
3803
4309
  this.handlers = /* @__PURE__ */ new Set();
3804
4310
  this.lruOrder = [];
4311
+ this.relevanceScores = /* @__PURE__ */ new Map();
3805
4312
  this.config = {
3806
4313
  ...DEFAULT_CONFIG4,
3807
4314
  ...config2,
3808
- profiles: { ...builtInProfiles, ...DEFAULT_CONFIG4.profiles, ...config2?.profiles }
4315
+ profiles: { ...builtInProfiles, ...DEFAULT_CONFIG4.profiles, ...config2?.profiles },
4316
+ confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS, ...config2?.confidenceThresholds }
3809
4317
  };
3810
- this.compiler = new LoadoutCompiler(storage);
4318
+ this.compiler = new LoadoutCompiler(storage, {
4319
+ retrieval: this.config.retrieval,
4320
+ scoringPoolSize: this.config.scoringPoolSize
4321
+ });
3811
4322
  this.projectDetector = new ProjectDetector();
3812
4323
  this.viewRenderer = new ViewRenderer({
3813
4324
  includeTokenEstimates: this.config.includeTokenEstimates
@@ -3848,11 +4359,32 @@ var SkillGraphServer = class {
3848
4359
  return this.applyLoadout(skills, { type: "criteria", criteria });
3849
4360
  }
3850
4361
  /**
3851
- * Set loadout based on task description (semantic matching)
4362
+ * Set loadout based on task description using hybrid confidence-tiered
4363
+ * compilation. Skills above the high threshold are auto-expanded,
4364
+ * skills between high and low thresholds are included as summaries,
4365
+ * and skills below the low threshold are excluded.
4366
+ *
4367
+ * Stores relevance scores for use by the 'relevance' eviction strategy.
3852
4368
  */
3853
4369
  async setLoadoutForTask(taskDescription) {
3854
- const skills = await this.compiler.compileForTask(taskDescription);
3855
- return this.applyLoadout(skills, { type: "task", taskDescription });
4370
+ const scored = await this.compiler.compileWithScoring(taskDescription);
4371
+ const { expand, summarize, excluded } = this.compiler.partitionByConfidence(
4372
+ scored,
4373
+ this.config.confidenceThresholds
4374
+ );
4375
+ const allIncluded = [...expand, ...summarize];
4376
+ const skills = allIncluded.map((s) => s.skill);
4377
+ const state = this.applyLoadout(skills, { type: "task", taskDescription });
4378
+ for (const item of allIncluded) {
4379
+ this.relevanceScores.set(item.skill.id, item.relevanceScore);
4380
+ }
4381
+ for (const item of expand) {
4382
+ if (this.state.expanded.size >= this.config.maxExpanded) break;
4383
+ this.state.expanded.add(item.skill.id);
4384
+ this.touchLru(item.skill.id);
4385
+ this.emit({ type: "skill:expanded", skillId: item.skill.id });
4386
+ }
4387
+ return state;
3856
4388
  }
3857
4389
  /**
3858
4390
  * Set loadout based on detected project context
@@ -3989,16 +4521,6 @@ var SkillGraphServer = class {
3989
4521
  this.emit({ type: "skill:collapsed", skillId });
3990
4522
  return true;
3991
4523
  }
3992
- /**
3993
- * Record skill usage (for LRU tracking and auto-expansion)
3994
- */
3995
- recordUsage(skillId) {
3996
- if (!this.state.available.has(skillId)) return;
3997
- this.touchLru(skillId);
3998
- if (this.config.autoExpandOnUse && !this.state.expanded.has(skillId)) {
3999
- this.expandSkill(skillId);
4000
- }
4001
- }
4002
4524
  // ===========================================================================
4003
4525
  // AGENT API - Methods for agent-side modifications
4004
4526
  // ===========================================================================
@@ -4062,12 +4584,8 @@ var SkillGraphServer = class {
4062
4584
  * Returns skill summaries for display
4063
4585
  */
4064
4586
  async agentSearchSkills(query, limit = 5) {
4065
- const allSkills = await this.storage.listSkills({ status: ["active"] });
4066
- const queryLower = query.toLowerCase();
4067
- const matches = allSkills.filter(
4068
- (skill) => skill.name.toLowerCase().includes(queryLower) || skill.description.toLowerCase().includes(queryLower) || skill.instructions.toLowerCase().includes(queryLower) || skill.tags.some((tag) => tag.toLowerCase().includes(queryLower))
4069
- ).slice(0, limit);
4070
- return matches.map((skill) => ({
4587
+ const matches = await this.storage.searchSkills(query);
4588
+ return matches.slice(0, limit).map((skill) => ({
4071
4589
  id: skill.id,
4072
4590
  name: skill.name,
4073
4591
  description: this.getSummary(skill),
@@ -4108,19 +4626,19 @@ var SkillGraphServer = class {
4108
4626
  * Returns rendered category view (subcategories or skill summaries at leaf).
4109
4627
  * Pass no path for the top-level overview.
4110
4628
  */
4111
- async agentBrowseCatalog(path18) {
4629
+ async agentBrowseCatalog(path19) {
4112
4630
  if (!this.catalogRenderer) {
4113
4631
  return "<error>Catalog browsing is not enabled</error>";
4114
4632
  }
4115
- if (!path18 || path18.length === 0) {
4633
+ if (!path19 || path19.length === 0) {
4116
4634
  const result2 = await this.catalogRenderer.renderOverview();
4117
4635
  if (result2) {
4118
4636
  this.emit({ type: "catalog:browsed", path: [] });
4119
4637
  }
4120
4638
  return result2;
4121
4639
  }
4122
- const result = await this.catalogRenderer.renderCategory(path18);
4123
- this.emit({ type: "catalog:browsed", path: path18 });
4640
+ const result = await this.catalogRenderer.renderCategory(path19);
4641
+ this.emit({ type: "catalog:browsed", path: path19 });
4124
4642
  return result;
4125
4643
  }
4126
4644
  /**
@@ -4152,13 +4670,25 @@ var SkillGraphServer = class {
4152
4670
  /**
4153
4671
  * Render current state as system prompt content.
4154
4672
  * Includes catalog overview when catalog is enabled.
4673
+ *
4674
+ * When `deferExpansion` is enabled, all skills are rendered as
4675
+ * summaries regardless of expansion state — the agent must
4676
+ * explicitly request expansion. This avoids the reactive-signals
4677
+ * problem where upfront skill injection derails model planning.
4155
4678
  */
4156
4679
  async renderSystemPrompt() {
4680
+ let renderState = this.state;
4681
+ if (this.config.deferExpansion) {
4682
+ renderState = {
4683
+ ...this.state,
4684
+ expanded: /* @__PURE__ */ new Set()
4685
+ };
4686
+ }
4157
4687
  let prompt;
4158
4688
  if (this.config.outputFormat === "markdown") {
4159
- prompt = this.viewRenderer.renderMarkdown(this.state);
4689
+ prompt = this.viewRenderer.renderMarkdown(renderState);
4160
4690
  } else {
4161
- prompt = this.viewRenderer.renderXml(this.state);
4691
+ prompt = this.viewRenderer.renderXml(renderState);
4162
4692
  }
4163
4693
  if (this.catalogRenderer) {
4164
4694
  const overview = await this.catalogRenderer.renderOverview();
@@ -4196,21 +4726,70 @@ var SkillGraphServer = class {
4196
4726
  // PRIVATE HELPERS
4197
4727
  // ===========================================================================
4198
4728
  /**
4199
- * Apply a new set of skills as the loadout
4729
+ * Get the relevance score for a skill (0 if not scored).
4730
+ */
4731
+ getRelevanceScore(skillId) {
4732
+ return this.relevanceScores.get(skillId) ?? 0;
4733
+ }
4734
+ /**
4735
+ * Apply a new set of skills as the loadout.
4736
+ * After populating the available set, evaluates autoExpand triggers
4737
+ * on each skill to determine if any should be pre-expanded.
4200
4738
  */
4201
4739
  applyLoadout(skills, source) {
4202
4740
  this.state.available.clear();
4203
4741
  this.state.expanded.clear();
4204
4742
  this.state.pending.clear();
4205
4743
  this.lruOrder = [];
4744
+ this.relevanceScores.clear();
4206
4745
  for (const skill of skills) {
4207
4746
  this.state.available.set(skill.id, skill);
4208
4747
  }
4748
+ this.evaluateAutoExpand(source);
4209
4749
  this.state.source = source;
4210
4750
  this.state.updatedAt = /* @__PURE__ */ new Date();
4211
4751
  this.emit({ type: "loadout:changed", state: this.state });
4212
4752
  return this.state;
4213
4753
  }
4754
+ /**
4755
+ * Evaluate autoExpand trigger conditions for all skills in the loadout.
4756
+ * Checks keyword matches against the task description, file pattern
4757
+ * matches against the project path, and framework matches.
4758
+ */
4759
+ evaluateAutoExpand(source) {
4760
+ const taskText = source.taskDescription ?? "";
4761
+ for (const [id, skill] of this.state.available) {
4762
+ if (this.state.expanded.size >= this.config.maxExpanded) break;
4763
+ if (this.state.expanded.has(id)) continue;
4764
+ const triggers = skill.serving?.autoExpand;
4765
+ if (!triggers || triggers.length === 0) continue;
4766
+ for (const trigger of triggers) {
4767
+ if (this.matchesTrigger(trigger, taskText, source)) {
4768
+ this.state.expanded.add(id);
4769
+ this.touchLru(id);
4770
+ this.emit({ type: "skill:expanded", skillId: id });
4771
+ break;
4772
+ }
4773
+ }
4774
+ }
4775
+ }
4776
+ /**
4777
+ * Check if a single autoExpand trigger matches the current context.
4778
+ */
4779
+ matchesTrigger(trigger, taskText, source) {
4780
+ const conditions = trigger.conditions;
4781
+ if (!conditions) return false;
4782
+ const taskLower = taskText.toLowerCase();
4783
+ if (trigger.on === "mention" && conditions.keywords?.length) {
4784
+ return conditions.keywords.some((kw) => taskLower.includes(kw.toLowerCase()));
4785
+ }
4786
+ if (trigger.on === "file-match" && conditions.filePatterns?.length && source.projectPath) {
4787
+ return conditions.filePatterns.some(
4788
+ (pattern) => source.projectPath.includes(pattern)
4789
+ );
4790
+ }
4791
+ return false;
4792
+ }
4214
4793
  /**
4215
4794
  * Evict a skill from expanded based on strategy
4216
4795
  */
@@ -4221,7 +4800,7 @@ var SkillGraphServer = class {
4221
4800
  case "lru":
4222
4801
  toEvict = this.lruOrder.shift();
4223
4802
  break;
4224
- case "priority":
4803
+ case "priority": {
4225
4804
  let lowestPriority = Infinity;
4226
4805
  for (const id of this.state.expanded) {
4227
4806
  const skill = this.state.available.get(id);
@@ -4232,6 +4811,18 @@ var SkillGraphServer = class {
4232
4811
  }
4233
4812
  }
4234
4813
  break;
4814
+ }
4815
+ case "relevance": {
4816
+ let lowestScore = Infinity;
4817
+ for (const id of this.state.expanded) {
4818
+ const score = this.relevanceScores.get(id) ?? 0;
4819
+ if (score < lowestScore) {
4820
+ lowestScore = score;
4821
+ toEvict = id;
4822
+ }
4823
+ }
4824
+ break;
4825
+ }
4235
4826
  case "manual":
4236
4827
  return;
4237
4828
  }
@@ -5116,12 +5707,7 @@ ${err.stderr || err.message}`
5116
5707
  tags: this.parseTags(metadata.tags),
5117
5708
  createdAt: metadata.created ? new Date(metadata.created) : /* @__PURE__ */ new Date(),
5118
5709
  updatedAt: metadata.updated ? new Date(metadata.updated) : /* @__PURE__ */ new Date(),
5119
- status: metadata.status || "active",
5120
- metrics: {
5121
- usageCount: parseInt(metadata.usageCount) || 0,
5122
- successRate: parseFloat(metadata.successRate) || 0,
5123
- feedbackScores: []
5124
- }
5710
+ status: metadata.status || "active"
5125
5711
  };
5126
5712
  }
5127
5713
  /**
@@ -5190,9 +5776,6 @@ ${body.join("\n")}
5190
5776
  if (filter.author && skill.author !== filter.author) {
5191
5777
  return false;
5192
5778
  }
5193
- if (filter.minSuccessRate !== void 0 && skill.metrics.successRate < filter.minSuccessRate) {
5194
- return false;
5195
- }
5196
5779
  if (filter.createdAfter && skill.createdAt < filter.createdAfter) {
5197
5780
  return false;
5198
5781
  }
@@ -6011,11 +6594,6 @@ ${skill.instructions}
6011
6594
  status,
6012
6595
  parentVersion: parentVersion || void 0,
6013
6596
  derivedFrom: derivedFrom.length > 0 ? derivedFrom : void 0,
6014
- metrics: {
6015
- usageCount: 0,
6016
- successRate: 0,
6017
- feedbackScores: []
6018
- },
6019
6597
  source: metadata?.source,
6020
6598
  upstream,
6021
6599
  namespace: metadata?.namespace
@@ -6462,11 +7040,6 @@ var LineageTracker = class {
6462
7040
  createdAt: /* @__PURE__ */ new Date(),
6463
7041
  updatedAt: /* @__PURE__ */ new Date(),
6464
7042
  status: "draft",
6465
- metrics: {
6466
- usageCount: 0,
6467
- successRate: 0,
6468
- feedbackScores: []
6469
- },
6470
7043
  source: {
6471
7044
  type: "composed",
6472
7045
  importedAt: /* @__PURE__ */ new Date()
@@ -7593,41 +8166,15 @@ var SkillBank = class {
7593
8166
  }
7594
8167
  }
7595
8168
  /**
7596
- * Handle events from serving layer
8169
+ * Handle events from serving layer.
8170
+ *
8171
+ * The `loadout:changed` event is currently the only one we react to.
8172
+ * Earlier versions also handled `skill:used` / `skill:feedback` to mutate
8173
+ * `Skill.metrics`, but skill-tree no longer tracks per-skill usage —
8174
+ * cognitive-core owns that signal via `playbook.evolution.*`.
7597
8175
  */
7598
8176
  async handleServingEvent(event) {
7599
8177
  switch (event.type) {
7600
- case "skill:used":
7601
- const skill = await this.storage.getSkill(event.skillId);
7602
- if (skill) {
7603
- skill.metrics.usageCount++;
7604
- skill.metrics.lastUsed = /* @__PURE__ */ new Date();
7605
- if (event.success) {
7606
- const total = skill.metrics.usageCount;
7607
- const currentSuccesses = skill.metrics.successRate * (total - 1);
7608
- skill.metrics.successRate = (currentSuccesses + 1) / total;
7609
- } else {
7610
- const total = skill.metrics.usageCount;
7611
- const currentSuccesses = skill.metrics.successRate * (total - 1);
7612
- skill.metrics.successRate = currentSuccesses / total;
7613
- }
7614
- skill.updatedAt = /* @__PURE__ */ new Date();
7615
- await this.storage.saveSkill(skill);
7616
- }
7617
- break;
7618
- case "skill:feedback":
7619
- const feedbackSkill = await this.storage.getSkill(event.skillId);
7620
- if (feedbackSkill) {
7621
- feedbackSkill.metrics.feedbackScores.push(event.score);
7622
- if (feedbackSkill.metrics.feedbackScores.length > 50) {
7623
- feedbackSkill.metrics.feedbackScores = feedbackSkill.metrics.feedbackScores.slice(-50);
7624
- }
7625
- feedbackSkill.updatedAt = /* @__PURE__ */ new Date();
7626
- await this.storage.saveSkill(feedbackSkill);
7627
- }
7628
- break;
7629
- case "skill:requested":
7630
- break;
7631
8178
  case "loadout:changed":
7632
8179
  break;
7633
8180
  }
@@ -7649,26 +8196,17 @@ var SkillBank = class {
7649
8196
  deprecated: 0,
7650
8197
  experimental: 0
7651
8198
  },
7652
- byTag: {},
7653
- avgSuccessRate: 0,
7654
- totalUsage: 0
8199
+ byTag: {}
7655
8200
  };
7656
8201
  if (this.namespaceConfig) {
7657
8202
  stats.byScope = { personal: 0, team: 0, global: 0 };
7658
8203
  stats.byVisibility = { private: 0, "team-only": 0, public: 0 };
7659
8204
  }
7660
- let successRateSum = 0;
7661
- let successRateCount = 0;
7662
8205
  for (const skill of skills) {
7663
8206
  stats.byStatus[skill.status]++;
7664
8207
  for (const tag of skill.tags) {
7665
8208
  stats.byTag[tag] = (stats.byTag[tag] || 0) + 1;
7666
8209
  }
7667
- stats.totalUsage += skill.metrics.usageCount;
7668
- if (skill.metrics.successRate > 0) {
7669
- successRateSum += skill.metrics.successRate;
7670
- successRateCount++;
7671
- }
7672
8210
  if (stats.byScope && stats.byVisibility) {
7673
8211
  const scope = skill.namespace?.scope || "personal";
7674
8212
  const visibility = skill.namespace?.visibility || "private";
@@ -7676,7 +8214,6 @@ var SkillBank = class {
7676
8214
  stats.byVisibility[visibility]++;
7677
8215
  }
7678
8216
  }
7679
- stats.avgSuccessRate = successRateCount > 0 ? successRateSum / successRateCount : 0;
7680
8217
  return stats;
7681
8218
  }
7682
8219
  /**
@@ -7866,8 +8403,8 @@ function substituteEnvVarsInObject(obj) {
7866
8403
  }
7867
8404
  return obj;
7868
8405
  }
7869
- function setNestedProperty(obj, path18, value) {
7870
- const parts = path18.split(".");
8406
+ function setNestedProperty(obj, path19, value) {
8407
+ const parts = path19.split(".");
7871
8408
  let current = obj;
7872
8409
  for (let i = 0; i < parts.length - 1; i++) {
7873
8410
  const part = parts[i];
@@ -8026,8 +8563,8 @@ var ConfigLoader = class {
8026
8563
  /**
8027
8564
  * Get a specific config value by path
8028
8565
  */
8029
- get(path18) {
8030
- const parts = path18.split(".");
8566
+ get(path19) {
8567
+ const parts = path19.split(".");
8031
8568
  let current = this.getConfig();
8032
8569
  for (const part of parts) {
8033
8570
  if (current === null || typeof current !== "object") {
@@ -8136,11 +8673,6 @@ function convertIndexerSkill(indexerSkill) {
8136
8673
  createdAt: new Date(indexerSkill.scrapedAt),
8137
8674
  updatedAt: new Date(indexerSkill.updatedAt),
8138
8675
  status,
8139
- metrics: {
8140
- usageCount: 0,
8141
- successRate: 0,
8142
- feedbackScores: []
8143
- },
8144
8676
  source: {
8145
8677
  type: "imported",
8146
8678
  location: indexerSkill.sourceUrl,
@@ -8977,15 +9509,349 @@ function createIntegratedIndexer(skillBank, config2 = {}) {
8977
9509
  return new IndexerService(config2, skillBank);
8978
9510
  }
8979
9511
 
9512
+ // src/import/skillmd.ts
9513
+ function splitFrontmatter(content) {
9514
+ const normalized = content.replace(/\r\n/g, "\n");
9515
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
9516
+ if (match) {
9517
+ return { frontmatter: match[1], body: match[2], hasFrontmatter: true };
9518
+ }
9519
+ return { frontmatter: "", body: normalized, hasFrontmatter: false };
9520
+ }
9521
+ function extractField(yaml, field) {
9522
+ const match = yaml.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
9523
+ if (!match) return void 0;
9524
+ return match[1].trim().replace(/^["']|["']$/g, "");
9525
+ }
9526
+ function extractMultiline(yaml, field) {
9527
+ const match = yaml.match(
9528
+ new RegExp(`^${field}:\\s*\\|\\n((?:\\s{2}.+\\n?)+)`, "m")
9529
+ );
9530
+ if (match) {
9531
+ return match[1].split("\n").map((line) => line.replace(/^\s{2}/, "")).join("\n").trim();
9532
+ }
9533
+ return extractField(yaml, field);
9534
+ }
9535
+ function extractList(yaml, field) {
9536
+ const match = yaml.match(new RegExp(`^${field}:\\n((?:\\s+-\\s+.+\\n?)+)`, "m"));
9537
+ if (match) {
9538
+ return match[1].split("\n").map((line) => line.replace(/^\s+-\s+/, "").trim().replace(/^["']|["']$/g, "")).filter((line) => line.length > 0);
9539
+ }
9540
+ return [];
9541
+ }
9542
+ function parseSkillMd(content) {
9543
+ const { frontmatter, body, hasFrontmatter } = splitFrontmatter(content);
9544
+ return {
9545
+ name: extractField(frontmatter, "name"),
9546
+ description: extractMultiline(frontmatter, "description"),
9547
+ version: extractField(frontmatter, "version"),
9548
+ author: extractField(frontmatter, "author"),
9549
+ status: extractField(frontmatter, "status"),
9550
+ date: extractField(frontmatter, "date"),
9551
+ tags: extractList(frontmatter, "tags"),
9552
+ body: body.trim(),
9553
+ hasFrontmatter
9554
+ };
9555
+ }
9556
+
9557
+ // src/import/skill-from-md.ts
9558
+ var VALID_STATUSES2 = ["draft", "active", "deprecated", "experimental"];
9559
+ function slugify(input) {
9560
+ return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "skill";
9561
+ }
9562
+ function skillFromSkillMd(content, options = {}) {
9563
+ const warnings = [];
9564
+ const parsed = parseSkillMd(content);
9565
+ if (!parsed.hasFrontmatter) {
9566
+ warnings.push("SKILL.md has no YAML frontmatter; using fallback metadata");
9567
+ }
9568
+ if (!parsed.body) {
9569
+ warnings.push("SKILL.md body is empty; instructions will be empty");
9570
+ }
9571
+ const id = slugify(options.id || parsed.name || options.defaultName || "skill");
9572
+ const tags = new Set(parsed.tags);
9573
+ for (const tag of options.extraTags ?? []) {
9574
+ if (tag) tags.add(tag);
9575
+ }
9576
+ const status = parsed.status && VALID_STATUSES2.includes(parsed.status) ? parsed.status : options.defaultStatus ?? "active";
9577
+ const now = options.now ?? /* @__PURE__ */ new Date();
9578
+ const skill = {
9579
+ id,
9580
+ name: parsed.name || options.defaultName || id,
9581
+ version: parsed.version || options.defaultVersion || "1.0.0",
9582
+ description: parsed.description || options.defaultDescription || "",
9583
+ instructions: parsed.body,
9584
+ author: parsed.author || options.defaultAuthor || "unknown",
9585
+ tags: Array.from(tags),
9586
+ createdAt: now,
9587
+ updatedAt: now,
9588
+ status,
9589
+ source: options.source,
9590
+ taxonomy: options.taxonomyPath && options.taxonomyPath.length > 0 ? { primaryPath: options.taxonomyPath } : void 0,
9591
+ externalSource: options.externalSource
9592
+ };
9593
+ return { skill, warnings, parsed };
9594
+ }
9595
+
9596
+ // src/services/skillnet.ts
9597
+ var DEFAULT_SKILLNET_API = "http://api-skillnet.openkg.cn/v1";
9598
+ function parseGitHubUrl(url) {
9599
+ const m = url.match(
9600
+ /github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/(?:tree|blob)\/([^/]+)\/(.*))?$/
9601
+ );
9602
+ if (!m) return null;
9603
+ const [, owner, repo, ref, path19] = m;
9604
+ return {
9605
+ owner,
9606
+ repo,
9607
+ ref: ref || "main",
9608
+ path: (path19 || "").replace(/\/$/, "")
9609
+ };
9610
+ }
9611
+ var SkillNetClient = class {
9612
+ constructor(config2 = {}) {
9613
+ this.apiBaseUrl = (config2.apiBaseUrl || DEFAULT_SKILLNET_API).replace(/\/$/, "");
9614
+ this.githubToken = config2.githubToken;
9615
+ this.githubMirror = config2.githubMirror;
9616
+ const resolved = config2.fetchImpl || globalThis.fetch;
9617
+ if (!resolved) {
9618
+ throw new Error(
9619
+ "No fetch implementation available. Provide config.fetchImpl or run on Node 18+."
9620
+ );
9621
+ }
9622
+ this.fetchImpl = resolved;
9623
+ }
9624
+ /**
9625
+ * Search the SkillNet index. Free and requires no API key.
9626
+ */
9627
+ async search(query, options = {}) {
9628
+ const params = new URLSearchParams({ q: query });
9629
+ if (options.mode) params.set("mode", options.mode);
9630
+ if (options.category) params.set("category", options.category);
9631
+ if (options.limit != null) params.set("limit", String(options.limit));
9632
+ if (options.page != null) params.set("page", String(options.page));
9633
+ if (options.minStars != null) params.set("min_stars", String(options.minStars));
9634
+ if (options.sortBy) params.set("sort_by", options.sortBy);
9635
+ if (options.threshold != null) params.set("threshold", String(options.threshold));
9636
+ const url = `${this.apiBaseUrl}/search?${params.toString()}`;
9637
+ const res = await this.fetchImpl(url);
9638
+ if (!res.ok) {
9639
+ throw new Error(`SkillNet search failed: ${res.status} ${res.statusText}`);
9640
+ }
9641
+ const body = await res.json();
9642
+ const rows = Array.isArray(body.data) ? body.data : [];
9643
+ return rows.map((row) => ({
9644
+ skillName: String(row.skill_name ?? ""),
9645
+ skillDescription: row.skill_description != null ? String(row.skill_description) : void 0,
9646
+ author: row.author != null ? String(row.author) : void 0,
9647
+ stars: typeof row.stars === "number" ? row.stars : Number(row.stars) || 0,
9648
+ skillUrl: String(row.skill_url ?? ""),
9649
+ category: row.category != null ? String(row.category) : void 0,
9650
+ evaluation: row.evaluation && typeof row.evaluation === "object" ? row.evaluation : void 0
9651
+ }));
9652
+ }
9653
+ /**
9654
+ * Convert a GitHub skill URL into the raw URL for its SKILL.md.
9655
+ * Applies the configured mirror prefix when set.
9656
+ */
9657
+ toRawSkillMdUrl(skillUrl) {
9658
+ const parsed = parseGitHubUrl(skillUrl);
9659
+ if (!parsed) {
9660
+ throw new Error(`Not a recognizable GitHub skill URL: ${skillUrl}`);
9661
+ }
9662
+ const { owner, repo, ref, path: path19 } = parsed;
9663
+ const filePath = /SKILL\.md$/i.test(path19) ? path19 : path19 ? `${path19}/SKILL.md` : "SKILL.md";
9664
+ const raw = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${filePath}`;
9665
+ return this.githubMirror ? `${this.githubMirror.replace(/\/$/, "")}/${raw}` : raw;
9666
+ }
9667
+ /**
9668
+ * Fetch the raw SKILL.md content for a skill URL.
9669
+ */
9670
+ async fetchSkillMd(skillUrl) {
9671
+ const rawUrl = this.toRawSkillMdUrl(skillUrl);
9672
+ const headers = {};
9673
+ if (this.githubToken) {
9674
+ headers.Authorization = `Bearer ${this.githubToken}`;
9675
+ }
9676
+ const res = await this.fetchImpl(rawUrl, { headers });
9677
+ if (!res.ok) {
9678
+ throw new Error(
9679
+ `Failed to fetch SKILL.md (${res.status} ${res.statusText}): ${rawUrl}`
9680
+ );
9681
+ }
9682
+ return { content: await res.text(), rawUrl };
9683
+ }
9684
+ /**
9685
+ * Convert a SkillNet search result + its SKILL.md content into a skill-tree Skill.
9686
+ */
9687
+ convertSkillNetSkill(result, content, rawUrl) {
9688
+ const gh = parseGitHubUrl(result.skillUrl);
9689
+ const folderName = gh?.path ? gh.path.split("/").filter(Boolean).pop() : void 0;
9690
+ const now = /* @__PURE__ */ new Date();
9691
+ const { skill, warnings } = skillFromSkillMd(content, {
9692
+ id: folderName || result.skillName,
9693
+ defaultName: result.skillName,
9694
+ defaultDescription: result.skillDescription,
9695
+ defaultAuthor: result.author || "skillnet",
9696
+ // SkillNet skills are curated/published → default active.
9697
+ defaultStatus: "active",
9698
+ extraTags: [
9699
+ ...result.category ? [result.category.toLowerCase()] : [],
9700
+ ...gh?.repo ? [gh.repo] : []
9701
+ ],
9702
+ taxonomyPath: result.category ? [result.category] : void 0,
9703
+ source: {
9704
+ type: "imported",
9705
+ location: result.skillUrl,
9706
+ importedAt: now
9707
+ },
9708
+ externalSource: {
9709
+ url: result.skillUrl,
9710
+ repo: gh ? `${gh.owner}/${gh.repo}` : "",
9711
+ scrapedAt: now
9712
+ },
9713
+ now
9714
+ });
9715
+ return { skill, warnings, rawUrl };
9716
+ }
9717
+ /**
9718
+ * Import a single skill by its SkillNet/GitHub URL into a SkillBank.
9719
+ */
9720
+ async importSkill(skillUrl, bank, meta = {}) {
9721
+ const { content, rawUrl } = await this.fetchSkillMd(skillUrl);
9722
+ const result = {
9723
+ skillName: meta.skillName || "",
9724
+ skillDescription: meta.skillDescription,
9725
+ author: meta.author,
9726
+ stars: meta.stars ?? 0,
9727
+ skillUrl,
9728
+ category: meta.category,
9729
+ evaluation: meta.evaluation
9730
+ };
9731
+ const converted = this.convertSkillNetSkill(result, content, rawUrl);
9732
+ await bank.saveSkill(converted.skill);
9733
+ return converted;
9734
+ }
9735
+ /**
9736
+ * Search SkillNet and import the matching skills into a SkillBank.
9737
+ */
9738
+ async importFromSearch(query, bank, options = {}) {
9739
+ const result = {
9740
+ imported: 0,
9741
+ failed: 0,
9742
+ skills: [],
9743
+ errors: []
9744
+ };
9745
+ const results = await this.search(query, options);
9746
+ for (const row of results) {
9747
+ if (!row.skillUrl) {
9748
+ result.failed++;
9749
+ result.errors.push(`Skipped "${row.skillName}": no skill_url`);
9750
+ continue;
9751
+ }
9752
+ try {
9753
+ const converted = await this.importSkill(row.skillUrl, bank, row);
9754
+ result.imported++;
9755
+ result.skills.push(converted.skill);
9756
+ } catch (err) {
9757
+ result.failed++;
9758
+ result.errors.push(
9759
+ `Failed to import ${row.skillUrl}: ${err.message}`
9760
+ );
9761
+ }
9762
+ }
9763
+ return result;
9764
+ }
9765
+ };
9766
+ function createSkillNetClient(config2 = {}) {
9767
+ return new SkillNetClient(config2);
9768
+ }
9769
+
9770
+ // src/import/local.ts
9771
+ var fs13 = __toESM(require("fs/promises"));
9772
+ var path13 = __toESM(require("path"));
9773
+ var SKILL_FILE_RE = /^skill\.md$/i;
9774
+ async function findSkillMdFiles(root, maxDepth = 6) {
9775
+ const found = [];
9776
+ async function walk(dir, depth) {
9777
+ if (depth > maxDepth) return;
9778
+ let entries;
9779
+ try {
9780
+ entries = await fs13.readdir(dir, { withFileTypes: true });
9781
+ } catch (err) {
9782
+ if (err.code === "ENOENT") return;
9783
+ throw err;
9784
+ }
9785
+ for (const entry of entries) {
9786
+ if (entry.isFile() && SKILL_FILE_RE.test(entry.name)) {
9787
+ found.push({
9788
+ filePath: path13.join(dir, entry.name),
9789
+ directory: dir,
9790
+ id: path13.basename(dir)
9791
+ });
9792
+ }
9793
+ }
9794
+ for (const entry of entries) {
9795
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
9796
+ await walk(path13.join(dir, entry.name), depth + 1);
9797
+ }
9798
+ }
9799
+ }
9800
+ await walk(root, 0);
9801
+ return found;
9802
+ }
9803
+ async function importSkillMdFile(filePath, bank, options = {}) {
9804
+ const content = await fs13.readFile(filePath, "utf-8");
9805
+ const dirName = path13.basename(path13.dirname(filePath));
9806
+ const now = /* @__PURE__ */ new Date();
9807
+ const { skill, warnings } = skillFromSkillMd(content, {
9808
+ source: { type: "imported", location: filePath, importedAt: now },
9809
+ now,
9810
+ ...options,
9811
+ id: options.id ?? dirName
9812
+ });
9813
+ await bank.saveSkill(skill);
9814
+ return { skill, warnings };
9815
+ }
9816
+ async function importLocalSkillDir(dirPath, bank, options = {}) {
9817
+ const result = {
9818
+ imported: 0,
9819
+ failed: 0,
9820
+ skills: [],
9821
+ errors: []
9822
+ };
9823
+ const files = await findSkillMdFiles(dirPath);
9824
+ const seen = /* @__PURE__ */ new Set();
9825
+ for (const file of files) {
9826
+ if (seen.has(file.id)) {
9827
+ result.errors.push(`Skipped duplicate id "${file.id}" at ${file.filePath}`);
9828
+ continue;
9829
+ }
9830
+ try {
9831
+ const { skill } = await importSkillMdFile(file.filePath, bank, {
9832
+ ...options,
9833
+ id: file.id
9834
+ });
9835
+ seen.add(skill.id);
9836
+ result.imported++;
9837
+ result.skills.push(skill);
9838
+ } catch (err) {
9839
+ result.failed++;
9840
+ result.errors.push(`Failed to import ${file.filePath}: ${err.message}`);
9841
+ }
9842
+ }
9843
+ return result;
9844
+ }
9845
+
8980
9846
  // src/index.ts
8981
- var VERSION = "0.1.0";
9847
+ var VERSION = "0.2.0";
8982
9848
 
8983
9849
  // src/cli/commands/list.ts
8984
9850
  var import_commander = require("commander");
8985
9851
 
8986
9852
  // src/cli/utils/paths.ts
8987
- var fs13 = __toESM(require("fs"));
8988
- var path13 = __toESM(require("path"));
9853
+ var fs14 = __toESM(require("fs"));
9854
+ var path14 = __toESM(require("path"));
8989
9855
  var os2 = __toESM(require("os"));
8990
9856
  var DEFAULT_PATHS = [
8991
9857
  ".claude/skills",
@@ -8999,13 +9865,13 @@ var DEFAULT_PATHS = [
8999
9865
  ];
9000
9866
  function expandHome(p) {
9001
9867
  if (p.startsWith("~/")) {
9002
- return path13.join(os2.homedir(), p.slice(2));
9868
+ return path14.join(os2.homedir(), p.slice(2));
9003
9869
  }
9004
9870
  return p;
9005
9871
  }
9006
9872
  function dirExists(p) {
9007
9873
  try {
9008
- return fs13.statSync(p).isDirectory();
9874
+ return fs14.statSync(p).isDirectory();
9009
9875
  } catch {
9010
9876
  return false;
9011
9877
  }
@@ -9013,23 +9879,23 @@ function dirExists(p) {
9013
9879
  function resolveSkillPath(explicitPath) {
9014
9880
  if (explicitPath) {
9015
9881
  const resolved = expandHome(explicitPath);
9016
- return path13.resolve(resolved);
9882
+ return path14.resolve(resolved);
9017
9883
  }
9018
9884
  const envPath = process.env.SKILL_TREE_PATH;
9019
9885
  if (envPath) {
9020
- return path13.resolve(expandHome(envPath));
9886
+ return path14.resolve(expandHome(envPath));
9021
9887
  }
9022
9888
  for (const defaultPath of DEFAULT_PATHS) {
9023
- const resolved = path13.resolve(expandHome(defaultPath));
9889
+ const resolved = path14.resolve(expandHome(defaultPath));
9024
9890
  if (dirExists(resolved)) {
9025
9891
  return resolved;
9026
9892
  }
9027
9893
  }
9028
- return path13.resolve(".claude/skills");
9894
+ return path14.resolve(".claude/skills");
9029
9895
  }
9030
9896
  function ensureDir(dir) {
9031
9897
  if (!dirExists(dir)) {
9032
- fs13.mkdirSync(dir, { recursive: true });
9898
+ fs14.mkdirSync(dir, { recursive: true });
9033
9899
  }
9034
9900
  }
9035
9901
 
@@ -9104,10 +9970,6 @@ function formatSkillDetail(skill) {
9104
9970
  if (skill.derivedFrom && skill.derivedFrom.length > 0) {
9105
9971
  lines.push(`${import_chalk.default.dim("Derived:")} ${skill.derivedFrom.join(", ")}`);
9106
9972
  }
9107
- const { usageCount, successRate } = skill.metrics;
9108
- if (usageCount > 0) {
9109
- lines.push(`${import_chalk.default.dim("Usage:")} ${usageCount} times, ${Math.round(successRate * 100)}% success`);
9110
- }
9111
9973
  lines.push("");
9112
9974
  lines.push(import_chalk.default.dim("Description"));
9113
9975
  lines.push(import_chalk.default.dim("\u2500".repeat(50)));
@@ -9187,11 +10049,6 @@ function formatStats(stats) {
9187
10049
  }
9188
10050
  lines.push("");
9189
10051
  }
9190
- if (stats.totalUsage > 0) {
9191
- lines.push(import_chalk.default.dim("Usage:"));
9192
- lines.push(` Total uses: ${stats.totalUsage}`);
9193
- lines.push(` Avg success: ${Math.round(stats.avgSuccessRate * 100)}%`);
9194
- }
9195
10052
  return lines.join("\n");
9196
10053
  }
9197
10054
  function printError(message) {
@@ -9485,7 +10342,7 @@ var deleteCommand = new import_commander11.Command("delete").description("Delete
9485
10342
 
9486
10343
  // src/cli/commands/export.ts
9487
10344
  var import_commander12 = require("commander");
9488
- var fs14 = __toESM(require("fs"));
10345
+ var fs15 = __toESM(require("fs"));
9489
10346
  var exportCommand = new import_commander12.Command("export").description("Export all skills to JSON").option("-o, --output <file>", "Output file path (defaults to stdout)").action(async (options, command) => {
9490
10347
  const globalOpts = command.optsWithGlobals();
9491
10348
  try {
@@ -9493,7 +10350,7 @@ var exportCommand = new import_commander12.Command("export").description("Export
9493
10350
  const skills = await skillBank.exportAll();
9494
10351
  const json = JSON.stringify(skills, null, 2);
9495
10352
  if (options.output) {
9496
- fs14.writeFileSync(options.output, json, "utf-8");
10353
+ fs15.writeFileSync(options.output, json, "utf-8");
9497
10354
  if (!globalOpts.quiet) {
9498
10355
  printSuccess(`Exported ${skills.length} skill(s) to ${options.output}`);
9499
10356
  }
@@ -9508,13 +10365,13 @@ var exportCommand = new import_commander12.Command("export").description("Export
9508
10365
 
9509
10366
  // src/cli/commands/import.ts
9510
10367
  var import_commander13 = require("commander");
9511
- var fs15 = __toESM(require("fs"));
10368
+ var fs16 = __toESM(require("fs"));
9512
10369
 
9513
10370
  // src/import/detect.ts
9514
10371
  function isSkillTreeSkill(obj) {
9515
10372
  if (typeof obj !== "object" || obj === null) return false;
9516
10373
  const skill = obj;
9517
- return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.instructions === "string" && typeof skill.metrics === "object";
10374
+ return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.instructions === "string";
9518
10375
  }
9519
10376
  function isIndexerSkill(obj) {
9520
10377
  if (typeof obj !== "object" || obj === null) return false;
@@ -9567,14 +10424,38 @@ function isLikelyIndexerFormat(content) {
9567
10424
  }
9568
10425
 
9569
10426
  // src/cli/commands/import.ts
9570
- var importCommand = new import_commander13.Command("import").description("Import skills from JSON file").argument("<file>", "JSON file to import").option("--from-indexer", "Import from skill-indexer export format").option("--auto-detect", "Auto-detect format (default: true)", true).action(async (file, options, command) => {
10427
+ var importCommand = new import_commander13.Command("import").description("Import skills from a JSON file, a SKILL.md file, or a directory of skills").argument("<path>", "JSON file, SKILL.md file, or directory to import").option("--from-indexer", "Import from skill-indexer export format").option("--skill-md", "Treat input as SKILL.md (auto-detected for directories and .md files)").option("--auto-detect", "Auto-detect format (default: true)", true).action(async (file, options, command) => {
9571
10428
  const globalOpts = command.optsWithGlobals();
9572
10429
  try {
9573
- if (!fs15.existsSync(file)) {
9574
- printError(`File not found: ${file}`);
10430
+ if (!fs16.existsSync(file)) {
10431
+ printError(`Path not found: ${file}`);
9575
10432
  process.exit(1);
9576
10433
  }
9577
- const content = fs15.readFileSync(file, "utf-8");
10434
+ const stat = fs16.statSync(file);
10435
+ const isMarkdown = options.skillMd || stat.isDirectory() || /\.md$/i.test(file);
10436
+ if (isMarkdown) {
10437
+ const bank = await createSkillBankFromOptions(globalOpts);
10438
+ if (stat.isDirectory()) {
10439
+ const result2 = await importLocalSkillDir(file, bank);
10440
+ if (globalOpts.json) {
10441
+ console.log(JSON.stringify(result2, null, 2));
10442
+ return;
10443
+ }
10444
+ printSuccess(`Imported ${result2.imported} skill(s) from ${file}`);
10445
+ if (result2.failed > 0) printWarning(`Failed to import ${result2.failed} skill(s)`);
10446
+ for (const e of result2.errors) printInfo(` - ${e}`);
10447
+ } else {
10448
+ const { skill, warnings } = await importSkillMdFile(file, bank);
10449
+ if (globalOpts.json) {
10450
+ console.log(JSON.stringify({ imported: 1, skill: skill.id, warnings }, null, 2));
10451
+ return;
10452
+ }
10453
+ printSuccess(`Imported "${skill.id}"`);
10454
+ for (const w of warnings) printWarning(w);
10455
+ }
10456
+ return;
10457
+ }
10458
+ const content = fs16.readFileSync(file, "utf-8");
9578
10459
  let skills;
9579
10460
  const isIndexerFormat = options.fromIndexer || options.autoDetect !== false && isLikelyIndexerFormat(content);
9580
10461
  if (isIndexerFormat) {
@@ -9605,9 +10486,6 @@ var importCommand = new import_commander13.Command("import").description("Import
9605
10486
  for (const skill of skills) {
9606
10487
  skill.createdAt = new Date(skill.createdAt);
9607
10488
  skill.updatedAt = new Date(skill.updatedAt);
9608
- if (skill.metrics.lastUsed) {
9609
- skill.metrics.lastUsed = new Date(skill.metrics.lastUsed);
9610
- }
9611
10489
  if (skill.source?.importedAt) {
9612
10490
  skill.source.importedAt = new Date(skill.source.importedAt);
9613
10491
  }
@@ -10103,8 +10981,8 @@ async function runSkillIndexer5(args, quiet) {
10103
10981
 
10104
10982
  // src/cli/commands/indexer/sync.ts
10105
10983
  var import_commander19 = require("commander");
10106
- var fs16 = __toESM(require("fs"));
10107
- var path14 = __toESM(require("path"));
10984
+ var fs17 = __toESM(require("fs"));
10985
+ var path15 = __toESM(require("path"));
10108
10986
  var os3 = __toESM(require("os"));
10109
10987
  var import_child_process7 = require("child_process");
10110
10988
 
@@ -10565,11 +11443,11 @@ async function runLegacyImport(options, globalOpts) {
10565
11443
  if (!globalOpts.quiet) {
10566
11444
  printInfo("Exporting skills from indexer...");
10567
11445
  }
10568
- exportPath = path14.join(os3.tmpdir(), `skill-tree-sync-${Date.now()}.json`);
11446
+ exportPath = path15.join(os3.tmpdir(), `skill-tree-sync-${Date.now()}.json`);
10569
11447
  const args = ["export-skilltree", "-o", exportPath, "-f", "json"];
10570
11448
  if (options.indexedOnly) args.push("--indexed-only");
10571
11449
  await runSkillIndexer6(args, true);
10572
- if (!fs16.existsSync(exportPath)) {
11450
+ if (!fs17.existsSync(exportPath)) {
10573
11451
  printError("Failed to export skills from indexer");
10574
11452
  process.exit(1);
10575
11453
  }
@@ -10577,7 +11455,7 @@ async function runLegacyImport(options, globalOpts) {
10577
11455
  if (!globalOpts.quiet) {
10578
11456
  printInfo(`Reading export from ${exportPath}...`);
10579
11457
  }
10580
- const content = fs16.readFileSync(exportPath, "utf-8");
11458
+ const content = fs17.readFileSync(exportPath, "utf-8");
10581
11459
  const { skills, stats } = parseIndexerExport(content);
10582
11460
  if (!globalOpts.quiet) {
10583
11461
  printInfo(`Found ${stats.total} skills (${stats.withStructuredContent} with structured content)`);
@@ -10597,7 +11475,7 @@ async function runLegacyImport(options, globalOpts) {
10597
11475
  const result = await skillBank.importSkills(skills);
10598
11476
  if (!options.exportPath && exportPath) {
10599
11477
  try {
10600
- fs16.unlinkSync(exportPath);
11478
+ fs17.unlinkSync(exportPath);
10601
11479
  } catch {
10602
11480
  }
10603
11481
  }
@@ -10634,7 +11512,7 @@ var indexerCommand = new import_commander20.Command("index").description("Skill
10634
11512
 
10635
11513
  // src/cli/commands/config.ts
10636
11514
  var import_commander21 = require("commander");
10637
- var fs17 = __toESM(require("fs"));
11515
+ var fs18 = __toESM(require("fs"));
10638
11516
  var configCommand = new import_commander21.Command("config").description("View and manage configuration");
10639
11517
  configCommand.command("show").description("Show current configuration").option("--path <path>", "Path to specific config value (e.g., storage.path)").action((options) => {
10640
11518
  const globalOpts = configCommand.parent?.opts() || {};
@@ -10667,7 +11545,7 @@ configCommand.command("show").description("Show current configuration").option("
10667
11545
  configCommand.command("init").description("Create default configuration file").option("-f, --force", "Overwrite existing config file").action((options) => {
10668
11546
  const globalOpts = configCommand.parent?.opts() || {};
10669
11547
  const configPath = expandPath(globalOpts.config || getConfigPath());
10670
- if (fs17.existsSync(configPath) && !options.force) {
11548
+ if (fs18.existsSync(configPath) && !options.force) {
10671
11549
  console.error(`Config file already exists: ${configPath}`);
10672
11550
  console.error("Use --force to overwrite");
10673
11551
  process.exit(1);
@@ -10686,7 +11564,7 @@ configCommand.command("path").description("Show configuration file path").action
10686
11564
  configCommand.command("edit").description("Open configuration file in editor").action(() => {
10687
11565
  const globalOpts = configCommand.parent?.opts() || {};
10688
11566
  const configPath = expandPath(globalOpts.config || getConfigPath());
10689
- if (!fs17.existsSync(configPath)) {
11567
+ if (!fs18.existsSync(configPath)) {
10690
11568
  const loader = new ConfigLoader(configPath);
10691
11569
  loader.createDefaultConfigFile();
10692
11570
  }
@@ -10717,7 +11595,7 @@ configCommand.command("defaults").description("Show default configuration values
10717
11595
  configCommand.command("validate").description("Validate configuration file").action(() => {
10718
11596
  const globalOpts = configCommand.parent?.opts() || {};
10719
11597
  const configPath = expandPath(globalOpts.config || getConfigPath());
10720
- if (!fs17.existsSync(configPath)) {
11598
+ if (!fs18.existsSync(configPath)) {
10721
11599
  console.error(`Config file not found: ${configPath}`);
10722
11600
  process.exit(1);
10723
11601
  }
@@ -10784,15 +11662,15 @@ function printConfig(obj, indent) {
10784
11662
 
10785
11663
  // src/cli/commands/sync.ts
10786
11664
  var import_commander22 = require("commander");
10787
- var path15 = __toESM(require("path"));
10788
- var fs18 = __toESM(require("fs"));
11665
+ var path16 = __toESM(require("path"));
11666
+ var fs19 = __toESM(require("fs"));
10789
11667
  function getSyncConfigPath(basePath) {
10790
- return path15.join(basePath, ".skillbank", "sync-config.json");
11668
+ return path16.join(basePath, ".skillbank", "sync-config.json");
10791
11669
  }
10792
11670
  async function loadSyncConfig(basePath) {
10793
11671
  const configPath = getSyncConfigPath(basePath);
10794
11672
  try {
10795
- const content = await fs18.promises.readFile(configPath, "utf-8");
11673
+ const content = await fs19.promises.readFile(configPath, "utf-8");
10796
11674
  return JSON.parse(content);
10797
11675
  } catch {
10798
11676
  return null;
@@ -10800,8 +11678,8 @@ async function loadSyncConfig(basePath) {
10800
11678
  }
10801
11679
  async function saveSyncConfig(basePath, config2) {
10802
11680
  const configPath = getSyncConfigPath(basePath);
10803
- await fs18.promises.mkdir(path15.dirname(configPath), { recursive: true });
10804
- await fs18.promises.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
11681
+ await fs19.promises.mkdir(path16.dirname(configPath), { recursive: true });
11682
+ await fs19.promises.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
10805
11683
  }
10806
11684
  async function createSyncAdapter(basePath, globalOpts) {
10807
11685
  const config2 = await loadSyncConfig(basePath);
@@ -10831,8 +11709,8 @@ function initCommand() {
10831
11709
  agentName: options.name,
10832
11710
  environment: options.env
10833
11711
  });
10834
- const gitDir = path15.join(basePath, ".git");
10835
- if (!fs18.existsSync(gitDir)) {
11712
+ const gitDir = path16.join(basePath, ".git");
11713
+ if (!fs19.existsSync(gitDir)) {
10836
11714
  printError(`Not a git repository: ${basePath}`);
10837
11715
  printInfo("Initialize a git repository first with: git init");
10838
11716
  process.exit(1);
@@ -11108,7 +11986,7 @@ function resolveCommand() {
11108
11986
 
11109
11987
  // src/cli/commands/read.ts
11110
11988
  var import_commander23 = require("commander");
11111
- var path16 = __toESM(require("path"));
11989
+ var path17 = __toESM(require("path"));
11112
11990
  function serializeSkillMd(skill) {
11113
11991
  const lines = [];
11114
11992
  lines.push("---");
@@ -11154,7 +12032,7 @@ var readCommand = new import_commander23.Command("read").description("Read skill
11154
12032
  } else {
11155
12033
  console.log(`Reading: ${skill.name}`);
11156
12034
  }
11157
- const skillDir = path16.join(basePath, ".skilltree", "skills", skill.id);
12035
+ const skillDir = path17.join(basePath, ".skilltree", "skills", skill.id);
11158
12036
  console.log(`Base directory: ${skillDir}`);
11159
12037
  console.log("");
11160
12038
  console.log(serializeSkillMd(skill));
@@ -11231,19 +12109,19 @@ var materializeCommand = new import_commander24.Command("materialize").descripti
11231
12109
  });
11232
12110
 
11233
12111
  // src/cli/commands/loadout/index.ts
11234
- var import_commander38 = require("commander");
12112
+ var import_commander37 = require("commander");
11235
12113
 
11236
12114
  // src/cli/commands/loadout/list.ts
11237
12115
  var import_commander25 = require("commander");
11238
12116
 
11239
12117
  // src/serving/state-persistence.ts
11240
- var fs19 = __toESM(require("fs"));
11241
- var path17 = __toESM(require("path"));
12118
+ var fs20 = __toESM(require("fs"));
12119
+ var path18 = __toESM(require("path"));
11242
12120
  var STATE_FILENAME = ".loadout-state.json";
11243
12121
  function loadState(skillPath) {
11244
- const filePath = path17.join(skillPath, STATE_FILENAME);
12122
+ const filePath = path18.join(skillPath, STATE_FILENAME);
11245
12123
  try {
11246
- const raw = fs19.readFileSync(filePath, "utf-8");
12124
+ const raw = fs20.readFileSync(filePath, "utf-8");
11247
12125
  const data = JSON.parse(raw);
11248
12126
  return {
11249
12127
  available: new Map(data.available),
@@ -11257,7 +12135,7 @@ function loadState(skillPath) {
11257
12135
  }
11258
12136
  }
11259
12137
  function saveState(skillPath, state) {
11260
- const filePath = path17.join(skillPath, STATE_FILENAME);
12138
+ const filePath = path18.join(skillPath, STATE_FILENAME);
11261
12139
  const data = {
11262
12140
  available: Array.from(state.available.entries()),
11263
12141
  expanded: Array.from(state.expanded),
@@ -11265,12 +12143,12 @@ function saveState(skillPath, state) {
11265
12143
  source: state.source,
11266
12144
  updatedAt: state.updatedAt.toISOString()
11267
12145
  };
11268
- fs19.writeFileSync(filePath, JSON.stringify(data, null, 2));
12146
+ fs20.writeFileSync(filePath, JSON.stringify(data, null, 2));
11269
12147
  }
11270
12148
  function clearState(skillPath) {
11271
- const filePath = path17.join(skillPath, STATE_FILENAME);
12149
+ const filePath = path18.join(skillPath, STATE_FILENAME);
11272
12150
  try {
11273
- fs19.unlinkSync(filePath);
12151
+ fs20.unlinkSync(filePath);
11274
12152
  return true;
11275
12153
  } catch {
11276
12154
  return false;
@@ -11523,36 +12401,9 @@ var collapseSubcommand = new import_commander32.Command("collapse").description(
11523
12401
  }
11524
12402
  });
11525
12403
 
11526
- // src/cli/commands/loadout/use.ts
11527
- var import_commander33 = require("commander");
11528
- var useSubcommand = new import_commander33.Command("use").description("Mark a skill as being used (auto-expands and records usage)").argument("<id>", "Skill ID to use").action(async (id, _options, command) => {
11529
- const globalOpts = command.optsWithGlobals();
11530
- try {
11531
- const { server, save } = await createLoadoutServer(globalOpts);
11532
- server.recordUsage(id);
11533
- const expanded = server.expandSkill(id);
11534
- if (!expanded) {
11535
- printError(`Skill "${id}" not found in loadout`);
11536
- process.exit(1);
11537
- }
11538
- save();
11539
- const skill = server.getState().available.get(id);
11540
- if (globalOpts.json) {
11541
- console.log(JSON.stringify(skill, null, 2));
11542
- return;
11543
- }
11544
- printSuccess(`Using "${id}"`);
11545
- console.log();
11546
- console.log(formatSkillDetail(skill));
11547
- } catch (error) {
11548
- printError(error.message);
11549
- process.exit(1);
11550
- }
11551
- });
11552
-
11553
12404
  // src/cli/commands/loadout/get.ts
11554
- var import_commander34 = require("commander");
11555
- var getSubcommand = new import_commander34.Command("get").description("Get details about a skill in the loadout").argument("<id>", "Skill ID").action(async (id, _options, command) => {
12405
+ var import_commander33 = require("commander");
12406
+ var getSubcommand = new import_commander33.Command("get").description("Get details about a skill in the loadout").argument("<id>", "Skill ID").action(async (id, _options, command) => {
11556
12407
  const globalOpts = command.optsWithGlobals();
11557
12408
  try {
11558
12409
  const { server } = await createLoadoutServer(globalOpts);
@@ -11574,8 +12425,8 @@ var getSubcommand = new import_commander34.Command("get").description("Get detai
11574
12425
  });
11575
12426
 
11576
12427
  // src/cli/commands/loadout/render.ts
11577
- var import_commander35 = require("commander");
11578
- var renderSubcommand = new import_commander35.Command("render").description("Render the current loadout as a system prompt (XML or Markdown)").option("--format <format>", "Output format: xml or markdown", "xml").action(async (options, command) => {
12428
+ var import_commander34 = require("commander");
12429
+ var renderSubcommand = new import_commander34.Command("render").description("Render the current loadout as a system prompt (XML or Markdown)").option("--format <format>", "Output format: xml or markdown", "xml").action(async (options, command) => {
11579
12430
  const globalOpts = command.optsWithGlobals();
11580
12431
  try {
11581
12432
  const { server } = await createLoadoutServer(globalOpts);
@@ -11593,8 +12444,8 @@ var renderSubcommand = new import_commander35.Command("render").description("Ren
11593
12444
  });
11594
12445
 
11595
12446
  // src/cli/commands/loadout/clear.ts
11596
- var import_commander36 = require("commander");
11597
- var clearSubcommand = new import_commander36.Command("clear").description("Clear the current loadout state").action(async (_options, command) => {
12447
+ var import_commander35 = require("commander");
12448
+ var clearSubcommand = new import_commander35.Command("clear").description("Clear the current loadout state").action(async (_options, command) => {
11598
12449
  const globalOpts = command.optsWithGlobals();
11599
12450
  try {
11600
12451
  const skillPath = resolveSkillPath(globalOpts.path);
@@ -11615,15 +12466,15 @@ var clearSubcommand = new import_commander36.Command("clear").description("Clear
11615
12466
  });
11616
12467
 
11617
12468
  // src/cli/commands/loadout/browse.ts
11618
- var import_commander37 = require("commander");
11619
- var browseSubcommand = new import_commander37.Command("browse").description("Browse the skill catalog by category").argument("[path]", "Category path to browse (e.g., Development/Python)").action(async (pathArg, _options, command) => {
12469
+ var import_commander36 = require("commander");
12470
+ var browseSubcommand = new import_commander36.Command("browse").description("Browse the skill catalog by category").argument("[path]", "Category path to browse (e.g., Development/Python)").action(async (pathArg, _options, command) => {
11620
12471
  const globalOpts = command.optsWithGlobals();
11621
12472
  try {
11622
12473
  const { server } = await createLoadoutServer(globalOpts);
11623
- const path18 = pathArg ? pathArg.split("/").filter(Boolean) : void 0;
11624
- const output = await server.agentBrowseCatalog(path18);
12474
+ const path19 = pathArg ? pathArg.split("/").filter(Boolean) : void 0;
12475
+ const output = await server.agentBrowseCatalog(path19);
11625
12476
  if (globalOpts.json) {
11626
- console.log(JSON.stringify({ path: path18 ?? [], output }, null, 2));
12477
+ console.log(JSON.stringify({ path: path19 ?? [], output }, null, 2));
11627
12478
  return;
11628
12479
  }
11629
12480
  if (!output) {
@@ -11638,7 +12489,94 @@ var browseSubcommand = new import_commander37.Command("browse").description("Bro
11638
12489
  });
11639
12490
 
11640
12491
  // src/cli/commands/loadout/index.ts
11641
- var loadoutCommand = new import_commander38.Command("loadout").description("Manage skill loadouts for agent sessions").addCommand(listSubcommand).addCommand(searchSubcommand).addCommand(addSubcommand).addCommand(removeSubcommand).addCommand(profileSubcommand).addCommand(setSubcommand).addCommand(expandSubcommand).addCommand(collapseSubcommand).addCommand(useSubcommand).addCommand(getSubcommand).addCommand(renderSubcommand).addCommand(clearSubcommand).addCommand(browseSubcommand);
12492
+ var loadoutCommand = new import_commander37.Command("loadout").description("Manage skill loadouts for agent sessions").addCommand(listSubcommand).addCommand(searchSubcommand).addCommand(addSubcommand).addCommand(removeSubcommand).addCommand(profileSubcommand).addCommand(setSubcommand).addCommand(expandSubcommand).addCommand(collapseSubcommand).addCommand(getSubcommand).addCommand(renderSubcommand).addCommand(clearSubcommand).addCommand(browseSubcommand);
12493
+
12494
+ // src/cli/commands/skillnet.ts
12495
+ var import_commander38 = require("commander");
12496
+ function buildClient() {
12497
+ return createSkillNetClient({
12498
+ apiBaseUrl: process.env.SKILLNET_API_URL,
12499
+ githubToken: process.env.GITHUB_TOKEN,
12500
+ githubMirror: process.env.GITHUB_MIRROR
12501
+ });
12502
+ }
12503
+ var searchSubcommand2 = new import_commander38.Command("search").description("Search the SkillNet index (free, no API key)").argument("<query>", "Search query (keywords or natural language)").option("-m, --mode <mode>", "Search mode: keyword or vector", "keyword").option("-c, --category <category>", "Category filter (Development, Research, ...)").option("-l, --limit <n>", "Results per page (max 50)", (v) => parseInt(v, 10)).option("--min-stars <n>", "Minimum star count (keyword mode)", (v) => parseInt(v, 10)).option("--sort-by <field>", "Sort by stars or recent (keyword mode)").option("--threshold <n>", "Similarity threshold 0-1 (vector mode)", (v) => parseFloat(v)).action(async (query, options, command) => {
12504
+ const globalOpts = command.optsWithGlobals();
12505
+ try {
12506
+ const client = buildClient();
12507
+ const searchOpts = {
12508
+ mode: options.mode,
12509
+ category: options.category,
12510
+ limit: options.limit,
12511
+ minStars: options.minStars,
12512
+ sortBy: options.sortBy,
12513
+ threshold: options.threshold
12514
+ };
12515
+ const results = await client.search(query, searchOpts);
12516
+ if (globalOpts.json) {
12517
+ console.log(JSON.stringify(results, null, 2));
12518
+ return;
12519
+ }
12520
+ if (results.length === 0) {
12521
+ printInfo("No skills found.");
12522
+ return;
12523
+ }
12524
+ printInfo(`Found ${results.length} skill(s):
12525
+ `);
12526
+ for (const r of results) {
12527
+ console.log(` ${r.skillName} \u2B50${r.stars}${r.category ? ` [${r.category}]` : ""}`);
12528
+ if (r.skillDescription) console.log(` ${r.skillDescription}`);
12529
+ console.log(` ${r.skillUrl}`);
12530
+ }
12531
+ } catch (err) {
12532
+ printError(err.message);
12533
+ process.exit(1);
12534
+ }
12535
+ });
12536
+ var importSubcommand = new import_commander38.Command("import").description("Import skills from SkillNet into the local skill bank").argument("[query]", "Search query to import matching skills").option("-u, --url <url>", "Import a single skill by its GitHub/SkillNet URL").option("-m, --mode <mode>", "Search mode: keyword or vector", "keyword").option("-c, --category <category>", "Category filter").option("-l, --limit <n>", "Max skills to import", (v) => parseInt(v, 10), 5).option("--min-stars <n>", "Minimum star count", (v) => parseInt(v, 10)).option("--threshold <n>", "Similarity threshold 0-1 (vector mode)", (v) => parseFloat(v)).action(async (query, options, command) => {
12537
+ const globalOpts = command.optsWithGlobals();
12538
+ if (!query && !options.url) {
12539
+ printError("Provide a search query or --url to import a single skill");
12540
+ process.exit(1);
12541
+ }
12542
+ try {
12543
+ const client = buildClient();
12544
+ const bank = await createSkillBankFromOptions(globalOpts);
12545
+ if (options.url) {
12546
+ const converted = await client.importSkill(options.url, bank);
12547
+ if (globalOpts.json) {
12548
+ console.log(JSON.stringify({ imported: 1, skill: converted.skill.id }, null, 2));
12549
+ } else {
12550
+ printSuccess(`Imported "${converted.skill.id}"`);
12551
+ for (const w of converted.warnings) printWarning(w);
12552
+ }
12553
+ return;
12554
+ }
12555
+ if (!globalOpts.quiet) {
12556
+ printInfo(`Searching SkillNet for "${query}"...`);
12557
+ }
12558
+ const result = await client.importFromSearch(query, bank, {
12559
+ mode: options.mode,
12560
+ category: options.category,
12561
+ limit: options.limit,
12562
+ minStars: options.minStars,
12563
+ threshold: options.threshold
12564
+ });
12565
+ if (globalOpts.json) {
12566
+ console.log(JSON.stringify(result, null, 2));
12567
+ return;
12568
+ }
12569
+ printSuccess(`Imported ${result.imported} skill(s)`);
12570
+ if (result.failed > 0) {
12571
+ printWarning(`Failed to import ${result.failed} skill(s)`);
12572
+ for (const e of result.errors) printInfo(` - ${e}`);
12573
+ }
12574
+ } catch (err) {
12575
+ printError(err.message);
12576
+ process.exit(1);
12577
+ }
12578
+ });
12579
+ var skillnetCommand = new import_commander38.Command("skillnet").description("Search and import skills from the SkillNet ecosystem").addCommand(searchSubcommand2).addCommand(importSubcommand);
11642
12580
 
11643
12581
  // src/cli/index.ts
11644
12582
  var program = new import_commander39.Command();
@@ -11663,5 +12601,5 @@ program.addCommand(syncCommand2);
11663
12601
  program.addCommand(readCommand);
11664
12602
  program.addCommand(materializeCommand);
11665
12603
  program.addCommand(loadoutCommand);
12604
+ program.addCommand(skillnetCommand);
11666
12605
  program.parse();
11667
- //# sourceMappingURL=index.js.map