skill-tree 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cli/index.js +1827 -1410
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/cli/index.mjs +9538 -1657
  4. package/dist/cli/index.mjs.map +1 -0
  5. package/dist/index.d.mts +318 -7
  6. package/dist/index.d.ts +318 -7
  7. package/dist/index.js +1539 -92
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +9614 -70
  10. package/dist/index.mjs.map +1 -0
  11. package/package.json +3 -2
  12. package/dist/chunk-3SRB47JW.mjs +0 -8344
  13. package/dist/chunk-43YOKLZP.mjs +0 -6081
  14. package/dist/chunk-4AGZU52D.mjs +0 -7918
  15. package/dist/chunk-4OC5QFIF.mjs +0 -11267
  16. package/dist/chunk-55SMGVTP.mjs +0 -7126
  17. package/dist/chunk-6FX4IK4Z.mjs +0 -5368
  18. package/dist/chunk-7EGDKOHV.mjs +0 -9439
  19. package/dist/chunk-7LMOQW5H.mjs +0 -4893
  20. package/dist/chunk-7QIQJVNP.mjs +0 -14206
  21. package/dist/chunk-7VB4ZRZO.mjs +0 -7127
  22. package/dist/chunk-BPVRW25O.mjs +0 -6089
  23. package/dist/chunk-CI4476KM.mjs +0 -6607
  24. package/dist/chunk-DDXYQ74I.mjs +0 -13969
  25. package/dist/chunk-DQOFJXBX.mjs +0 -6595
  26. package/dist/chunk-E2CVK23F.mjs +0 -8751
  27. package/dist/chunk-F3YEUQAP.mjs +0 -654
  28. package/dist/chunk-FKJJ4RJG.mjs +0 -13874
  29. package/dist/chunk-II7DECZQ.mjs +0 -9111
  30. package/dist/chunk-INKVOZXK.mjs +0 -15898
  31. package/dist/chunk-K6NRCSAZ.mjs +0 -4355
  32. package/dist/chunk-OYHYXKXO.mjs +0 -7297
  33. package/dist/chunk-PDPN7FW7.mjs +0 -1045
  34. package/dist/chunk-TEUB6DZR.mjs +0 -6453
  35. package/dist/chunk-TWPEHDW4.mjs +0 -1067
  36. package/dist/chunk-Y54UK2J3.mjs +0 -13071
  37. package/dist/chunk-ZQVS7MQK.mjs +0 -6081
  38. package/dist/sqlite-OLU72GHB.mjs +0 -6
  39. package/dist/sqlite-XJRPMNAJ.mjs +0 -6
  40. package/dist/sync-BSWMMDA6.mjs +0 -14
