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.
- package/README.md +102 -2
- package/dist/bowser-CQI7RKRA.mjs +2821 -0
- package/dist/chunk-2NL4MXNX.mjs +3156 -0
- package/dist/chunk-2STDJU5Y.mjs +1174 -0
- package/dist/chunk-3BCRI4CA.mjs +101 -0
- package/dist/chunk-3SRB47JW.mjs +8344 -0
- package/dist/chunk-43YOKLZP.mjs +6081 -0
- package/dist/chunk-4AGZU52D.mjs +7918 -0
- package/dist/chunk-4HXHCEFH.mjs +9157 -0
- package/dist/chunk-4OC5QFIF.mjs +11267 -0
- package/dist/chunk-4QGSDVGH.mjs +580 -0
- package/dist/chunk-4TFMKAVC.mjs +1225 -0
- package/dist/chunk-55SMGVTP.mjs +7126 -0
- package/dist/chunk-5C4MEQMR.mjs +125 -0
- package/dist/chunk-6FX4IK4Z.mjs +5368 -0
- package/dist/chunk-6UPDN5QM.mjs +163 -0
- package/dist/chunk-7EGDKOHV.mjs +9439 -0
- package/dist/chunk-7LMOQW5H.mjs +4893 -0
- package/dist/chunk-7QIQJVNP.mjs +14206 -0
- package/dist/chunk-7VB4ZRZO.mjs +7127 -0
- package/dist/chunk-A3SILZYX.mjs +8360 -0
- package/dist/chunk-BPVRW25O.mjs +6089 -0
- package/dist/chunk-BZ2JKJ54.mjs +1057 -0
- package/dist/chunk-CI4476KM.mjs +6607 -0
- package/dist/chunk-DCRKELD5.mjs +46 -0
- package/dist/chunk-DDXYQ74I.mjs +13969 -0
- package/dist/chunk-DQOFJXBX.mjs +6595 -0
- package/dist/chunk-E2CVK23F.mjs +8751 -0
- package/dist/chunk-F3YEUQAP.mjs +654 -0
- package/dist/chunk-FKJJ4RJG.mjs +13874 -0
- package/dist/chunk-II7DECZQ.mjs +9111 -0
- package/dist/chunk-INKVOZXK.mjs +15898 -0
- package/dist/chunk-J2JM7HAK.mjs +8787 -0
- package/dist/chunk-K6NRCSAZ.mjs +4355 -0
- package/dist/chunk-LACI6YL4.mjs +1379 -0
- package/dist/chunk-MBIGW6KU.mjs +644 -0
- package/dist/chunk-OYHYXKXO.mjs +7297 -0
- package/dist/chunk-P5GJJ4JB.mjs +9237 -0
- package/dist/chunk-PDPN7FW7.mjs +1045 -0
- package/dist/chunk-QNK3WYNA.mjs +8971 -0
- package/dist/chunk-QZ7TP4HQ.mjs +7 -0
- package/dist/chunk-RJYJGJO3.mjs +349 -0
- package/dist/chunk-T4PVQW5O.mjs +124 -0
- package/dist/chunk-TEUB6DZR.mjs +6453 -0
- package/dist/chunk-TWPEHDW4.mjs +1067 -0
- package/dist/chunk-VHFTX33A.mjs +6724 -0
- package/dist/chunk-Y54UK2J3.mjs +13071 -0
- package/dist/chunk-YDVZIFIU.mjs +2102 -0
- package/dist/chunk-ZQVS7MQK.mjs +6081 -0
- package/dist/chunk-ZYKRDDFO.mjs +163 -0
- package/dist/cli/index.js +1324 -386
- package/dist/cli/index.mjs +212 -9074
- package/dist/dist-es-2JG6ZWFR.mjs +69 -0
- package/dist/dist-es-2JGXQKUP.mjs +6077 -0
- package/dist/dist-es-644EP2LP.mjs +317 -0
- package/dist/dist-es-DSNCHWLJ.mjs +170 -0
- package/dist/dist-es-FIVW7BUZ.mjs +317 -0
- package/dist/dist-es-GXJAFBE5.mjs +22 -0
- package/dist/dist-es-HRBPKDMR.mjs +935 -0
- package/dist/dist-es-LHPJ63IO.mjs +4437 -0
- package/dist/dist-es-LT2AQAG7.mjs +4437 -0
- package/dist/dist-es-ORE4PQTL.mjs +87 -0
- package/dist/dist-es-TLCYJJ25.mjs +495 -0
- package/dist/dist-es-V4LHTSRG.mjs +69 -0
- package/dist/dist-es-XHTU3ZU2.mjs +935 -0
- package/dist/dist-es-Y2MPJ6IO.mjs +378 -0
- package/dist/dist-es-ZYHLY2E6.mjs +487 -0
- package/dist/event-streams-KIAAAC7Z.mjs +42 -0
- package/dist/index.d.mts +1143 -56
- package/dist/index.d.ts +1143 -56
- package/dist/index.js +38701 -499
- package/dist/index.mjs +129 -9612
- package/dist/loadSso-NPRY7QRT.mjs +579 -0
- package/dist/loadSso-OYKG6ZRE.mjs +579 -0
- package/dist/signin-LMFNL434.mjs +665 -0
- package/dist/signin-LUKXFXSI.mjs +743 -0
- package/dist/sqlite-MG45OOTV.mjs +6 -0
- package/dist/sqlite-OLU72GHB.mjs +6 -0
- package/dist/sqlite-RR2SJ3SR.mjs +7 -0
- package/dist/sqlite-XJRPMNAJ.mjs +6 -0
- package/dist/sso-oidc-NNH6SQIH.mjs +832 -0
- package/dist/sso-oidc-STZH2XK2.mjs +832 -0
- package/dist/sts-EF755UBF.mjs +6290 -0
- package/dist/sts-ZIS4G6FQ.mjs +6290 -0
- package/dist/sync-BSWMMDA6.mjs +14 -0
- package/dist/sync-WHIIDHML.mjs +14 -0
- package/dist/sync-XRWFQYBY.mjs +15 -0
- package/package.json +9 -2
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/index.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- 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
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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,
|
|
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(
|
|
1023
|
+
async ensureTaxonomyNode(path19) {
|
|
857
1024
|
this.ensureInitialized();
|
|
858
1025
|
const db = this.getDb();
|
|
859
|
-
const pathStr =
|
|
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 =
|
|
864
|
-
const parentPath =
|
|
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
|
|
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(
|
|
3055
|
+
async renderCategory(path19) {
|
|
2924
3056
|
if (hasCatalogSupport(this.storage)) {
|
|
2925
|
-
return this.renderCategoryFromTaxonomy(this.storage,
|
|
3057
|
+
return this.renderCategoryFromTaxonomy(this.storage, path19);
|
|
2926
3058
|
}
|
|
2927
|
-
return this.renderCategoryFromTags(
|
|
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,
|
|
2966
|
-
const tree = await storage.getTaxonomyTree(
|
|
2967
|
-
const pathStr =
|
|
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 = [...
|
|
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(
|
|
3002
|
-
if (
|
|
3133
|
+
async renderCategoryFromTags(path19) {
|
|
3134
|
+
if (path19.length === 0) {
|
|
3003
3135
|
return this.renderOverviewFromTags();
|
|
3004
3136
|
}
|
|
3005
|
-
const tag =
|
|
3137
|
+
const tag = path19[0];
|
|
3006
3138
|
const matching = await this.storage.listSkills({ status: ["active"], tags: [tag] });
|
|
3007
|
-
const pathStr =
|
|
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
|
|
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
|
|
3644
|
+
* Apply semantic filters (task description matching).
|
|
3183
3645
|
*
|
|
3184
|
-
*
|
|
3185
|
-
*
|
|
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,
|
|
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
|
|
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
|
|
3715
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3855
|
-
|
|
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
|
|
4066
|
-
|
|
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(
|
|
4629
|
+
async agentBrowseCatalog(path19) {
|
|
4112
4630
|
if (!this.catalogRenderer) {
|
|
4113
4631
|
return "<error>Catalog browsing is not enabled</error>";
|
|
4114
4632
|
}
|
|
4115
|
-
if (!
|
|
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(
|
|
4123
|
-
this.emit({ type: "catalog:browsed", path:
|
|
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(
|
|
4689
|
+
prompt = this.viewRenderer.renderMarkdown(renderState);
|
|
4160
4690
|
} else {
|
|
4161
|
-
prompt = this.viewRenderer.renderXml(
|
|
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
|
-
*
|
|
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,
|
|
7870
|
-
const parts =
|
|
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(
|
|
8030
|
-
const parts =
|
|
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.
|
|
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
|
|
8988
|
-
var
|
|
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
|
|
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
|
|
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
|
|
9882
|
+
return path14.resolve(resolved);
|
|
9017
9883
|
}
|
|
9018
9884
|
const envPath = process.env.SKILL_TREE_PATH;
|
|
9019
9885
|
if (envPath) {
|
|
9020
|
-
return
|
|
9886
|
+
return path14.resolve(expandHome(envPath));
|
|
9021
9887
|
}
|
|
9022
9888
|
for (const defaultPath of DEFAULT_PATHS) {
|
|
9023
|
-
const resolved =
|
|
9889
|
+
const resolved = path14.resolve(expandHome(defaultPath));
|
|
9024
9890
|
if (dirExists(resolved)) {
|
|
9025
9891
|
return resolved;
|
|
9026
9892
|
}
|
|
9027
9893
|
}
|
|
9028
|
-
return
|
|
9894
|
+
return path14.resolve(".claude/skills");
|
|
9029
9895
|
}
|
|
9030
9896
|
function ensureDir(dir) {
|
|
9031
9897
|
if (!dirExists(dir)) {
|
|
9032
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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"
|
|
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("<
|
|
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 (!
|
|
9574
|
-
printError(`
|
|
10430
|
+
if (!fs16.existsSync(file)) {
|
|
10431
|
+
printError(`Path not found: ${file}`);
|
|
9575
10432
|
process.exit(1);
|
|
9576
10433
|
}
|
|
9577
|
-
const
|
|
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
|
|
10107
|
-
var
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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
|
|
10788
|
-
var
|
|
11665
|
+
var path16 = __toESM(require("path"));
|
|
11666
|
+
var fs19 = __toESM(require("fs"));
|
|
10789
11667
|
function getSyncConfigPath(basePath) {
|
|
10790
|
-
return
|
|
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
|
|
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
|
|
10804
|
-
await
|
|
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 =
|
|
10835
|
-
if (!
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
11241
|
-
var
|
|
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 =
|
|
12122
|
+
const filePath = path18.join(skillPath, STATE_FILENAME);
|
|
11245
12123
|
try {
|
|
11246
|
-
const raw =
|
|
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 =
|
|
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
|
-
|
|
12146
|
+
fs20.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
11269
12147
|
}
|
|
11270
12148
|
function clearState(skillPath) {
|
|
11271
|
-
const filePath =
|
|
12149
|
+
const filePath = path18.join(skillPath, STATE_FILENAME);
|
|
11272
12150
|
try {
|
|
11273
|
-
|
|
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
|
|
11555
|
-
var getSubcommand = new
|
|
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
|
|
11578
|
-
var renderSubcommand = new
|
|
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
|
|
11597
|
-
var clearSubcommand = new
|
|
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
|
|
11619
|
-
var browseSubcommand = new
|
|
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
|
|
11624
|
-
const output = await server.agentBrowseCatalog(
|
|
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:
|
|
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
|
|
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
|