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.
@@ -2,15 +2,17 @@
2
2
  import {
3
3
  CachedStorageAdapter,
4
4
  GitSyncAdapter,
5
+ Materializer,
5
6
  SkillBank,
6
7
  SkillGraphServer,
7
8
  VERSION,
8
9
  createDefaultSyncConfig
9
- } from "../chunk-OYHYXKXO.mjs";
10
- import "../chunk-PDPN7FW7.mjs";
10
+ } from "../chunk-TEUB6DZR.mjs";
11
+ import "../chunk-TWPEHDW4.mjs";
12
+ import "../chunk-F3YEUQAP.mjs";
11
13
 
12
14
  // src/cli/index.ts
13
- import { Command as Command36 } from "commander";
15
+ import { Command as Command38 } from "commander";
14
16
 
15
17
  // src/config/types.ts
16
18
  var DEFAULT_CONFIG = {
@@ -35,6 +37,14 @@ var DEFAULT_CONFIG = {
35
37
  output_format: "table",
36
38
  color: true,
37
39
  quiet: false
40
+ },
41
+ materialization: {
42
+ enabled: false,
43
+ mode: "symlink",
44
+ symlink_paths: [],
45
+ agents_md_path: "",
46
+ agents_md_format: "xml",
47
+ debounce_ms: 500
38
48
  }
39
49
  };
40
50
 
@@ -90,8 +100,8 @@ function substituteEnvVarsInObject(obj) {
90
100
  }
91
101
  return obj;
92
102
  }