package/dist/index.js CHANGED
@@ -853,15 +853,15 @@ var init_sqlite = __esm({
853
853
  /**
854
854
  * Get or create a taxonomy node
855
855
  */
856
- async ensureTaxonomyNode(path11) {
856
+ async ensureTaxonomyNode(path13) {
857
857
  this.ensureInitialized();
858
858
  const db = this.getDb();
859
- const pathStr = path11.join("/");
859
+ const pathStr = path13.join("/");
860
860
  const existing = db.prepare("SELECT id FROM taxonomy_nodes WHERE path = ?").get(pathStr);
861
861
  if (existing) return existing.id;
862
862
  const id = `node-${pathStr.replace(/\//g, "-").toLowerCase()}`;
863
- const name = path11[path11.length - 1] || "Root";
864
- const parentPath = path11.slice(0, -1);
863
+ const name = path13[path13.length - 1] || "Root";
864
+ const parentPath = path13.slice(0, -1);
865
865
  let parentId = null;
866
866
  if (parentPath.length > 0) {
867
867
  parentId = await this.ensureTaxonomyNode(parentPath);
@@ -1789,11 +1789,13 @@ __export(index_exports, {
1789
1789
  AgentsParser: () => AgentsParser,
1790
1790
  AgentsSync: () => AgentsSync,
1791
1791
  CachedStorageAdapter: () => CachedStorageAdapter,
1792
+ CatalogRenderer: () => CatalogRenderer,
1792
1793
  ConflictStore: () => ConflictStore,
1793
1794
  DEFAULT_AGENTS_CONFIG: () => DEFAULT_AGENTS_CONFIG,
1794
1795
  FederationManager: () => FederationManager,
1795
1796
  GitSyncAdapter: () => GitSyncAdapter,
1796
1797
  HookRegistry: () => HookRegistry,
1798
+ IndexerService: () => IndexerService,
1797
1799
  LineageTracker: () => LineageTracker,
1798
1800
  LoadoutCompiler: () => LoadoutCompiler,
1799
1801
  Materializer: () => Materializer,
@@ -2934,8 +2936,214 @@ function createSyncManager(options) {
2934
2936
  return new SyncManager(options);
2935
2937
  }
2936
2938
 
2937
- // src/serving/loadout-compiler.ts
2939
+ // src/serving/xml-utils.ts
2940
+ function escapeXml(text) {
2941
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2942
+ }
2943
+ function getSkillSummary(skill, maxLength = 150) {
2944
+ if (skill.serving?.summary) {
2945
+ return skill.serving.summary;
2946
+ }
2947
+ const firstSentence = skill.description.split(/[.!?]/)[0];
2948
+ if (firstSentence.length <= maxLength) {
2949
+ return firstSentence;
2950
+ }
2951
+ return skill.description.substring(0, maxLength - 3) + "...";
2952
+ }
2953
+
2954
+ // src/serving/catalog-renderer.ts
2955
+ function hasCatalogSupport(storage) {
2956
+ const s = storage;
2957
+ return typeof s.getTaxonomyTree === "function" && typeof s.getSkillsInTaxonomyNode === "function";
2958
+ }
2959
+ var UNTAGGED_CATEGORY = "other";
2960
+ var CATALOG_USAGE_LINES = [
2961
+ " Use skill_browse to explore categories.",
2962
+ " Use skill_search to find by keyword.",
2963
+ " Use skill_expand to load a skill into your context."
2964
+ ];
2938
2965
  var DEFAULT_CONFIG = {
2966
+ maxCategoriesPerLevel: 12,
2967
+ maxSkillsAtLeaf: 8,
2968
+ maxSummaryLength: 80,
2969
+ format: "xml"
2970
+ };
2971
+ var CatalogRenderer = class _CatalogRenderer {
2972
+ constructor(storage, config) {
2973
+ this.storage = storage;
2974
+ this.overviewCache = null;
2975
+ this.config = { ...DEFAULT_CONFIG, ...config };
2976
+ }
2977
+ static {
2978
+ this.CACHE_TTL_MS = 6e4;
2979
+ }
2980
+ /**
2981
+ * Render level-0 catalog overview for system prompt injection.
2982
+ * Shows top-level categories with counts. ~200 tokens.
2983
+ * Returns empty string if no skills exist.
2984
+ * Results are cached for 60 seconds to avoid repeated storage queries.
2985
+ */
2986
+ async renderOverview() {
2987
+ if (this.overviewCache && Date.now() - this.overviewCache.timestamp < _CatalogRenderer.CACHE_TTL_MS) {
2988
+ return this.overviewCache.value;
2989
+ }
2990
+ let result;
2991
+ if (hasCatalogSupport(this.storage)) {
2992
+ const tree = await this.storage.getTaxonomyTree();
2993
+ if (tree.length > 0) {
2994
+ result = this.renderOverviewFromNodes(tree);
2995
+ this.overviewCache = { value: result, timestamp: Date.now() };
2996
+ return result;
2997
+ }
2998
+ }
2999
+ result = await this.renderOverviewFromTags();
3000
+ this.overviewCache = { value: result, timestamp: Date.now() };
3001
+ return result;
3002
+ }
3003
+ /**
3004
+ * Render a specific category path for browse drill-down.
3005
+ * Shows subcategories at intermediate nodes, or skill summaries at leaf nodes.
3006
+ */
3007
+ async renderCategory(path13) {
3008
+ if (hasCatalogSupport(this.storage)) {
3009
+ return this.renderCategoryFromTaxonomy(this.storage, path13);
3010
+ }
3011
+ return this.renderCategoryFromTags(path13);
3012
+ }
3013
+ /**
3014
+ * Invalidate the overview cache (e.g., after skill changes).
3015
+ */
3016
+ invalidateCache() {
3017
+ this.overviewCache = null;
3018
+ }
3019
+ // ===========================================================================
3020
+ // OVERVIEW RENDERING (shared structure)
3021
+ // ===========================================================================
3022
+ /**
3023
+ * Render the overview XML shell with a list of categories.
3024
+ * Used by both taxonomy-based and tag-based paths.
3025
+ */
3026
+ renderOverviewXml(totalSkills, categories) {
3027
+ const lines = [];
3028
+ lines.push(`<skill_catalog total="${totalSkills}">`);
3029
+ lines.push(" <usage>");
3030
+ lines.push(...CATALOG_USAGE_LINES);
3031
+ lines.push(" </usage>");
3032
+ lines.push(" <categories>");
3033
+ for (const cat of categories) {
3034
+ lines.push(` <category path="${escapeXml(cat.name)}" count="${cat.count}" />`);
3035
+ }
3036
+ lines.push(" </categories>");
3037
+ lines.push("</skill_catalog>");
3038
+ return lines.join("\n");
3039
+ }
3040
+ // ===========================================================================
3041
+ // TAXONOMY-BASED RENDERING
3042
+ // ===========================================================================
3043
+ renderOverviewFromNodes(roots) {
3044
+ const counted = this.withCounts(roots);
3045
+ const totalSkills = counted.reduce((sum, c) => sum + c.count, 0);
3046
+ const categories = counted.sort((a, b) => b.count - a.count).slice(0, this.config.maxCategoriesPerLevel).map((c) => ({ name: c.node.name, count: c.count }));
3047
+ return this.renderOverviewXml(totalSkills, categories);
3048
+ }
3049
+ async renderCategoryFromTaxonomy(storage, path13) {
3050
+ const tree = await storage.getTaxonomyTree(path13);
3051
+ const pathStr = path13.join("/");
3052
+ if (tree.length > 0 && tree.some((n) => n.children.length > 0)) {
3053
+ const root = tree[0];
3054
+ const rootCount = this.countNodeSkills(root);
3055
+ const counted = this.withCounts(root.children);
3056
+ const children = counted.sort((a, b) => b.count - a.count).slice(0, this.config.maxCategoriesPerLevel);
3057
+ const lines = [];
3058
+ lines.push(`<catalog_browse path="${escapeXml(pathStr)}" count="${rootCount}">`);
3059
+ lines.push(" <subcategories>");
3060
+ for (const { node: child, count } of children) {
3061
+ const childPath = [...path13, child.name].join("/");
3062
+ lines.push(` <category path="${escapeXml(childPath)}" count="${count}" />`);
3063
+ }
3064
+ lines.push(" </subcategories>");
3065
+ lines.push("</catalog_browse>");
3066
+ return lines.join("\n");
3067
+ }
3068
+ if (tree.length > 0) {
3069
+ const nodeId = tree[0].id;
3070
+ const skills = await storage.getSkillsInTaxonomyNode(nodeId);
3071
+ return this.renderLeafSkills(skills, pathStr, tree[0].skillCount);
3072
+ }
3073
+ return `<catalog_browse path="${escapeXml(pathStr)}" count="0" />`;
3074
+ }
3075
+ // ===========================================================================
3076
+ // TAG-BASED FALLBACK
3077
+ // ===========================================================================
3078
+ async renderOverviewFromTags() {
3079
+ const skills = await this.storage.listSkills({ status: ["active"] });
3080
+ if (skills.length === 0) return "";
3081
+ const tagCounts = this.groupByPrimaryTag(skills);
3082
+ const categories = Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, this.config.maxCategoriesPerLevel).map(([name, count]) => ({ name, count }));
3083
+ return this.renderOverviewXml(skills.length, categories);
3084
+ }
3085
+ async renderCategoryFromTags(path13) {
3086
+ if (path13.length === 0) {
3087
+ return this.renderOverviewFromTags();
3088
+ }
3089
+ const tag = path13[0];
3090
+ const matching = await this.storage.listSkills({ status: ["active"], tags: [tag] });
3091
+ const pathStr = path13.join("/");
3092
+ return this.renderLeafSkills(matching, pathStr, matching.length);
3093
+ }
3094
+ // ===========================================================================
3095
+ // SHARED RENDERING HELPERS
3096
+ // ===========================================================================
3097
+ renderLeafSkills(skills, pathStr, totalCount) {
3098
+ const displayed = skills.slice(0, this.config.maxSkillsAtLeaf);
3099
+ const remaining = totalCount - displayed.length;
3100
+ const lines = [];
3101
+ lines.push(`<catalog_browse path="${escapeXml(pathStr)}" count="${totalCount}">`);
3102
+ lines.push(" <skills>");
3103
+ for (const skill of displayed) {
3104
+ const tags = skill.tags.length > 0 ? ` tags="${escapeXml(skill.tags.join(", "))}"` : "";
3105
+ const desc = getSkillSummary(skill, this.config.maxSummaryLength);
3106
+ lines.push(` <skill id="${escapeXml(skill.id)}"${tags}>`);
3107
+ lines.push(` ${escapeXml(desc)}`);
3108
+ lines.push(" </skill>");
3109
+ }
3110
+ lines.push(" </skills>");
3111
+ if (remaining > 0) {
3112
+ lines.push(` <more remaining="${remaining}">`);
3113
+ lines.push(" Use skill_search for more, or skill_expand {id} to load.");
3114
+ lines.push(" </more>");
3115
+ }
3116
+ lines.push("</catalog_browse>");
3117
+ return lines.join("\n");
3118
+ }
3119
+ // ===========================================================================
3120
+ // UTILITIES
3121
+ // ===========================================================================
3122
+ groupByPrimaryTag(skills) {
3123
+ const tagCounts = /* @__PURE__ */ new Map();
3124
+ for (const skill of skills) {
3125
+ const tag = skill.tags[0] || UNTAGGED_CATEGORY;
3126
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
3127
+ }
3128
+ return tagCounts;
3129
+ }
3130
+ /**
3131
+ * Pre-compute counts for a list of nodes, avoiding redundant recursive walks.
3132
+ */
3133
+ withCounts(nodes) {
3134
+ return nodes.map((node) => ({ node, count: this.countNodeSkills(node) }));
3135
+ }
3136
+ countNodeSkills(node) {
3137
+ let count = node.skillCount;
3138
+ for (const child of node.children) {
3139
+ count += this.countNodeSkills(child);
3140
+ }
3141
+ return count;
3142
+ }
3143
+ };
3144
+
3145
+ // src/serving/loadout-compiler.ts
3146
+ var DEFAULT_CONFIG2 = {
2939
3147
  defaultMaxSkills: 15,
2940
3148
  defaultStatus: ["active"],
2941
3149
  semanticThreshold: 0.6
@@ -2944,7 +3152,7 @@ var LoadoutCompiler = class {
2944
3152
  constructor(storage, config) {
2945
3153
  this.storage = storage;
2946
3154
  this.config = {
2947
- ...DEFAULT_CONFIG,
3155
+ ...DEFAULT_CONFIG2,
2948
3156
  ...config
2949
3157
  };
2950
3158
  }
@@ -3165,11 +3373,76 @@ var LoadoutCompiler = class {
3165
3373
  // src/serving/project-detector.ts
3166
3374
  var import_fs = require("fs");
3167
3375
  var import_path = require("path");
3168
- var _ProjectDetector = class _ProjectDetector {
3376
+ var ProjectDetector = class _ProjectDetector {
3169
3377
  constructor() {
3170
3378
  /** Cache for project context */
3171
3379
  this.cache = /* @__PURE__ */ new Map();
3172
3380
  }
3381
+ static {
3382
+ /** Project type patterns */
3383
+ this.PROJECT_TYPES = [
3384
+ { manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
3385
+ { manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
3386
+ { manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
3387
+ { manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
3388
+ { manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
3389
+ { manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
3390
+ { manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
3391
+ { manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
3392
+ ];
3393
+ }
3394
+ static {
3395
+ /** TypeScript detection */
3396
+ this.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
3397
+ }
3398
+ static {
3399
+ /** Node.js framework patterns */
3400
+ this.NODE_FRAMEWORKS = [
3401
+ { name: "react", packageName: "react", tags: ["react", "frontend"] },
3402
+ { name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
3403
+ { name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
3404
+ { name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
3405
+ { name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
3406
+ { name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
3407
+ { name: "express", packageName: "express", tags: ["express", "backend", "api"] },
3408
+ { name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
3409
+ { name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
3410
+ { name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
3411
+ { name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
3412
+ { name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
3413
+ { name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
3414
+ { name: "jest", packageName: "jest", tags: ["testing", "jest"] },
3415
+ { name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
3416
+ { name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
3417
+ { name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
3418
+ ];
3419
+ }
3420
+ static {
3421
+ /** Python framework patterns (from pyproject.toml or requirements.txt) */
3422
+ this.PYTHON_FRAMEWORKS = [
3423
+ { name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
3424
+ { name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
3425
+ { name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
3426
+ { name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
3427
+ { name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
3428
+ { name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
3429
+ ];
3430
+ }
3431
+ static {
3432
+ /** Directory patterns */
3433
+ this.DIRECTORY_PATTERNS = [
3434
+ { pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
3435
+ { pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
3436
+ { pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
3437
+ { pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
3438
+ { pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
3439
+ { pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
3440
+ { pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3441
+ { pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3442
+ { pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
3443
+ { pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
3444
+ ];
3445
+ }
3173
3446
  /**
3174
3447
  * Detect project context from a directory
3175
3448
  */
@@ -3331,71 +3604,15 @@ var _ProjectDetector = class _ProjectDetector {
3331
3604
  }
3332
3605
  }
3333
3606
  };
3334
- /** Project type patterns */
3335
- _ProjectDetector.PROJECT_TYPES = [
3336
- { manifestFile: "package.json", type: "nodejs", tags: ["nodejs", "javascript"], packageManager: "npm" },
3337
- { manifestFile: "pyproject.toml", type: "python", tags: ["python"], packageManager: "pip" },
3338
- { manifestFile: "requirements.txt", type: "python", tags: ["python"], packageManager: "pip" },
3339
- { manifestFile: "Cargo.toml", type: "rust", tags: ["rust"], packageManager: "cargo" },
3340
- { manifestFile: "go.mod", type: "go", tags: ["go", "golang"] },
3341
- { manifestFile: "pom.xml", type: "java", tags: ["java", "maven"], packageManager: "maven" },
3342
- { manifestFile: "build.gradle", type: "java", tags: ["java", "gradle"], packageManager: "gradle" },
3343
- { manifestFile: "build.gradle.kts", type: "kotlin", tags: ["kotlin", "gradle"], packageManager: "gradle" }
3344
- ];
3345
- /** TypeScript detection */
3346
- _ProjectDetector.TYPESCRIPT_FILES = ["tsconfig.json", "tsconfig.base.json"];
3347
- /** Node.js framework patterns */
3348
- _ProjectDetector.NODE_FRAMEWORKS = [
3349
- { name: "react", packageName: "react", tags: ["react", "frontend"] },
3350
- { name: "next", packageName: "next", tags: ["nextjs", "react", "fullstack"] },
3351
- { name: "vue", packageName: "vue", tags: ["vue", "frontend"] },
3352
- { name: "nuxt", packageName: "nuxt", tags: ["nuxt", "vue", "fullstack"] },
3353
- { name: "angular", packageName: "@angular/core", tags: ["angular", "frontend"] },
3354
- { name: "svelte", packageName: "svelte", tags: ["svelte", "frontend"] },
3355
- { name: "express", packageName: "express", tags: ["express", "backend", "api"] },
3356
- { name: "fastify", packageName: "fastify", tags: ["fastify", "backend", "api"] },
3357
- { name: "nestjs", packageName: "@nestjs/core", tags: ["nestjs", "backend", "api"] },
3358
- { name: "hono", packageName: "hono", tags: ["hono", "backend", "api"] },
3359
- { name: "prisma", packageName: "@prisma/client", tags: ["prisma", "database", "orm"] },
3360
- { name: "drizzle", packageName: "drizzle-orm", tags: ["drizzle", "database", "orm"] },
3361
- { name: "typeorm", packageName: "typeorm", tags: ["typeorm", "database", "orm"] },
3362
- { name: "jest", packageName: "jest", tags: ["testing", "jest"] },
3363
- { name: "vitest", packageName: "vitest", tags: ["testing", "vitest"] },
3364
- { name: "playwright", packageName: "@playwright/test", tags: ["testing", "e2e", "playwright"] },
3365
- { name: "cypress", packageName: "cypress", tags: ["testing", "e2e", "cypress"] }
3366
- ];
3367
- /** Python framework patterns (from pyproject.toml or requirements.txt) */
3368
- _ProjectDetector.PYTHON_FRAMEWORKS = [
3369
- { name: "fastapi", packageName: "fastapi", tags: ["fastapi", "backend", "api"] },
3370
- { name: "django", packageName: "django", tags: ["django", "backend", "fullstack"] },
3371
- { name: "flask", packageName: "flask", tags: ["flask", "backend", "api"] },
3372
- { name: "sqlalchemy", packageName: "sqlalchemy", tags: ["sqlalchemy", "database", "orm"] },
3373
- { name: "pytest", packageName: "pytest", tags: ["testing", "pytest"] },
3374
- { name: "pydantic", packageName: "pydantic", tags: ["pydantic", "validation"] }
3375
- ];
3376
- /** Directory patterns */
3377
- _ProjectDetector.DIRECTORY_PATTERNS = [
3378
- { pattern: ".github/workflows", feature: "github-actions", tags: ["ci", "github-actions"] },
3379
- { pattern: ".gitlab-ci.yml", feature: "gitlab-ci", tags: ["ci", "gitlab"] },
3380
- { pattern: "Dockerfile", feature: "docker", tags: ["docker", "containers"] },
3381
- { pattern: "docker-compose.yml", feature: "docker-compose", tags: ["docker", "containers"] },
3382
- { pattern: "docker-compose.yaml", feature: "docker-compose", tags: ["docker", "containers"] },
3383
- { pattern: "terraform", feature: "terraform", tags: ["terraform", "infrastructure"] },
3384
- { pattern: "kubernetes", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3385
- { pattern: "k8s", feature: "kubernetes", tags: ["kubernetes", "infrastructure"] },
3386
- { pattern: ".env.example", feature: "env-config", tags: ["configuration"] },
3387
- { pattern: "prisma/schema.prisma", feature: "prisma", tags: ["prisma", "database"] }
3388
- ];
3389
- var ProjectDetector = _ProjectDetector;
3390
3607
 
3391
3608
  // src/serving/view-renderer.ts
3392
- var DEFAULT_CONFIG2 = {
3609
+ var DEFAULT_CONFIG3 = {
3393
3610
  includeTokenEstimates: false,
3394
3611
  maxSummaryLength: 150
3395
3612
  };
3396
3613
  var ViewRenderer = class {
3397
3614
  constructor(config) {
3398
- this.config = { ...DEFAULT_CONFIG2, ...config };
3615
+ this.config = { ...DEFAULT_CONFIG3, ...config };
3399
3616
  }
3400
3617
  /**
3401
3618
  * Render loadout state as OpenSkills-compatible XML
@@ -3422,6 +3639,18 @@ var ViewRenderer = class {
3422
3639
  lines.push(" <content>");
3423
3640
  lines.push(skill.instructions.split("\n").map((line) => " " + line).join("\n"));
3424
3641
  lines.push(" </content>");
3642
+ if (skill.relationships && skill.relationships.length > 0) {
3643
+ const hints = skill.relationships.filter((r) => r.confidence >= 0.5).sort((a, b) => b.confidence - a.confidence).slice(0, 5);
3644
+ if (hints.length > 0) {
3645
+ lines.push(" <related>");
3646
+ for (const rel of hints) {
3647
+ const relatedSkill = state.available.get(rel.targetSkillId);
3648
+ const desc = relatedSkill ? this.getSummary(relatedSkill) : rel.targetSkillId;
3649
+ lines.push(` <see_also id="${this.escapeXml(rel.targetSkillId)}" type="${rel.type}">${this.escapeXml(desc)}</see_also>`);
3650
+ }
3651
+ lines.push(" </related>");
3652
+ }
3653
+ }
3425
3654
  lines.push("</skill>");
3426
3655
  } else {
3427
3656
  const summary = this.getSummary(skill);
@@ -3534,23 +3763,18 @@ var ViewRenderer = class {
3534
3763
  // Private helpers
3535
3764
  // ===========================================================================
3536
3765
  /**
3537
- * Get summary for a skill (short description)
3766
+ * Get summary for a skill (short description).
3767
+ * Delegates to shared getSkillSummary utility.
3538
3768
  */
3539
3769
  getSummary(skill) {
3540
- if (skill.serving?.summary) {
3541
- return skill.serving.summary;
3542
- }
3543
- const firstSentence = skill.description.split(/[.!?]/)[0];
3544
- if (firstSentence.length <= this.config.maxSummaryLength) {
3545
- return firstSentence;
3546
- }
3547
- return skill.description.substring(0, this.config.maxSummaryLength - 3) + "...";
3770
+ return getSkillSummary(skill, this.config.maxSummaryLength);
3548
3771
  }
3549
3772
  /**
3550
- * Escape special XML characters
3773
+ * Escape special XML characters.
3774
+ * Delegates to shared escapeXml utility.
3551
3775
  */
3552
3776
  escapeXml(str) {
3553
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3777
+ return escapeXml(str);
3554
3778
  }
3555
3779
  /**
3556
3780
  * Estimate tokens for a full skill
@@ -3646,7 +3870,7 @@ function listBuiltInProfiles() {
3646
3870
  }
3647
3871
 
3648
3872
  // src/serving/graph-server.ts
3649
- var DEFAULT_CONFIG3 = {
3873
+ var DEFAULT_CONFIG4 = {
3650
3874
  agentCanModify: true,
3651
3875
  agentCanSetLoadout: false,
3652
3876
  agentCanSwitchProfile: true,
@@ -3658,24 +3882,29 @@ var DEFAULT_CONFIG3 = {
3658
3882
  persistState: false,
3659
3883
  outputFormat: "xml",
3660
3884
  includeTokenEstimates: false,
3885
+ enableCatalog: true,
3661
3886
  profiles: {}
3662
3887
  };
3663
3888
  var SkillGraphServer = class {
3664
3889
  // Track LRU for eviction
3665
3890
  constructor(storage, config) {
3666
3891
  this.storage = storage;
3892
+ this.catalogRenderer = null;
3667
3893
  this.handlers = /* @__PURE__ */ new Set();
3668
3894
  this.lruOrder = [];
3669
3895
  this.config = {
3670
- ...DEFAULT_CONFIG3,
3896
+ ...DEFAULT_CONFIG4,
3671
3897
  ...config,
3672
- profiles: { ...builtInProfiles, ...DEFAULT_CONFIG3.profiles, ...config?.profiles }
3898
+ profiles: { ...builtInProfiles, ...DEFAULT_CONFIG4.profiles, ...config?.profiles }
3673
3899
  };
3674
3900
  this.compiler = new LoadoutCompiler(storage);
3675
3901
  this.projectDetector = new ProjectDetector();
3676
3902
  this.viewRenderer = new ViewRenderer({
3677
3903
  includeTokenEstimates: this.config.includeTokenEstimates
3678
3904
  });
3905
+ if (this.config.enableCatalog !== false) {
3906
+ this.catalogRenderer = new CatalogRenderer(storage, config?.catalogConfig);
3907
+ }
3679
3908
  this.state = {
3680
3909
  available: /* @__PURE__ */ new Map(),
3681
3910
  expanded: /* @__PURE__ */ new Set(),
@@ -3964,17 +4193,70 @@ var SkillGraphServer = class {
3964
4193
  profiles: this.getProfiles()
3965
4194
  };
3966
4195
  }
4196
+ /**
4197
+ * Agent browses the skill catalog at a given path.
4198
+ * Returns rendered category view (subcategories or skill summaries at leaf).
4199
+ * Pass no path for the top-level overview.
4200
+ */
4201
+ async agentBrowseCatalog(path13) {
4202
+ if (!this.catalogRenderer) {
4203
+ return "<error>Catalog browsing is not enabled</error>";
4204
+ }
4205
+ if (!path13 || path13.length === 0) {
4206
+ const result2 = await this.catalogRenderer.renderOverview();
4207
+ if (result2) {
4208
+ this.emit({ type: "catalog:browsed", path: [] });
4209
+ }
4210
+ return result2;
4211
+ }
4212
+ const result = await this.catalogRenderer.renderCategory(path13);
4213
+ this.emit({ type: "catalog:browsed", path: path13 });
4214
+ return result;
4215
+ }
4216
+ /**
4217
+ * Agent adds a skill discovered via browsing directly to loadout and expands it.
4218
+ * Bridges browse → loadout: found it in catalog, now load it.
4219
+ */
4220
+ async agentAddFromCatalog(skillId) {
4221
+ if (!this.config.agentCanModify) {
4222
+ return { added: false, pending: false, expanded: false };
4223
+ }
4224
+ if (this.state.available.has(skillId)) {
4225
+ const expanded = this.expandSkill(skillId);
4226
+ return { added: false, pending: false, expanded };
4227
+ }
4228
+ const result = await this.agentRequestSkills([skillId]);
4229
+ if (result.added.length > 0) {
4230
+ const expanded = this.expandSkill(skillId);
4231
+ this.emit({ type: "catalog:added", skillId });
4232
+ return { added: true, pending: false, expanded };
4233
+ }
4234
+ if (result.pending.length > 0) {
4235
+ return { added: false, pending: true, expanded: false };
4236
+ }
4237
+ return { added: false, pending: false, expanded: false };
4238
+ }
3967
4239
  // ===========================================================================
3968
4240
  // RENDERING
3969
4241
  // ===========================================================================
3970
4242
  /**
3971
- * Render current state as system prompt content
4243
+ * Render current state as system prompt content.
4244
+ * Includes catalog overview when catalog is enabled.
3972
4245
  */
3973
- renderSystemPrompt() {
4246
+ async renderSystemPrompt() {
4247
+ let prompt;
3974
4248
  if (this.config.outputFormat === "markdown") {
3975
- return this.viewRenderer.renderMarkdown(this.state);
4249
+ prompt = this.viewRenderer.renderMarkdown(this.state);
4250
+ } else {
4251
+ prompt = this.viewRenderer.renderXml(this.state);
3976
4252
  }
3977
- return this.viewRenderer.renderXml(this.state);
4253
+ if (this.catalogRenderer) {
4254
+ const overview = await this.catalogRenderer.renderOverview();
4255
+ if (overview) {
4256
+ prompt += "\n" + overview;
4257
+ }
4258
+ }
4259
+ return prompt;
3978
4260
  }
3979
4261
  /**
3980
4262
  * Estimate total tokens for current loadout
@@ -4345,7 +4627,7 @@ function resolveSkilltreeDir(repoRoot) {
4345
4627
  if (fs4.existsSync(swarmDir)) return swarmDir;
4346
4628
  return path4.join(repoRoot, ".skilltree");
4347
4629
  }
4348
- var DEFAULT_CONFIG4 = {
4630
+ var DEFAULT_CONFIG5 = {
4349
4631
  version: 1,
4350
4632
  discovery: "default",
4351
4633
  paths: ["skills"],
@@ -4373,7 +4655,7 @@ function getSkilltreeDir(repoRoot) {
4373
4655
  }
4374
4656
  function getSkillsDir(repoRoot, config) {
4375
4657
  const skilltreeDir = getSkilltreeDir(repoRoot);
4376
- const paths = config?.paths || DEFAULT_CONFIG4.paths;
4658
+ const paths = config?.paths || DEFAULT_CONFIG5.paths;
4377
4659
  return path4.join(skilltreeDir, paths[0]);
4378
4660
  }
4379
4661
  function parseSkilltreeConfig(content) {
@@ -4386,10 +4668,10 @@ function validateConfig(config) {
4386
4668
  }
4387
4669
  return {
4388
4670
  version: 1,
4389
- discovery: config.discovery || DEFAULT_CONFIG4.discovery,
4390
- paths: config.paths || DEFAULT_CONFIG4.paths,
4391
- exclude: [...DEFAULT_CONFIG4.exclude || [], ...config.exclude || []],
4392
- skillFilePatterns: config.skillFilePatterns || DEFAULT_CONFIG4.skillFilePatterns,
4671
+ discovery: config.discovery || DEFAULT_CONFIG5.discovery,
4672
+ paths: config.paths || DEFAULT_CONFIG5.paths,
4673
+ exclude: [...DEFAULT_CONFIG5.exclude || [], ...config.exclude || []],
4674
+ skillFilePatterns: config.skillFilePatterns || DEFAULT_CONFIG5.skillFilePatterns,
4393
4675
  namespace: config.namespace
4394
4676
  };
4395
4677
  }
@@ -4400,7 +4682,7 @@ async function loadSkilltreeConfig(repoRoot) {
4400
4682
  return parseSkilltreeConfig(content);
4401
4683
  } catch (error) {
4402
4684
  if (error.code === "ENOENT") {
4403
- return { ...DEFAULT_CONFIG4 };
4685
+ return { ...DEFAULT_CONFIG5 };
4404
4686
  }
4405
4687
  throw error;
4406
4688
  }
@@ -4435,7 +4717,7 @@ async function initSkilltreeDir(repoRoot, config) {
4435
4717
  );
4436
4718
  }
4437
4719
  }
4438
- function isSkillFile(filename, patterns = DEFAULT_CONFIG4.skillFilePatterns) {
4720
+ function isSkillFile(filename, patterns = DEFAULT_CONFIG5.skillFilePatterns) {
4439
4721
  const lower = filename.toLowerCase();
4440
4722
  for (const pattern of patterns) {
4441
4723
  if (pattern.includes("*")) {
@@ -4468,7 +4750,7 @@ async function discoverSkills(repoRoot) {
4468
4750
  if (config.discovery === "scan") {
4469
4751
  await scanForSkills(skilltreeDir, skilltreeDir, config, discovered);
4470
4752
  } else {
4471
- const paths = config.paths || DEFAULT_CONFIG4.paths;
4753
+ const paths = config.paths || DEFAULT_CONFIG5.paths;
4472
4754
  for (const searchPath of paths) {
4473
4755
  const fullPath = path4.join(skilltreeDir, searchPath);
4474
4756
  await scanForSkills(skilltreeDir, fullPath, config, discovered);
@@ -8257,6 +8539,1168 @@ function createDefaultSyncConfig(remoteUrl, agentId, options) {
8257
8539
  };
8258
8540
  }
8259
8541
 
8542
+ // src/services/indexer.ts
8543
+ var path12 = __toESM(require("path"));
8544
+ var fs12 = __toESM(require("fs"));
8545
+
8546
+ // src/config/types.ts
8547
+ var DEFAULT_CONFIG6 = {
8548
+ storage: {
8549
+ path: "~/.skill-tree"
8550
+ },
8551
+ indexer: {
8552
+ sources: [],
8553
+ batch_size: 10,
8554
+ min_confidence: 0.7
8555
+ },
8556
+ matching: {
8557
+ embedding_model: "text-embedding-3-small",
8558
+ similarity_threshold: 0.8
8559
+ },
8560
+ sync: {
8561
+ auto_check: false,
8562
+ check_interval_hours: 24,
8563
+ conflict_resolution: "prompt"
8564
+ },
8565
+ cli: {
8566
+ output_format: "table",
8567
+ color: true,
8568
+ quiet: false
8569
+ },
8570
+ materialization: {
8571
+ enabled: false,
8572
+ mode: "symlink",
8573
+ symlink_paths: [],
8574
+ agents_md_path: "",
8575
+ agents_md_format: "xml",
8576
+ debounce_ms: 500
8577
+ }
8578
+ };
8579
+
8580
+ // src/config/loader.ts
8581
+ var fs11 = __toESM(require("fs"));
8582
+ var path11 = __toESM(require("path"));
8583
+ var os = __toESM(require("os"));
8584
+ var ENV_MAPPINGS = {
8585
+ GITHUB_TOKEN: "indexer.github_token",
8586
+ ANTHROPIC_API_KEY: "indexer.anthropic_key",
8587
+ SKILL_TREE_STORAGE_PATH: "storage.path",
8588
+ SKILL_TREE_OUTPUT_FORMAT: "cli.output_format",
8589
+ SKILL_TREE_QUIET: "cli.quiet",
8590
+ SKILL_TREE_NO_COLOR: "cli.color"
8591
+ };
8592
+ function getConfigDir() {
8593
+ return path11.join(os.homedir(), ".skill-tree");
8594
+ }
8595
+ function getConfigPath() {
8596
+ return path11.join(getConfigDir(), "config.yaml");
8597
+ }
8598
+ function expandPath(filePath) {
8599
+ if (filePath.startsWith("~/")) {
8600
+ return path11.join(os.homedir(), filePath.slice(2));
8601
+ }
8602
+ if (filePath.startsWith("~")) {
8603
+ return path11.join(os.homedir(), filePath.slice(1));
8604
+ }
8605
+ return filePath;
8606
+ }
8607
+ function substituteEnvVars(value) {
8608
+ let result = value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
8609
+ return process.env[varName] || "";
8610
+ });
8611
+ result = result.replace(/\$([A-Z_][A-Z0-9_]*)/gi, (_, varName) => {
8612
+ return process.env[varName] || "";
8613
+ });
8614
+ return result;
8615
+ }
8616
+ function substituteEnvVarsInObject(obj) {
8617
+ if (typeof obj === "string") {
8618
+ return substituteEnvVars(obj);
8619
+ }
8620
+ if (Array.isArray(obj)) {
8621
+ return obj.map((item) => substituteEnvVarsInObject(item));
8622
+ }
8623
+ if (obj !== null && typeof obj === "object") {
8624
+ const result = {};
8625
+ for (const [key, value] of Object.entries(obj)) {
8626
+ result[key] = substituteEnvVarsInObject(value);
8627
+ }
8628
+ return result;
8629
+ }
8630
+ return obj;
8631
+ }
8632
+ function setNestedProperty(obj, path13, value) {
8633
+ const parts = path13.split(".");
8634
+ let current = obj;
8635
+ for (let i = 0; i < parts.length - 1; i++) {
8636
+ const part = parts[i];
8637
+ if (!(part in current)) {
8638
+ current[part] = {};
8639
+ }
8640
+ current = current[part];
8641
+ }
8642
+ current[parts[parts.length - 1]] = value;
8643
+ }
8644
+ function deepMerge(target, source) {
8645
+ const result = { ...target };
8646
+ for (const key of Object.keys(source)) {
8647
+ const sourceValue = source[key];
8648
+ const targetValue = result[key];
8649
+ if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
8650
+ result[key] = deepMerge(
8651
+ targetValue,
8652
+ sourceValue
8653
+ );
8654
+ } else if (sourceValue !== void 0) {
8655
+ result[key] = sourceValue;
8656
+ }
8657
+ }
8658
+ return result;
8659
+ }
8660
+ function parseSimpleYaml(content) {
8661
+ const result = {};
8662
+ const lines = content.split("\n");
8663
+ const stack = [{ indent: -1, obj: result }];
8664
+ for (const line of lines) {
8665
+ const trimmed = line.trim();
8666
+ if (!trimmed || trimmed.startsWith("#")) {
8667
+ continue;
8668
+ }
8669
+ const indent = line.search(/\S/);
8670
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
8671
+ stack.pop();
8672
+ }
8673
+ const parent = stack[stack.length - 1].obj;
8674
+ const colonIndex = trimmed.indexOf(":");
8675
+ if (colonIndex === -1) continue;
8676
+ const key = trimmed.slice(0, colonIndex).trim();
8677
+ let value = trimmed.slice(colonIndex + 1).trim();
8678
+ if (value === "") {
8679
+ const newObj = {};
8680
+ parent[key] = newObj;
8681
+ stack.push({ indent, obj: newObj });
8682
+ } else if (value.startsWith("[") && value.endsWith("]")) {
8683
+ const items = value.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean);
8684
+ parent[key] = items.map((item) => {
8685
+ if (item.startsWith('"') && item.endsWith('"') || item.startsWith("'") && item.endsWith("'")) {
8686
+ return item.slice(1, -1);
8687
+ }
8688
+ if (item === "true") return true;
8689
+ if (item === "false") return false;
8690
+ const num = Number(item);
8691
+ if (!isNaN(num)) return num;
8692
+ return item;
8693
+ });
8694
+ } else {
8695
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
8696
+ value = value.slice(1, -1);
8697
+ }
8698
+ if (value === "true") {
8699
+ parent[key] = true;
8700
+ } else if (value === "false") {
8701
+ parent[key] = false;
8702
+ } else {
8703
+ const num = Number(value);
8704
+ if (!isNaN(num) && value !== "") {
8705
+ parent[key] = num;
8706
+ } else {
8707
+ parent[key] = value;
8708
+ }
8709
+ }
8710
+ }
8711
+ }
8712
+ return result;
8713
+ }
8714
+ function loadConfigFromFile(configPath) {
8715
+ const expandedPath = expandPath(configPath);
8716
+ if (!fs11.existsSync(expandedPath)) {
8717
+ return null;
8718
+ }
8719
+ try {
8720
+ const content = fs11.readFileSync(expandedPath, "utf-8");
8721
+ const parsed = parseSimpleYaml(content);
8722
+ return substituteEnvVarsInObject(parsed);
8723
+ } catch (error) {
8724
+ console.error(`Warning: Failed to parse config file ${configPath}:`, error);
8725
+ return null;
8726
+ }
8727
+ }
8728
+ function loadConfigFromEnv() {
8729
+ const config = {};
8730
+ for (const [envVar, configPath] of Object.entries(ENV_MAPPINGS)) {
8731
+ const value = process.env[envVar];
8732
+ if (value !== void 0) {
8733
+ if (envVar === "SKILL_TREE_NO_COLOR") {
8734
+ setNestedProperty(config, configPath, value !== "true" && value !== "1");
8735
+ } else if (envVar === "SKILL_TREE_QUIET") {
8736
+ setNestedProperty(config, configPath, value === "true" || value === "1");
8737
+ } else {
8738
+ setNestedProperty(config, configPath, value);
8739
+ }
8740
+ }
8741
+ }
8742
+ return config;
8743
+ }
8744
+ var ConfigLoader = class {
8745
+ constructor(configPath) {
8746
+ this.loaded = false;
8747
+ this.configPath = configPath || getConfigPath();
8748
+ this.config = { ...DEFAULT_CONFIG6 };
8749
+ }
8750
+ /**
8751
+ * Load configuration from all sources
8752
+ * Priority (highest to lowest):
8753
+ * 1. Environment variables
8754
+ * 2. Config file
8755
+ * 3. Default values
8756
+ */
8757
+ load() {
8758
+ if (this.loaded) {
8759
+ return this.config;
8760
+ }
8761
+ let config = deepMerge({}, DEFAULT_CONFIG6);
8762
+ const fileConfig = loadConfigFromFile(this.configPath);
8763
+ if (fileConfig) {
8764
+ config = deepMerge(config, fileConfig);
8765
+ }
8766
+ const envConfig = loadConfigFromEnv();
8767
+ config = deepMerge(config, envConfig);
8768
+ config.storage.path = expandPath(config.storage.path);
8769
+ this.config = config;
8770
+ this.loaded = true;
8771
+ return this.config;
8772
+ }
8773
+ /**
8774
+ * Get current configuration
8775
+ */
8776
+ getConfig() {
8777
+ if (!this.loaded) {
8778
+ return this.load();
8779
+ }
8780
+ return this.config;
8781
+ }
8782
+ /**
8783
+ * Override configuration with partial values
8784
+ */
8785
+ override(overrides) {
8786
+ this.config = deepMerge(this.getConfig(), overrides);
8787
+ return this.config;
8788
+ }
8789
+ /**
8790
+ * Get a specific config value by path
8791
+ */
8792
+ get(path13) {
8793
+ const parts = path13.split(".");
8794
+ let current = this.getConfig();
8795
+ for (const part of parts) {
8796
+ if (current === null || typeof current !== "object") {
8797
+ return void 0;
8798
+ }
8799
+ current = current[part];
8800
+ }
8801
+ return current;
8802
+ }
8803
+ /**
8804
+ * Check if config file exists
8805
+ */
8806
+ configFileExists() {
8807
+ return fs11.existsSync(expandPath(this.configPath));
8808
+ }
8809
+ /**
8810
+ * Create default config file
8811
+ */
8812
+ createDefaultConfigFile() {
8813
+ const configDir = path11.dirname(expandPath(this.configPath));
8814
+ if (!fs11.existsSync(configDir)) {
8815
+ fs11.mkdirSync(configDir, { recursive: true });
8816
+ }
8817
+ const configContent = `# Skill Tree Configuration
8818
+ # Generated by skill-tree
8819
+
8820
+ storage:
8821
+ # Path to .skilltree directory (skills stored as files, SQLite cache auto-generated)
8822
+ path: ~/.skill-tree
8823
+
8824
+ indexer:
8825
+ # GitHub token for API access (or use GITHUB_TOKEN env var)
8826
+ github_token: \${GITHUB_TOKEN}
8827
+ # Anthropic API key for classification (or use ANTHROPIC_API_KEY env var)
8828
+ anthropic_key: \${ANTHROPIC_API_KEY}
8829
+ sources: []
8830
+ batch_size: 10
8831
+ min_confidence: 0.7
8832
+
8833
+ matching:
8834
+ embedding_model: text-embedding-3-small
8835
+ similarity_threshold: 0.8
8836
+
8837
+ sync:
8838
+ auto_check: false
8839
+ check_interval_hours: 24
8840
+ conflict_resolution: prompt
8841
+
8842
+ cli:
8843
+ output_format: table
8844
+ color: true
8845
+ quiet: false
8846
+
8847
+ materialization:
8848
+ # Enable automatic materialization to agent-discoverable paths
8849
+ enabled: false
8850
+ # Mode: 'symlink' (default) or 'copy'
8851
+ mode: symlink
8852
+ # Directories to expose skills in (e.g. .claude/skills, .agent/skills)
8853
+ symlink_paths: []
8854
+ # Path to auto-generate AGENTS.md (empty = disabled)
8855
+ agents_md_path: ""
8856
+ # Format for AGENTS.md: xml, markdown, json
8857
+ agents_md_format: xml
8858
+ # Debounce interval in ms for batch updates
8859
+ debounce_ms: 500
8860
+ `;
8861
+ fs11.writeFileSync(expandPath(this.configPath), configContent);
8862
+ }
8863
+ };
8864
+ var globalConfig = null;
8865
+ function getConfigLoader(configPath) {
8866
+ if (!globalConfig) {
8867
+ globalConfig = new ConfigLoader(configPath);
8868
+ }
8869
+ return globalConfig;
8870
+ }
8871
+ function loadConfig(configPath) {
8872
+ return getConfigLoader(configPath).load();
8873
+ }
8874
+
8875
+ // src/import/converter.ts
8876
+ function convertIndexerSkill(indexerSkill) {
8877
+ const warnings = [];
8878
+ const instructions = indexerSkill.content || "";
8879
+ const hasStructuredContent = instructions.trim().length > 0;
8880
+ if (!hasStructuredContent) {
8881
+ warnings.push("No content found, instructions will be empty");
8882
+ }
8883
+ const tags = [...indexerSkill.tags || []];
8884
+ if (indexerSkill.sourceRepo) {
8885
+ const repoName = indexerSkill.sourceRepo.split("/").pop();
8886
+ if (repoName && !tags.includes(repoName)) {
8887
+ tags.push(repoName);
8888
+ }
8889
+ }
8890
+ const status = indexerSkill.status === "indexed" ? "active" : indexerSkill.status === "raw" ? "draft" : "draft";
8891
+ const skill = {
8892
+ id: indexerSkill.slug,
8893
+ name: indexerSkill.displayName || indexerSkill.name,
8894
+ version: indexerSkill.version,
8895
+ description: indexerSkill.description,
8896
+ instructions,
8897
+ author: indexerSkill.author,
8898
+ tags,
8899
+ createdAt: new Date(indexerSkill.scrapedAt),
8900
+ updatedAt: new Date(indexerSkill.updatedAt),
8901
+ status,
8902
+ metrics: {
8903
+ usageCount: 0,
8904
+ successRate: 0,
8905
+ feedbackScores: []
8906
+ },
8907
+ source: {
8908
+ type: "imported",
8909
+ location: indexerSkill.sourceUrl,
8910
+ importedAt: /* @__PURE__ */ new Date()
8911
+ },
8912
+ // Extended fields for indexed skills
8913
+ taxonomy: indexerSkill.primaryPath ? {
8914
+ primaryPath: indexerSkill.primaryPath,
8915
+ secondaryPaths: indexerSkill.secondaryPaths,
8916
+ confidence: indexerSkill.confidence
8917
+ } : void 0,
8918
+ externalSource: {
8919
+ url: indexerSkill.sourceUrl,
8920
+ repo: indexerSkill.sourceRepo,
8921
+ scrapedAt: new Date(indexerSkill.scrapedAt)
8922
+ }
8923
+ };
8924
+ return { skill, warnings, hasStructuredContent };
8925
+ }
8926
+
8927
+ // src/services/indexer.ts
8928
+ function hasIndexerSupport(storage) {
8929
+ const s = storage;
8930
+ return typeof s.addRelationship === "function" && typeof s.getTaxonomyTree === "function";
8931
+ }
8932
+ var IndexerService = class {
8933
+ constructor(config = {}, skillBank) {
8934
+ this.initialized = false;
8935
+ this.globalConfig = loadConfig();
8936
+ this.serviceConfig = {
8937
+ githubToken: config.githubToken || this.globalConfig.indexer.github_token,
8938
+ anthropicApiKey: config.anthropicApiKey || this.globalConfig.indexer.anthropic_key,
8939
+ batchSize: config.batchSize || this.globalConfig.indexer.batch_size,
8940
+ minConfidence: config.minConfidence || this.globalConfig.indexer.min_confidence,
8941
+ concurrency: config.concurrency || 3,
8942
+ cacheTtlSeconds: config.cacheTtlSeconds || 3600,
8943
+ databasePath: config.databasePath,
8944
+ cacheDir: config.cacheDir,
8945
+ claudeModel: config.claudeModel,
8946
+ scraperModules: config.scraperModules
8947
+ };
8948
+ this.skillBank = skillBank;
8949
+ }
8950
+ /**
8951
+ * Get effective configuration
8952
+ */
8953
+ getConfig() {
8954
+ return { ...this.serviceConfig };
8955
+ }
8956
+ /**
8957
+ * Initialize the service (lazy load scraper modules)
8958
+ */
8959
+ async initialize() {
8960
+ if (this.initialized) return;
8961
+ try {
8962
+ if (this.serviceConfig.scraperModules) {
8963
+ const mod = this.serviceConfig.scraperModules;
8964
+ this.databaseModule = mod;
8965
+ if (mod.createDatabase) {
8966
+ const dbPath = this.serviceConfig.databasePath || path12.join(process.cwd(), "scraper/data/skills.db");
8967
+ const dbDir = path12.dirname(dbPath);
8968
+ if (!fs12.existsSync(dbDir)) fs12.mkdirSync(dbDir, { recursive: true });
8969
+ this.db = mod.createDatabase({ type: "sqlite", path: dbPath });
8970
+ if (this.db.connect) await this.db.connect();
8971
+ if (this.db.migrate) await this.db.migrate();
8972
+ }
8973
+ const localDb = this.db;
8974
+ if (mod.ScraperService) {
8975
+ this.scraperModule = {
8976
+ createScraper: (config) => {
8977
+ const scraperConfig = {
8978
+ githubToken: config.githubToken || "",
8979
+ cacheEnabled: true,
8980
+ cacheDir: config.cacheDir || path12.join(process.cwd(), ".cache/scraper"),
8981
+ cacheTtlSeconds: config.cacheTtlSeconds || 3600,
8982
+ requestDelayMs: 100,
8983
+ maxRetries: 3
8984
+ };
8985
+ const svc = new mod.ScraperService(localDb, scraperConfig);
8986
+ let initialized = false;
8987
+ const ensureInit = async () => {
8988
+ if (!initialized) {
8989
+ await svc.init();
8990
+ initialized = true;
8991
+ }
8992
+ };
8993
+ return {
8994
+ scrapeAwesomeList: async (url, opts) => {
8995
+ await ensureInit();
8996
+ await svc.scrape({ type: "awesome-list", url }, opts);
8997
+ return await localDb.listSkills() || [];
8998
+ },
8999
+ scrapeRepository: async (url, opts) => {
9000
+ await ensureInit();
9001
+ await svc.scrape({ type: "repository", url }, opts);
9002
+ return await localDb.listSkills() || [];
9003
+ }
9004
+ };
9005
+ }
9006
+ };
9007
+ }
9008
+ if (mod.SkillClassifier) {
9009
+ this.indexerModule = {
9010
+ createIndexer: (config) => {
9011
+ const classifier = new mod.SkillClassifier(config);
9012
+ return {
9013
+ classifySkill: async (skill) => {
9014
+ let tree = null;
9015
+ if (mod.TaxonomyManager && localDb) {
9016
+ try {
9017
+ const tm = new mod.TaxonomyManager(localDb);
9018
+ tree = await tm.getTree();
9019
+ } catch {
9020
+ }
9021
+ }
9022
+ return classifier.classify(skill, tree);
9023
+ }
9024
+ };
9025
+ }
9026
+ };
9027
+ }
9028
+ this.initialized = true;
9029
+ return;
9030
+ }
9031
+ const possiblePaths = [
9032
+ // Relative to this file in dist
9033
+ path12.resolve(__dirname, "../../scraper/dist"),
9034
+ // Relative to project root
9035
+ path12.resolve(process.cwd(), "scraper/dist"),
9036
+ // Absolute paths from config
9037
+ this.serviceConfig.cacheDir ? path12.resolve(this.serviceConfig.cacheDir, "../scraper/dist") : null
9038
+ ].filter(Boolean);
9039
+ let scraperBasePath = null;
9040
+ for (const basePath of possiblePaths) {
9041
+ const scraperIndex = path12.join(basePath, "scraper/index.js");
9042
+ if (fs12.existsSync(scraperIndex)) {
9043
+ scraperBasePath = basePath;
9044
+ break;
9045
+ }
9046
+ }
9047
+ if (!scraperBasePath) {
9048
+ throw new Error(
9049
+ "Scraper modules not found. Run `cd scraper && npm run build` first."
9050
+ );
9051
+ }
9052
+ const scraperPath = path12.join(scraperBasePath, "scraper/index.js");
9053
+ const indexerPath = path12.join(scraperBasePath, "indexer/index.js");
9054
+ const databasePath = path12.join(scraperBasePath, "database/index.js");
9055
+ this.scraperModule = await import(
9056
+ /* webpackIgnore: true */
9057
+ scraperPath
9058
+ );
9059
+ this.indexerModule = await import(
9060
+ /* webpackIgnore: true */
9061
+ indexerPath
9062
+ );
9063
+ this.databaseModule = await import(
9064
+ /* webpackIgnore: true */
9065
+ databasePath
9066
+ );
9067
+ if (this.databaseModule.createDatabase) {
9068
+ const dbPath = this.serviceConfig.databasePath || path12.join(process.cwd(), "scraper/data/skills.db");
9069
+ this.db = this.databaseModule.createDatabase(dbPath);
9070
+ }
9071
+ this.initialized = true;
9072
+ } catch (err) {
9073
+ console.warn(`Scraper modules not available: ${err.message}`);
9074
+ console.warn("Some indexer features will be limited.");
9075
+ this.initialized = true;
9076
+ }
9077
+ }
9078
+ /**
9079
+ * Check if the indexer is available
9080
+ */
9081
+ isAvailable() {
9082
+ return this.initialized && !!this.scraperModule;
9083
+ }
9084
+ /**
9085
+ * Check if running in degraded mode (no scraper modules)
9086
+ */
9087
+ isDegradedMode() {
9088
+ return this.initialized && !this.scraperModule;
9089
+ }
9090
+ /**
9091
+ * Scrape skills from GitHub sources
9092
+ */
9093
+ async scrape(sources, options) {
9094
+ await this.initialize();
9095
+ if (!this.scraperModule || !this.db) {
9096
+ throw new Error(
9097
+ "Scraper not available. Build the scraper module first: cd scraper && npm run build"
9098
+ );
9099
+ }
9100
+ const result = {
9101
+ discovered: 0,
9102
+ scraped: 0,
9103
+ skipped: 0,
9104
+ failed: 0,
9105
+ unchanged: 0,
9106
+ errors: []
9107
+ };
9108
+ try {
9109
+ const scraper = this.scraperModule.createScraper({
9110
+ githubToken: this.serviceConfig.githubToken,
9111
+ cacheDir: this.serviceConfig.cacheDir,
9112
+ cacheTtlSeconds: this.serviceConfig.cacheTtlSeconds
9113
+ });
9114
+ for (const source of sources) {
9115
+ try {
9116
+ let skills;
9117
+ if (source.type === "awesome-list") {
9118
+ skills = await scraper.scrapeAwesomeList(source.url, {
9119
+ force: options?.force
9120
+ });
9121
+ } else {
9122
+ skills = await scraper.scrapeRepository(source.url, {
9123
+ force: options?.force
9124
+ });
9125
+ }
9126
+ result.discovered += skills.length;
9127
+ for (const skill of skills) {
9128
+ try {
9129
+ const existing = this.db.getSkillBySlug?.(skill.slug);
9130
+ if (existing && !options?.force) {
9131
+ result.unchanged++;
9132
+ } else {
9133
+ this.db.saveSkill?.(skill);
9134
+ result.scraped++;
9135
+ }
9136
+ } catch (err) {
9137
+ result.failed++;
9138
+ result.errors.push(
9139
+ `Failed to save skill ${skill.slug}: ${err.message}`
9140
+ );
9141
+ }
9142
+ }
9143
+ } catch (err) {
9144
+ result.failed++;
9145
+ result.errors.push(
9146
+ `Failed to scrape ${source.url}: ${err.message}`
9147
+ );
9148
+ }
9149
+ }
9150
+ } catch (err) {
9151
+ result.errors.push(`Scrape failed: ${err.message}`);
9152
+ }
9153
+ return result;
9154
+ }
9155
+ /**
9156
+ * Classify unindexed skills using AI
9157
+ */
9158
+ async classify(options) {
9159
+ await this.initialize();
9160
+ if (!this.indexerModule || !this.db) {
9161
+ throw new Error(
9162
+ "Indexer not available. Build the scraper module first: cd scraper && npm run build"
9163
+ );
9164
+ }
9165
+ const result = {
9166
+ indexed: 0,
9167
+ skipped: 0,
9168
+ failed: 0,
9169
+ errors: []
9170
+ };
9171
+ try {
9172
+ const indexer = this.indexerModule.createIndexer({
9173
+ anthropicApiKey: this.serviceConfig.anthropicApiKey,
9174
+ model: this.serviceConfig.claudeModel || "claude-sonnet-4-20250514",
9175
+ minConfidence: this.serviceConfig.minConfidence
9176
+ });
9177
+ let skills;
9178
+ if (options?.skillId) {
9179
+ const skill = this.db.getSkillBySlug?.(options.skillId);
9180
+ skills = skill ? [skill] : [];
9181
+ } else if (options?.all) {
9182
+ skills = this.db.getAllSkills?.() || [];
9183
+ } else {
9184
+ skills = this.db.getSkillsByStatus?.("raw") || [];
9185
+ }
9186
+ const batchSize = this.serviceConfig.batchSize || 10;
9187
+ for (let i = 0; i < skills.length; i += batchSize) {
9188
+ const batch = skills.slice(i, i + batchSize);
9189
+ for (const skill of batch) {
9190
+ if (skill.status === "indexed" && !options?.all) {
9191
+ result.skipped++;
9192
+ continue;
9193
+ }
9194
+ try {
9195
+ const classification = await indexer.classifySkill(skill);
9196
+ skill.status = "indexed";
9197
+ skill.primaryPath = classification.primaryPath;
9198
+ skill.secondaryPaths = classification.secondaryPaths;
9199
+ skill.confidence = classification.confidence;
9200
+ skill.classificationReasoning = classification.reasoning;
9201
+ skill.indexedAt = (/* @__PURE__ */ new Date()).toISOString();
9202
+ this.db.saveSkill?.(skill);
9203
+ result.indexed++;
9204
+ } catch (err) {
9205
+ result.failed++;
9206
+ result.errors.push(
9207
+ `Failed to classify ${skill.slug}: ${err.message}`
9208
+ );
9209
+ }
9210
+ }
9211
+ }
9212
+ } catch (err) {
9213
+ result.errors.push(`Classification failed: ${err.message}`);
9214
+ }
9215
+ return result;
9216
+ }
9217
+ /**
9218
+ * Detect relationships between skills
9219
+ */
9220
+ async detectRelationships(options) {
9221
+ await this.initialize();
9222
+ const result = {
9223
+ detected: 0,
9224
+ skipped: 0,
9225
+ errors: []
9226
+ };
9227
+ if (this.skillBank) {
9228
+ try {
9229
+ const storage = this.skillBank.getStorage();
9230
+ if (hasIndexerSupport(storage)) {
9231
+ const skills = await this.skillBank.listSkills();
9232
+ const targetSkills = options?.skillId ? skills.filter((s) => s.id === options.skillId) : skills;
9233
+ for (const skill of targetSkills) {
9234
+ const relationships = this.detectSkillRelationships(skill, skills);
9235
+ for (const rel of relationships) {
9236
+ try {
9237
+ await storage.addRelationship(
9238
+ skill.id,
9239
+ rel.targetSkillId,
9240
+ rel.type,
9241
+ rel.confidence,
9242
+ rel.reasoning
9243
+ );
9244
+ result.detected++;
9245
+ } catch (err) {
9246
+ result.skipped++;
9247
+ }
9248
+ }
9249
+ }
9250
+ }
9251
+ } catch (err) {
9252
+ result.errors.push(
9253
+ `Relationship detection failed: ${err.message}`
9254
+ );
9255
+ }
9256
+ return result;
9257
+ }
9258
+ if (!this.db) {
9259
+ throw new Error("Neither SkillBank nor scraper database available.");
9260
+ }
9261
+ try {
9262
+ const skills = this.db.getAllSkills?.() || [];
9263
+ const targetSkills = options?.skillId ? skills.filter((s) => s.slug === options.skillId) : skills.filter((s) => s.status === "indexed");
9264
+ for (const skill of targetSkills) {
9265
+ const relationships = this.detectSkillRelationships(skill, skills);
9266
+ for (const rel of relationships) {
9267
+ try {
9268
+ this.db.saveRelationship?.({
9269
+ sourceSkillId: skill.id,
9270
+ ...rel
9271
+ });
9272
+ result.detected++;
9273
+ } catch (err) {
9274
+ result.skipped++;
9275
+ }
9276
+ }
9277
+ }
9278
+ } catch (err) {
9279
+ result.errors.push(
9280
+ `Relationship detection failed: ${err.message}`
9281
+ );
9282
+ }
9283
+ return result;
9284
+ }
9285
+ /**
9286
+ * Detect relationships for a single skill
9287
+ */
9288
+ detectSkillRelationships(skill, allSkills) {
9289
+ const relationships = [];
9290
+ const skillId = skill.id || skill.slug;
9291
+ const skillContent = `${skill.name} ${skill.description} ${skill.instructions || ""} ${skill.content || ""}`.toLowerCase();
9292
+ for (const other of allSkills) {
9293
+ const otherId = other.id || other.slug;
9294
+ if (otherId === skillId) continue;
9295
+ const otherName = (other.name || "").toLowerCase();
9296
+ const dependencyPatterns = [
9297
+ "requires",
9298
+ "depends on",
9299
+ "uses",
9300
+ "needs",
9301
+ "builds on"
9302
+ ];
9303
+ for (const pattern of dependencyPatterns) {
9304
+ if (skillContent.includes(`${pattern} ${otherName}`)) {
9305
+ relationships.push({
9306
+ targetSkillId: otherId,
9307
+ type: "depends_on",
9308
+ confidence: 0.7,
9309
+ reasoning: `Content mentions "${pattern} ${other.name}"`
9310
+ });
9311
+ break;
9312
+ }
9313
+ }
9314
+ const extensionPatterns = [
9315
+ "extends",
9316
+ "improves",
9317
+ "enhances",
9318
+ "builds upon"
9319
+ ];
9320
+ for (const pattern of extensionPatterns) {
9321
+ if (skillContent.includes(`${pattern} ${otherName}`)) {
9322
+ relationships.push({
9323
+ targetSkillId: otherId,
9324
+ type: "extends",
9325
+ confidence: 0.7,
9326
+ reasoning: `Content mentions "${pattern} ${other.name}"`
9327
+ });
9328
+ break;
9329
+ }
9330
+ }
9331
+ const alternativePatterns = [
9332
+ "alternative to",
9333
+ "instead of",
9334
+ "replacement for"
9335
+ ];
9336
+ for (const pattern of alternativePatterns) {
9337
+ if (skillContent.includes(`${pattern} ${otherName}`)) {
9338
+ relationships.push({
9339
+ targetSkillId: otherId,
9340
+ type: "alternative",
9341
+ confidence: 0.6,
9342
+ reasoning: `Content mentions "${pattern} ${other.name}"`
9343
+ });
9344
+ break;
9345
+ }
9346
+ }
9347
+ const skillPath = skill.taxonomy?.primaryPath || skill.primaryPath || [];
9348
+ const otherPath = other.taxonomy?.primaryPath || other.primaryPath || [];
9349
+ if (skillPath.length >= 2 && otherPath.length >= 2) {
9350
+ if (skillPath[0] === otherPath[0] && skillPath[1] === otherPath[1]) {
9351
+ relationships.push({
9352
+ targetSkillId: otherId,
9353
+ type: "related",
9354
+ confidence: 0.5,
9355
+ reasoning: `Same taxonomy category: ${skillPath.slice(0, 2).join(" > ")}`
9356
+ });
9357
+ }
9358
+ }
9359
+ }
9360
+ return relationships;
9361
+ }
9362
+ /**
9363
+ * Get taxonomy tree
9364
+ */
9365
+ async getTaxonomyTree(rootPath) {
9366
+ await this.initialize();
9367
+ if (this.skillBank) {
9368
+ const storage = this.skillBank.getStorage();
9369
+ if (hasIndexerSupport(storage)) {
9370
+ const nodes = await storage.getTaxonomyTree(rootPath);
9371
+ return this.wrapTreeNodes(nodes, rootPath);
9372
+ }
9373
+ }
9374
+ const skills = this.skillBank ? await this.skillBank.listSkills() : this.db?.getAllSkills?.() || [];
9375
+ return this.buildTaxonomyTreeFromSkills(skills, rootPath);
9376
+ }
9377
+ /**
9378
+ * Wrap tree nodes returned from SQLite storage into a root node
9379
+ */
9380
+ wrapTreeNodes(nodes, rootPath) {
9381
+ const root = {
9382
+ id: "root",
9383
+ name: rootPath?.join(" > ") || "All Skills",
9384
+ path: rootPath || [],
9385
+ skillCount: 0,
9386
+ children: []
9387
+ };
9388
+ for (const node of nodes) {
9389
+ root.children.push(this.convertToTaxonomyNode(node));
9390
+ root.skillCount += this.countNodeSkills(node);
9391
+ }
9392
+ return root;
9393
+ }
9394
+ /**
9395
+ * Convert storage tree node to TaxonomyNode format
9396
+ */
9397
+ convertToTaxonomyNode(node) {
9398
+ return {
9399
+ id: node.id,
9400
+ name: node.name,
9401
+ path: Array.isArray(node.path) ? node.path : (node.path || "").split("/"),
9402
+ skillCount: node.skillCount || 0,
9403
+ children: (node.children || []).map(
9404
+ (child) => this.convertToTaxonomyNode(child)
9405
+ )
9406
+ };
9407
+ }
9408
+ /**
9409
+ * Count total skills in a node tree
9410
+ */
9411
+ countNodeSkills(node) {
9412
+ let count = node.skillCount || 0;
9413
+ for (const child of node.children || []) {
9414
+ count += this.countNodeSkills(child);
9415
+ }
9416
+ return count;
9417
+ }
9418
+ /**
9419
+ * Build taxonomy tree from flat nodes
9420
+ */
9421
+ buildTaxonomyTree(nodes, rootPath) {
9422
+ const root = {
9423
+ id: "root",
9424
+ name: rootPath?.join(" > ") || "All Skills",
9425
+ path: rootPath || [],
9426
+ skillCount: 0,
9427
+ children: []
9428
+ };
9429
+ const nodeMap = /* @__PURE__ */ new Map();
9430
+ nodeMap.set("root", root);
9431
+ for (const node of nodes) {
9432
+ const taxNode = {
9433
+ id: node.id,
9434
+ name: node.name,
9435
+ path: node.path,
9436
+ skillCount: node.skillCount || 0,
9437
+ children: []
9438
+ };
9439
+ nodeMap.set(node.id, taxNode);
9440
+ }
9441
+ for (const node of nodes) {
9442
+ const taxNode = nodeMap.get(node.id);
9443
+ const parentId = node.parentId || "root";
9444
+ const parent = nodeMap.get(parentId);
9445
+ if (parent) {
9446
+ parent.children.push(taxNode);
9447
+ parent.skillCount += taxNode.skillCount;
9448
+ }
9449
+ }
9450
+ return root;
9451
+ }
9452
+ /**
9453
+ * Build taxonomy tree from skills
9454
+ */
9455
+ buildTaxonomyTreeFromSkills(skills, rootPath) {
9456
+ const root = {
9457
+ id: "root",
9458
+ name: rootPath?.join(" > ") || "All Skills",
9459
+ path: rootPath || [],
9460
+ skillCount: 0,
9461
+ children: []
9462
+ };
9463
+ const nodeMap = /* @__PURE__ */ new Map();
9464
+ for (const skill of skills) {
9465
+ const taxonomy = skill.taxonomy || (skill.primaryPath ? { primaryPath: skill.primaryPath } : null);
9466
+ if (!taxonomy?.primaryPath) continue;
9467
+ const skillPath = taxonomy.primaryPath;
9468
+ if (rootPath && rootPath.length > 0) {
9469
+ let matches = true;
9470
+ for (let i = 0; i < rootPath.length; i++) {
9471
+ if (skillPath[i] !== rootPath[i]) {
9472
+ matches = false;
9473
+ break;
9474
+ }
9475
+ }
9476
+ if (!matches) continue;
9477
+ }
9478
+ let currentPath = [];
9479
+ let parent = root;
9480
+ for (const segment of skillPath) {
9481
+ currentPath = [...currentPath, segment];
9482
+ const pathKey = currentPath.join("/");
9483
+ let node = nodeMap.get(pathKey);
9484
+ if (!node) {
9485
+ node = {
9486
+ id: pathKey,
9487
+ name: segment,
9488
+ path: [...currentPath],
9489
+ skillCount: 0,
9490
+ children: []
9491
+ };
9492
+ nodeMap.set(pathKey, node);
9493
+ parent.children.push(node);
9494
+ }
9495
+ parent = node;
9496
+ }
9497
+ parent.skillCount++;
9498
+ root.skillCount++;
9499
+ }
9500
+ return root;
9501
+ }
9502
+ /**
9503
+ * Get indexer statistics
9504
+ */
9505
+ async getStats() {
9506
+ await this.initialize();
9507
+ if (this.skillBank) {
9508
+ const skills = await this.skillBank.listSkills();
9509
+ const storage = this.skillBank.getStorage();
9510
+ let taxonomyNodes = 0;
9511
+ let relationships = 0;
9512
+ let sources = 0;
9513
+ if (storage) {
9514
+ if (hasIndexerSupport(storage)) {
9515
+ const tree = await storage.getTaxonomyTree();
9516
+ taxonomyNodes = this.countTaxonomyNodes(tree);
9517
+ if (storage.getRelationships) {
9518
+ for (const skill of skills) {
9519
+ const rels = await storage.getRelationships(skill.id);
9520
+ relationships += rels.length;
9521
+ }
9522
+ }
9523
+ }
9524
+ const sourceSet = /* @__PURE__ */ new Set();
9525
+ for (const skill of skills) {
9526
+ if (skill.externalSource?.repo) {
9527
+ sourceSet.add(skill.externalSource.repo);
9528
+ }
9529
+ if (skill.source?.type === "imported" && skill.externalSource?.url) {
9530
+ sourceSet.add(skill.externalSource.url);
9531
+ }
9532
+ }
9533
+ sources = sourceSet.size;
9534
+ }
9535
+ const indexed = skills.filter(
9536
+ (s) => s.status === "active" && (s.taxonomy || s.source?.type === "imported")
9537
+ ).length;
9538
+ return {
9539
+ totalSkills: skills.length,
9540
+ indexedSkills: indexed,
9541
+ rawSkills: skills.filter((s) => s.status === "draft").length,
9542
+ failedSkills: skills.filter((s) => s.status === "deprecated").length,
9543
+ taxonomyNodes,
9544
+ relationships,
9545
+ sources
9546
+ };
9547
+ }
9548
+ if (this.db) {
9549
+ const stats = this.db.getStats?.() || {};
9550
+ return {
9551
+ totalSkills: stats.totalSkills || 0,
9552
+ indexedSkills: stats.indexedSkills || 0,
9553
+ rawSkills: stats.rawSkills || 0,
9554
+ failedSkills: stats.failedSkills || 0,
9555
+ taxonomyNodes: stats.taxonomyNodes || 0,
9556
+ relationships: stats.relationships || 0,
9557
+ sources: stats.sources || 0
9558
+ };
9559
+ }
9560
+ return {
9561
+ totalSkills: 0,
9562
+ indexedSkills: 0,
9563
+ rawSkills: 0,
9564
+ failedSkills: 0,
9565
+ taxonomyNodes: 0,
9566
+ relationships: 0,
9567
+ sources: 0
9568
+ };
9569
+ }
9570
+ /**
9571
+ * Count nodes in taxonomy tree
9572
+ */
9573
+ countTaxonomyNodes(nodes) {
9574
+ let count = 0;
9575
+ for (const node of nodes) {
9576
+ count++;
9577
+ if (node.children) {
9578
+ count += this.countTaxonomyNodes(node.children);
9579
+ }
9580
+ }
9581
+ return count;
9582
+ }
9583
+ /**
9584
+ * Scrape and index skills directly into SkillBank
9585
+ * This is the streamlined workflow for integrated mode
9586
+ */
9587
+ async scrapeAndIndex(sources, options) {
9588
+ if (!this.skillBank) {
9589
+ throw new Error(
9590
+ "SkillBank required for scrapeAndIndex. Use integrated mode."
9591
+ );
9592
+ }
9593
+ await this.initialize();
9594
+ const skillsAdded = [];
9595
+ const scraped = await this.scrape(sources, { force: options?.force });
9596
+ let indexed = {
9597
+ indexed: 0,
9598
+ skipped: 0,
9599
+ failed: 0,
9600
+ errors: []
9601
+ };
9602
+ if (options?.autoClassify !== false) {
9603
+ indexed = await this.classify({ all: options?.force });
9604
+ }
9605
+ if (this.db) {
9606
+ const skills = await (this.db.getAllSkills?.() || this.db.listSkills?.()) || [];
9607
+ for (const skill of skills) {
9608
+ if (skill.status !== "indexed" && !options?.force && !options?.importAll) continue;
9609
+ try {
9610
+ const converted = convertIndexerSkill(skill);
9611
+ await this.skillBank.saveSkill(converted.skill);
9612
+ skillsAdded.push(converted.skill.id);
9613
+ } catch (err) {
9614
+ indexed.errors.push(
9615
+ `Failed to import ${skill.slug}: ${err.message}`
9616
+ );
9617
+ }
9618
+ }
9619
+ }
9620
+ let relationships = {
9621
+ detected: 0,
9622
+ skipped: 0,
9623
+ errors: []
9624
+ };
9625
+ if (options?.detectRelationships !== false) {
9626
+ relationships = await this.detectRelationships();
9627
+ }
9628
+ return {
9629
+ scraped,
9630
+ indexed,
9631
+ relationships,
9632
+ skillsAdded
9633
+ };
9634
+ }
9635
+ /**
9636
+ * Import skills from indexer database into SkillBank
9637
+ */
9638
+ async importFromIndexerDb(options) {
9639
+ if (!this.skillBank) {
9640
+ throw new Error("SkillBank not configured. Use integrated mode.");
9641
+ }
9642
+ await this.initialize();
9643
+ if (!this.db) {
9644
+ throw new Error(
9645
+ "Scraper database not available. Build the scraper module first."
9646
+ );
9647
+ }
9648
+ const result = { imported: 0, failed: 0, skills: [] };
9649
+ try {
9650
+ let skills;
9651
+ if (options?.status) {
9652
+ skills = this.db.getSkillsByStatus?.(options.status) || [];
9653
+ } else {
9654
+ skills = this.db.getAllSkills?.() || [];
9655
+ }
9656
+ if (options?.limit) {
9657
+ skills = skills.slice(0, options.limit);
9658
+ }
9659
+ for (const skill of skills) {
9660
+ try {
9661
+ const converted = convertIndexerSkill(skill);
9662
+ await this.skillBank.saveSkill(converted.skill);
9663
+ result.imported++;
9664
+ result.skills.push(converted.skill);
9665
+ } catch (err) {
9666
+ result.failed++;
9667
+ }
9668
+ }
9669
+ } catch (err) {
9670
+ throw new Error(`Import failed: ${err.message}`);
9671
+ }
9672
+ return result;
9673
+ }
9674
+ /**
9675
+ * Get default skill sources from config
9676
+ */
9677
+ getDefaultSources() {
9678
+ const configSources = this.globalConfig.indexer.sources;
9679
+ if (configSources.length > 0) {
9680
+ return configSources.map((url) => ({
9681
+ type: "awesome-list",
9682
+ url
9683
+ }));
9684
+ }
9685
+ return [
9686
+ {
9687
+ type: "awesome-list",
9688
+ url: "https://github.com/VoltAgent/awesome-agent-skills"
9689
+ }
9690
+ ];
9691
+ }
9692
+ /**
9693
+ * Close database connections
9694
+ */
9695
+ async close() {
9696
+ if (this.db && typeof this.db.close === "function") {
9697
+ this.db.close();
9698
+ }
9699
+ this.db = void 0;
9700
+ this.initialized = false;
9701
+ }
9702
+ };
9703
+
8260
9704
  // src/index.ts
8261
9705
  var VERSION = "0.1.0";
8262
9706
  // Annotate the CommonJS export names for ESM import in node:
@@ -8265,11 +9709,13 @@ var VERSION = "0.1.0";
8265
9709
  AgentsParser,
8266
9710
  AgentsSync,
8267
9711
  CachedStorageAdapter,
9712
+ CatalogRenderer,
8268
9713
  ConflictStore,
8269
9714
  DEFAULT_AGENTS_CONFIG,
8270
9715
  FederationManager,
8271
9716
  GitSyncAdapter,
8272
9717
  HookRegistry,
9718
+ IndexerService,
8273
9719
  LineageTracker,
8274
9720
  LoadoutCompiler,
8275
9721
  Materializer,
@@ -8326,3 +9772,4 @@ var VERSION = "0.1.0";
8326
9772
  testingProfile,
8327
9773
  writeAgentsMd
8328
9774
  });
9775
+ //# sourceMappingURL=index.js.map