skill-tree 0.1.4 → 0.1.5

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/dist/cli/index.js CHANGED
@@ -136,10 +136,8 @@ var init_base = __esm({
136
136
  const searchText = [
137
137
  skill.name,
138
138
  skill.description,
139
- skill.problem,
140
- skill.solution,
141
- ...skill.tags,
142
- skill.triggerConditions.map((t) => t.value).join(" ")
139
+ skill.instructions,
140
+ ...skill.tags
143
141
  ].join(" ").toLowerCase();
144
142
  return terms.every((term) => searchText.includes(term));
145
143
  });
@@ -264,9 +262,7 @@ var init_base = __esm({
264
262
  }
265
263
  hashSkill(skill) {
266
264
  const content = JSON.stringify({
267
- problem: skill.problem,
268
- solution: skill.solution,
269
- triggerConditions: skill.triggerConditions
265
+ instructions: skill.instructions
270
266
  });
271
267
  let hash = 0;
272
268
  for (let i = 0; i < content.length; i++) {
@@ -315,7 +311,7 @@ var init_sqlite = __esm({
315
311
  path7 = __toESM(require("path"));
316
312
  fs7 = __toESM(require("fs"));
317
313
  init_base();
318
- SCHEMA_VERSION = 2;
314
+ SCHEMA_VERSION = 3;
319
315
  SQLiteStorageAdapter = class extends BaseStorageAdapter {
320
316
  constructor(config2) {
321
317
  super();
@@ -353,12 +349,8 @@ var init_sqlite = __esm({
353
349
  version TEXT NOT NULL,
354
350
  name TEXT NOT NULL,
355
351
  description TEXT NOT NULL,
356
- problem TEXT NOT NULL,
357
- trigger_conditions TEXT NOT NULL,
358
- solution TEXT NOT NULL,
359
- verification TEXT NOT NULL,
360
- examples TEXT NOT NULL,
361
- notes TEXT,
352
+ instructions TEXT NOT NULL DEFAULT '',
353
+ related TEXT,
362
354
  author TEXT NOT NULL,
363
355
  tags TEXT NOT NULL,
364
356
  created_at TEXT NOT NULL,
@@ -413,10 +405,8 @@ var init_sqlite = __esm({
413
405
  skill_id,
414
406
  name,
415
407
  description,
416
- problem,
417
- solution,
418
- tags,
419
- trigger_values
408
+ instructions,
409
+ tags
420
410
  )
421
411
  `);
422
412
  }
@@ -491,6 +481,49 @@ var init_sqlite = __esm({
491
481
  } catch {
492
482
  }
493
483
  }
484
+ if (currentVersion < 3) {
485
+ try {
486
+ db.exec("ALTER TABLE skills ADD COLUMN instructions TEXT NOT NULL DEFAULT ''");
487
+ } catch {
488
+ }
489
+ try {
490
+ db.exec("ALTER TABLE skills ADD COLUMN related TEXT");
491
+ } catch {
492
+ }
493
+ try {
494
+ db.exec(`
495
+ UPDATE skills SET instructions =
496
+ COALESCE(problem, '') || CHAR(10) || CHAR(10) ||
497
+ COALESCE(solution, '') || CHAR(10) || CHAR(10) ||
498
+ COALESCE(verification, '')
499
+ WHERE instructions = ''
500
+ `);
501
+ } catch {
502
+ }
503
+ if (this.config.enableFTS) {
504
+ try {
505
+ db.exec("DROP TABLE IF EXISTS skills_fts");
506
+ db.exec(`
507
+ CREATE VIRTUAL TABLE skills_fts USING fts5(
508
+ skill_id, name, description, instructions, tags
509
+ )
510
+ `);
511
+ const rows = db.prepare("SELECT id, name, description, instructions, tags FROM skills").all();
512
+ const insertFts = db.prepare("INSERT INTO skills_fts (skill_id, name, description, instructions, tags) VALUES (?, ?, ?, ?, ?)");
513
+ for (const row2 of rows) {
514
+ const tags = (() => {
515
+ try {
516
+ return JSON.parse(row2.tags).join(" ");
517
+ } catch {
518
+ return row2.tags;
519
+ }
520
+ })();
521
+ insertFts.run(row2.id, row2.name, row2.description, row2.instructions, tags);
522
+ }
523
+ } catch {
524
+ }
525
+ }
526
+ }
494
527
  db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
495
528
  }
496
529
  }
@@ -505,11 +538,11 @@ var init_sqlite = __esm({
505
538
  const db = this.getDb();
506
539
  const stmt = db.prepare(`
507
540
  INSERT OR REPLACE INTO skills (
508
- id, version, name, description, problem, trigger_conditions, solution,
509
- verification, examples, notes, author, tags, created_at, updated_at,
510
- status, parent_version, derived_from, metrics, source, taxonomy, external_source
541
+ id, version, name, description, instructions, related, author, tags,
542
+ created_at, updated_at, status, parent_version, derived_from, metrics,
543
+ source, taxonomy, external_source
511
544
  ) VALUES (
512
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
545
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
513
546
  )
514
547
  `);
515
548
  stmt.run(
@@ -517,12 +550,8 @@ var init_sqlite = __esm({
517
550
  skill.version,
518
551
  skill.name,
519
552
  skill.description,
520
- skill.problem,
521
- JSON.stringify(skill.triggerConditions),
522
- skill.solution,
523
- skill.verification,
524
- JSON.stringify(skill.examples),
525
- skill.notes || null,
553
+ skill.instructions,
554
+ skill.related ? JSON.stringify(skill.related) : null,
526
555
  skill.author,
527
556
  JSON.stringify(skill.tags),
528
557
  skill.createdAt.toISOString(),
@@ -550,12 +579,11 @@ var init_sqlite = __esm({
550
579
  updateFTSIndex(skill) {
551
580
  const db = this.getDb();
552
581
  db.prepare("DELETE FROM skills_fts WHERE skill_id = ?").run(skill.id);
553
- const triggerValues = skill.triggerConditions.map((t) => t.value).join(" ");
554
582
  const tags = skill.tags.join(" ");
555
583
  db.prepare(`
556
- INSERT INTO skills_fts (skill_id, name, description, problem, solution, tags, trigger_values)
557
- VALUES (?, ?, ?, ?, ?, ?, ?)
558
- `).run(skill.id, skill.name, skill.description, skill.problem, skill.solution, tags, triggerValues);
584
+ INSERT INTO skills_fts (skill_id, name, description, instructions, tags)
585
+ VALUES (?, ?, ?, ?, ?)
586
+ `).run(skill.id, skill.name, skill.description, skill.instructions, tags);
559
587
  }
560
588
  async saveVersionSnapshot(skill) {
561
589
  const db = this.getDb();
@@ -825,15 +853,15 @@ var init_sqlite = __esm({
825
853
  /**
826
854
  * Get or create a taxonomy node
827
855
  */
828
- async ensureTaxonomyNode(path15) {
856
+ async ensureTaxonomyNode(path18) {
829
857
  this.ensureInitialized();
830
858
  const db = this.getDb();
831
- const pathStr = path15.join("/");
859
+ const pathStr = path18.join("/");
832
860
  const existing = db.prepare("SELECT id FROM taxonomy_nodes WHERE path = ?").get(pathStr);
833
861
  if (existing) return existing.id;
834
862
  const id = `node-${pathStr.replace(/\//g, "-").toLowerCase()}`;
835
- const name = path15[path15.length - 1] || "Root";
836
- const parentPath = path15.slice(0, -1);
863
+ const name = path18[path18.length - 1] || "Root";
864
+ const parentPath = path18.slice(0, -1);
837
865
  let parentId = null;
838
866
  if (parentPath.length > 0) {
839
867
  parentId = await this.ensureTaxonomyNode(parentPath);
@@ -1015,12 +1043,8 @@ var init_sqlite = __esm({
1015
1043
  version: row.version,
1016
1044
  name: row.name,
1017
1045
  description: row.description,
1018
- problem: row.problem,
1019
- triggerConditions: JSON.parse(row.trigger_conditions),
1020
- solution: row.solution,
1021
- verification: row.verification,
1022
- examples: JSON.parse(row.examples),
1023
- notes: row.notes || void 0,
1046
+ instructions: row.instructions,
1047
+ related: row.related ? JSON.parse(row.related) : void 0,
1024
1048
  author: row.author,
1025
1049
  tags: JSON.parse(row.tags),
1026
1050
  createdAt: new Date(row.created_at),
@@ -1069,9 +1093,7 @@ var init_sqlite = __esm({
1069
1093
  }
1070
1094
  hashSkill(skill) {
1071
1095
  const content = JSON.stringify({
1072
- problem: skill.problem,
1073
- solution: skill.solution,
1074
- triggerConditions: skill.triggerConditions
1096
+ instructions: skill.instructions
1075
1097
  });
1076
1098
  let hash = 0;
1077
1099
  for (let i = 0; i < content.length; i++) {
@@ -1085,8 +1107,677 @@ var init_sqlite = __esm({
1085
1107
  }
1086
1108
  });
1087
1109
 
1110
+ // src/agents/types.ts
1111
+ var DEFAULT_AGENTS_CONFIG;
1112
+ var init_types = __esm({
1113
+ "src/agents/types.ts"() {
1114
+ "use strict";
1115
+ DEFAULT_AGENTS_CONFIG = {
1116
+ format: "xml",
1117
+ includeIds: true,
1118
+ includeVersions: true,
1119
+ groupByTags: false
1120
+ };
1121
+ }
1122
+ });
1123
+
1124
+ // src/agents/generator.ts
1125
+ var AgentsGenerator;
1126
+ var init_generator = __esm({
1127
+ "src/agents/generator.ts"() {
1128
+ "use strict";
1129
+ init_types();
1130
+ AgentsGenerator = class {
1131
+ constructor(config2) {
1132
+ this.config = { ...DEFAULT_AGENTS_CONFIG, ...config2 };
1133
+ }
1134
+ /**
1135
+ * Generate AGENTS.md content from a skill bank
1136
+ */
1137
+ async generate(storage) {
1138
+ let skills = await storage.listSkills({
1139
+ status: this.config.filter?.status || ["active"]
1140
+ });
1141
+ skills = this.filterSkills(skills, this.config.filter);
1142
+ skills.sort((a, b) => a.name.localeCompare(b.name));
1143
+ const sections = [];
1144
+ if (this.config.header) {
1145
+ sections.push(this.config.header);
1146
+ } else {
1147
+ sections.push(this.generateDefaultHeader());
1148
+ }
1149
+ if (this.config.groupByTags) {
1150
+ sections.push(this.generateGroupedSkills(skills));
1151
+ } else {
1152
+ sections.push(this.generateSkillsList(skills));
1153
+ }
1154
+ if (this.config.footer) {
1155
+ sections.push(this.config.footer);
1156
+ }
1157
+ return sections.join("\n\n");
1158
+ }
1159
+ /**
1160
+ * Generate content for a single skill
1161
+ */
1162
+ generateSkill(skill) {
1163
+ switch (this.config.format) {
1164
+ case "xml":
1165
+ return this.generateXmlSkill(skill);
1166
+ case "markdown":
1167
+ return this.generateMarkdownSkill(skill);
1168
+ case "json":
1169
+ return this.generateJsonSkill(skill);
1170
+ default:
1171
+ return this.generateXmlSkill(skill);
1172
+ }
1173
+ }
1174
+ /**
1175
+ * Generate skills list
1176
+ */
1177
+ generateSkillsList(skills) {
1178
+ return skills.map((skill) => this.generateSkill(skill)).join("\n\n");
1179
+ }
1180
+ /**
1181
+ * Generate skills grouped by tags
1182
+ */
1183
+ generateGroupedSkills(skills) {
1184
+ const tagGroups = /* @__PURE__ */ new Map();
1185
+ const untagged = [];
1186
+ for (const skill of skills) {
1187
+ if (skill.tags.length === 0) {
1188
+ untagged.push(skill);
1189
+ } else {
1190
+ const primaryTag = skill.tags[0];
1191
+ if (!tagGroups.has(primaryTag)) {
1192
+ tagGroups.set(primaryTag, []);
1193
+ }
1194
+ tagGroups.get(primaryTag).push(skill);
1195
+ }
1196
+ }
1197
+ const sections = [];
1198
+ const sortedTags = Array.from(tagGroups.keys()).sort();
1199
+ for (const tag of sortedTags) {
1200
+ const tagSkills = tagGroups.get(tag);
1201
+ sections.push(`## ${this.formatTagName(tag)}
1202
+
1203
+ ${this.generateSkillsList(tagSkills)}`);
1204
+ }
1205
+ if (untagged.length > 0) {
1206
+ sections.push(`## Other
1207
+
1208
+ ${this.generateSkillsList(untagged)}`);
1209
+ }
1210
+ return sections.join("\n\n");
1211
+ }
1212
+ /**
1213
+ * Generate XML format skill (Claude Code native format)
1214
+ */
1215
+ generateXmlSkill(skill) {
1216
+ const lines = [];
1217
+ const attrs = [];
1218
+ if (this.config.includeIds) {
1219
+ attrs.push(`id="${this.escapeXml(skill.id)}"`);
1220
+ }
1221
+ if (this.config.includeVersions) {
1222
+ attrs.push(`version="${this.escapeXml(skill.version)}"`);
1223
+ }
1224
+ const attrStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
1225
+ lines.push(`<skill${attrStr}>`);
1226
+ lines.push(` <name>${this.escapeXml(skill.name)}</name>`);
1227
+ if (skill.description) {
1228
+ lines.push(` <description>${this.escapeXml(skill.description)}</description>`);
1229
+ }
1230
+ if (skill.instructions) {
1231
+ lines.push(` <content>
1232
+ ${this.indentContent(skill.instructions, 4)}
1233
+ </content>`);
1234
+ }
1235
+ if (skill.tags.length > 0) {
1236
+ lines.push(` <tags>${skill.tags.map((t) => this.escapeXml(t)).join(", ")}</tags>`);
1237
+ }
1238
+ lines.push("</skill>");
1239
+ return lines.join("\n");
1240
+ }
1241
+ /**
1242
+ * Generate Markdown format skill
1243
+ */
1244
+ generateMarkdownSkill(skill) {
1245
+ const lines = [];
1246
+ const idSuffix = this.config.includeIds ? ` {#${skill.id}}` : "";
1247
+ const versionSuffix = this.config.includeVersions ? ` (v${skill.version})` : "";
1248
+ lines.push(`### ${skill.name}${versionSuffix}${idSuffix}`);
1249
+ lines.push("");
1250
+ if (skill.description) {
1251
+ lines.push(`*${skill.description}*`);
1252
+ lines.push("");
1253
+ }
1254
+ if (skill.instructions) {
1255
+ lines.push(skill.instructions);
1256
+ lines.push("");
1257
+ }
1258
+ if (skill.tags.length > 0) {
1259
+ lines.push(`Tags: ${skill.tags.map((t) => `\`${t}\``).join(", ")}`);
1260
+ }
1261
+ return lines.join("\n");
1262
+ }
1263
+ /**
1264
+ * Generate JSON format skill
1265
+ */
1266
+ generateJsonSkill(skill) {
1267
+ const obj = {
1268
+ name: skill.name,
1269
+ description: skill.description
1270
+ };
1271
+ if (this.config.includeIds) {
1272
+ obj.id = skill.id;
1273
+ }
1274
+ if (this.config.includeVersions) {
1275
+ obj.version = skill.version;
1276
+ }
1277
+ if (skill.instructions) {
1278
+ obj.instructions = skill.instructions;
1279
+ }
1280
+ if (skill.tags.length > 0) {
1281
+ obj.tags = skill.tags;
1282
+ }
1283
+ return "```json\n" + JSON.stringify(obj, null, 2) + "\n```";
1284
+ }
1285
+ /**
1286
+ * Filter skills based on selector
1287
+ */
1288
+ filterSkills(skills, filter) {
1289
+ if (!filter) return skills;
1290
+ let result = skills;
1291
+ if (filter.ids && filter.ids.length > 0) {
1292
+ result = result.filter((s) => filter.ids.includes(s.id));
1293
+ }
1294
+ if (filter.tags && filter.tags.length > 0) {
1295
+ result = result.filter((s) => s.tags.some((t) => filter.tags.includes(t)));
1296
+ }
1297
+ if (filter.minSuccessRate !== void 0) {
1298
+ result = result.filter((s) => s.metrics.successRate >= filter.minSuccessRate);
1299
+ }
1300
+ if (filter.limit) {
1301
+ result = result.slice(0, filter.limit);
1302
+ }
1303
+ return result;
1304
+ }
1305
+ /**
1306
+ * Generate default header
1307
+ */
1308
+ generateDefaultHeader() {
1309
+ return `# Agent Skills
1310
+
1311
+ This file contains skills for the AI agent. Each skill describes a pattern for solving a specific type of problem.
1312
+
1313
+ ---`;
1314
+ }
1315
+ /**
1316
+ * Format tag name for section header
1317
+ */
1318
+ formatTagName(tag) {
1319
+ return tag.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1320
+ }
1321
+ /**
1322
+ * Escape XML special characters
1323
+ */
1324
+ escapeXml(str) {
1325
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1326
+ }
1327
+ /**
1328
+ * Indent content
1329
+ */
1330
+ indentContent(content, spaces) {
1331
+ const indent = " ".repeat(spaces);
1332
+ return content.split("\n").map((line) => indent + line).join("\n");
1333
+ }
1334
+ };
1335
+ }
1336
+ });
1337
+
1338
+ // src/agents/parser.ts
1339
+ var AgentsParser;
1340
+ var init_parser = __esm({
1341
+ "src/agents/parser.ts"() {
1342
+ "use strict";
1343
+ AgentsParser = class {
1344
+ /**
1345
+ * Parse AGENTS.md content
1346
+ */
1347
+ parse(content) {
1348
+ const warnings = [];
1349
+ const skills = [];
1350
+ const xmlSkills = this.parseXmlSkills(content);
1351
+ if (xmlSkills.length > 0) {
1352
+ skills.push(...xmlSkills);
1353
+ }
1354
+ const mdSkills = this.parseMarkdownSkills(content, xmlSkills);
1355
+ if (mdSkills.length > 0) {
1356
+ const existingNames = new Set(skills.map((s) => s.name.toLowerCase()));
1357
+ for (const skill of mdSkills) {
1358
+ if (!existingNames.has(skill.name.toLowerCase())) {
1359
+ skills.push(skill);
1360
+ }
1361
+ }
1362
+ }
1363
+ const header = this.extractHeader(content, skills);
1364
+ const footer = this.extractFooter(content, skills);
1365
+ if (skills.length === 0) {
1366
+ warnings.push("No skills found in AGENTS.md");
1367
+ }
1368
+ return { header, skills, footer, warnings };
1369
+ }
1370
+ /**
1371
+ * Convert parsed skill to Skill type
1372
+ */
1373
+ toSkill(parsed, defaults) {
1374
+ const now = /* @__PURE__ */ new Date();
1375
+ const id = parsed.id || this.generateId(parsed.name);
1376
+ let instructions = parsed.instructions || "";
1377
+ if (!instructions) {
1378
+ const instructionParts = [];
1379
+ if (parsed.problem) instructionParts.push(parsed.problem);
1380
+ if (parsed.solution) instructionParts.push(parsed.solution);
1381
+ if (parsed.verification) instructionParts.push(parsed.verification);
1382
+ if (parsed.notes) instructionParts.push(parsed.notes);
1383
+ instructions = instructionParts.length > 0 ? instructionParts.join("\n\n") : "";
1384
+ }
1385
+ return {
1386
+ id,
1387
+ name: parsed.name,
1388
+ version: parsed.version || "1.0.0",
1389
+ description: parsed.description || "",
1390
+ instructions,
1391
+ author: defaults?.author || "imported",
1392
+ tags: parsed.tags || [],
1393
+ createdAt: defaults?.createdAt || now,
1394
+ updatedAt: now,
1395
+ status: "active",
1396
+ metrics: defaults?.metrics || {
1397
+ usageCount: 0,
1398
+ successRate: 0,
1399
+ feedbackScores: []
1400
+ },
1401
+ source: {
1402
+ type: "imported",
1403
+ location: "AGENTS.md",
1404
+ importedAt: now
1405
+ },
1406
+ ...defaults
1407
+ };
1408
+ }
1409
+ /**
1410
+ * Parse XML format skills
1411
+ */
1412
+ parseXmlSkills(content) {
1413
+ const skills = [];
1414
+ const skillRegex = /<skill([^>]*)>([\s\S]*?)<\/skill>/gi;
1415
+ let match;
1416
+ while ((match = skillRegex.exec(content)) !== null) {
1417
+ const attrs = match[1];
1418
+ const body = match[2];
1419
+ const skill = {
1420
+ rawContent: match[0],
1421
+ name: this.extractXmlTag(body, "name") || "Unnamed Skill",
1422
+ description: this.extractXmlTag(body, "description") || ""
1423
+ };
1424
+ const idMatch = /id="([^"]+)"/.exec(attrs);
1425
+ if (idMatch) {
1426
+ skill.id = idMatch[1];
1427
+ }
1428
+ const versionMatch = /version="([^"]+)"/.exec(attrs);
1429
+ if (versionMatch) {
1430
+ skill.version = versionMatch[1];
1431
+ }
1432
+ const contentTag = this.extractXmlTag(body, "content");
1433
+ if (contentTag) {
1434
+ skill.instructions = contentTag;
1435
+ } else {
1436
+ skill.problem = this.extractXmlTag(body, "problem");
1437
+ skill.solution = this.extractXmlTag(body, "solution");
1438
+ skill.verification = this.extractXmlTag(body, "verification");
1439
+ skill.notes = this.extractXmlTag(body, "notes");
1440
+ }
1441
+ const tagsStr = this.extractXmlTag(body, "tags");
1442
+ if (tagsStr) {
1443
+ skill.tags = tagsStr.split(",").map((t) => t.trim()).filter((t) => t);
1444
+ }
1445
+ skills.push(skill);
1446
+ }
1447
+ return skills;
1448
+ }
1449
+ /**
1450
+ * Parse Markdown format skills
1451
+ */
1452
+ parseMarkdownSkills(content, existingSkills) {
1453
+ const skills = [];
1454
+ const sections = content.split(/(?=^###\s)/m);
1455
+ for (const section of sections) {
1456
+ const headerMatch = /^###\s+(.+?)(?:\s+\(v([\d.]+)\))?(?:\s+\{#([^}]+)\})?\s*$/m.exec(section);
1457
+ if (!headerMatch) continue;
1458
+ const name = headerMatch[1].trim();
1459
+ const version = headerMatch[2];
1460
+ const id = headerMatch[3];
1461
+ if (existingSkills.some((s) => s.name.toLowerCase() === name.toLowerCase())) {
1462
+ continue;
1463
+ }
1464
+ const skill = {
1465
+ rawContent: section,
1466
+ name,
1467
+ description: ""
1468
+ };
1469
+ if (version) skill.version = version;
1470
+ if (id) skill.id = id;
1471
+ const descMatch = /^\*([^*]+)\*$/m.exec(section);
1472
+ if (descMatch) {
1473
+ skill.description = descMatch[1];
1474
+ }
1475
+ const bodyStart = section.indexOf("\n", section.indexOf(headerMatch[0]) + headerMatch[0].length);
1476
+ if (bodyStart >= 0) {
1477
+ let bodyContent = section.slice(bodyStart).trim();
1478
+ bodyContent = bodyContent.replace(/^\*[^*]+\*\s*\n?/, "").trim();
1479
+ bodyContent = bodyContent.replace(/\nTags:\s*.+$/m, "").trim();
1480
+ if (bodyContent) {
1481
+ skill.instructions = bodyContent;
1482
+ }
1483
+ }
1484
+ if (!skill.instructions) {
1485
+ skill.problem = this.extractMarkdownSection(section, "Problem");
1486
+ skill.solution = this.extractMarkdownSection(section, "Solution");
1487
+ skill.verification = this.extractMarkdownSection(section, "Verification");
1488
+ skill.notes = this.extractMarkdownSection(section, "Notes");
1489
+ }
1490
+ const tagsMatch = /Tags:\s*(.+)$/m.exec(section);
1491
+ if (tagsMatch) {
1492
+ skill.tags = tagsMatch[1].split(",").map((t) => t.replace(/`/g, "").trim()).filter((t) => t);
1493
+ }
1494
+ skills.push(skill);
1495
+ }
1496
+ return skills;
1497
+ }
1498
+ /**
1499
+ * Extract content from XML tag
1500
+ */
1501
+ extractXmlTag(body, tagName) {
1502
+ const regex = new RegExp(`<${tagName}>([\\s\\S]*?)<\\/${tagName}>`, "i");
1503
+ const match = regex.exec(body);
1504
+ if (match) {
1505
+ return this.unescapeXml(match[1].trim());
1506
+ }
1507
+ return void 0;
1508
+ }
1509
+ /**
1510
+ * Extract CDATA content
1511
+ */
1512
+ extractCData(body, tagName) {
1513
+ const regex = new RegExp(`<${tagName}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/${tagName}>`, "i");
1514
+ const match = regex.exec(body);
1515
+ if (match) {
1516
+ return match[1];
1517
+ }
1518
+ return void 0;
1519
+ }
1520
+ /**
1521
+ * Unescape XML entities
1522
+ */
1523
+ unescapeXml(str) {
1524
+ return str.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
1525
+ }
1526
+ /**
1527
+ * Extract a markdown section by header
1528
+ */
1529
+ extractMarkdownSection(content, header) {
1530
+ const regex = new RegExp(`\\*\\*${header}:\\*\\*\\s*([\\s\\S]*?)(?=\\*\\*|Tags:|$)`, "i");
1531
+ const match = regex.exec(content);
1532
+ if (match) {
1533
+ return match[1].trim();
1534
+ }
1535
+ return void 0;
1536
+ }
1537
+ /**
1538
+ * Generate ID from skill name
1539
+ */
1540
+ generateId(name) {
1541
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1542
+ }
1543
+ /**
1544
+ * Extract header content
1545
+ */
1546
+ extractHeader(content, skills) {
1547
+ if (skills.length === 0) return content.trim() || void 0;
1548
+ const firstSkill = skills[0];
1549
+ const idx = content.indexOf(firstSkill.rawContent);
1550
+ if (idx > 0) {
1551
+ return content.slice(0, idx).trim() || void 0;
1552
+ }
1553
+ return void 0;
1554
+ }
1555
+ /**
1556
+ * Extract footer content
1557
+ */
1558
+ extractFooter(content, skills) {
1559
+ if (skills.length === 0) return void 0;
1560
+ const lastSkill = skills[skills.length - 1];
1561
+ const idx = content.indexOf(lastSkill.rawContent);
1562
+ if (idx >= 0) {
1563
+ const afterIdx = idx + lastSkill.rawContent.length;
1564
+ const footer = content.slice(afterIdx).trim();
1565
+ return footer || void 0;
1566
+ }
1567
+ return void 0;
1568
+ }
1569
+ };
1570
+ }
1571
+ });
1572
+
1573
+ // src/agents/sync.ts
1574
+ var sync_exports = {};
1575
+ __export(sync_exports, {
1576
+ AgentsSync: () => AgentsSync,
1577
+ createAgentsSync: () => createAgentsSync,
1578
+ generateAgentsMd: () => generateAgentsMd,
1579
+ importFromAgentsMd: () => importFromAgentsMd,
1580
+ writeAgentsMd: () => writeAgentsMd
1581
+ });
1582
+ function createAgentsSync() {
1583
+ return new AgentsSync();
1584
+ }
1585
+ async function generateAgentsMd(storage, config2) {
1586
+ const generator = new AgentsGenerator(config2);
1587
+ return generator.generate(storage);
1588
+ }
1589
+ async function writeAgentsMd(storage, filePath, config2) {
1590
+ const content = await generateAgentsMd(storage, config2);
1591
+ const dir = path9.dirname(filePath);
1592
+ if (dir && !fs9.existsSync(dir)) {
1593
+ fs9.mkdirSync(dir, { recursive: true });
1594
+ }
1595
+ fs9.writeFileSync(filePath, content);
1596
+ }
1597
+ async function importFromAgentsMd(filePath, storage, options) {
1598
+ const sync = new AgentsSync();
1599
+ return sync.sync(filePath, storage, {
1600
+ direction: "import",
1601
+ conflictStrategy: options?.conflictStrategy || "skip",
1602
+ dryRun: options?.dryRun
1603
+ });
1604
+ }
1605
+ var fs9, path9, AgentsSync;
1606
+ var init_sync = __esm({
1607
+ "src/agents/sync.ts"() {
1608
+ "use strict";
1609
+ fs9 = __toESM(require("fs"));
1610
+ path9 = __toESM(require("path"));
1611
+ init_generator();
1612
+ init_parser();
1613
+ AgentsSync = class {
1614
+ constructor() {
1615
+ this.generator = new AgentsGenerator();
1616
+ this.parser = new AgentsParser();
1617
+ }
1618
+ /**
1619
+ * Sync AGENTS.md with skill bank
1620
+ */
1621
+ async sync(agentsPath, storage, options) {
1622
+ const result = {
1623
+ added: [],
1624
+ updated: [],
1625
+ removed: [],
1626
+ unchanged: [],
1627
+ warnings: []
1628
+ };
1629
+ switch (options.direction) {
1630
+ case "import":
1631
+ await this.importFromAgents(agentsPath, storage, options, result);
1632
+ break;
1633
+ case "export":
1634
+ await this.exportToAgents(agentsPath, storage, options, result);
1635
+ break;
1636
+ case "bidirectional":
1637
+ await this.bidirectionalSync(agentsPath, storage, options, result);
1638
+ break;
1639
+ }
1640
+ return result;
1641
+ }
1642
+ /**
1643
+ * Import skills from AGENTS.md to skill bank
1644
+ */
1645
+ async importFromAgents(agentsPath, storage, options, result) {
1646
+ if (!fs9.existsSync(agentsPath)) {
1647
+ result.warnings.push(`AGENTS.md not found at ${agentsPath}`);
1648
+ return;
1649
+ }
1650
+ const content = fs9.readFileSync(agentsPath, "utf-8");
1651
+ const parsed = this.parser.parse(content);
1652
+ result.warnings.push(...parsed.warnings);
1653
+ const existingSkills = await storage.listSkills();
1654
+ const existingById = new Map(existingSkills.map((s) => [s.id, s]));
1655
+ const existingByName = new Map(existingSkills.map((s) => [s.name.toLowerCase(), s]));
1656
+ const processedIds = /* @__PURE__ */ new Set();
1657
+ for (const parsedSkill of parsed.skills) {
1658
+ const skill = this.parser.toSkill(parsedSkill);
1659
+ processedIds.add(skill.id);
1660
+ let existing = existingById.get(skill.id);
1661
+ if (!existing) {
1662
+ existing = existingByName.get(skill.name.toLowerCase());
1663
+ }
1664
+ if (existing) {
1665
+ const shouldUpdate = this.resolveConflict(existing, skill, options.conflictStrategy);
1666
+ if (shouldUpdate) {
1667
+ if (!options.dryRun) {
1668
+ const merged = this.mergeSkills(existing, skill);
1669
+ await storage.saveSkill(merged);
1670
+ }
1671
+ result.updated.push(skill.id);
1672
+ } else {
1673
+ result.unchanged.push(skill.id);
1674
+ }
1675
+ } else {
1676
+ if (!options.dryRun) {
1677
+ await storage.saveSkill(skill);
1678
+ }
1679
+ result.added.push(skill.id);
1680
+ }
1681
+ }
1682
+ if (options.removeOrphans) {
1683
+ for (const existing of existingSkills) {
1684
+ if (!processedIds.has(existing.id)) {
1685
+ if (!options.dryRun) {
1686
+ await storage.deleteSkill(existing.id);
1687
+ }
1688
+ result.removed.push(existing.id);
1689
+ }
1690
+ }
1691
+ }
1692
+ }
1693
+ /**
1694
+ * Export skills from skill bank to AGENTS.md
1695
+ */
1696
+ async exportToAgents(agentsPath, storage, options, result) {
1697
+ const generator = new AgentsGenerator(options.generatorConfig);
1698
+ const content = await generator.generate(storage);
1699
+ let existingContent = "";
1700
+ if (fs9.existsSync(agentsPath)) {
1701
+ existingContent = fs9.readFileSync(agentsPath, "utf-8");
1702
+ }
1703
+ const existingParsed = this.parser.parse(existingContent);
1704
+ const existingIds = new Set(existingParsed.skills.map((s) => s.id || s.name.toLowerCase()));
1705
+ const skills = await storage.listSkills({
1706
+ status: options.generatorConfig?.filter?.status || ["active"]
1707
+ });
1708
+ for (const skill of skills) {
1709
+ if (existingIds.has(skill.id) || existingIds.has(skill.name.toLowerCase())) {
1710
+ result.updated.push(skill.id);
1711
+ } else {
1712
+ result.added.push(skill.id);
1713
+ }
1714
+ }
1715
+ if (!options.dryRun) {
1716
+ const dir = path9.dirname(agentsPath);
1717
+ if (!fs9.existsSync(dir)) {
1718
+ fs9.mkdirSync(dir, { recursive: true });
1719
+ }
1720
+ fs9.writeFileSync(agentsPath, content);
1721
+ }
1722
+ }
1723
+ /**
1724
+ * Bidirectional sync
1725
+ */
1726
+ async bidirectionalSync(agentsPath, storage, options, result) {
1727
+ await this.importFromAgents(agentsPath, storage, { ...options, removeOrphans: false }, result);
1728
+ const exportResult = {
1729
+ added: [],
1730
+ updated: [],
1731
+ removed: [],
1732
+ unchanged: [],
1733
+ warnings: []
1734
+ };
1735
+ await this.exportToAgents(agentsPath, storage, options, exportResult);
1736
+ for (const id of exportResult.added) {
1737
+ if (!result.added.includes(id) && !result.updated.includes(id)) {
1738
+ result.added.push(id);
1739
+ }
1740
+ }
1741
+ }
1742
+ /**
1743
+ * Resolve conflict between existing and new skill
1744
+ */
1745
+ resolveConflict(existing, incoming, strategy) {
1746
+ switch (strategy) {
1747
+ case "bank-wins":
1748
+ return false;
1749
+ case "agents-wins":
1750
+ return true;
1751
+ case "newer-wins":
1752
+ return incoming.updatedAt > existing.updatedAt;
1753
+ case "skip":
1754
+ default:
1755
+ return false;
1756
+ }
1757
+ }
1758
+ /**
1759
+ * Merge skills, preserving important data from existing
1760
+ */
1761
+ mergeSkills(existing, incoming) {
1762
+ return {
1763
+ ...incoming,
1764
+ id: existing.id,
1765
+ // Keep existing ID
1766
+ createdAt: existing.createdAt,
1767
+ // Preserve creation date
1768
+ metrics: existing.metrics,
1769
+ // Preserve usage metrics
1770
+ source: incoming.source || existing.source,
1771
+ parentVersion: existing.version
1772
+ // Track update lineage
1773
+ };
1774
+ }
1775
+ };
1776
+ }
1777
+ });
1778
+
1088
1779
  // src/cli/index.ts
1089
- var import_commander36 = require("commander");
1780
+ var import_commander38 = require("commander");
1090
1781
 
1091
1782
  // src/types.ts
1092
1783
  function hasTaxonomySupport(storage) {
@@ -1860,13 +2551,8 @@ ${remoteValue}`;
1860
2551
  }
1861
2552
  getConflictingFields(local, remote) {
1862
2553
  const fields = [
1863
- "problem",
1864
- "solution",
1865
- "verification",
1866
- "triggerConditions",
1867
- "examples",
1868
- "tags",
1869
- "notes"
2554
+ "instructions",
2555
+ "tags"
1870
2556
  ];
1871
2557
  return fields.filter((field) => {
1872
2558
  const localValue = local[field];
@@ -1901,12 +2587,7 @@ ${remoteValue}`;
1901
2587
  name: metadata.name || skillId,
1902
2588
  version: metadata.version || "1.0.0",
1903
2589
  description: metadata.description || "",
1904
- problem: this.extractSection(body, "Problem") || "",
1905
- solution: this.extractSection(body, "Solution") || "",
1906
- verification: this.extractSection(body, "Verification") || "",
1907
- triggerConditions: [],
1908
- examples: [],
1909
- notes: this.extractSection(body, "Notes"),
2590
+ instructions: body.trim(),
1910
2591
  author: metadata.author || "unknown",
1911
2592
  tags: metadata.tags ? metadata.tags.split(",").map((t) => t.trim()) : [],
1912
2593
  createdAt: metadata.created ? new Date(metadata.created) : /* @__PURE__ */ new Date(),
@@ -1919,14 +2600,6 @@ ${remoteValue}`;
1919
2600
  }
1920
2601
  };
1921
2602
  }
1922
- extractSection(content, heading) {
1923
- const regex = new RegExp(
1924
- `## ${heading}\\n([\\s\\S]*?)(?=\\n## |$)`,
1925
- "i"
1926
- );
1927
- const match = content.match(regex);
1928
- return match ? match[1].trim() : void 0;
1929
- }
1930
2603
  async writeSkill(skill) {
1931
2604
  const skillPath = this.getSkillFilePath(skill.id);
1932
2605
  const fullPath = path2.join(this.repoPath, skillPath);
@@ -1951,21 +2624,8 @@ ${remoteValue}`;
1951
2624
  "",
1952
2625
  skill.description,
1953
2626
  "",
1954
- "## Problem",
1955
- "",
1956
- skill.problem,
1957
- "",
1958
- "## Solution",
1959
- "",
1960
- skill.solution,
1961
- "",
1962
- "## Verification",
1963
- "",
1964
- skill.verification
2627
+ skill.instructions
1965
2628
  ];
1966
- if (skill.notes) {
1967
- body.push("", "## Notes", "", skill.notes);
1968
- }
1969
2629
  return `${frontmatter}
1970
2630
 
1971
2631
  ${body.join("\n")}
@@ -2017,13 +2677,8 @@ ${body.join("\n")}
2017
2677
  }
2018
2678
  hashSkill(skill) {
2019
2679
  const content = JSON.stringify({
2020
- problem: skill.problem,
2021
- solution: skill.solution,
2022
- verification: skill.verification,
2023
- triggerConditions: skill.triggerConditions,
2024
- examples: skill.examples,
2025
- tags: skill.tags,
2026
- notes: skill.notes
2680
+ instructions: skill.instructions,
2681
+ tags: skill.tags
2027
2682
  });
2028
2683
  let hash = 0;
2029
2684
  for (let i = 0; i < content.length; i++) {
@@ -2419,11 +3074,7 @@ var LoadoutCompiler = class {
2419
3074
  const text = [
2420
3075
  skill.name,
2421
3076
  skill.description,
2422
- skill.problem,
2423
- skill.solution,
2424
- skill.verification,
2425
- skill.notes ?? "",
2426
- ...skill.examples.map((e) => `${e.scenario} ${e.before} ${e.after}`)
3077
+ skill.instructions
2427
3078
  ].join(" ");
2428
3079
  return Math.ceil(text.length / 4);
2429
3080
  }
@@ -2658,8 +3309,7 @@ var ProjectDetector = _ProjectDetector;
2658
3309
  // src/serving/view-renderer.ts
2659
3310
  var DEFAULT_CONFIG2 = {
2660
3311
  includeTokenEstimates: false,
2661
- maxSummaryLength: 150,
2662
- includeExamples: true
3312
+ maxSummaryLength: 150
2663
3313
  };
2664
3314
  var ViewRenderer = class {
2665
3315
  constructor(config2) {
@@ -2688,7 +3338,7 @@ var ViewRenderer = class {
2688
3338
  lines.push(` <tokens>${tokens}</tokens>`);
2689
3339
  }
2690
3340
  lines.push(" <content>");
2691
- lines.push(this.renderSkillContent(skill));
3341
+ lines.push(skill.instructions.split("\n").map((line) => " " + line).join("\n"));
2692
3342
  lines.push(" </content>");
2693
3343
  lines.push("</skill>");
2694
3344
  } else {
@@ -2742,12 +3392,7 @@ var ViewRenderer = class {
2742
3392
  for (const [, skill] of expandedSkills) {
2743
3393
  lines.push(`### ${skill.name}`);
2744
3394
  lines.push("");
2745
- lines.push(`**Problem:** ${skill.problem}`);
2746
- lines.push("");
2747
- lines.push(`**Solution:**`);
2748
- lines.push(skill.solution);
2749
- lines.push("");
2750
- lines.push(`**Verification:** ${skill.verification}`);
3395
+ lines.push(skill.instructions);
2751
3396
  lines.push("");
2752
3397
  lines.push("---");
2753
3398
  lines.push("");
@@ -2768,44 +3413,8 @@ var ViewRenderer = class {
2768
3413
  }
2769
3414
  lines.push("---");
2770
3415
  lines.push("");
2771
- lines.push(`# ${skill.name}`);
2772
- lines.push("");
2773
- lines.push("## Problem");
2774
- lines.push("");
2775
- lines.push(skill.problem);
2776
- lines.push("");
2777
- lines.push("## Solution");
3416
+ lines.push(skill.instructions);
2778
3417
  lines.push("");
2779
- lines.push(skill.solution);
2780
- lines.push("");
2781
- lines.push("## Verification");
2782
- lines.push("");
2783
- lines.push(skill.verification);
2784
- lines.push("");
2785
- if (this.config.includeExamples && skill.examples.length > 0) {
2786
- lines.push("## Examples");
2787
- lines.push("");
2788
- for (const example of skill.examples) {
2789
- lines.push(`### ${example.scenario}`);
2790
- lines.push("");
2791
- lines.push("**Before:**");
2792
- lines.push("```");
2793
- lines.push(example.before);
2794
- lines.push("```");
2795
- lines.push("");
2796
- lines.push("**After:**");
2797
- lines.push("```");
2798
- lines.push(example.after);
2799
- lines.push("```");
2800
- lines.push("");
2801
- }
2802
- }
2803
- if (skill.notes) {
2804
- lines.push("## Notes");
2805
- lines.push("");
2806
- lines.push(skill.notes);
2807
- lines.push("");
2808
- }
2809
3418
  return lines.join("\n");
2810
3419
  }
2811
3420
  /**
@@ -2855,35 +3464,6 @@ var ViewRenderer = class {
2855
3464
  }
2856
3465
  return skill.description.substring(0, this.config.maxSummaryLength - 3) + "...";
2857
3466
  }
2858
- /**
2859
- * Render skill content for expanded view
2860
- */
2861
- renderSkillContent(skill) {
2862
- const lines = [];
2863
- lines.push("## Problem");
2864
- lines.push(skill.problem);
2865
- lines.push("");
2866
- lines.push("## Solution");
2867
- lines.push(skill.solution);
2868
- lines.push("");
2869
- lines.push("## Verification");
2870
- lines.push(skill.verification);
2871
- if (skill.notes) {
2872
- lines.push("");
2873
- lines.push("## Notes");
2874
- lines.push(skill.notes);
2875
- }
2876
- if (this.config.includeExamples && skill.examples.length > 0) {
2877
- lines.push("");
2878
- lines.push("## Examples");
2879
- for (const ex of skill.examples) {
2880
- lines.push(`### ${ex.scenario}`);
2881
- lines.push(`Before: ${ex.before}`);
2882
- lines.push(`After: ${ex.after}`);
2883
- }
2884
- }
2885
- return lines.map((line) => " " + line).join("\n");
2886
- }
2887
3467
  /**
2888
3468
  * Escape special XML characters
2889
3469
  */
@@ -2900,11 +3480,7 @@ var ViewRenderer = class {
2900
3480
  const content = [
2901
3481
  skill.name,
2902
3482
  skill.description,
2903
- skill.problem,
2904
- skill.solution,
2905
- skill.verification,
2906
- skill.notes ?? "",
2907
- ...skill.examples.map((e) => `${e.scenario} ${e.before} ${e.after}`)
3483
+ skill.instructions
2908
3484
  ].join(" ");
2909
3485
  return Math.ceil(content.length / 4);
2910
3486
  }
@@ -3262,7 +3838,7 @@ var SkillGraphServer = class {
3262
3838
  const allSkills = await this.storage.listSkills({ status: ["active"] });
3263
3839
  const queryLower = query.toLowerCase();
3264
3840
  const matches = allSkills.filter(
3265
- (skill) => skill.name.toLowerCase().includes(queryLower) || skill.description.toLowerCase().includes(queryLower) || skill.problem.toLowerCase().includes(queryLower) || skill.tags.some((tag) => tag.toLowerCase().includes(queryLower))
3841
+ (skill) => skill.name.toLowerCase().includes(queryLower) || skill.description.toLowerCase().includes(queryLower) || skill.instructions.toLowerCase().includes(queryLower) || skill.tags.some((tag) => tag.toLowerCase().includes(queryLower))
3266
3842
  ).slice(0, limit);
3267
3843
  return matches.map((skill) => ({
3268
3844
  id: skill.id,
@@ -4255,12 +4831,7 @@ ${err.stderr || err.message}`
4255
4831
  name: metadata.name || skillId,
4256
4832
  version: metadata.version || "1.0.0",
4257
4833
  description: metadata.description || "",
4258
- problem: this.extractSection(body, "Problem") || "",
4259
- solution: this.extractSection(body, "Solution") || "",
4260
- verification: this.extractSection(body, "Verification") || "",
4261
- triggerConditions: this.parseTriggerConditions(metadata.triggers),
4262
- examples: this.parseExamples(body),
4263
- notes: this.extractSection(body, "Notes"),
4834
+ instructions: body.trim(),
4264
4835
  author: metadata.author || "unknown",
4265
4836
  tags: this.parseTags(metadata.tags),
4266
4837
  createdAt: metadata.created ? new Date(metadata.created) : /* @__PURE__ */ new Date(),
@@ -4290,28 +4861,6 @@ ${err.stderr || err.message}`
4290
4861
  }
4291
4862
  return metadata;
4292
4863
  }
4293
- /**
4294
- * Extract a section from markdown body
4295
- */
4296
- extractSection(content, heading) {
4297
- const regex = new RegExp(`## ${heading}\\n([\\s\\S]*?)(?=\\n## |$)`, "i");
4298
- const match = content.match(regex);
4299
- return match ? match[1].trim() : void 0;
4300
- }
4301
- /**
4302
- * Parse trigger conditions from metadata
4303
- */
4304
- parseTriggerConditions(triggers) {
4305
- if (!triggers) return [];
4306
- try {
4307
- if (triggers.startsWith("[")) {
4308
- return JSON.parse(triggers);
4309
- }
4310
- return [{ type: "keyword", value: triggers }];
4311
- } catch {
4312
- return [];
4313
- }
4314
- }
4315
4864
  /**
4316
4865
  * Parse tags from metadata
4317
4866
  */
@@ -4319,12 +4868,6 @@ ${err.stderr || err.message}`
4319
4868
  if (!tags) return [];
4320
4869
  return tags.split(",").map((t) => t.trim()).filter(Boolean);
4321
4870
  }
4322
- /**
4323
- * Parse examples from body
4324
- */
4325
- parseExamples(body) {
4326
- return [];
4327
- }
4328
4871
  /**
4329
4872
  * Serialize a skill to SKILL.md format
4330
4873
  */
@@ -4345,21 +4888,8 @@ ${err.stderr || err.message}`
4345
4888
  "",
4346
4889
  skill.description,
4347
4890
  "",
4348
- "## Problem",
4349
- "",
4350
- skill.problem,
4351
- "",
4352
- "## Solution",
4353
- "",
4354
- skill.solution,
4355
- "",
4356
- "## Verification",
4357
- "",
4358
- skill.verification
4891
+ skill.instructions
4359
4892
  ];
4360
- if (skill.notes) {
4361
- body.push("", "## Notes", "", skill.notes);
4362
- }
4363
4893
  return `${frontmatter}
4364
4894
 
4365
4895
  ${body.join("\n")}
@@ -4871,12 +5401,12 @@ var FederationManager = class {
4871
5401
  return {
4872
5402
  ...remote,
4873
5403
  id: local.id,
4874
- // Preserve local notes if they were modified
4875
- notes: local.notes !== remote.notes ? `${local.notes}
5404
+ // Preserve local instructions if they were modified
5405
+ instructions: local.instructions !== remote.instructions ? `${local.instructions}
4876
5406
 
4877
5407
  ---
4878
5408
 
4879
- ${remote.notes}` : remote.notes,
5409
+ ${remote.instructions}` : remote.instructions,
4880
5410
  // Update upstream tracking
4881
5411
  upstream: {
4882
5412
  remote: local.upstream.remote,
@@ -5122,11 +5652,11 @@ var FilesystemStorageAdapter = class extends BaseStorageAdapter {
5122
5652
  */
5123
5653
  serializeSkill(skill) {
5124
5654
  const frontmatter = this.buildFrontmatter(skill);
5125
- const body = this.buildBody(skill);
5126
5655
  return `---
5127
5656
  ${frontmatter}---
5128
5657
 
5129
- ${body}`;
5658
+ ${skill.instructions}
5659
+ `;
5130
5660
  }
5131
5661
  /**
5132
5662
  * Build YAML frontmatter
@@ -5155,59 +5685,19 @@ ${body}`;
5155
5685
  lines.push(` - ${id}`);
5156
5686
  }
5157
5687
  }
5158
- return lines.join("\n") + "\n";
5159
- }
5160
- /**
5161
- * Build Markdown body (Claudeception-inspired structure)
5162
- */
5163
- buildBody(skill) {
5164
- const sections = [];
5165
- sections.push("## Problem\n");
5166
- sections.push(skill.problem);
5167
- sections.push("");
5168
- if (skill.triggerConditions.length > 0) {
5169
- sections.push("## Trigger Conditions\n");
5170
- for (const trigger of skill.triggerConditions) {
5171
- const desc = trigger.description ? ` - ${trigger.description}` : "";
5172
- sections.push(`- **${trigger.type}**: \`${trigger.value}\`${desc}`);
5173
- }
5174
- sections.push("");
5175
- }
5176
- sections.push("## Solution\n");
5177
- sections.push(skill.solution);
5178
- sections.push("");
5179
- sections.push("## Verification\n");
5180
- sections.push(skill.verification);
5181
- sections.push("");
5182
- if (skill.examples.length > 0) {
5183
- sections.push("## Examples\n");
5184
- for (const example of skill.examples) {
5185
- sections.push(`### ${example.scenario}
5186
- `);
5187
- sections.push("**Before:**");
5188
- sections.push("```");
5189
- sections.push(example.before);
5190
- sections.push("```\n");
5191
- sections.push("**After:**");
5192
- sections.push("```");
5193
- sections.push(example.after);
5194
- sections.push("```");
5195
- sections.push("");
5688
+ if (skill.related && skill.related.length > 0) {
5689
+ lines.push(`related:`);
5690
+ for (const id of skill.related) {
5691
+ lines.push(` - ${id}`);
5196
5692
  }
5197
5693
  }
5198
- if (skill.notes) {
5199
- sections.push("## Notes\n");
5200
- sections.push(skill.notes);
5201
- sections.push("");
5202
- }
5203
- return sections.join("\n");
5694
+ return lines.join("\n") + "\n";
5204
5695
  }
5205
5696
  /**
5206
5697
  * Parse skill from YAML frontmatter + Markdown content
5207
5698
  */
5208
5699
  parseSkill(id, content, metadata) {
5209
5700
  const { frontmatter, body } = this.parseFrontmatterAndBody(content);
5210
- const sections = this.parseBodySections(body);
5211
5701
  const name = this.extractYamlField(frontmatter, "name") || id;
5212
5702
  const description = this.extractYamlMultiline(frontmatter, "description") || "";
5213
5703
  const version = this.extractYamlField(frontmatter, "version") || "1.0.0";
@@ -5217,12 +5707,8 @@ ${body}`;
5217
5707
  const tags = this.extractYamlList(frontmatter, "tags");
5218
5708
  const parentVersion = this.extractYamlField(frontmatter, "parentVersion");
5219
5709
  const derivedFrom = this.extractYamlList(frontmatter, "derivedFrom");
5220
- const problem = sections["problem"] || "";
5221
- const solution = sections["solution"] || "";
5222
- const verification = sections["verification"] || "";
5223
- const notes = sections["notes"];
5224
- const triggerConditions = this.parseTriggerConditions(sections["trigger conditions"] || "");
5225
- const examples = this.parseExamples(sections["examples"] || "");
5710
+ const related = this.extractYamlList(frontmatter, "related");
5711
+ const instructions = body.trim();
5226
5712
  const date = dateStr ? new Date(dateStr) : /* @__PURE__ */ new Date();
5227
5713
  let upstream = metadata?.upstream;
5228
5714
  if (upstream?.syncedAt) {
@@ -5236,12 +5722,8 @@ ${body}`;
5236
5722
  name,
5237
5723
  version,
5238
5724
  description,
5239
- problem,
5240
- triggerConditions,
5241
- solution,
5242
- verification,
5243
- examples,
5244
- notes,
5725
+ instructions,
5726
+ related: related.length > 0 ? related : void 0,
5245
5727
  author,
5246
5728
  tags,
5247
5729
  createdAt: date,
@@ -5269,23 +5751,6 @@ ${body}`;
5269
5751
  }
5270
5752
  return { frontmatter: "", body: content };
5271
5753
  }
5272
- /**
5273
- * Parse body into sections by ## headers
5274
- */
5275
- parseBodySections(body) {
5276
- const sections = {};
5277
- const parts = body.split(/^## /m);
5278
- for (const part of parts) {
5279
- if (!part.trim()) continue;
5280
- const lines = part.split("\n");
5281
- const header = lines[0]?.trim().toLowerCase();
5282
- const content = lines.slice(1).join("\n").trim();
5283
- if (header) {
5284
- sections[header] = content;
5285
- }
5286
- }
5287
- return sections;
5288
- }
5289
5754
  /**
5290
5755
  * Extract a single-line YAML field
5291
5756
  */
@@ -5313,47 +5778,6 @@ ${body}`;
5313
5778
  }
5314
5779
  return [];
5315
5780
  }
5316
- /**
5317
- * Parse trigger conditions from markdown
5318
- */
5319
- parseTriggerConditions(content) {
5320
- const conditions = [];
5321
- const lines = content.split("\n");
5322
- for (const line of lines) {
5323
- const match = line.match(/^-\s+\*\*(\w+)\*\*:\s*`([^`]+)`(?:\s*-\s*(.+))?/);
5324
- if (match) {
5325
- conditions.push({
5326
- type: match[1],
5327
- value: match[2],
5328
- description: match[3]?.trim()
5329
- });
5330
- }
5331
- }
5332
- return conditions;
5333
- }
5334
- /**
5335
- * Parse examples from markdown
5336
- */
5337
- parseExamples(content) {
5338
- const examples = [];
5339
- const parts = content.split(/^### /m);
5340
- for (const part of parts) {
5341
- if (!part.trim()) continue;
5342
- const lines = part.split("\n");
5343
- const scenario = lines[0]?.trim() || "Example";
5344
- const rest = lines.slice(1).join("\n");
5345
- const beforeMatch = rest.match(/\*\*Before:\*\*\n```\n?([\s\S]*?)```/);
5346
- const afterMatch = rest.match(/\*\*After:\*\*\n```\n?([\s\S]*?)```/);
5347
- if (beforeMatch || afterMatch) {
5348
- examples.push({
5349
- scenario,
5350
- before: beforeMatch?.[1]?.trim() || "",
5351
- after: afterMatch?.[1]?.trim() || ""
5352
- });
5353
- }
5354
- }
5355
- return examples;
5356
- }
5357
5781
  // ==========================================================================
5358
5782
  // Version Management
5359
5783
  // ==========================================================================
@@ -5468,9 +5892,7 @@ ${body}`;
5468
5892
  */
5469
5893
  hashContent(skill) {
5470
5894
  const content = JSON.stringify({
5471
- problem: skill.problem,
5472
- solution: skill.solution,
5473
- triggerConditions: skill.triggerConditions
5895
+ instructions: skill.instructions
5474
5896
  });
5475
5897
  let hash = 0;
5476
5898
  for (let i = 0; i < content.length; i++) {
@@ -5796,30 +6218,17 @@ var LineageTracker = class {
5796
6218
  if (!targetSkill) throw new Error(`Target skill not found: ${targetId}`);
5797
6219
  if (!sourceSkill) throw new Error(`Source skill not found: ${sourceId}`);
5798
6220
  const fields = options.fields || [
5799
- "solution",
5800
- "triggerConditions",
5801
- "examples",
5802
- "notes"
6221
+ "instructions"
5803
6222
  ];
5804
6223
  const strategy = options.conflictStrategy || "combine";
5805
6224
  const updates = {};
5806
6225
  for (const field of fields) {
5807
- if (field === "triggerConditions") {
5808
- updates.triggerConditions = this.mergeTriggerConditions(
5809
- targetSkill.triggerConditions,
5810
- sourceSkill.triggerConditions
5811
- );
5812
- } else if (field === "examples") {
5813
- updates.examples = this.mergeExamples(
5814
- targetSkill.examples,
5815
- sourceSkill.examples
5816
- );
5817
- } else if (field === "tags") {
6226
+ if (field === "tags") {
5818
6227
  updates.tags = [.../* @__PURE__ */ new Set([...targetSkill.tags, ...sourceSkill.tags])];
5819
- } else if (field === "solution" || field === "notes") {
5820
- updates[field] = this.mergeText(
5821
- targetSkill[field] || "",
5822
- sourceSkill[field] || "",
6228
+ } else if (field === "instructions") {
6229
+ updates.instructions = this.mergeText(
6230
+ targetSkill.instructions || "",
6231
+ sourceSkill.instructions || "",
5823
6232
  strategy
5824
6233
  );
5825
6234
  }
@@ -5883,14 +6292,15 @@ var LineageTracker = class {
5883
6292
  const newVersion = bumpVersion(currentSkill.version, "patch");
5884
6293
  const rollbackReason = options?.reason || "No reason specified";
5885
6294
  const changelogEntry = options?.changelog || `Rollback from ${currentSkill.version} to ${toVersion}: ${rollbackReason}`;
5886
- const rollbackNote = `[rollback] ${currentSkill.version} \u2192 ${toVersion} (${rollbackReason})`;
5887
- const notes = targetSkill.notes ? `${targetSkill.notes}
5888
- ${rollbackNote}` : rollbackNote;
6295
+ const rollbackNote = `
6296
+
6297
+ [rollback] ${currentSkill.version} \u2192 ${toVersion} (${rollbackReason})`;
6298
+ const instructions = targetSkill.instructions ? `${targetSkill.instructions}${rollbackNote}` : rollbackNote.trim();
5889
6299
  const rolledBackSkill = {
5890
6300
  ...targetSkill,
5891
6301
  version: newVersion,
5892
6302
  parentVersion: currentSkill.version,
5893
- notes,
6303
+ instructions,
5894
6304
  updatedAt: /* @__PURE__ */ new Date()
5895
6305
  };
5896
6306
  await this.storage.saveSkill(rolledBackSkill);
@@ -5922,29 +6332,6 @@ ${rollbackNote}` : rollbackNote;
5922
6332
  // ==========================================================================
5923
6333
  // Private helpers
5924
6334
  // ==========================================================================
5925
- mergeTriggerConditions(target, source) {
5926
- const seen = new Set(target.map((t) => `${t.type}:${t.value}`));
5927
- const merged = [...target];
5928
- for (const trigger of source) {
5929
- const key = `${trigger.type}:${trigger.value}`;
5930
- if (!seen.has(key)) {
5931
- merged.push(trigger);
5932
- seen.add(key);
5933
- }
5934
- }
5935
- return merged;
5936
- }
5937
- mergeExamples(target, source) {
5938
- const seen = new Set(target.map((e) => e.scenario));
5939
- const merged = [...target];
5940
- for (const example of source) {
5941
- if (!seen.has(example.scenario)) {
5942
- merged.push(example);
5943
- seen.add(example.scenario);
5944
- }
5945
- }
5946
- return merged;
5947
- }
5948
6335
  mergeText(target, source, strategy) {
5949
6336
  switch (strategy) {
5950
6337
  case "source":
@@ -5970,10 +6357,7 @@ ${source}`;
5970
6357
  const textFields = [
5971
6358
  "name",
5972
6359
  "description",
5973
- "problem",
5974
- "solution",
5975
- "verification",
5976
- "notes"
6360
+ "instructions"
5977
6361
  ];
5978
6362
  for (const field of textFields) {
5979
6363
  const valueA = skillA[field];
@@ -5986,18 +6370,6 @@ ${source}`;
5986
6370
  });
5987
6371
  }
5988
6372
  }
5989
- const triggersA = new Set(skillA.triggerConditions.map((t) => `${t.type}:${t.value}`));
5990
- const triggersB = new Set(skillB.triggerConditions.map((t) => `${t.type}:${t.value}`));
5991
- for (const trigger of triggersB) {
5992
- if (!triggersA.has(trigger)) {
5993
- changes.added.push({ type: "triggerCondition", value: trigger });
5994
- }
5995
- }
5996
- for (const trigger of triggersA) {
5997
- if (!triggersB.has(trigger)) {
5998
- changes.removed.push({ type: "triggerCondition", value: trigger });
5999
- }
6000
- }
6001
6373
  const tagsA = new Set(skillA.tags);
6002
6374
  const tagsB = new Set(skillB.tags);
6003
6375
  for (const tag of tagsB) {
@@ -6152,51 +6524,382 @@ var HookRegistry = class {
6152
6524
  };
6153
6525
  }
6154
6526
  /**
6155
- * Execute hooks and allow modification of data (for 'before' hooks)
6527
+ * Execute hooks and allow modification of data (for 'before' hooks)
6528
+ */
6529
+ async executeWithTransform(event, context, data) {
6530
+ const executionResult = await this.execute(event, context);
6531
+ let transformedData = data;
6532
+ for (const hookResult of executionResult.results) {
6533
+ if (hookResult.result.modified !== void 0) {
6534
+ transformedData = hookResult.result.modified;
6535
+ }
6536
+ }
6537
+ return { result: executionResult, data: transformedData };
6538
+ }
6539
+ /**
6540
+ * Clear all hooks
6541
+ */
6542
+ clear() {
6543
+ this.hooks.clear();
6544
+ this.eventIndex.clear();
6545
+ }
6546
+ /**
6547
+ * Get statistics about registered hooks
6548
+ */
6549
+ stats() {
6550
+ const hooks = Array.from(this.hooks.values());
6551
+ const hooksByEvent = {};
6552
+ const hooksByPriority = {
6553
+ high: 0,
6554
+ normal: 0,
6555
+ low: 0
6556
+ };
6557
+ for (const hook of hooks) {
6558
+ hooksByPriority[hook.priority]++;
6559
+ for (const event of hook.events) {
6560
+ hooksByEvent[event] = (hooksByEvent[event] || 0) + 1;
6561
+ }
6562
+ }
6563
+ return {
6564
+ totalHooks: hooks.length,
6565
+ enabledHooks: hooks.filter((h) => h.enabled).length,
6566
+ hooksByEvent,
6567
+ hooksByPriority
6568
+ };
6569
+ }
6570
+ };
6571
+ var hookRegistry = new HookRegistry();
6572
+
6573
+ // src/materialization/materializer.ts
6574
+ var fs10 = __toESM(require("fs"));
6575
+ var path10 = __toESM(require("path"));
6576
+ var SKILLTREE_MARKER_START = "<!-- SKILLTREE_START -->";
6577
+ var SKILLTREE_MARKER_END = "<!-- SKILLTREE_END -->";
6578
+ var Materializer = class {
6579
+ constructor(storage, basePath, config2) {
6580
+ this.storage = storage;
6581
+ this.basePath = basePath;
6582
+ this.config = config2;
6583
+ this.debounceTimer = null;
6584
+ this.pendingRegen = false;
6585
+ this.regenerating = false;
6586
+ this.watcher = null;
6587
+ this.watchDebounceTimer = null;
6588
+ }
6589
+ get mode() {
6590
+ return this.config.mode ?? "symlink";
6591
+ }
6592
+ get skillsSourceDir() {
6593
+ return path10.join(this.basePath, ".skilltree", "skills");
6594
+ }
6595
+ /**
6596
+ * Initial materialization: create all symlinks/copies + generate AGENTS.md
6597
+ */
6598
+ async initialize() {
6599
+ if (!this.config.enabled) return;
6600
+ for (const targetPath of this.config.symlinkPaths ?? []) {
6601
+ const resolved = this.resolvePath(targetPath);
6602
+ fs10.mkdirSync(resolved, { recursive: true });
6603
+ }
6604
+ const skills = await this.storage.listSkills();
6605
+ for (const skill of skills) {
6606
+ await this.ensureMaterialized(skill.id);
6607
+ }
6608
+ await this.cleanup();
6609
+ if (this.config.agentsMdPath) {
6610
+ await this.regenerateAgentsMd();
6611
+ }
6612
+ }
6613
+ /**
6614
+ * Called when a skill is created or updated
6615
+ */
6616
+ async onSkillChanged(skillId) {
6617
+ if (!this.config.enabled) return;
6618
+ await this.ensureMaterialized(skillId);
6619
+ this.scheduleAgentsMdRegen();
6620
+ }
6621
+ /**
6622
+ * Called when a skill is deleted
6623
+ */
6624
+ async onSkillDeleted(skillId) {
6625
+ if (!this.config.enabled) return;
6626
+ await this.removeMaterialized(skillId);
6627
+ this.scheduleAgentsMdRegen();
6628
+ }
6629
+ // ===========================================================================
6630
+ // Symlink / Copy operations
6631
+ // ===========================================================================
6632
+ /**
6633
+ * Materialize a skill to all configured paths (symlink or copy)
6634
+ */
6635
+ async ensureMaterialized(skillId) {
6636
+ const sourcePath = path10.join(this.skillsSourceDir, skillId);
6637
+ if (!fs10.existsSync(sourcePath)) return;
6638
+ for (const targetBase of this.config.symlinkPaths ?? []) {
6639
+ const targetDir = this.resolvePath(targetBase);
6640
+ const targetPath = path10.join(targetDir, skillId);
6641
+ if (this.mode === "copy") {
6642
+ this.copyDir(sourcePath, targetPath);
6643
+ } else {
6644
+ this.ensureSymlink(sourcePath, targetPath);
6645
+ }
6646
+ }
6647
+ }
6648
+ /**
6649
+ * Remove a materialized skill from all configured paths
6650
+ */
6651
+ async removeMaterialized(skillId) {
6652
+ const sourcePath = path10.resolve(path10.join(this.skillsSourceDir, skillId));
6653
+ for (const targetBase of this.config.symlinkPaths ?? []) {
6654
+ const targetPath = path10.join(this.resolvePath(targetBase), skillId);
6655
+ if (!fs10.existsSync(targetPath)) continue;
6656
+ if (this.mode === "copy") {
6657
+ const markerPath = path10.join(targetPath, ".skilltree-managed");
6658
+ if (fs10.existsSync(markerPath)) {
6659
+ fs10.rmSync(targetPath, { recursive: true, force: true });
6660
+ }
6661
+ } else {
6662
+ try {
6663
+ const linkTarget = fs10.readlinkSync(targetPath);
6664
+ if (path10.resolve(linkTarget) === sourcePath) {
6665
+ fs10.unlinkSync(targetPath);
6666
+ }
6667
+ } catch {
6668
+ }
6669
+ }
6670
+ }
6671
+ }
6672
+ /**
6673
+ * Create or verify a symlink
6674
+ */
6675
+ ensureSymlink(sourcePath, targetPath) {
6676
+ if (fs10.existsSync(targetPath)) {
6677
+ try {
6678
+ const existing = fs10.readlinkSync(targetPath);
6679
+ if (path10.resolve(existing) === path10.resolve(sourcePath)) return;
6680
+ fs10.unlinkSync(targetPath);
6681
+ } catch {
6682
+ return;
6683
+ }
6684
+ }
6685
+ try {
6686
+ fs10.symlinkSync(sourcePath, targetPath, "dir");
6687
+ } catch (err) {
6688
+ if (err.code === "EPERM") {
6689
+ try {
6690
+ fs10.symlinkSync(sourcePath, targetPath, "junction");
6691
+ } catch {
6692
+ }
6693
+ }
6694
+ }
6695
+ }
6696
+ /**
6697
+ * Copy a skill directory, adding a marker file so we know we manage it
6698
+ */
6699
+ copyDir(sourcePath, targetPath) {
6700
+ if (fs10.existsSync(targetPath)) {
6701
+ const markerPath = path10.join(targetPath, ".skilltree-managed");
6702
+ if (fs10.existsSync(markerPath)) {
6703
+ fs10.rmSync(targetPath, { recursive: true, force: true });
6704
+ } else {
6705
+ return;
6706
+ }
6707
+ }
6708
+ fs10.cpSync(sourcePath, targetPath, { recursive: true });
6709
+ fs10.writeFileSync(
6710
+ path10.join(targetPath, ".skilltree-managed"),
6711
+ JSON.stringify({ source: sourcePath, copiedAt: (/* @__PURE__ */ new Date()).toISOString() })
6712
+ );
6713
+ }
6714
+ // ===========================================================================
6715
+ // AGENTS.md generation
6716
+ // ===========================================================================
6717
+ /**
6718
+ * Schedule debounced AGENTS.md regeneration
6719
+ */
6720
+ scheduleAgentsMdRegen() {
6721
+ if (!this.config.agentsMdPath) return;
6722
+ this.pendingRegen = true;
6723
+ if (this.debounceTimer) {
6724
+ clearTimeout(this.debounceTimer);
6725
+ }
6726
+ const debounceMs = this.config.debounceMs ?? 500;
6727
+ this.debounceTimer = setTimeout(async () => {
6728
+ this.debounceTimer = null;
6729
+ if (this.pendingRegen) {
6730
+ this.pendingRegen = false;
6731
+ await this.regenerateAgentsMd();
6732
+ }
6733
+ }, debounceMs);
6734
+ }
6735
+ /**
6736
+ * Regenerate AGENTS.md with marker-based section replacement
6737
+ */
6738
+ async regenerateAgentsMd() {
6739
+ const agentsMdPath = this.config.agentsMdPath;
6740
+ if (!agentsMdPath) return;
6741
+ if (this.regenerating) {
6742
+ this.pendingRegen = true;
6743
+ return;
6744
+ }
6745
+ this.regenerating = true;
6746
+ try {
6747
+ await this._doRegenerateAgentsMd(agentsMdPath);
6748
+ } finally {
6749
+ this.regenerating = false;
6750
+ if (this.pendingRegen) {
6751
+ this.pendingRegen = false;
6752
+ await this.regenerateAgentsMd();
6753
+ }
6754
+ }
6755
+ }
6756
+ async _doRegenerateAgentsMd(agentsMdPath) {
6757
+ const resolvedPath = this.resolvePath(agentsMdPath);
6758
+ const generatorConfig = {
6759
+ format: this.config.agentsMdFormat ?? "xml"
6760
+ };
6761
+ const { generateAgentsMd: generateAgentsMd2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
6762
+ const skillContent = await generateAgentsMd2(this.storage, generatorConfig);
6763
+ const markedContent = `${SKILLTREE_MARKER_START}
6764
+ ${skillContent}
6765
+ ${SKILLTREE_MARKER_END}`;
6766
+ if (fs10.existsSync(resolvedPath)) {
6767
+ const existing = fs10.readFileSync(resolvedPath, "utf-8");
6768
+ const startIdx = existing.indexOf(SKILLTREE_MARKER_START);
6769
+ const endIdx = existing.indexOf(SKILLTREE_MARKER_END);
6770
+ if (startIdx !== -1 && endIdx !== -1) {
6771
+ const before = existing.slice(0, startIdx);
6772
+ const after = existing.slice(endIdx + SKILLTREE_MARKER_END.length);
6773
+ fs10.writeFileSync(resolvedPath, before + markedContent + after);
6774
+ return;
6775
+ }
6776
+ fs10.writeFileSync(resolvedPath, existing + "\n\n" + markedContent + "\n");
6777
+ return;
6778
+ }
6779
+ const dir = path10.dirname(resolvedPath);
6780
+ if (!fs10.existsSync(dir)) {
6781
+ fs10.mkdirSync(dir, { recursive: true });
6782
+ }
6783
+ fs10.writeFileSync(resolvedPath, markedContent + "\n");
6784
+ }
6785
+ // ===========================================================================
6786
+ // Cleanup
6787
+ // ===========================================================================
6788
+ /**
6789
+ * Remove stale entries (symlinks or copies) that point to non-existent skills
6156
6790
  */
6157
- async executeWithTransform(event, context, data) {
6158
- const executionResult = await this.execute(event, context);
6159
- let transformedData = data;
6160
- for (const hookResult of executionResult.results) {
6161
- if (hookResult.result.modified !== void 0) {
6162
- transformedData = hookResult.result.modified;
6791
+ async cleanup() {
6792
+ const skills = await this.storage.listSkills();
6793
+ const skillIds = new Set(skills.map((s) => s.id));
6794
+ for (const targetBase of this.config.symlinkPaths ?? []) {
6795
+ const resolved = this.resolvePath(targetBase);
6796
+ if (!fs10.existsSync(resolved)) continue;
6797
+ const entries = fs10.readdirSync(resolved);
6798
+ for (const entry of entries) {
6799
+ const entryPath = path10.join(resolved, entry);
6800
+ if (skillIds.has(entry)) continue;
6801
+ if (this.mode === "copy") {
6802
+ const markerPath = path10.join(entryPath, ".skilltree-managed");
6803
+ if (fs10.existsSync(markerPath)) {
6804
+ fs10.rmSync(entryPath, { recursive: true, force: true });
6805
+ }
6806
+ } else {
6807
+ try {
6808
+ const linkTarget = fs10.readlinkSync(entryPath);
6809
+ const resolvedTarget = path10.resolve(linkTarget);
6810
+ if (resolvedTarget.startsWith(this.skillsSourceDir + path10.sep) || resolvedTarget === this.skillsSourceDir) {
6811
+ fs10.unlinkSync(entryPath);
6812
+ }
6813
+ } catch {
6814
+ }
6815
+ }
6163
6816
  }
6164
6817
  }
6165
- return { result: executionResult, data: transformedData };
6166
6818
  }
6819
+ // ===========================================================================
6820
+ // File watcher
6821
+ // ===========================================================================
6167
6822
  /**
6168
- * Clear all hooks
6823
+ * Start watching .skilltree/skills/ for external changes.
6824
+ * Re-materializes when files are added/changed/removed outside the SkillBank API.
6825
+ */
6826
+ watch() {
6827
+ if (this.watcher) return;
6828
+ if (!this.config.enabled) return;
6829
+ const watchDir = this.skillsSourceDir;
6830
+ if (!fs10.existsSync(watchDir)) return;
6831
+ this.watcher = fs10.watch(watchDir, { recursive: true }, (_eventType, filename) => {
6832
+ if (!filename) return;
6833
+ if (this.watchDebounceTimer) {
6834
+ clearTimeout(this.watchDebounceTimer);
6835
+ }
6836
+ this.watchDebounceTimer = setTimeout(async () => {
6837
+ this.watchDebounceTimer = null;
6838
+ try {
6839
+ const skills = await this.storage.listSkills();
6840
+ for (const skill of skills) {
6841
+ await this.ensureMaterialized(skill.id);
6842
+ }
6843
+ await this.cleanup();
6844
+ if (this.config.agentsMdPath) {
6845
+ await this.regenerateAgentsMd();
6846
+ }
6847
+ } catch {
6848
+ }
6849
+ }, this.config.debounceMs ?? 500);
6850
+ });
6851
+ }
6852
+ /**
6853
+ * Stop watching for file changes
6169
6854
  */
6170
- clear() {
6171
- this.hooks.clear();
6172
- this.eventIndex.clear();
6855
+ unwatch() {
6856
+ if (this.watcher) {
6857
+ this.watcher.close();
6858
+ this.watcher = null;
6859
+ }
6860
+ if (this.watchDebounceTimer) {
6861
+ clearTimeout(this.watchDebounceTimer);
6862
+ this.watchDebounceTimer = null;
6863
+ }
6173
6864
  }
6865
+ // ===========================================================================
6866
+ // Lifecycle
6867
+ // ===========================================================================
6174
6868
  /**
6175
- * Get statistics about registered hooks
6869
+ * Flush any pending debounced operations (useful for testing)
6176
6870
  */
6177
- stats() {
6178
- const hooks = Array.from(this.hooks.values());
6179
- const hooksByEvent = {};
6180
- const hooksByPriority = {
6181
- high: 0,
6182
- normal: 0,
6183
- low: 0
6184
- };
6185
- for (const hook of hooks) {
6186
- hooksByPriority[hook.priority]++;
6187
- for (const event of hook.events) {
6188
- hooksByEvent[event] = (hooksByEvent[event] || 0) + 1;
6189
- }
6871
+ async flush() {
6872
+ if (this.debounceTimer) {
6873
+ clearTimeout(this.debounceTimer);
6874
+ this.debounceTimer = null;
6190
6875
  }
6191
- return {
6192
- totalHooks: hooks.length,
6193
- enabledHooks: hooks.filter((h) => h.enabled).length,
6194
- hooksByEvent,
6195
- hooksByPriority
6196
- };
6876
+ if (this.pendingRegen) {
6877
+ this.pendingRegen = false;
6878
+ await this.regenerateAgentsMd();
6879
+ }
6880
+ }
6881
+ /**
6882
+ * Tear down: cancel pending timers and stop watcher
6883
+ */
6884
+ shutdown() {
6885
+ if (this.debounceTimer) {
6886
+ clearTimeout(this.debounceTimer);
6887
+ this.debounceTimer = null;
6888
+ }
6889
+ this.pendingRegen = false;
6890
+ this.unwatch();
6891
+ }
6892
+ /**
6893
+ * Resolve a path relative to basePath, handling ~ expansion
6894
+ */
6895
+ resolvePath(p) {
6896
+ if (p.startsWith("~")) {
6897
+ return path10.join(process.env.HOME || process.env.USERPROFILE || "", p.slice(1));
6898
+ }
6899
+ if (path10.isAbsolute(p)) return p;
6900
+ return path10.resolve(this.basePath, p);
6197
6901
  }
6198
6902
  };
6199
- var hookRegistry = new HookRegistry();
6200
6903
 
6201
6904
  // src/skill-bank.ts
6202
6905
  var SkillBank = class {
@@ -6239,6 +6942,13 @@ var SkillBank = class {
6239
6942
  });
6240
6943
  this.syncPullOnInit = config2.sync.pullOnInit ?? false;
6241
6944
  }
6945
+ if (config2.materialization?.enabled && this.basePath) {
6946
+ this.materializer = new Materializer(
6947
+ this.storage,
6948
+ this.basePath,
6949
+ config2.materialization
6950
+ );
6951
+ }
6242
6952
  }
6243
6953
  /**
6244
6954
  * Initialize the skill bank (required before use)
@@ -6258,6 +6968,9 @@ var SkillBank = class {
6258
6968
  await this.syncManager.pull();
6259
6969
  }
6260
6970
  }
6971
+ if (this.materializer) {
6972
+ await this.materializer.initialize();
6973
+ }
6261
6974
  this.initialized = true;
6262
6975
  }
6263
6976
  /**
@@ -6267,6 +6980,9 @@ var SkillBank = class {
6267
6980
  if (this.syncManager) {
6268
6981
  await this.syncManager.shutdown();
6269
6982
  }
6983
+ if (this.materializer) {
6984
+ this.materializer.shutdown();
6985
+ }
6270
6986
  }
6271
6987
  /**
6272
6988
  * Ensure initialized before operations
@@ -6479,6 +7195,18 @@ var SkillBank = class {
6479
7195
  console.error("Hook execution error:", err);
6480
7196
  });
6481
7197
  }
7198
+ if (this.materializer) {
7199
+ const materialize = async () => {
7200
+ if (event.type === "skill:created" || event.type === "skill:updated") {
7201
+ await this.materializer.onSkillChanged(event.skill.id);
7202
+ } else if (event.type === "skill:deleted") {
7203
+ await this.materializer.onSkillDeleted(event.skillId);
7204
+ }
7205
+ };
7206
+ materialize().catch((err) => {
7207
+ console.error("Materialization error:", err);
7208
+ });
7209
+ }
6482
7210
  }
6483
7211
  /**
6484
7212
  * Map SkillTreeEvent type to HookEvent type
@@ -6734,6 +7462,12 @@ var SkillBank = class {
6734
7462
  init_base();
6735
7463
  init_sqlite();
6736
7464
 
7465
+ // src/agents/index.ts
7466
+ init_types();
7467
+ init_generator();
7468
+ init_parser();
7469
+ init_sync();
7470
+
6737
7471
  // src/sync/index.ts
6738
7472
  function createDefaultSyncConfig(remoteUrl, agentId, options) {
6739
7473
  return {
@@ -6756,9 +7490,7 @@ function createDefaultSyncConfig(remoteUrl, agentId, options) {
6756
7490
  conflicts: {
6757
7491
  defaultStrategy: "prefer-newer",
6758
7492
  fieldStrategies: {
6759
- tags: "union",
6760
- examples: "union",
6761
- triggerConditions: "union"
7493
+ tags: "union"
6762
7494
  }
6763
7495
  }
6764
7496
  };
@@ -6790,12 +7522,20 @@ var DEFAULT_CONFIG5 = {
6790
7522
  output_format: "table",
6791
7523
  color: true,
6792
7524
  quiet: false
7525
+ },
7526
+ materialization: {
7527
+ enabled: false,
7528
+ mode: "symlink",
7529
+ symlink_paths: [],
7530
+ agents_md_path: "",
7531
+ agents_md_format: "xml",
7532
+ debounce_ms: 500
6793
7533
  }
6794
7534
  };
6795
7535
 
6796
7536
  // src/config/loader.ts
6797
- var fs9 = __toESM(require("fs"));
6798
- var path9 = __toESM(require("path"));
7537
+ var fs11 = __toESM(require("fs"));
7538
+ var path11 = __toESM(require("path"));
6799
7539
  var os = __toESM(require("os"));
6800
7540
  var ENV_MAPPINGS = {
6801
7541
  GITHUB_TOKEN: "indexer.github_token",
@@ -6806,17 +7546,17 @@ var ENV_MAPPINGS = {
6806
7546
  SKILL_TREE_NO_COLOR: "cli.color"
6807
7547
  };
6808
7548
  function getConfigDir() {
6809
- return path9.join(os.homedir(), ".skill-tree");
7549
+ return path11.join(os.homedir(), ".skill-tree");
6810
7550
  }
6811
7551
  function getConfigPath() {
6812
- return path9.join(getConfigDir(), "config.yaml");
7552
+ return path11.join(getConfigDir(), "config.yaml");
6813
7553
  }
6814
7554
  function expandPath(filePath) {
6815
7555
  if (filePath.startsWith("~/")) {
6816
- return path9.join(os.homedir(), filePath.slice(2));
7556
+ return path11.join(os.homedir(), filePath.slice(2));
6817
7557
  }
6818
7558
  if (filePath.startsWith("~")) {
6819
- return path9.join(os.homedir(), filePath.slice(1));
7559
+ return path11.join(os.homedir(), filePath.slice(1));
6820
7560
  }
6821
7561
  return filePath;
6822
7562
  }
@@ -6845,8 +7585,8 @@ function substituteEnvVarsInObject(obj) {
6845
7585
  }
6846
7586
  return obj;
6847
7587
  }
6848
- function setNestedProperty(obj, path15, value) {
6849
- const parts = path15.split(".");
7588
+ function setNestedProperty(obj, path18, value) {
7589
+ const parts = path18.split(".");
6850
7590
  let current = obj;
6851
7591
  for (let i = 0; i < parts.length - 1; i++) {
6852
7592
  const part = parts[i];
@@ -6929,11 +7669,11 @@ function parseSimpleYaml(content) {
6929
7669
  }
6930
7670
  function loadConfigFromFile(configPath) {
6931
7671
  const expandedPath = expandPath(configPath);
6932
- if (!fs9.existsSync(expandedPath)) {
7672
+ if (!fs11.existsSync(expandedPath)) {
6933
7673
  return null;
6934
7674
  }
6935
7675
  try {
6936
- const content = fs9.readFileSync(expandedPath, "utf-8");
7676
+ const content = fs11.readFileSync(expandedPath, "utf-8");
6937
7677
  const parsed = parseSimpleYaml(content);
6938
7678
  return substituteEnvVarsInObject(parsed);
6939
7679
  } catch (error) {
@@ -7005,8 +7745,8 @@ var ConfigLoader = class {
7005
7745
  /**
7006
7746
  * Get a specific config value by path
7007
7747
  */
7008
- get(path15) {
7009
- const parts = path15.split(".");
7748
+ get(path18) {
7749
+ const parts = path18.split(".");
7010
7750
  let current = this.getConfig();
7011
7751
  for (const part of parts) {
7012
7752
  if (current === null || typeof current !== "object") {
@@ -7020,15 +7760,15 @@ var ConfigLoader = class {
7020
7760
  * Check if config file exists
7021
7761
  */
7022
7762
  configFileExists() {
7023
- return fs9.existsSync(expandPath(this.configPath));
7763
+ return fs11.existsSync(expandPath(this.configPath));
7024
7764
  }
7025
7765
  /**
7026
7766
  * Create default config file
7027
7767
  */
7028
7768
  createDefaultConfigFile() {
7029
- const configDir = path9.dirname(expandPath(this.configPath));
7030
- if (!fs9.existsSync(configDir)) {
7031
- fs9.mkdirSync(configDir, { recursive: true });
7769
+ const configDir = path11.dirname(expandPath(this.configPath));
7770
+ if (!fs11.existsSync(configDir)) {
7771
+ fs11.mkdirSync(configDir, { recursive: true });
7032
7772
  }
7033
7773
  const configContent = `# Skill Tree Configuration
7034
7774
  # Generated by skill-tree
@@ -7059,8 +7799,22 @@ cli:
7059
7799
  output_format: table
7060
7800
  color: true
7061
7801
  quiet: false
7802
+
7803
+ materialization:
7804
+ # Enable automatic materialization to agent-discoverable paths
7805
+ enabled: false
7806
+ # Mode: 'symlink' (default) or 'copy'
7807
+ mode: symlink
7808
+ # Directories to expose skills in (e.g. .claude/skills, .agent/skills)
7809
+ symlink_paths: []
7810
+ # Path to auto-generate AGENTS.md (empty = disabled)
7811
+ agents_md_path: ""
7812
+ # Format for AGENTS.md: xml, markdown, json
7813
+ agents_md_format: xml
7814
+ # Debounce interval in ms for batch updates
7815
+ debounce_ms: 500
7062
7816
  `;
7063
- fs9.writeFileSync(expandPath(this.configPath), configContent);
7817
+ fs11.writeFileSync(expandPath(this.configPath), configContent);
7064
7818
  }
7065
7819
  };
7066
7820
  var globalConfig = null;
@@ -7078,8 +7832,8 @@ function loadConfig(configPath) {
7078
7832
  var import_commander = require("commander");
7079
7833
 
7080
7834
  // src/cli/utils/paths.ts
7081
- var fs10 = __toESM(require("fs"));
7082
- var path10 = __toESM(require("path"));
7835
+ var fs12 = __toESM(require("fs"));
7836
+ var path12 = __toESM(require("path"));
7083
7837
  var os2 = __toESM(require("os"));
7084
7838
  var DEFAULT_PATHS = [
7085
7839
  ".claude/skills",
@@ -7093,13 +7847,13 @@ var DEFAULT_PATHS = [
7093
7847
  ];
7094
7848
  function expandHome(p) {
7095
7849
  if (p.startsWith("~/")) {
7096
- return path10.join(os2.homedir(), p.slice(2));
7850
+ return path12.join(os2.homedir(), p.slice(2));
7097
7851
  }
7098
7852
  return p;
7099
7853
  }
7100
7854
  function dirExists(p) {
7101
7855
  try {
7102
- return fs10.statSync(p).isDirectory();
7856
+ return fs12.statSync(p).isDirectory();
7103
7857
  } catch {
7104
7858
  return false;
7105
7859
  }
@@ -7107,27 +7861,40 @@ function dirExists(p) {
7107
7861
  function resolveSkillPath(explicitPath) {
7108
7862
  if (explicitPath) {
7109
7863
  const resolved = expandHome(explicitPath);
7110
- return path10.resolve(resolved);
7864
+ return path12.resolve(resolved);
7111
7865
  }
7112
7866
  const envPath = process.env.SKILL_TREE_PATH;
7113
7867
  if (envPath) {
7114
- return path10.resolve(expandHome(envPath));
7868
+ return path12.resolve(expandHome(envPath));
7115
7869
  }
7116
7870
  for (const defaultPath of DEFAULT_PATHS) {
7117
- const resolved = path10.resolve(expandHome(defaultPath));
7871
+ const resolved = path12.resolve(expandHome(defaultPath));
7118
7872
  if (dirExists(resolved)) {
7119
7873
  return resolved;
7120
7874
  }
7121
7875
  }
7122
- return path10.resolve(".claude/skills");
7876
+ return path12.resolve(".claude/skills");
7123
7877
  }
7124
7878
  function ensureDir(dir) {
7125
7879
  if (!dirExists(dir)) {
7126
- fs10.mkdirSync(dir, { recursive: true });
7880
+ fs12.mkdirSync(dir, { recursive: true });
7127
7881
  }
7128
7882
  }
7129
7883
 
7130
7884
  // src/cli/utils/skillbank.ts
7885
+ function getMaterializationConfig() {
7886
+ const config2 = loadConfig();
7887
+ const mat = config2.materialization;
7888
+ if (!mat?.enabled) return void 0;
7889
+ return {
7890
+ enabled: true,
7891
+ mode: mat.mode ?? "symlink",
7892
+ symlinkPaths: mat.symlink_paths?.length ? mat.symlink_paths : void 0,
7893
+ agentsMdPath: mat.agents_md_path || void 0,
7894
+ agentsMdFormat: mat.agents_md_format ?? "xml",
7895
+ debounceMs: mat.debounce_ms ?? 500
7896
+ };
7897
+ }
7131
7898
  async function createSkillBankFromOptions(options) {
7132
7899
  const skillPath = resolveSkillPath(options.path);
7133
7900
  ensureDir(skillPath);
@@ -7135,7 +7902,8 @@ async function createSkillBankFromOptions(options) {
7135
7902
  storage: {
7136
7903
  basePath: skillPath,
7137
7904
  openSkillsCompatible: true
7138
- }
7905
+ },
7906
+ materialization: getMaterializationConfig()
7139
7907
  });
7140
7908
  await skillBank.initialize();
7141
7909
  return skillBank;
@@ -7193,33 +7961,10 @@ function formatSkillDetail(skill) {
7193
7961
  lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7194
7962
  lines.push(skill.description);
7195
7963
  lines.push("");
7196
- lines.push(import_chalk.default.dim("Problem"));
7197
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7198
- lines.push(skill.problem);
7199
- lines.push("");
7200
- if (skill.triggerConditions.length > 0) {
7201
- lines.push(import_chalk.default.dim("Trigger Conditions"));
7202
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7203
- for (const trigger of skill.triggerConditions) {
7204
- const desc = trigger.description ? ` - ${trigger.description}` : "";
7205
- lines.push(` ${import_chalk.default.cyan(trigger.type)}: ${trigger.value}${import_chalk.default.dim(desc)}`);
7206
- }
7207
- lines.push("");
7208
- }
7209
- lines.push(import_chalk.default.dim("Solution"));
7210
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7211
- lines.push(skill.solution);
7212
- lines.push("");
7213
- if (skill.verification) {
7214
- lines.push(import_chalk.default.dim("Verification"));
7215
- lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7216
- lines.push(skill.verification);
7217
- lines.push("");
7218
- }
7219
- if (skill.notes) {
7220
- lines.push(import_chalk.default.dim("Notes"));
7964
+ if (skill.instructions) {
7965
+ lines.push(import_chalk.default.dim("Instructions"));
7221
7966
  lines.push(import_chalk.default.dim("\u2500".repeat(50)));
7222
- lines.push(skill.notes);
7967
+ lines.push(skill.instructions);
7223
7968
  }
7224
7969
  return lines.join("\n");
7225
7970
  }
@@ -7588,7 +8333,7 @@ var deleteCommand = new import_commander11.Command("delete").description("Delete
7588
8333
 
7589
8334
  // src/cli/commands/export.ts
7590
8335
  var import_commander12 = require("commander");
7591
- var fs11 = __toESM(require("fs"));
8336
+ var fs13 = __toESM(require("fs"));
7592
8337
  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) => {
7593
8338
  const globalOpts = command.optsWithGlobals();
7594
8339
  try {
@@ -7596,7 +8341,7 @@ var exportCommand = new import_commander12.Command("export").description("Export
7596
8341
  const skills = await skillBank.exportAll();
7597
8342
  const json = JSON.stringify(skills, null, 2);
7598
8343
  if (options.output) {
7599
- fs11.writeFileSync(options.output, json, "utf-8");
8344
+ fs13.writeFileSync(options.output, json, "utf-8");
7600
8345
  if (!globalOpts.quiet) {
7601
8346
  printSuccess(`Exported ${skills.length} skill(s) to ${options.output}`);
7602
8347
  }
@@ -7611,228 +8356,16 @@ var exportCommand = new import_commander12.Command("export").description("Export
7611
8356
 
7612
8357
  // src/cli/commands/import.ts
7613
8358
  var import_commander13 = require("commander");
7614
- var fs12 = __toESM(require("fs"));
8359
+ var fs14 = __toESM(require("fs"));
7615
8360
 
7616
8361
  // src/import/converter.ts
7617
- var DEFAULT_SECTION_PATTERNS = {
7618
- problem: [
7619
- /^#+\s*(?:problem|issue|what\s+problem|the\s+problem)/i,
7620
- /^#+\s*(?:why|motivation|background)/i
7621
- ],
7622
- solution: [
7623
- /^#+\s*(?:solution|how\s+to|implementation|approach)/i,
7624
- /^#+\s*(?:usage|instructions|steps)/i
7625
- ],
7626
- verification: [
7627
- /^#+\s*(?:verification|verify|testing|test|validate)/i,
7628
- /^#+\s*(?:how\s+to\s+verify|checking)/i
7629
- ],
7630
- examples: [
7631
- /^#+\s*(?:examples?|sample|demo)/i,
7632
- /^#+\s*(?:use\s+cases?|scenarios?)/i
7633
- ],
7634
- notes: [
7635
- /^#+\s*(?:notes?|caveats?|warnings?|tips?)/i,
7636
- /^#+\s*(?:additional|edge\s+cases?|considerations?)/i
7637
- ],
7638
- triggers: [
7639
- /^#+\s*(?:triggers?|when\s+to\s+use|conditions?)/i,
7640
- /^#+\s*(?:activation|applies\s+when)/i
7641
- ]
7642
- };
7643
- function parseMarkdownSections(content) {
7644
- const lines = content.split("\n");
7645
- const sections = {
7646
- remainingContent: ""
7647
- };
7648
- let currentSection = "remaining";
7649
- let currentContent = [];
7650
- const flushSection = () => {
7651
- const text = currentContent.join("\n").trim();
7652
- if (!text) return;
7653
- switch (currentSection) {
7654
- case "problem":
7655
- sections.problem = text;
7656
- break;
7657
- case "solution":
7658
- sections.solution = text;
7659
- break;
7660
- case "verification":
7661
- sections.verification = text;
7662
- break;
7663
- case "examples":
7664
- sections.examples = parseExamples(text);
7665
- break;
7666
- case "notes":
7667
- sections.notes = text;
7668
- break;
7669
- case "triggers":
7670
- sections.triggers = parseTriggerList(text);
7671
- break;
7672
- case "remaining":
7673
- sections.remainingContent += (sections.remainingContent ? "\n\n" : "") + text;
7674
- break;
7675
- }
7676
- currentContent = [];
7677
- };
7678
- for (const line of lines) {
7679
- let matchedSection = null;
7680
- for (const [section, sectionPatterns] of Object.entries(DEFAULT_SECTION_PATTERNS)) {
7681
- for (const pattern of sectionPatterns) {
7682
- if (pattern.test(line)) {
7683
- matchedSection = section;
7684
- break;
7685
- }
7686
- }
7687
- if (matchedSection) break;
7688
- }
7689
- if (matchedSection) {
7690
- flushSection();
7691
- currentSection = matchedSection;
7692
- } else {
7693
- currentContent.push(line);
7694
- }
7695
- }
7696
- flushSection();
7697
- return sections;
7698
- }
7699
- function parseExamples(text) {
7700
- const examples = [];
7701
- const exampleBlocks = text.split(/^###\s+/m).filter(Boolean);
7702
- for (const block of exampleBlocks) {
7703
- const lines = block.split("\n");
7704
- const scenario = lines[0]?.trim() || "Example";
7705
- const content = lines.slice(1).join("\n");
7706
- const beforeMatch = content.match(/(?:before|input|given)[:\s]*\n?([\s\S]*?)(?=(?:after|output|then|result)|$)/i);
7707
- const afterMatch = content.match(/(?:after|output|then|result)[:\s]*\n?([\s\S]*?)$/i);
7708
- if (beforeMatch || afterMatch) {
7709
- examples.push({
7710
- scenario,
7711
- before: beforeMatch?.[1]?.trim() || "",
7712
- after: afterMatch?.[1]?.trim() || content.trim()
7713
- });
7714
- } else {
7715
- examples.push({
7716
- scenario,
7717
- before: "",
7718
- after: content.trim()
7719
- });
7720
- }
7721
- }
7722
- if (examples.length === 0 && text.trim()) {
7723
- examples.push({
7724
- scenario: "Example usage",
7725
- before: "",
7726
- after: text.trim()
7727
- });
7728
- }
7729
- return examples;
7730
- }
7731
- function parseTriggerList(text) {
7732
- const triggers = [];
7733
- const listItems = text.match(/^[\s]*[-*•]\s*(.+)$/gm) || text.match(/^[\s]*\d+\.\s*(.+)$/gm);
7734
- if (listItems) {
7735
- for (const item of listItems) {
7736
- const cleaned = item.replace(/^[\s]*[-*•\d.]+\s*/, "").trim();
7737
- if (cleaned) triggers.push(cleaned);
7738
- }
7739
- } else {
7740
- triggers.push(...text.split("\n").map((t) => t.trim()).filter(Boolean));
7741
- }
7742
- return triggers;
7743
- }
7744
- function inferTriggerType(text) {
7745
- const lower = text.toLowerCase();
7746
- if (lower.includes("error") || lower.includes("exception") || lower.includes("fail")) {
7747
- return { type: "error", value: text, description: "Triggered by error conditions" };
7748
- }
7749
- if (lower.includes("pattern") || lower.includes("regex") || lower.includes("match")) {
7750
- return { type: "pattern", value: text };
7751
- }
7752
- if (lower.includes("when") || lower.includes("context") || lower.includes("if ")) {
7753
- return { type: "context", value: text };
7754
- }
7755
- return { type: "keyword", value: text };
7756
- }
7757
- function extractKeywordsFromDescription(description) {
7758
- const keywords = [];
7759
- const techPatterns = [
7760
- /\b(react|vue|angular|svelte|next\.?js|nuxt)\b/gi,
7761
- /\b(node\.?js|deno|bun|python|rust|go|java|typescript|javascript)\b/gi,
7762
- /\b(docker|kubernetes|k8s|aws|gcp|azure)\b/gi,
7763
- /\b(git|github|gitlab|npm|yarn|pnpm)\b/gi,
7764
- /\b(sql|postgres|mysql|mongodb|redis)\b/gi,
7765
- /\b(api|rest|graphql|grpc)\b/gi,
7766
- /\b(test|testing|jest|vitest|pytest)\b/gi
7767
- ];
7768
- for (const pattern of techPatterns) {
7769
- const matches = description.match(pattern);
7770
- if (matches) {
7771
- keywords.push(...matches.map((m) => m.toLowerCase()));
7772
- }
7773
- }
7774
- return [...new Set(keywords)];
7775
- }
7776
- function extractTriggerConditions(skill, sections) {
7777
- const conditions = [];
7778
- if (sections?.triggers) {
7779
- for (const trigger of sections.triggers) {
7780
- conditions.push(inferTriggerType(trigger));
7781
- }
7782
- }
7783
- const keywords = extractKeywordsFromDescription(skill.description);
7784
- for (const keyword of keywords) {
7785
- if (!conditions.some((c) => c.value.toLowerCase() === keyword.toLowerCase())) {
7786
- conditions.push({
7787
- type: "keyword",
7788
- value: keyword,
7789
- description: "Keyword from description"
7790
- });
7791
- }
7792
- }
7793
- if (skill.tags) {
7794
- for (const tag of skill.tags.slice(0, 5)) {
7795
- if (!conditions.some((c) => c.value.toLowerCase() === tag.toLowerCase())) {
7796
- conditions.push({
7797
- type: "keyword",
7798
- value: tag,
7799
- description: "Tag from classification"
7800
- });
7801
- }
7802
- }
7803
- }
7804
- if (conditions.length === 0) {
7805
- conditions.push({
7806
- type: "context",
7807
- value: skill.description.substring(0, 100),
7808
- description: "Generated from skill description"
7809
- });
7810
- }
7811
- return conditions;
7812
- }
7813
8362
  function convertIndexerSkill(indexerSkill) {
7814
8363
  const warnings = [];
7815
- const sections = parseMarkdownSections(indexerSkill.content);
7816
- const hasStructuredContent = !!(sections.problem || sections.solution || sections.verification || sections.examples?.length);
8364
+ const instructions = indexerSkill.content || "";
8365
+ const hasStructuredContent = instructions.trim().length > 0;
7817
8366
  if (!hasStructuredContent) {
7818
- warnings.push("No structured content sections found, using raw content");
7819
- }
7820
- const triggerConditions = extractTriggerConditions(indexerSkill, sections);
7821
- let examples = sections.examples || [];
7822
- if (examples.length === 0) {
7823
- examples = [{
7824
- scenario: "Usage example",
7825
- before: "",
7826
- after: indexerSkill.content.substring(0, 500)
7827
- }];
7828
- warnings.push("No examples found, created placeholder");
7829
- }
7830
- const problem = sections.problem || indexerSkill.description;
7831
- const solution = sections.solution || indexerSkill.content;
7832
- const verification = sections.verification || "Verify the skill output meets expectations";
7833
- if (!sections.problem) warnings.push("No problem section found, using description");
7834
- if (!sections.solution) warnings.push("No solution section found, using full content");
7835
- if (!sections.verification) warnings.push("No verification section found, using default");
8367
+ warnings.push("No content found, instructions will be empty");
8368
+ }
7836
8369
  const tags = [...indexerSkill.tags || []];
7837
8370
  if (indexerSkill.sourceRepo) {
7838
8371
  const repoName = indexerSkill.sourceRepo.split("/").pop();
@@ -7841,26 +8374,12 @@ function convertIndexerSkill(indexerSkill) {
7841
8374
  }
7842
8375
  }
7843
8376
  const status = indexerSkill.status === "indexed" ? "active" : indexerSkill.status === "raw" ? "draft" : "draft";
7844
- let notes = sections.notes || void 0;
7845
- if (indexerSkill.classificationReasoning) {
7846
- const classificationNote = `
7847
-
7848
- ---
7849
- Classification: ${indexerSkill.primaryPath?.join(" > ") || "uncategorized"}
7850
- Reasoning: ${indexerSkill.classificationReasoning}`;
7851
- notes = (notes || "") + classificationNote;
7852
- }
7853
8377
  const skill = {
7854
8378
  id: indexerSkill.slug,
7855
8379
  name: indexerSkill.displayName || indexerSkill.name,
7856
8380
  version: indexerSkill.version,
7857
8381
  description: indexerSkill.description,
7858
- problem,
7859
- triggerConditions,
7860
- solution,
7861
- verification,
7862
- examples,
7863
- notes,
8382
+ instructions,
7864
8383
  author: indexerSkill.author,
7865
8384
  tags,
7866
8385
  createdAt: new Date(indexerSkill.scrapedAt),
@@ -7931,7 +8450,7 @@ function parseIndexerExport(content) {
7931
8450
  function isSkillTreeSkill(obj) {
7932
8451
  if (typeof obj !== "object" || obj === null) return false;
7933
8452
  const skill = obj;
7934
- return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.problem === "string" && typeof skill.solution === "string" && Array.isArray(skill.triggerConditions) && typeof skill.metrics === "object";
8453
+ return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.instructions === "string" && typeof skill.metrics === "object";
7935
8454
  }
7936
8455
  function isIndexerSkill(obj) {
7937
8456
  if (typeof obj !== "object" || obj === null) return false;
@@ -7987,11 +8506,11 @@ function isLikelyIndexerFormat(content) {
7987
8506
  var importCommand = new import_commander13.Command("import").description("Import skills from JSON file").argument("<file>", "JSON file to import").option("--from-indexer", "Import from skill-indexer export format").option("--auto-detect", "Auto-detect format (default: true)", true).action(async (file, options, command) => {
7988
8507
  const globalOpts = command.optsWithGlobals();
7989
8508
  try {
7990
- if (!fs12.existsSync(file)) {
8509
+ if (!fs14.existsSync(file)) {
7991
8510
  printError(`File not found: ${file}`);
7992
8511
  process.exit(1);
7993
8512
  }
7994
- const content = fs12.readFileSync(file, "utf-8");
8513
+ const content = fs14.readFileSync(file, "utf-8");
7995
8514
  let skills;
7996
8515
  const isIndexerFormat = options.fromIndexer || options.autoDetect !== false && isLikelyIndexerFormat(content);
7997
8516
  if (isIndexerFormat) {
@@ -8060,8 +8579,8 @@ var import_commander14 = require("commander");
8060
8579
  var import_child_process2 = require("child_process");
8061
8580
 
8062
8581
  // src/services/indexer.ts
8063
- var path11 = __toESM(require("path"));
8064
- var fs13 = __toESM(require("fs"));
8582
+ var path13 = __toESM(require("path"));
8583
+ var fs15 = __toESM(require("fs"));
8065
8584
  function hasIndexerSupport(storage) {
8066
8585
  const s = storage;
8067
8586
  return typeof s.addRelationship === "function" && typeof s.getTaxonomyTree === "function";
@@ -8097,16 +8616,16 @@ var IndexerService = class {
8097
8616
  try {
8098
8617
  const possiblePaths = [
8099
8618
  // Relative to this file in dist
8100
- path11.resolve(__dirname, "../../scraper/dist"),
8619
+ path13.resolve(__dirname, "../../scraper/dist"),
8101
8620
  // Relative to project root
8102
- path11.resolve(process.cwd(), "scraper/dist"),
8621
+ path13.resolve(process.cwd(), "scraper/dist"),
8103
8622
  // Absolute paths from config
8104
- this.serviceConfig.cacheDir ? path11.resolve(this.serviceConfig.cacheDir, "../scraper/dist") : null
8623
+ this.serviceConfig.cacheDir ? path13.resolve(this.serviceConfig.cacheDir, "../scraper/dist") : null
8105
8624
  ].filter(Boolean);
8106
8625
  let scraperBasePath = null;
8107
8626
  for (const basePath of possiblePaths) {
8108
- const scraperIndex = path11.join(basePath, "scraper/index.js");
8109
- if (fs13.existsSync(scraperIndex)) {
8627
+ const scraperIndex = path13.join(basePath, "scraper/index.js");
8628
+ if (fs15.existsSync(scraperIndex)) {
8110
8629
  scraperBasePath = basePath;
8111
8630
  break;
8112
8631
  }
@@ -8114,9 +8633,9 @@ var IndexerService = class {
8114
8633
  if (!scraperBasePath) {
8115
8634
  throw new Error("Scraper modules not found. Run `cd scraper && npm run build` first.");
8116
8635
  }
8117
- const scraperPath = path11.join(scraperBasePath, "scraper/index.js");
8118
- const indexerPath = path11.join(scraperBasePath, "indexer/index.js");
8119
- const databasePath = path11.join(scraperBasePath, "database/index.js");
8636
+ const scraperPath = path13.join(scraperBasePath, "scraper/index.js");
8637
+ const indexerPath = path13.join(scraperBasePath, "indexer/index.js");
8638
+ const databasePath = path13.join(scraperBasePath, "database/index.js");
8120
8639
  this.scraperModule = await import(
8121
8640
  /* webpackIgnore: true */
8122
8641
  scraperPath
@@ -8130,7 +8649,7 @@ var IndexerService = class {
8130
8649
  databasePath
8131
8650
  );
8132
8651
  if (this.databaseModule.createDatabase) {
8133
- const dbPath = this.serviceConfig.databasePath || path11.join(process.cwd(), "scraper/data/skills.db");
8652
+ const dbPath = this.serviceConfig.databasePath || path13.join(process.cwd(), "scraper/data/skills.db");
8134
8653
  this.db = this.databaseModule.createDatabase(dbPath);
8135
8654
  }
8136
8655
  this.initialized = true;
@@ -8345,7 +8864,7 @@ var IndexerService = class {
8345
8864
  detectSkillRelationships(skill, allSkills) {
8346
8865
  const relationships = [];
8347
8866
  const skillId = skill.id || skill.slug;
8348
- const skillContent = `${skill.name} ${skill.description} ${skill.problem || ""} ${skill.solution || ""} ${skill.content || ""}`.toLowerCase();
8867
+ const skillContent = `${skill.name} ${skill.description} ${skill.instructions || ""} ${skill.content || ""}`.toLowerCase();
8349
8868
  for (const other of allSkills) {
8350
8869
  const otherId = other.id || other.slug;
8351
8870
  if (otherId === skillId) continue;
@@ -8832,7 +9351,7 @@ async function runStandaloneMode(url, options, globalOpts) {
8832
9351
  }
8833
9352
  }
8834
9353
  async function runSkillIndexer(args, quiet) {
8835
- return new Promise((resolve3, reject) => {
9354
+ return new Promise((resolve4, reject) => {
8836
9355
  const proc = (0, import_child_process2.spawn)("skillindexer", args, {
8837
9356
  stdio: quiet ? "pipe" : "inherit",
8838
9357
  shell: true
@@ -8842,7 +9361,7 @@ async function runSkillIndexer(args, quiet) {
8842
9361
  });
8843
9362
  proc.on("close", (code) => {
8844
9363
  if (code === 0) {
8845
- resolve3();
9364
+ resolve4();
8846
9365
  } else {
8847
9366
  reject(new Error(`skillindexer exited with code ${code}`));
8848
9367
  }
@@ -8926,7 +9445,7 @@ async function runStandaloneMode2(options, globalOpts) {
8926
9445
  }
8927
9446
  }
8928
9447
  async function runSkillIndexer2(args, quiet) {
8929
- return new Promise((resolve3, reject) => {
9448
+ return new Promise((resolve4, reject) => {
8930
9449
  const proc = (0, import_child_process3.spawn)("skillindexer", args, {
8931
9450
  stdio: quiet ? "pipe" : "inherit",
8932
9451
  shell: true
@@ -8936,7 +9455,7 @@ async function runSkillIndexer2(args, quiet) {
8936
9455
  });
8937
9456
  proc.on("close", (code) => {
8938
9457
  if (code === 0) {
8939
- resolve3();
9458
+ resolve4();
8940
9459
  } else {
8941
9460
  reject(new Error(`skillindexer exited with code ${code}`));
8942
9461
  }
@@ -9006,7 +9525,7 @@ async function runStandaloneMode3(pathArg, options, globalOpts) {
9006
9525
  }
9007
9526
  }
9008
9527
  async function runSkillIndexer3(args, quiet) {
9009
- return new Promise((resolve3, reject) => {
9528
+ return new Promise((resolve4, reject) => {
9010
9529
  const proc = (0, import_child_process4.spawn)("skillindexer", args, {
9011
9530
  stdio: quiet ? "pipe" : "inherit",
9012
9531
  shell: true
@@ -9016,7 +9535,7 @@ async function runSkillIndexer3(args, quiet) {
9016
9535
  });
9017
9536
  proc.on("close", (code) => {
9018
9537
  if (code === 0) {
9019
- resolve3();
9538
+ resolve4();
9020
9539
  } else {
9021
9540
  reject(new Error(`skillindexer exited with code ${code}`));
9022
9541
  }
@@ -9092,7 +9611,7 @@ async function runStandaloneMode4(options, globalOpts) {
9092
9611
  }
9093
9612
  }
9094
9613
  async function runSkillIndexer4(args, quiet) {
9095
- return new Promise((resolve3, reject) => {
9614
+ return new Promise((resolve4, reject) => {
9096
9615
  const proc = (0, import_child_process5.spawn)("skillindexer", args, {
9097
9616
  stdio: quiet ? "pipe" : "inherit",
9098
9617
  shell: true
@@ -9102,7 +9621,7 @@ async function runSkillIndexer4(args, quiet) {
9102
9621
  });
9103
9622
  proc.on("close", (code) => {
9104
9623
  if (code === 0) {
9105
- resolve3();
9624
+ resolve4();
9106
9625
  } else {
9107
9626
  reject(new Error(`skillindexer exited with code ${code}`));
9108
9627
  }
@@ -9171,7 +9690,7 @@ async function runStandaloneMode5(globalOpts) {
9171
9690
  }
9172
9691
  }
9173
9692
  async function runSkillIndexer5(args, quiet) {
9174
- return new Promise((resolve3, reject) => {
9693
+ return new Promise((resolve4, reject) => {
9175
9694
  const proc = (0, import_child_process6.spawn)("skillindexer", args, {
9176
9695
  stdio: quiet ? "pipe" : "inherit",
9177
9696
  shell: true
@@ -9181,7 +9700,7 @@ async function runSkillIndexer5(args, quiet) {
9181
9700
  });
9182
9701
  proc.on("close", (code) => {
9183
9702
  if (code === 0) {
9184
- resolve3();
9703
+ resolve4();
9185
9704
  } else {
9186
9705
  reject(new Error(`skillindexer exited with code ${code}`));
9187
9706
  }
@@ -9191,8 +9710,8 @@ async function runSkillIndexer5(args, quiet) {
9191
9710
 
9192
9711
  // src/cli/commands/indexer/sync.ts
9193
9712
  var import_commander19 = require("commander");
9194
- var fs14 = __toESM(require("fs"));
9195
- var path12 = __toESM(require("path"));
9713
+ var fs16 = __toESM(require("fs"));
9714
+ var path14 = __toESM(require("path"));
9196
9715
  var os3 = __toESM(require("os"));
9197
9716
  var import_child_process7 = require("child_process");
9198
9717
 
@@ -9653,11 +10172,11 @@ async function runLegacyImport(options, globalOpts) {
9653
10172
  if (!globalOpts.quiet) {
9654
10173
  printInfo("Exporting skills from indexer...");
9655
10174
  }
9656
- exportPath = path12.join(os3.tmpdir(), `skill-tree-sync-${Date.now()}.json`);
10175
+ exportPath = path14.join(os3.tmpdir(), `skill-tree-sync-${Date.now()}.json`);
9657
10176
  const args = ["export-skilltree", "-o", exportPath, "-f", "json"];
9658
10177
  if (options.indexedOnly) args.push("--indexed-only");
9659
10178
  await runSkillIndexer6(args, true);
9660
- if (!fs14.existsSync(exportPath)) {
10179
+ if (!fs16.existsSync(exportPath)) {
9661
10180
  printError("Failed to export skills from indexer");
9662
10181
  process.exit(1);
9663
10182
  }
@@ -9665,7 +10184,7 @@ async function runLegacyImport(options, globalOpts) {
9665
10184
  if (!globalOpts.quiet) {
9666
10185
  printInfo(`Reading export from ${exportPath}...`);
9667
10186
  }
9668
- const content = fs14.readFileSync(exportPath, "utf-8");
10187
+ const content = fs16.readFileSync(exportPath, "utf-8");
9669
10188
  const { skills, stats } = parseIndexerExport(content);
9670
10189
  if (!globalOpts.quiet) {
9671
10190
  printInfo(`Found ${stats.total} skills (${stats.withStructuredContent} with structured content)`);
@@ -9685,7 +10204,7 @@ async function runLegacyImport(options, globalOpts) {
9685
10204
  const result = await skillBank.importSkills(skills);
9686
10205
  if (!options.exportPath && exportPath) {
9687
10206
  try {
9688
- fs14.unlinkSync(exportPath);
10207
+ fs16.unlinkSync(exportPath);
9689
10208
  } catch {
9690
10209
  }
9691
10210
  }
@@ -9699,7 +10218,7 @@ async function runLegacyImport(options, globalOpts) {
9699
10218
  }
9700
10219
  }
9701
10220
  async function runSkillIndexer6(args, quiet) {
9702
- return new Promise((resolve3, reject) => {
10221
+ return new Promise((resolve4, reject) => {
9703
10222
  const proc = (0, import_child_process7.spawn)("skillindexer", args, {
9704
10223
  stdio: quiet ? "pipe" : "inherit",
9705
10224
  shell: true
@@ -9709,7 +10228,7 @@ async function runSkillIndexer6(args, quiet) {
9709
10228
  });
9710
10229
  proc.on("close", (code) => {
9711
10230
  if (code === 0) {
9712
- resolve3();
10231
+ resolve4();
9713
10232
  } else {
9714
10233
  reject(new Error(`skillindexer exited with code ${code}`));
9715
10234
  }
@@ -9722,7 +10241,7 @@ var indexerCommand = new import_commander20.Command("index").description("Skill
9722
10241
 
9723
10242
  // src/cli/commands/config.ts
9724
10243
  var import_commander21 = require("commander");
9725
- var fs15 = __toESM(require("fs"));
10244
+ var fs17 = __toESM(require("fs"));
9726
10245
  var configCommand = new import_commander21.Command("config").description("View and manage configuration");
9727
10246
  configCommand.command("show").description("Show current configuration").option("--path <path>", "Path to specific config value (e.g., storage.path)").action((options) => {
9728
10247
  const globalOpts = configCommand.parent?.opts() || {};
@@ -9755,7 +10274,7 @@ configCommand.command("show").description("Show current configuration").option("
9755
10274
  configCommand.command("init").description("Create default configuration file").option("-f, --force", "Overwrite existing config file").action((options) => {
9756
10275
  const globalOpts = configCommand.parent?.opts() || {};
9757
10276
  const configPath = expandPath(globalOpts.config || getConfigPath());
9758
- if (fs15.existsSync(configPath) && !options.force) {
10277
+ if (fs17.existsSync(configPath) && !options.force) {
9759
10278
  console.error(`Config file already exists: ${configPath}`);
9760
10279
  console.error("Use --force to overwrite");
9761
10280
  process.exit(1);
@@ -9774,7 +10293,7 @@ configCommand.command("path").description("Show configuration file path").action
9774
10293
  configCommand.command("edit").description("Open configuration file in editor").action(() => {
9775
10294
  const globalOpts = configCommand.parent?.opts() || {};
9776
10295
  const configPath = expandPath(globalOpts.config || getConfigPath());
9777
- if (!fs15.existsSync(configPath)) {
10296
+ if (!fs17.existsSync(configPath)) {
9778
10297
  const loader = new ConfigLoader(configPath);
9779
10298
  loader.createDefaultConfigFile();
9780
10299
  }
@@ -9805,7 +10324,7 @@ configCommand.command("defaults").description("Show default configuration values
9805
10324
  configCommand.command("validate").description("Validate configuration file").action(() => {
9806
10325
  const globalOpts = configCommand.parent?.opts() || {};
9807
10326
  const configPath = expandPath(globalOpts.config || getConfigPath());
9808
- if (!fs15.existsSync(configPath)) {
10327
+ if (!fs17.existsSync(configPath)) {
9809
10328
  console.error(`Config file not found: ${configPath}`);
9810
10329
  process.exit(1);
9811
10330
  }
@@ -9872,15 +10391,15 @@ function printConfig(obj, indent) {
9872
10391
 
9873
10392
  // src/cli/commands/sync.ts
9874
10393
  var import_commander22 = require("commander");
9875
- var path13 = __toESM(require("path"));
9876
- var fs16 = __toESM(require("fs"));
10394
+ var path15 = __toESM(require("path"));
10395
+ var fs18 = __toESM(require("fs"));
9877
10396
  function getSyncConfigPath(basePath) {
9878
- return path13.join(basePath, ".skillbank", "sync-config.json");
10397
+ return path15.join(basePath, ".skillbank", "sync-config.json");
9879
10398
  }
9880
10399
  async function loadSyncConfig(basePath) {
9881
10400
  const configPath = getSyncConfigPath(basePath);
9882
10401
  try {
9883
- const content = await fs16.promises.readFile(configPath, "utf-8");
10402
+ const content = await fs18.promises.readFile(configPath, "utf-8");
9884
10403
  return JSON.parse(content);
9885
10404
  } catch {
9886
10405
  return null;
@@ -9888,8 +10407,8 @@ async function loadSyncConfig(basePath) {
9888
10407
  }
9889
10408
  async function saveSyncConfig(basePath, config2) {
9890
10409
  const configPath = getSyncConfigPath(basePath);
9891
- await fs16.promises.mkdir(path13.dirname(configPath), { recursive: true });
9892
- await fs16.promises.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
10410
+ await fs18.promises.mkdir(path15.dirname(configPath), { recursive: true });
10411
+ await fs18.promises.writeFile(configPath, JSON.stringify(config2, null, 2), "utf-8");
9893
10412
  }
9894
10413
  async function createSyncAdapter(basePath, globalOpts) {
9895
10414
  const config2 = await loadSyncConfig(basePath);
@@ -9919,8 +10438,8 @@ function initCommand() {
9919
10438
  agentName: options.name,
9920
10439
  environment: options.env
9921
10440
  });
9922
- const gitDir = path13.join(basePath, ".git");
9923
- if (!fs16.existsSync(gitDir)) {
10441
+ const gitDir = path15.join(basePath, ".git");
10442
+ if (!fs18.existsSync(gitDir)) {
9924
10443
  printError(`Not a git repository: ${basePath}`);
9925
10444
  printInfo("Initialize a git repository first with: git init");
9926
10445
  process.exit(1);
@@ -10194,20 +10713,144 @@ function resolveCommand() {
10194
10713
  });
10195
10714
  }
10196
10715
 
10716
+ // src/cli/commands/read.ts
10717
+ var import_commander23 = require("commander");
10718
+ var path16 = __toESM(require("path"));
10719
+ function serializeSkillMd(skill) {
10720
+ const lines = [];
10721
+ lines.push("---");
10722
+ lines.push(`name: ${skill.name}`);
10723
+ lines.push(`description: |`);
10724
+ lines.push(` ${skill.description}`);
10725
+ lines.push(`version: ${skill.version}`);
10726
+ if (skill.tags.length > 0) {
10727
+ lines.push(`tags:`);
10728
+ for (const tag of skill.tags) {
10729
+ lines.push(` - ${tag}`);
10730
+ }
10731
+ }
10732
+ lines.push("---");
10733
+ lines.push("");
10734
+ lines.push(skill.instructions);
10735
+ return lines.join("\n");
10736
+ }
10737
+ async function resolveSkill(bank, query) {
10738
+ const exact = await bank.getSkill(query);
10739
+ if (exact) return { skill: exact, fuzzy: false };
10740
+ const results = await bank.searchSkills(query);
10741
+ if (results.length > 0) {
10742
+ return { skill: results[0], fuzzy: true };
10743
+ }
10744
+ return null;
10745
+ }
10746
+ var readCommand = new import_commander23.Command("read").description("Read skill(s) to stdout (for agent activation)").argument("<names>", "Skill name(s), comma-separated").action(async (names, _options, command) => {
10747
+ const globalOpts = command.optsWithGlobals();
10748
+ try {
10749
+ const skillBank = await createSkillBankFromOptions(globalOpts);
10750
+ const basePath = getSkillPath(globalOpts);
10751
+ const nameList = names.split(",").map((n) => n.trim()).filter(Boolean);
10752
+ for (const name of nameList) {
10753
+ const result = await resolveSkill(skillBank, name);
10754
+ if (!result) {
10755
+ printError(`Skill not found: ${name}`);
10756
+ process.exit(1);
10757
+ }
10758
+ const { skill, fuzzy } = result;
10759
+ if (fuzzy) {
10760
+ console.log(`Reading: ${skill.name} (matched: ${skill.id})`);
10761
+ } else {
10762
+ console.log(`Reading: ${skill.name}`);
10763
+ }
10764
+ const skillDir = path16.join(basePath, ".skilltree", "skills", skill.id);
10765
+ console.log(`Base directory: ${skillDir}`);
10766
+ console.log("");
10767
+ console.log(serializeSkillMd(skill));
10768
+ console.log("");
10769
+ console.log(`Skill read: ${skill.name}`);
10770
+ if (nameList.length > 1 && name !== nameList[nameList.length - 1]) {
10771
+ console.log("---");
10772
+ }
10773
+ }
10774
+ } catch (error) {
10775
+ printError(error.message);
10776
+ process.exit(1);
10777
+ }
10778
+ });
10779
+
10780
+ // src/cli/commands/materialize.ts
10781
+ var import_commander24 = require("commander");
10782
+ var materializeCommand = new import_commander24.Command("materialize").description("Materialize skills to agent-discoverable paths").option("--paths <dirs>", "Comma-separated target directories (e.g. .claude/skills,.agent/skills)").option("--agents-md <path>", "Path to write AGENTS.md").option("--format <format>", "AGENTS.md format: xml, markdown, json", "xml").option("--mode <mode>", "Materialization mode: symlink or copy", "symlink").option("-w, --watch", "Watch for changes and re-materialize").action(async (options, command) => {
10783
+ const globalOpts = command.optsWithGlobals();
10784
+ try {
10785
+ if (!options.paths && !options.agentsMd) {
10786
+ printError("Specify at least --paths or --agents-md");
10787
+ console.log("");
10788
+ printInfo("Examples:");
10789
+ console.log(" skill-tree materialize --paths .claude/skills,.agent/skills");
10790
+ console.log(" skill-tree materialize --agents-md ./AGENTS.md");
10791
+ console.log(" skill-tree materialize --paths .claude/skills --agents-md ./AGENTS.md --mode copy");
10792
+ process.exit(1);
10793
+ }
10794
+ const skillBank = await createSkillBankFromOptions(globalOpts);
10795
+ const storage = skillBank.getStorage();
10796
+ const basePath = getSkillPath(globalOpts);
10797
+ const symlinkPaths = options.paths ? options.paths.split(",").map((p) => p.trim()).filter(Boolean) : [];
10798
+ const config2 = {
10799
+ enabled: true,
10800
+ mode: options.mode ?? "symlink",
10801
+ symlinkPaths,
10802
+ agentsMdPath: options.agentsMd,
10803
+ agentsMdFormat: options.format ?? "xml",
10804
+ debounceMs: 500
10805
+ };
10806
+ const materializer = new Materializer(storage, basePath, config2);
10807
+ await materializer.initialize();
10808
+ const skills = await storage.listSkills();
10809
+ printSuccess(`Materialized ${skills.length} skill(s)`);
10810
+ if (symlinkPaths.length > 0) {
10811
+ const modeLabel = config2.mode === "copy" ? "Copied" : "Symlinked";
10812
+ for (const p of symlinkPaths) {
10813
+ printInfo(`${modeLabel} to: ${p}`);
10814
+ }
10815
+ }
10816
+ if (options.agentsMd) {
10817
+ printInfo(`AGENTS.md written to: ${options.agentsMd}`);
10818
+ }
10819
+ if (options.watch) {
10820
+ printInfo("Watching for changes... (Ctrl+C to stop)");
10821
+ printWarning(
10822
+ "Note: Only detects changes to .skilltree/skills/. Changes via the SkillBank API are handled automatically when materialization is enabled in config."
10823
+ );
10824
+ materializer.watch();
10825
+ await new Promise((resolve4) => {
10826
+ process.on("SIGINT", () => {
10827
+ materializer.shutdown();
10828
+ resolve4();
10829
+ });
10830
+ });
10831
+ } else {
10832
+ materializer.shutdown();
10833
+ }
10834
+ } catch (error) {
10835
+ printError(error.message);
10836
+ process.exit(1);
10837
+ }
10838
+ });
10839
+
10197
10840
  // src/cli/commands/loadout/index.ts
10198
- var import_commander35 = require("commander");
10841
+ var import_commander37 = require("commander");
10199
10842
 
10200
10843
  // src/cli/commands/loadout/list.ts
10201
- var import_commander23 = require("commander");
10844
+ var import_commander25 = require("commander");
10202
10845
 
10203
10846
  // src/serving/state-persistence.ts
10204
- var fs17 = __toESM(require("fs"));
10205
- var path14 = __toESM(require("path"));
10847
+ var fs19 = __toESM(require("fs"));
10848
+ var path17 = __toESM(require("path"));
10206
10849
  var STATE_FILENAME = ".loadout-state.json";
10207
10850
  function loadState(skillPath) {
10208
- const filePath = path14.join(skillPath, STATE_FILENAME);
10851
+ const filePath = path17.join(skillPath, STATE_FILENAME);
10209
10852
  try {
10210
- const raw = fs17.readFileSync(filePath, "utf-8");
10853
+ const raw = fs19.readFileSync(filePath, "utf-8");
10211
10854
  const data = JSON.parse(raw);
10212
10855
  return {
10213
10856
  available: new Map(data.available),
@@ -10221,7 +10864,7 @@ function loadState(skillPath) {
10221
10864
  }
10222
10865
  }
10223
10866
  function saveState(skillPath, state) {
10224
- const filePath = path14.join(skillPath, STATE_FILENAME);
10867
+ const filePath = path17.join(skillPath, STATE_FILENAME);
10225
10868
  const data = {
10226
10869
  available: Array.from(state.available.entries()),
10227
10870
  expanded: Array.from(state.expanded),
@@ -10229,12 +10872,12 @@ function saveState(skillPath, state) {
10229
10872
  source: state.source,
10230
10873
  updatedAt: state.updatedAt.toISOString()
10231
10874
  };
10232
- fs17.writeFileSync(filePath, JSON.stringify(data, null, 2));
10875
+ fs19.writeFileSync(filePath, JSON.stringify(data, null, 2));
10233
10876
  }
10234
10877
  function clearState(skillPath) {
10235
- const filePath = path14.join(skillPath, STATE_FILENAME);
10878
+ const filePath = path17.join(skillPath, STATE_FILENAME);
10236
10879
  try {
10237
- fs17.unlinkSync(filePath);
10880
+ fs19.unlinkSync(filePath);
10238
10881
  return true;
10239
10882
  } catch {
10240
10883
  return false;
@@ -10264,7 +10907,7 @@ async function createLoadoutServer(options) {
10264
10907
  }
10265
10908
 
10266
10909
  // src/cli/commands/loadout/list.ts
10267
- var listSubcommand = new import_commander23.Command("list").description("List skills in the current loadout").option("-f, --filter <filter>", "Filter: all, expanded, available, pending", "all").action(async (options, command) => {
10910
+ var listSubcommand = new import_commander25.Command("list").description("List skills in the current loadout").option("-f, --filter <filter>", "Filter: all, expanded, available, pending", "all").action(async (options, command) => {
10268
10911
  const globalOpts = command.optsWithGlobals();
10269
10912
  try {
10270
10913
  const { server } = await createLoadoutServer(globalOpts);
@@ -10308,8 +10951,8 @@ var listSubcommand = new import_commander23.Command("list").description("List sk
10308
10951
  });
10309
10952
 
10310
10953
  // src/cli/commands/loadout/search.ts
10311
- var import_commander24 = require("commander");
10312
- var searchSubcommand = new import_commander24.Command("search").description("Search for skills to add to the loadout").argument("<query>", "Search query").option("-l, --limit <n>", "Maximum results", "10").action(async (query, options, command) => {
10954
+ var import_commander26 = require("commander");
10955
+ var searchSubcommand = new import_commander26.Command("search").description("Search for skills to add to the loadout").argument("<query>", "Search query").option("-l, --limit <n>", "Maximum results", "10").action(async (query, options, command) => {
10313
10956
  const globalOpts = command.optsWithGlobals();
10314
10957
  try {
10315
10958
  const { server } = await createLoadoutServer(globalOpts);
@@ -10339,8 +10982,8 @@ var searchSubcommand = new import_commander24.Command("search").description("Sea
10339
10982
  });
10340
10983
 
10341
10984
  // src/cli/commands/loadout/add.ts
10342
- var import_commander25 = require("commander");
10343
- var addSubcommand = new import_commander25.Command("add").description("Add skills to the loadout").argument("<ids...>", "Skill IDs to add").action(async (ids, _options, command) => {
10985
+ var import_commander27 = require("commander");
10986
+ var addSubcommand = new import_commander27.Command("add").description("Add skills to the loadout").argument("<ids...>", "Skill IDs to add").action(async (ids, _options, command) => {
10344
10987
  const globalOpts = command.optsWithGlobals();
10345
10988
  try {
10346
10989
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -10358,8 +11001,8 @@ var addSubcommand = new import_commander25.Command("add").description("Add skill
10358
11001
  });
10359
11002
 
10360
11003
  // src/cli/commands/loadout/remove.ts
10361
- var import_commander26 = require("commander");
10362
- var removeSubcommand = new import_commander26.Command("remove").description("Remove skills from the loadout").argument("<ids...>", "Skill IDs to remove").action(async (ids, _options, command) => {
11004
+ var import_commander28 = require("commander");
11005
+ var removeSubcommand = new import_commander28.Command("remove").description("Remove skills from the loadout").argument("<ids...>", "Skill IDs to remove").action(async (ids, _options, command) => {
10363
11006
  const globalOpts = command.optsWithGlobals();
10364
11007
  try {
10365
11008
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -10377,8 +11020,8 @@ var removeSubcommand = new import_commander26.Command("remove").description("Rem
10377
11020
  });
10378
11021
 
10379
11022
  // src/cli/commands/loadout/profile.ts
10380
- var import_commander27 = require("commander");
10381
- var profileSubcommand = new import_commander27.Command("profile").description("Switch to a named skill profile").argument("[name]", "Profile name (e.g. debugging, security, code-review)").option("--list", "List available profiles").action(async (name, options, command) => {
11023
+ var import_commander29 = require("commander");
11024
+ var profileSubcommand = new import_commander29.Command("profile").description("Switch to a named skill profile").argument("[name]", "Profile name (e.g. debugging, security, code-review)").option("--list", "List available profiles").action(async (name, options, command) => {
10382
11025
  const globalOpts = command.optsWithGlobals();
10383
11026
  try {
10384
11027
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -10409,8 +11052,8 @@ var profileSubcommand = new import_commander27.Command("profile").description("S
10409
11052
  });
10410
11053
 
10411
11054
  // src/cli/commands/loadout/set.ts
10412
- var import_commander28 = require("commander");
10413
- var setSubcommand = new import_commander28.Command("set").description("Set loadout from criteria (tags, task description, etc.)").option("--tags <tags>", "Comma-separated tags to match").option("--task <description>", "Task description for semantic matching").option("--max-skills <n>", "Maximum number of skills").action(async (options, command) => {
11055
+ var import_commander30 = require("commander");
11056
+ var setSubcommand = new import_commander30.Command("set").description("Set loadout from criteria (tags, task description, etc.)").option("--tags <tags>", "Comma-separated tags to match").option("--task <description>", "Task description for semantic matching").option("--max-skills <n>", "Maximum number of skills").action(async (options, command) => {
10414
11057
  const globalOpts = command.optsWithGlobals();
10415
11058
  try {
10416
11059
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -10439,8 +11082,8 @@ var setSubcommand = new import_commander28.Command("set").description("Set loado
10439
11082
  });
10440
11083
 
10441
11084
  // src/cli/commands/loadout/expand.ts
10442
- var import_commander29 = require("commander");
10443
- var expandSubcommand = new import_commander29.Command("expand").description("Expand a skill to see its full content").argument("<id>", "Skill ID to expand").action(async (id, _options, command) => {
11085
+ var import_commander31 = require("commander");
11086
+ var expandSubcommand = new import_commander31.Command("expand").description("Expand a skill to see its full content").argument("<id>", "Skill ID to expand").action(async (id, _options, command) => {
10444
11087
  const globalOpts = command.optsWithGlobals();
10445
11088
  try {
10446
11089
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -10465,8 +11108,8 @@ var expandSubcommand = new import_commander29.Command("expand").description("Exp
10465
11108
  });
10466
11109
 
10467
11110
  // src/cli/commands/loadout/collapse.ts
10468
- var import_commander30 = require("commander");
10469
- var collapseSubcommand = new import_commander30.Command("collapse").description("Collapse an expanded skill back to summary").argument("<id>", "Skill ID to collapse").action(async (id, _options, command) => {
11111
+ var import_commander32 = require("commander");
11112
+ var collapseSubcommand = new import_commander32.Command("collapse").description("Collapse an expanded skill back to summary").argument("<id>", "Skill ID to collapse").action(async (id, _options, command) => {
10470
11113
  const globalOpts = command.optsWithGlobals();
10471
11114
  try {
10472
11115
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -10488,8 +11131,8 @@ var collapseSubcommand = new import_commander30.Command("collapse").description(
10488
11131
  });
10489
11132
 
10490
11133
  // src/cli/commands/loadout/use.ts
10491
- var import_commander31 = require("commander");
10492
- var useSubcommand = new import_commander31.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) => {
11134
+ var import_commander33 = require("commander");
11135
+ 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) => {
10493
11136
  const globalOpts = command.optsWithGlobals();
10494
11137
  try {
10495
11138
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -10515,8 +11158,8 @@ var useSubcommand = new import_commander31.Command("use").description("Mark a sk
10515
11158
  });
10516
11159
 
10517
11160
  // src/cli/commands/loadout/get.ts
10518
- var import_commander32 = require("commander");
10519
- var getSubcommand = new import_commander32.Command("get").description("Get details about a skill in the loadout").argument("<id>", "Skill ID").action(async (id, _options, command) => {
11161
+ var import_commander34 = require("commander");
11162
+ var getSubcommand = new import_commander34.Command("get").description("Get details about a skill in the loadout").argument("<id>", "Skill ID").action(async (id, _options, command) => {
10520
11163
  const globalOpts = command.optsWithGlobals();
10521
11164
  try {
10522
11165
  const { server } = await createLoadoutServer(globalOpts);
@@ -10538,8 +11181,8 @@ var getSubcommand = new import_commander32.Command("get").description("Get detai
10538
11181
  });
10539
11182
 
10540
11183
  // src/cli/commands/loadout/render.ts
10541
- var import_commander33 = require("commander");
10542
- var renderSubcommand = new import_commander33.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) => {
11184
+ var import_commander35 = require("commander");
11185
+ var renderSubcommand = new import_commander35.Command("render").description("Render the current loadout as a system prompt (XML or Markdown)").option("--format <format>", "Output format: xml or markdown", "xml").action(async (options, command) => {
10543
11186
  const globalOpts = command.optsWithGlobals();
10544
11187
  try {
10545
11188
  const { server } = await createLoadoutServer(globalOpts);
@@ -10557,8 +11200,8 @@ var renderSubcommand = new import_commander33.Command("render").description("Ren
10557
11200
  });
10558
11201
 
10559
11202
  // src/cli/commands/loadout/clear.ts
10560
- var import_commander34 = require("commander");
10561
- var clearSubcommand = new import_commander34.Command("clear").description("Clear the current loadout state").action(async (_options, command) => {
11203
+ var import_commander36 = require("commander");
11204
+ var clearSubcommand = new import_commander36.Command("clear").description("Clear the current loadout state").action(async (_options, command) => {
10562
11205
  const globalOpts = command.optsWithGlobals();
10563
11206
  try {
10564
11207
  const skillPath = resolveSkillPath(globalOpts.path);
@@ -10579,10 +11222,10 @@ var clearSubcommand = new import_commander34.Command("clear").description("Clear
10579
11222
  });
10580
11223
 
10581
11224
  // src/cli/commands/loadout/index.ts
10582
- var loadoutCommand = new import_commander35.Command("loadout").description("Manage skill loadouts for agent sessions").addCommand(listSubcommand).addCommand(searchSubcommand).addCommand(addSubcommand).addCommand(removeSubcommand).addCommand(profileSubcommand).addCommand(setSubcommand).addCommand(expandSubcommand).addCommand(collapseSubcommand).addCommand(useSubcommand).addCommand(getSubcommand).addCommand(renderSubcommand).addCommand(clearSubcommand);
11225
+ 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(useSubcommand).addCommand(getSubcommand).addCommand(renderSubcommand).addCommand(clearSubcommand);
10583
11226
 
10584
11227
  // src/cli/index.ts
10585
- var program = new import_commander36.Command();
11228
+ var program = new import_commander38.Command();
10586
11229
  var config = loadConfig();
10587
11230
  program.name("skill-tree").description("Management CLI for agent skills").version(VERSION).option("-p, --path <dir>", "Skills directory path", config.storage.path).option("-c, --config <file>", "Config file path", getConfigPath()).option("--json", "Output as JSON", config.cli.output_format === "json").option("-q, --quiet", "Suppress non-essential output", config.cli.quiet).option("--no-color", "Disable colored output", !config.cli.color);
10588
11231
  program.addCommand(listCommand);
@@ -10601,5 +11244,7 @@ program.addCommand(importCommand);
10601
11244
  program.addCommand(indexerCommand);
10602
11245
  program.addCommand(configCommand);
10603
11246
  program.addCommand(syncCommand2);
11247
+ program.addCommand(readCommand);
11248
+ program.addCommand(materializeCommand);
10604
11249
  program.addCommand(loadoutCommand);
10605
11250
  program.parse();