93
- function setNestedProperty(obj, path7, value) {
94
- const parts = path7.split(".");
103
+ function setNestedProperty(obj, path8, value) {
104
+ const parts = path8.split(".");
95
105
  let current = obj;
96
106
  for (let i = 0; i < parts.length - 1; i++) {
97
107
  const part = parts[i];
@@ -250,8 +260,8 @@ var ConfigLoader = class {
250
260
  /**
251
261
  * Get a specific config value by path
252
262
  */
253
- get(path7) {
254
- const parts = path7.split(".");
263
+ get(path8) {
264
+ const parts = path8.split(".");
255
265
  let current = this.getConfig();
256
266
  for (const part of parts) {
257
267
  if (current === null || typeof current !== "object") {
@@ -304,6 +314,20 @@ cli:
304
314
  output_format: table
305
315
  color: true
306
316
  quiet: false
317
+
318
+ materialization:
319
+ # Enable automatic materialization to agent-discoverable paths
320
+ enabled: false
321
+ # Mode: 'symlink' (default) or 'copy'
322
+ mode: symlink
323
+ # Directories to expose skills in (e.g. .claude/skills, .agent/skills)
324
+ symlink_paths: []
325
+ # Path to auto-generate AGENTS.md (empty = disabled)
326
+ agents_md_path: ""
327
+ # Format for AGENTS.md: xml, markdown, json
328
+ agents_md_format: xml
329
+ # Debounce interval in ms for batch updates
330
+ debounce_ms: 500
307
331
  `;
308
332
  fs.writeFileSync(expandPath(this.configPath), configContent);
309
333
  }
@@ -373,6 +397,19 @@ function ensureDir(dir) {
373
397
  }
374
398
 
375
399
  // src/cli/utils/skillbank.ts
400
+ function getMaterializationConfig() {
401
+ const config2 = loadConfig();
402
+ const mat = config2.materialization;
403
+ if (!mat?.enabled) return void 0;
404
+ return {
405
+ enabled: true,
406
+ mode: mat.mode ?? "symlink",
407
+ symlinkPaths: mat.symlink_paths?.length ? mat.symlink_paths : void 0,
408
+ agentsMdPath: mat.agents_md_path || void 0,
409
+ agentsMdFormat: mat.agents_md_format ?? "xml",
410
+ debounceMs: mat.debounce_ms ?? 500
411
+ };
412
+ }
376
413
  async function createSkillBankFromOptions(options) {
377
414
  const skillPath = resolveSkillPath(options.path);
378
415
  ensureDir(skillPath);
@@ -380,7 +417,8 @@ async function createSkillBankFromOptions(options) {
380
417
  storage: {
381
418
  basePath: skillPath,
382
419
  openSkillsCompatible: true
383
- }
420
+ },
421
+ materialization: getMaterializationConfig()
384
422
  });
385
423
  await skillBank.initialize();
386
424
  return skillBank;
@@ -438,33 +476,10 @@ function formatSkillDetail(skill) {
438
476
  lines.push(chalk.dim("\u2500".repeat(50)));
439
477
  lines.push(skill.description);
440
478
  lines.push("");
441
- lines.push(chalk.dim("Problem"));
442
- lines.push(chalk.dim("\u2500".repeat(50)));
443
- lines.push(skill.problem);
444
- lines.push("");
445
- if (skill.triggerConditions.length > 0) {
446
- lines.push(chalk.dim("Trigger Conditions"));
447
- lines.push(chalk.dim("\u2500".repeat(50)));
448
- for (const trigger of skill.triggerConditions) {
449
- const desc = trigger.description ? ` - ${trigger.description}` : "";
450
- lines.push(` ${chalk.cyan(trigger.type)}: ${trigger.value}${chalk.dim(desc)}`);
451
- }
452
- lines.push("");
453
- }
454
- lines.push(chalk.dim("Solution"));
455
- lines.push(chalk.dim("\u2500".repeat(50)));
456
- lines.push(skill.solution);
457
- lines.push("");
458
- if (skill.verification) {
459
- lines.push(chalk.dim("Verification"));
479
+ if (skill.instructions) {
480
+ lines.push(chalk.dim("Instructions"));
460
481
  lines.push(chalk.dim("\u2500".repeat(50)));
461
- lines.push(skill.verification);
462
- lines.push("");
463
- }
464
- if (skill.notes) {
465
- lines.push(chalk.dim("Notes"));
466
- lines.push(chalk.dim("\u2500".repeat(50)));
467
- lines.push(skill.notes);
482
+ lines.push(skill.instructions);
468
483
  }
469
484
  return lines.join("\n");
470
485
  }
@@ -859,225 +874,13 @@ import { Command as Command13 } from "commander";
859
874
  import * as fs4 from "fs";
860
875
 
861
876
  // src/import/converter.ts
862
- var DEFAULT_SECTION_PATTERNS = {
863
- problem: [
864
- /^#+\s*(?:problem|issue|what\s+problem|the\s+problem)/i,
865
- /^#+\s*(?:why|motivation|background)/i
866
- ],
867
- solution: [
868
- /^#+\s*(?:solution|how\s+to|implementation|approach)/i,
869
- /^#+\s*(?:usage|instructions|steps)/i
870
- ],
871
- verification: [
872
- /^#+\s*(?:verification|verify|testing|test|validate)/i,
873
- /^#+\s*(?:how\s+to\s+verify|checking)/i
874
- ],
875
- examples: [
876
- /^#+\s*(?:examples?|sample|demo)/i,
877
- /^#+\s*(?:use\s+cases?|scenarios?)/i
878
- ],
879
- notes: [
880
- /^#+\s*(?:notes?|caveats?|warnings?|tips?)/i,
881
- /^#+\s*(?:additional|edge\s+cases?|considerations?)/i
882
- ],
883
- triggers: [
884
- /^#+\s*(?:triggers?|when\s+to\s+use|conditions?)/i,
885
- /^#+\s*(?:activation|applies\s+when)/i
886
- ]
887
- };
888
- function parseMarkdownSections(content) {
889
- const lines = content.split("\n");
890
- const sections = {
891
- remainingContent: ""
892
- };
893
- let currentSection = "remaining";
894
- let currentContent = [];
895
- const flushSection = () => {
896
- const text = currentContent.join("\n").trim();
897
- if (!text) return;
898
- switch (currentSection) {
899
- case "problem":
900
- sections.problem = text;
901
- break;
902
- case "solution":
903
- sections.solution = text;
904
- break;
905
- case "verification":
906
- sections.verification = text;
907
- break;
908
- case "examples":
909
- sections.examples = parseExamples(text);
910
- break;
911
- case "notes":
912
- sections.notes = text;
913
- break;
914
- case "triggers":
915
- sections.triggers = parseTriggerList(text);
916
- break;
917
- case "remaining":
918
- sections.remainingContent += (sections.remainingContent ? "\n\n" : "") + text;
919
- break;
920
- }
921
- currentContent = [];
922
- };
923
- for (const line of lines) {
924
- let matchedSection = null;
925
- for (const [section, sectionPatterns] of Object.entries(DEFAULT_SECTION_PATTERNS)) {
926
- for (const pattern of sectionPatterns) {
927
- if (pattern.test(line)) {
928
- matchedSection = section;
929
- break;
930
- }
931
- }
932
- if (matchedSection) break;
933
- }
934
- if (matchedSection) {
935
- flushSection();
936
- currentSection = matchedSection;
937
- } else {
938
- currentContent.push(line);
939
- }
940
- }
941
- flushSection();
942
- return sections;
943
- }
944
- function parseExamples(text) {
945
- const examples = [];
946
- const exampleBlocks = text.split(/^###\s+/m).filter(Boolean);
947
- for (const block of exampleBlocks) {
948
- const lines = block.split("\n");
949
- const scenario = lines[0]?.trim() || "Example";
950
- const content = lines.slice(1).join("\n");
951
- const beforeMatch = content.match(/(?:before|input|given)[:\s]*\n?([\s\S]*?)(?=(?:after|output|then|result)|$)/i);
952
- const afterMatch = content.match(/(?:after|output|then|result)[:\s]*\n?([\s\S]*?)$/i);
953
- if (beforeMatch || afterMatch) {
954
- examples.push({
955
- scenario,
956
- before: beforeMatch?.[1]?.trim() || "",
957
- after: afterMatch?.[1]?.trim() || content.trim()
958
- });
959
- } else {
960
- examples.push({
961
- scenario,
962
- before: "",
963
- after: content.trim()
964
- });
965
- }
966
- }
967
- if (examples.length === 0 && text.trim()) {
968
- examples.push({
969
- scenario: "Example usage",
970
- before: "",
971
- after: text.trim()
972
- });
973
- }
974
- return examples;
975
- }
976
- function parseTriggerList(text) {
977
- const triggers = [];
978
- const listItems = text.match(/^[\s]*[-*•]\s*(.+)$/gm) || text.match(/^[\s]*\d+\.\s*(.+)$/gm);
979
- if (listItems) {
980
- for (const item of listItems) {
981
- const cleaned = item.replace(/^[\s]*[-*•\d.]+\s*/, "").trim();
982
- if (cleaned) triggers.push(cleaned);
983
- }
984
- } else {
985
- triggers.push(...text.split("\n").map((t) => t.trim()).filter(Boolean));
986
- }
987
- return triggers;
988
- }
989
- function inferTriggerType(text) {
990
- const lower = text.toLowerCase();
991
- if (lower.includes("error") || lower.includes("exception") || lower.includes("fail")) {
992
- return { type: "error", value: text, description: "Triggered by error conditions" };
993
- }
994
- if (lower.includes("pattern") || lower.includes("regex") || lower.includes("match")) {
995
- return { type: "pattern", value: text };
996
- }
997
- if (lower.includes("when") || lower.includes("context") || lower.includes("if ")) {
998
- return { type: "context", value: text };
999
- }
1000
- return { type: "keyword", value: text };
1001
- }
1002
- function extractKeywordsFromDescription(description) {
1003
- const keywords = [];
1004
- const techPatterns = [
1005
- /\b(react|vue|angular|svelte|next\.?js|nuxt)\b/gi,
1006
- /\b(node\.?js|deno|bun|python|rust|go|java|typescript|javascript)\b/gi,
1007
- /\b(docker|kubernetes|k8s|aws|gcp|azure)\b/gi,
1008
- /\b(git|github|gitlab|npm|yarn|pnpm)\b/gi,
1009
- /\b(sql|postgres|mysql|mongodb|redis)\b/gi,
1010
- /\b(api|rest|graphql|grpc)\b/gi,
1011
- /\b(test|testing|jest|vitest|pytest)\b/gi
1012
- ];
1013
- for (const pattern of techPatterns) {
1014
- const matches = description.match(pattern);
1015
- if (matches) {
1016
- keywords.push(...matches.map((m) => m.toLowerCase()));
1017
- }
1018
- }
1019
- return [...new Set(keywords)];
1020
- }
1021
- function extractTriggerConditions(skill, sections) {
1022
- const conditions = [];
1023
- if (sections?.triggers) {
1024
- for (const trigger of sections.triggers) {
1025
- conditions.push(inferTriggerType(trigger));
1026
- }
1027
- }
1028
- const keywords = extractKeywordsFromDescription(skill.description);
1029
- for (const keyword of keywords) {
1030
- if (!conditions.some((c) => c.value.toLowerCase() === keyword.toLowerCase())) {
1031
- conditions.push({
1032
- type: "keyword",
1033
- value: keyword,
1034
- description: "Keyword from description"
1035
- });
1036
- }
1037
- }
1038
- if (skill.tags) {
1039
- for (const tag of skill.tags.slice(0, 5)) {
1040
- if (!conditions.some((c) => c.value.toLowerCase() === tag.toLowerCase())) {
1041
- conditions.push({
1042
- type: "keyword",
1043
- value: tag,
1044
- description: "Tag from classification"
1045
- });
1046
- }
1047
- }
1048
- }
1049
- if (conditions.length === 0) {
1050
- conditions.push({
1051
- type: "context",
1052
- value: skill.description.substring(0, 100),
1053
- description: "Generated from skill description"
1054
- });
1055
- }
1056
- return conditions;
1057
- }
1058
877
  function convertIndexerSkill(indexerSkill) {
1059
878
  const warnings = [];
1060
- const sections = parseMarkdownSections(indexerSkill.content);
1061
- const hasStructuredContent = !!(sections.problem || sections.solution || sections.verification || sections.examples?.length);
879
+ const instructions = indexerSkill.content || "";
880
+ const hasStructuredContent = instructions.trim().length > 0;
1062
881
  if (!hasStructuredContent) {
1063
- warnings.push("No structured content sections found, using raw content");
1064
- }
1065
- const triggerConditions = extractTriggerConditions(indexerSkill, sections);
1066
- let examples = sections.examples || [];
1067
- if (examples.length === 0) {
1068
- examples = [{
1069
- scenario: "Usage example",
1070
- before: "",
1071
- after: indexerSkill.content.substring(0, 500)
1072
- }];
1073
- warnings.push("No examples found, created placeholder");
1074
- }
1075
- const problem = sections.problem || indexerSkill.description;
1076
- const solution = sections.solution || indexerSkill.content;
1077
- const verification = sections.verification || "Verify the skill output meets expectations";
1078
- if (!sections.problem) warnings.push("No problem section found, using description");
1079
- if (!sections.solution) warnings.push("No solution section found, using full content");
1080
- if (!sections.verification) warnings.push("No verification section found, using default");
882
+ warnings.push("No content found, instructions will be empty");
883
+ }
1081
884
  const tags = [...indexerSkill.tags || []];
1082
885
  if (indexerSkill.sourceRepo) {
1083
886
  const repoName = indexerSkill.sourceRepo.split("/").pop();
@@ -1086,26 +889,12 @@ function convertIndexerSkill(indexerSkill) {
1086
889
  }
1087
890
  }
1088
891
  const status = indexerSkill.status === "indexed" ? "active" : indexerSkill.status === "raw" ? "draft" : "draft";
1089
- let notes = sections.notes || void 0;
1090
- if (indexerSkill.classificationReasoning) {
1091
- const classificationNote = `
1092
-
1093
- ---
1094
- Classification: ${indexerSkill.primaryPath?.join(" > ") || "uncategorized"}
1095
- Reasoning: ${indexerSkill.classificationReasoning}`;
1096
- notes = (notes || "") + classificationNote;
1097
- }
1098
892
  const skill = {
1099
893
  id: indexerSkill.slug,
1100
894
  name: indexerSkill.displayName || indexerSkill.name,
1101
895
  version: indexerSkill.version,
1102
896
  description: indexerSkill.description,
1103
- problem,
1104
- triggerConditions,
1105
- solution,
1106
- verification,
1107
- examples,
1108
- notes,
897
+ instructions,
1109
898
  author: indexerSkill.author,
1110
899
  tags,
1111
900
  createdAt: new Date(indexerSkill.scrapedAt),
@@ -1176,7 +965,7 @@ function parseIndexerExport(content) {
1176
965
  function isSkillTreeSkill(obj) {
1177
966
  if (typeof obj !== "object" || obj === null) return false;
1178
967
  const skill = obj;
1179
- 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";
968
+ return typeof skill.id === "string" && typeof skill.name === "string" && typeof skill.version === "string" && typeof skill.instructions === "string" && typeof skill.metrics === "object";
1180
969
  }
1181
970
  function isIndexerSkill(obj) {
1182
971
  if (typeof obj !== "object" || obj === null) return false;
@@ -1590,7 +1379,7 @@ var IndexerService = class {
1590
1379
  detectSkillRelationships(skill, allSkills) {
1591
1380
  const relationships = [];
1592
1381
  const skillId = skill.id || skill.slug;
1593
- const skillContent = `${skill.name} ${skill.description} ${skill.problem || ""} ${skill.solution || ""} ${skill.content || ""}`.toLowerCase();
1382
+ const skillContent = `${skill.name} ${skill.description} ${skill.instructions || ""} ${skill.content || ""}`.toLowerCase();
1594
1383
  for (const other of allSkills) {
1595
1384
  const otherId = other.id || other.slug;
1596
1385
  if (otherId === skillId) continue;
@@ -3439,18 +3228,142 @@ function resolveCommand() {
3439
3228
  });
3440
3229
  }
3441
3230
 
3231
+ // src/cli/commands/read.ts
3232
+ import { Command as Command23 } from "commander";
3233
+ import * as path6 from "path";
3234
+ function serializeSkillMd(skill) {
3235
+ const lines = [];
3236
+ lines.push("---");
3237
+ lines.push(`name: ${skill.name}`);
3238
+ lines.push(`description: |`);
3239
+ lines.push(` ${skill.description}`);
3240
+ lines.push(`version: ${skill.version}`);
3241
+ if (skill.tags.length > 0) {
3242
+ lines.push(`tags:`);
3243
+ for (const tag of skill.tags) {
3244
+ lines.push(` - ${tag}`);
3245
+ }
3246
+ }
3247
+ lines.push("---");
3248
+ lines.push("");
3249
+ lines.push(skill.instructions);
3250
+ return lines.join("\n");
3251
+ }
3252
+ async function resolveSkill(bank, query) {
3253
+ const exact = await bank.getSkill(query);
3254
+ if (exact) return { skill: exact, fuzzy: false };
3255
+ const results = await bank.searchSkills(query);
3256
+ if (results.length > 0) {
3257
+ return { skill: results[0], fuzzy: true };
3258
+ }
3259
+ return null;
3260
+ }
3261
+ var readCommand = new Command23("read").description("Read skill(s) to stdout (for agent activation)").argument("<names>", "Skill name(s), comma-separated").action(async (names, _options, command) => {
3262
+ const globalOpts = command.optsWithGlobals();
3263
+ try {
3264
+ const skillBank = await createSkillBankFromOptions(globalOpts);
3265
+ const basePath = getSkillPath(globalOpts);
3266
+ const nameList = names.split(",").map((n) => n.trim()).filter(Boolean);
3267
+ for (const name of nameList) {
3268
+ const result = await resolveSkill(skillBank, name);
3269
+ if (!result) {
3270
+ printError(`Skill not found: ${name}`);
3271
+ process.exit(1);
3272
+ }
3273
+ const { skill, fuzzy } = result;
3274
+ if (fuzzy) {
3275
+ console.log(`Reading: ${skill.name} (matched: ${skill.id})`);
3276
+ } else {
3277
+ console.log(`Reading: ${skill.name}`);
3278
+ }
3279
+ const skillDir = path6.join(basePath, ".skilltree", "skills", skill.id);
3280
+ console.log(`Base directory: ${skillDir}`);
3281
+ console.log("");
3282
+ console.log(serializeSkillMd(skill));
3283
+ console.log("");
3284
+ console.log(`Skill read: ${skill.name}`);
3285
+ if (nameList.length > 1 && name !== nameList[nameList.length - 1]) {
3286
+ console.log("---");
3287
+ }
3288
+ }
3289
+ } catch (error) {
3290
+ printError(error.message);
3291
+ process.exit(1);
3292
+ }
3293
+ });
3294
+
3295
+ // src/cli/commands/materialize.ts
3296
+ import { Command as Command24 } from "commander";
3297
+ var materializeCommand = new Command24("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) => {
3298
+ const globalOpts = command.optsWithGlobals();
3299
+ try {
3300
+ if (!options.paths && !options.agentsMd) {
3301
+ printError("Specify at least --paths or --agents-md");
3302
+ console.log("");
3303
+ printInfo("Examples:");
3304
+ console.log(" skill-tree materialize --paths .claude/skills,.agent/skills");
3305
+ console.log(" skill-tree materialize --agents-md ./AGENTS.md");
3306
+ console.log(" skill-tree materialize --paths .claude/skills --agents-md ./AGENTS.md --mode copy");
3307
+ process.exit(1);
3308
+ }
3309
+ const skillBank = await createSkillBankFromOptions(globalOpts);
3310
+ const storage = skillBank.getStorage();
3311
+ const basePath = getSkillPath(globalOpts);
3312
+ const symlinkPaths = options.paths ? options.paths.split(",").map((p) => p.trim()).filter(Boolean) : [];
3313
+ const config2 = {
3314
+ enabled: true,
3315
+ mode: options.mode ?? "symlink",
3316
+ symlinkPaths,
3317
+ agentsMdPath: options.agentsMd,
3318
+ agentsMdFormat: options.format ?? "xml",
3319
+ debounceMs: 500
3320
+ };
3321
+ const materializer = new Materializer(storage, basePath, config2);
3322
+ await materializer.initialize();
3323
+ const skills = await storage.listSkills();
3324
+ printSuccess(`Materialized ${skills.length} skill(s)`);
3325
+ if (symlinkPaths.length > 0) {
3326
+ const modeLabel = config2.mode === "copy" ? "Copied" : "Symlinked";
3327
+ for (const p of symlinkPaths) {
3328
+ printInfo(`${modeLabel} to: ${p}`);
3329
+ }
3330
+ }
3331
+ if (options.agentsMd) {
3332
+ printInfo(`AGENTS.md written to: ${options.agentsMd}`);
3333
+ }
3334
+ if (options.watch) {
3335
+ printInfo("Watching for changes... (Ctrl+C to stop)");
3336
+ printWarning(
3337
+ "Note: Only detects changes to .skilltree/skills/. Changes via the SkillBank API are handled automatically when materialization is enabled in config."
3338
+ );
3339
+ materializer.watch();
3340
+ await new Promise((resolve3) => {
3341
+ process.on("SIGINT", () => {
3342
+ materializer.shutdown();
3343
+ resolve3();
3344
+ });
3345
+ });
3346
+ } else {
3347
+ materializer.shutdown();
3348
+ }
3349
+ } catch (error) {
3350
+ printError(error.message);
3351
+ process.exit(1);
3352
+ }
3353
+ });
3354
+
3442
3355
  // src/cli/commands/loadout/index.ts
3443
- import { Command as Command35 } from "commander";
3356
+ import { Command as Command37 } from "commander";
3444
3357
 
3445
3358
  // src/cli/commands/loadout/list.ts
3446
- import { Command as Command23 } from "commander";
3359
+ import { Command as Command25 } from "commander";
3447
3360
 
3448
3361
  // src/serving/state-persistence.ts
3449
3362
  import * as fs9 from "fs";
3450
- import * as path6 from "path";
3363
+ import * as path7 from "path";
3451
3364
  var STATE_FILENAME = ".loadout-state.json";
3452
3365
  function loadState(skillPath) {
3453
- const filePath = path6.join(skillPath, STATE_FILENAME);
3366
+ const filePath = path7.join(skillPath, STATE_FILENAME);
3454
3367
  try {
3455
3368
  const raw = fs9.readFileSync(filePath, "utf-8");
3456
3369
  const data = JSON.parse(raw);
@@ -3466,7 +3379,7 @@ function loadState(skillPath) {
3466
3379
  }
3467
3380
  }
3468
3381
  function saveState(skillPath, state) {
3469
- const filePath = path6.join(skillPath, STATE_FILENAME);
3382
+ const filePath = path7.join(skillPath, STATE_FILENAME);
3470
3383
  const data = {
3471
3384
  available: Array.from(state.available.entries()),
3472
3385
  expanded: Array.from(state.expanded),
@@ -3477,7 +3390,7 @@ function saveState(skillPath, state) {
3477
3390
  fs9.writeFileSync(filePath, JSON.stringify(data, null, 2));
3478
3391
  }
3479
3392
  function clearState(skillPath) {
3480
- const filePath = path6.join(skillPath, STATE_FILENAME);
3393
+ const filePath = path7.join(skillPath, STATE_FILENAME);
3481
3394
  try {
3482
3395
  fs9.unlinkSync(filePath);
3483
3396
  return true;
@@ -3509,7 +3422,7 @@ async function createLoadoutServer(options) {
3509
3422
  }
3510
3423
 
3511
3424
  // src/cli/commands/loadout/list.ts
3512
- var listSubcommand = new Command23("list").description("List skills in the current loadout").option("-f, --filter <filter>", "Filter: all, expanded, available, pending", "all").action(async (options, command) => {
3425
+ var listSubcommand = new Command25("list").description("List skills in the current loadout").option("-f, --filter <filter>", "Filter: all, expanded, available, pending", "all").action(async (options, command) => {
3513
3426
  const globalOpts = command.optsWithGlobals();
3514
3427
  try {
3515
3428
  const { server } = await createLoadoutServer(globalOpts);
@@ -3553,8 +3466,8 @@ var listSubcommand = new Command23("list").description("List skills in the curre
3553
3466
  });
3554
3467
 
3555
3468
  // src/cli/commands/loadout/search.ts
3556
- import { Command as Command24 } from "commander";
3557
- var searchSubcommand = new Command24("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) => {
3469
+ import { Command as Command26 } from "commander";
3470
+ var searchSubcommand = new Command26("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) => {
3558
3471
  const globalOpts = command.optsWithGlobals();
3559
3472
  try {
3560
3473
  const { server } = await createLoadoutServer(globalOpts);
@@ -3584,8 +3497,8 @@ var searchSubcommand = new Command24("search").description("Search for skills to
3584
3497
  });
3585
3498
 
3586
3499
  // src/cli/commands/loadout/add.ts
3587
- import { Command as Command25 } from "commander";
3588
- var addSubcommand = new Command25("add").description("Add skills to the loadout").argument("<ids...>", "Skill IDs to add").action(async (ids, _options, command) => {
3500
+ import { Command as Command27 } from "commander";
3501
+ var addSubcommand = new Command27("add").description("Add skills to the loadout").argument("<ids...>", "Skill IDs to add").action(async (ids, _options, command) => {
3589
3502
  const globalOpts = command.optsWithGlobals();
3590
3503
  try {
3591
3504
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -3603,8 +3516,8 @@ var addSubcommand = new Command25("add").description("Add skills to the loadout"
3603
3516
  });
3604
3517
 
3605
3518
  // src/cli/commands/loadout/remove.ts
3606
- import { Command as Command26 } from "commander";
3607
- var removeSubcommand = new Command26("remove").description("Remove skills from the loadout").argument("<ids...>", "Skill IDs to remove").action(async (ids, _options, command) => {
3519
+ import { Command as Command28 } from "commander";
3520
+ var removeSubcommand = new Command28("remove").description("Remove skills from the loadout").argument("<ids...>", "Skill IDs to remove").action(async (ids, _options, command) => {
3608
3521
  const globalOpts = command.optsWithGlobals();
3609
3522
  try {
3610
3523
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -3622,8 +3535,8 @@ var removeSubcommand = new Command26("remove").description("Remove skills from t
3622
3535
  });
3623
3536
 
3624
3537
  // src/cli/commands/loadout/profile.ts
3625
- import { Command as Command27 } from "commander";
3626
- var profileSubcommand = new Command27("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) => {
3538
+ import { Command as Command29 } from "commander";
3539
+ var profileSubcommand = new Command29("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) => {
3627
3540
  const globalOpts = command.optsWithGlobals();
3628
3541
  try {
3629
3542
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -3654,8 +3567,8 @@ var profileSubcommand = new Command27("profile").description("Switch to a named
3654
3567
  });
3655
3568
 
3656
3569
  // src/cli/commands/loadout/set.ts
3657
- import { Command as Command28 } from "commander";
3658
- var setSubcommand = new Command28("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) => {
3570
+ import { Command as Command30 } from "commander";
3571
+ var setSubcommand = new Command30("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) => {
3659
3572
  const globalOpts = command.optsWithGlobals();
3660
3573
  try {
3661
3574
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -3684,8 +3597,8 @@ var setSubcommand = new Command28("set").description("Set loadout from criteria
3684
3597
  });
3685
3598
 
3686
3599
  // src/cli/commands/loadout/expand.ts
3687
- import { Command as Command29 } from "commander";
3688
- var expandSubcommand = new Command29("expand").description("Expand a skill to see its full content").argument("<id>", "Skill ID to expand").action(async (id, _options, command) => {
3600
+ import { Command as Command31 } from "commander";
3601
+ var expandSubcommand = new Command31("expand").description("Expand a skill to see its full content").argument("<id>", "Skill ID to expand").action(async (id, _options, command) => {
3689
3602
  const globalOpts = command.optsWithGlobals();
3690
3603
  try {
3691
3604
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -3710,8 +3623,8 @@ var expandSubcommand = new Command29("expand").description("Expand a skill to se
3710
3623
  });
3711
3624
 
3712
3625
  // src/cli/commands/loadout/collapse.ts
3713
- import { Command as Command30 } from "commander";
3714
- var collapseSubcommand = new Command30("collapse").description("Collapse an expanded skill back to summary").argument("<id>", "Skill ID to collapse").action(async (id, _options, command) => {
3626
+ import { Command as Command32 } from "commander";
3627
+ var collapseSubcommand = new Command32("collapse").description("Collapse an expanded skill back to summary").argument("<id>", "Skill ID to collapse").action(async (id, _options, command) => {
3715
3628
  const globalOpts = command.optsWithGlobals();
3716
3629
  try {
3717
3630
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -3733,8 +3646,8 @@ var collapseSubcommand = new Command30("collapse").description("Collapse an expa
3733
3646
  });
3734
3647
 
3735
3648
  // src/cli/commands/loadout/use.ts
3736
- import { Command as Command31 } from "commander";
3737
- var useSubcommand = new Command31("use").description("Mark a skill as being used (auto-expands and records usage)").argument("<id>", "Skill ID to use").action(async (id, _options, command) => {
3649
+ import { Command as Command33 } from "commander";
3650
+ var useSubcommand = new Command33("use").description("Mark a skill as being used (auto-expands and records usage)").argument("<id>", "Skill ID to use").action(async (id, _options, command) => {
3738
3651
  const globalOpts = command.optsWithGlobals();
3739
3652
  try {
3740
3653
  const { server, save } = await createLoadoutServer(globalOpts);
@@ -3760,8 +3673,8 @@ var useSubcommand = new Command31("use").description("Mark a skill as being used
3760
3673
  });
3761
3674
 
3762
3675
  // src/cli/commands/loadout/get.ts
3763
- import { Command as Command32 } from "commander";
3764
- var getSubcommand = new Command32("get").description("Get details about a skill in the loadout").argument("<id>", "Skill ID").action(async (id, _options, command) => {
3676
+ import { Command as Command34 } from "commander";
3677
+ var getSubcommand = new Command34("get").description("Get details about a skill in the loadout").argument("<id>", "Skill ID").action(async (id, _options, command) => {
3765
3678
  const globalOpts = command.optsWithGlobals();
3766
3679
  try {
3767
3680
  const { server } = await createLoadoutServer(globalOpts);
@@ -3783,8 +3696,8 @@ var getSubcommand = new Command32("get").description("Get details about a skill
3783
3696
  });
3784
3697
 
3785
3698
  // src/cli/commands/loadout/render.ts
3786
- import { Command as Command33 } from "commander";
3787
- var renderSubcommand = new Command33("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) => {
3699
+ import { Command as Command35 } from "commander";
3700
+ var renderSubcommand = new Command35("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) => {
3788
3701
  const globalOpts = command.optsWithGlobals();
3789
3702
  try {
3790
3703
  const { server } = await createLoadoutServer(globalOpts);
@@ -3802,8 +3715,8 @@ var renderSubcommand = new Command33("render").description("Render the current l
3802
3715
  });
3803
3716
 
3804
3717
  // src/cli/commands/loadout/clear.ts
3805
- import { Command as Command34 } from "commander";
3806
- var clearSubcommand = new Command34("clear").description("Clear the current loadout state").action(async (_options, command) => {
3718
+ import { Command as Command36 } from "commander";
3719
+ var clearSubcommand = new Command36("clear").description("Clear the current loadout state").action(async (_options, command) => {
3807
3720
  const globalOpts = command.optsWithGlobals();
3808
3721
  try {
3809
3722
  const skillPath = resolveSkillPath(globalOpts.path);
@@ -3824,10 +3737,10 @@ var clearSubcommand = new Command34("clear").description("Clear the current load
3824
3737
  });
3825
3738
 
3826
3739
  // src/cli/commands/loadout/index.ts
3827
- var loadoutCommand = new Command35("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);
3740
+ var loadoutCommand = new Command37("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);
3828
3741
 
3829
3742
  // src/cli/index.ts
3830
- var program = new Command36();
3743
+ var program = new Command38();
3831
3744
  var config = loadConfig();
3832
3745
  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);
3833
3746
  program.addCommand(listCommand);
@@ -3846,5 +3759,7 @@ program.addCommand(importCommand);
3846
3759
  program.addCommand(indexerCommand);
3847
3760
  program.addCommand(configCommand);
3848
3761
  program.addCommand(syncCommand2);
3762
+ program.addCommand(readCommand);
3763
+ program.addCommand(materializeCommand);
3849
3764
  program.addCommand(loadoutCommand);
3850
3765
  program.parse();