@skill-tools/router 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +61 -0
- package/dist/index.cjs +956 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +426 -0
- package/dist/index.d.ts +426 -0
- package/dist/index.js +950 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bm25/index.ts","../src/context/extractor.ts","../src/embeddings/local.ts","../src/stores/memory.ts","../src/router.ts"],"names":["tokenize","STOP_WORDS","resolveSkillFiles","parseSkill"],"mappings":"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AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;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","file":"index.cjs","sourcesContent":["/**\n * Okapi BM25 — Zero-dependency, optimized text search index.\n *\n * Builds an inverted index from document text and scores queries\n * using the BM25 ranking function. Designed for fast skill routing\n * with catalogs up to ~10,000 entries.\n *\n * Performance:\n * - Index build: O(n * avg_doc_len)\n * - Query: O(q * avg_posting_len) — only visits docs containing query terms\n * - Memory: O(vocabulary_size * avg_posting_len)\n *\n * @packageDocumentation\n */\n\n/** A document stored in the index */\ninterface BM25Document {\n\treadonly id: string;\n\treadonly length: number;\n\treadonly metadata: Record<string, unknown>;\n}\n\n/** A posting list entry: document index + term frequency */\ninterface Posting {\n\treadonly docIdx: number;\n\treadonly tf: number;\n}\n\n/** Serialized snapshot of BM25Index state */\nexport interface BM25Snapshot {\n\treadonly version: 2;\n\treadonly documents: ReadonlyArray<{\n\t\treadonly id: string;\n\t\treadonly length: number;\n\t\treadonly metadata: Record<string, unknown>;\n\t}>;\n\treadonly invertedIndex: ReadonlyArray<[string, Posting[]]>;\n\treadonly idf: ReadonlyArray<[string, number]>;\n\treadonly avgdl: number;\n\treadonly k1: number;\n\treadonly b: number;\n}\n\n/** Options for creating a BM25Index */\nexport interface BM25Options {\n\t/** Term frequency saturation parameter (default: 1.2) */\n\treadonly k1?: number;\n\t/** Document length normalization parameter (default: 0.75) */\n\treadonly b?: number;\n}\n\n/**\n * BM25Index — Fast, zero-dependency full-text search using Okapi BM25.\n *\n * @example\n * ```ts\n * const idx = new BM25Index();\n * idx.add([\n * { id: 'deploy', text: 'Deploy apps to Vercel production', metadata: {} },\n * { id: 'test', text: 'Run unit tests with coverage', metadata: {} },\n * ]);\n * const results = idx.search('deploy production', 5);\n * ```\n */\nexport class BM25Index {\n\tprivate readonly k1: number;\n\tprivate readonly b: number;\n\n\tprivate documents: BM25Document[] = [];\n\tprivate invertedIndex: Map<string, Posting[]> = new Map();\n\tprivate idfCache: Map<string, number> = new Map();\n\tprivate avgdl = 0;\n\tprivate totalDocLength = 0;\n\n\tconstructor(options?: BM25Options) {\n\t\tthis.k1 = options?.k1 ?? 1.2;\n\t\tthis.b = options?.b ?? 0.75;\n\t}\n\n\t/**\n\t * Add documents to the index.\n\t * Batch operation — IDF is recomputed once after all documents are added.\n\t */\n\tadd(\n\t\tentries: ReadonlyArray<{\n\t\t\treadonly id: string;\n\t\t\treadonly text: string;\n\t\t\treadonly metadata: Record<string, unknown>;\n\t\t}>,\n\t): void {\n\t\tif (entries.length === 0) return;\n\n\t\tfor (const entry of entries) {\n\t\t\tconst tokens = tokenize(entry.text);\n\t\t\tconst docIdx = this.documents.length;\n\n\t\t\tthis.documents.push({\n\t\t\t\tid: entry.id,\n\t\t\t\tlength: tokens.length,\n\t\t\t\tmetadata: entry.metadata,\n\t\t\t});\n\n\t\t\tthis.totalDocLength += tokens.length;\n\n\t\t\t// Build term frequencies for this document\n\t\t\tconst termFreq = new Map<string, number>();\n\t\t\tfor (const token of tokens) {\n\t\t\t\ttermFreq.set(token, (termFreq.get(token) ?? 0) + 1);\n\t\t\t}\n\n\t\t\t// Append to inverted index\n\t\t\tfor (const [term, tf] of termFreq) {\n\t\t\t\tlet postings = this.invertedIndex.get(term);\n\t\t\t\tif (!postings) {\n\t\t\t\t\tpostings = [];\n\t\t\t\t\tthis.invertedIndex.set(term, postings);\n\t\t\t\t}\n\t\t\t\tpostings.push({ docIdx, tf });\n\t\t\t}\n\t\t}\n\n\t\t// Recompute avgdl and IDF after batch add\n\t\tthis.avgdl = this.totalDocLength / this.documents.length;\n\t\tthis.recomputeIDF();\n\t}\n\n\t/**\n\t * Remove documents by ID.\n\t * Rebuilds internal index mappings after removal.\n\t */\n\tremove(ids: readonly string[]): void {\n\t\tconst idSet = new Set(ids);\n\t\tconst removedIndices = new Set<number>();\n\n\t\tfor (let i = 0; i < this.documents.length; i++) {\n\t\t\tif (idSet.has(this.documents[i]!.id)) {\n\t\t\t\tremovedIndices.add(i);\n\t\t\t\tthis.totalDocLength -= this.documents[i]!.length;\n\t\t\t}\n\t\t}\n\n\t\tif (removedIndices.size === 0) return;\n\n\t\t// Rebuild documents array and create old→new index mapping\n\t\tconst newDocuments: BM25Document[] = [];\n\t\tconst indexMap = new Map<number, number>();\n\t\tfor (let i = 0; i < this.documents.length; i++) {\n\t\t\tif (!removedIndices.has(i)) {\n\t\t\t\tindexMap.set(i, newDocuments.length);\n\t\t\t\tnewDocuments.push(this.documents[i]!);\n\t\t\t}\n\t\t}\n\t\tthis.documents = newDocuments;\n\n\t\t// Rebuild inverted index with remapped indices\n\t\tfor (const [term, postings] of this.invertedIndex) {\n\t\t\tconst filtered: Posting[] = [];\n\t\t\tfor (const p of postings) {\n\t\t\t\tif (!removedIndices.has(p.docIdx)) {\n\t\t\t\t\tfiltered.push({ docIdx: indexMap.get(p.docIdx)!, tf: p.tf });\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (filtered.length === 0) {\n\t\t\t\tthis.invertedIndex.delete(term);\n\t\t\t} else {\n\t\t\t\tthis.invertedIndex.set(term, filtered);\n\t\t\t}\n\t\t}\n\n\t\t// Recompute stats\n\t\tthis.avgdl = this.documents.length > 0 ? this.totalDocLength / this.documents.length : 0;\n\t\tthis.recomputeIDF();\n\t}\n\n\t/**\n\t * Search the index with a query string.\n\t *\n\t * Returns results sorted by BM25 score (highest first).\n\t * Scores are normalized to [0, 1] — the top result gets 1.0.\n\t *\n\t * Only documents containing at least one query term are scored,\n\t * making queries fast even on large indexes.\n\t */\n\tsearch(\n\t\tquery: string,\n\t\ttopK: number,\n\t\tthreshold = 0.0,\n\t): Array<{\n\t\treadonly id: string;\n\t\treadonly score: number;\n\t\treadonly metadata: Record<string, unknown>;\n\t}> {\n\t\tif (this.documents.length === 0) return [];\n\n\t\tconst queryTokens = tokenize(query);\n\t\tif (queryTokens.length === 0) return [];\n\n\t\t// Sparse score accumulator — only touched documents get entries\n\t\tconst scores = new Float64Array(this.documents.length);\n\t\tlet hasScores = false;\n\n\t\tfor (const token of queryTokens) {\n\t\t\tconst idf = this.idfCache.get(token);\n\t\t\tif (idf === undefined || idf <= 0) continue;\n\n\t\t\tconst postings = this.invertedIndex.get(token);\n\t\t\tif (!postings) continue;\n\n\t\t\tfor (const posting of postings) {\n\t\t\t\tconst docLen = this.documents[posting.docIdx]!.length;\n\t\t\t\tconst tf = posting.tf;\n\n\t\t\t\t// Okapi BM25 scoring\n\t\t\t\tconst numerator = tf * (this.k1 + 1);\n\t\t\t\tconst denominator = tf + this.k1 * (1 - this.b + this.b * (docLen / this.avgdl));\n\t\t\t\tscores[posting.docIdx] = scores[posting.docIdx]! + idf * (numerator / denominator);\n\t\t\t\thasScores = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!hasScores) return [];\n\n\t\t// Find max score for normalization\n\t\tlet maxScore = 0;\n\t\tfor (let i = 0; i < scores.length; i++) {\n\t\t\tif (scores[i]! > maxScore) maxScore = scores[i]!;\n\t\t}\n\n\t\tif (maxScore === 0) return [];\n\n\t\t// Collect results, normalize to [0, 1], filter by threshold\n\t\tconst results: Array<{ id: string; score: number; metadata: Record<string, unknown> }> = [];\n\n\t\tfor (let i = 0; i < scores.length; i++) {\n\t\t\tconst raw = scores[i]!;\n\t\t\tif (raw === 0) continue;\n\n\t\t\tconst normalized = raw / maxScore;\n\t\t\tif (normalized >= threshold) {\n\t\t\t\tresults.push({\n\t\t\t\t\tid: this.documents[i]!.id,\n\t\t\t\t\tscore: normalized,\n\t\t\t\t\tmetadata: this.documents[i]!.metadata,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Sort by score descending, take topK\n\t\tresults.sort((a, b) => b.score - a.score);\n\t\treturn results.slice(0, topK);\n\t}\n\n\t/** Number of indexed documents */\n\tsize(): number {\n\t\treturn this.documents.length;\n\t}\n\n\t/** Serialize to a JSON-compatible snapshot */\n\tserialize(): BM25Snapshot {\n\t\treturn {\n\t\t\tversion: 2,\n\t\t\tdocuments: this.documents.map((d) => ({\n\t\t\t\tid: d.id,\n\t\t\t\tlength: d.length,\n\t\t\t\tmetadata: d.metadata,\n\t\t\t})),\n\t\t\tinvertedIndex: Array.from(this.invertedIndex.entries()),\n\t\t\tidf: Array.from(this.idfCache.entries()),\n\t\t\tavgdl: this.avgdl,\n\t\t\tk1: this.k1,\n\t\t\tb: this.b,\n\t\t};\n\t}\n\n\t/** Restore from a serialized snapshot */\n\tdeserialize(data: unknown): void {\n\t\tconst obj = data as BM25Snapshot;\n\t\tif (!obj || typeof obj !== 'object') {\n\t\t\tthrow new Error('Invalid BM25 snapshot: expected an object');\n\t\t}\n\t\tif (obj.version !== 2) {\n\t\t\tthrow new Error(`Unsupported BM25 snapshot version: ${obj.version}`);\n\t\t}\n\n\t\tthis.documents = obj.documents.map((d) => ({\n\t\t\tid: d.id,\n\t\t\tlength: d.length,\n\t\t\tmetadata: d.metadata,\n\t\t}));\n\t\tthis.invertedIndex = new Map(obj.invertedIndex.map(([k, v]) => [k, [...v]]));\n\t\tthis.idfCache = new Map(obj.idf);\n\t\tthis.avgdl = obj.avgdl;\n\t\tthis.totalDocLength = this.documents.reduce((sum, d) => sum + d.length, 0);\n\t}\n\n\t/** Recompute IDF values for all terms in the inverted index */\n\tprivate recomputeIDF(): void {\n\t\tconst N = this.documents.length;\n\t\tthis.idfCache.clear();\n\n\t\tfor (const [term, postings] of this.invertedIndex) {\n\t\t\tconst df = postings.length;\n\t\t\t// BM25 IDF: log((N - df + 0.5) / (df + 0.5) + 1)\n\t\t\tthis.idfCache.set(term, Math.log((N - df + 0.5) / (df + 0.5) + 1));\n\t\t}\n\t}\n}\n\n/**\n * Tokenize text: lowercase, strip punctuation, split on whitespace,\n * filter stop words and single-character tokens.\n */\nfunction tokenize(text: string): string[] {\n\treturn text\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9\\s-]/g, ' ')\n\t\t.split(/\\s+/)\n\t\t.filter((t) => t.length > 1 && !STOP_WORDS.has(t));\n}\n\n/**\n * Common English stop words — filtered during tokenization.\n */\nconst STOP_WORDS = new Set([\n\t'a',\n\t'an',\n\t'the',\n\t'is',\n\t'are',\n\t'was',\n\t'were',\n\t'be',\n\t'been',\n\t'being',\n\t'have',\n\t'has',\n\t'had',\n\t'do',\n\t'does',\n\t'did',\n\t'will',\n\t'would',\n\t'could',\n\t'should',\n\t'may',\n\t'might',\n\t'shall',\n\t'can',\n\t'must',\n\t'to',\n\t'of',\n\t'in',\n\t'for',\n\t'on',\n\t'with',\n\t'at',\n\t'by',\n\t'from',\n\t'as',\n\t'into',\n\t'through',\n\t'during',\n\t'before',\n\t'after',\n\t'above',\n\t'below',\n\t'between',\n\t'out',\n\t'off',\n\t'over',\n\t'under',\n\t'again',\n\t'further',\n\t'then',\n\t'once',\n\t'here',\n\t'there',\n\t'when',\n\t'where',\n\t'why',\n\t'how',\n\t'all',\n\t'each',\n\t'every',\n\t'both',\n\t'few',\n\t'more',\n\t'most',\n\t'other',\n\t'some',\n\t'such',\n\t'no',\n\t'nor',\n\t'not',\n\t'only',\n\t'own',\n\t'same',\n\t'so',\n\t'than',\n\t'too',\n\t'very',\n\t'and',\n\t'but',\n\t'or',\n\t'if',\n\t'it',\n\t'its',\n\t'this',\n\t'that',\n\t'these',\n\t'those',\n\t'he',\n\t'she',\n\t'they',\n\t'we',\n\t'you',\n\t'i',\n\t'me',\n\t'my',\n\t'your',\n\t'his',\n\t'her',\n\t'their',\n\t'our',\n\t'what',\n\t'which',\n\t'who',\n\t'whom',\n]);\n","/**\n * Context extractor for contextual retrieval.\n *\n * Extracts supplementary terms from a skill's body and sections\n * to enrich the description before BM25 indexing. This is a\n * deterministic, zero-dependency alternative to LLM-generated\n * chunk context (see: Anthropic's contextual retrieval paper).\n *\n * @packageDocumentation\n */\n\n/** Minimal skill shape required for context extraction */\nexport interface ContextInput {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly body?: string;\n\treadonly sections?: ReadonlyArray<{\n\t\treadonly heading: string;\n\t\treadonly depth: number;\n\t\treadonly content: string;\n\t}>;\n}\n\n/** Maximum number of context tokens to prepend */\nconst MAX_CONTEXT_TOKENS = 80;\n\n/**\n * Extract supplementary context from a skill's body and structure.\n *\n * Returns a space-separated string of unique terms derived from:\n * 1. Skill name parts (split on `-` and `_`)\n * 2. Section headings\n * 3. Inline code references (backtick-wrapped)\n * 4. Key terms from body text\n *\n * Terms already present in the description are omitted.\n * Result is truncated to ~80 tokens.\n *\n * Returns empty string if no useful context can be extracted.\n */\nexport function extractContext(skill: ContextInput): string {\n\tconst terms: string[] = [];\n\n\t// 1. Split skill name on - and _\n\tconst nameParts = skill.name\n\t\t.split(/[-_]/)\n\t\t.map((p) => p.toLowerCase())\n\t\t.filter((p) => p.length > 1);\n\tterms.push(...nameParts);\n\n\t// 2. Section headings\n\tif (skill.sections) {\n\t\tfor (const section of skill.sections) {\n\t\t\tconst headingWords = section.heading\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9\\s-]/g, ' ')\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter((w) => w.length > 1);\n\t\t\tterms.push(...headingWords);\n\t\t}\n\t}\n\n\t// 3. Inline code references from body\n\tif (skill.body) {\n\t\tconst codeRefs = extractCodeRefs(skill.body);\n\t\tterms.push(...codeRefs);\n\t}\n\n\t// 4. Key terms from section content (longer words, likely meaningful)\n\tif (skill.sections) {\n\t\tfor (const section of skill.sections) {\n\t\t\tconst contentWords = section.content\n\t\t\t\t.toLowerCase()\n\t\t\t\t.replace(/[^a-z0-9\\s-]/g, ' ')\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter((w) => w.length > 3 && !COMMON_WORDS.has(w));\n\t\t\tterms.push(...contentWords);\n\t\t}\n\t}\n\n\t// Dedup against description\n\tconst descLower = skill.description.toLowerCase();\n\tconst descTokens = new Set(\n\t\tdescLower\n\t\t\t.replace(/[^a-z0-9\\s-]/g, ' ')\n\t\t\t.split(/\\s+/)\n\t\t\t.filter((t) => t.length > 0),\n\t);\n\n\tconst seen = new Set<string>();\n\tconst unique: string[] = [];\n\n\tfor (const term of terms) {\n\t\tconst lower = term.toLowerCase();\n\t\tif (lower.length < 2) continue;\n\t\tif (seen.has(lower)) continue;\n\t\tif (descTokens.has(lower)) continue;\n\t\tseen.add(lower);\n\t\tunique.push(lower);\n\t}\n\n\t// Truncate to MAX_CONTEXT_TOKENS\n\treturn truncateTokens(unique, MAX_CONTEXT_TOKENS).join(' ');\n}\n\n/**\n * Extract inline code references from markdown body.\n * Matches single-backtick code spans (not fenced blocks).\n */\nfunction extractCodeRefs(body: string): string[] {\n\tconst refs: string[] = [];\n\tconst pattern = /`([^`\\n]+)`/g;\n\n\tfor (const match of body.matchAll(pattern)) {\n\t\tconst code = match[1]!.trim();\n\t\t// Split compound code refs (e.g. \"vercel --prod\" → [\"vercel\", \"--prod\"])\n\t\tconst parts = code\n\t\t\t.split(/\\s+/)\n\t\t\t.map((p) => p.replace(/^-+/, '').toLowerCase())\n\t\t\t.filter((p) => p.length > 1);\n\t\trefs.push(...parts);\n\t}\n\n\treturn refs;\n}\n\n/**\n * Truncate a list of tokens to a maximum count.\n */\nfunction truncateTokens(tokens: string[], max: number): string[] {\n\tif (tokens.length <= max) return tokens;\n\treturn tokens.slice(0, max);\n}\n\n/**\n * Common English words to filter from section content.\n * More aggressive than BM25 stop words — we only want meaningful terms.\n */\nconst COMMON_WORDS = new Set([\n\t'also',\n\t'about',\n\t'after',\n\t'again',\n\t'been',\n\t'before',\n\t'being',\n\t'between',\n\t'both',\n\t'check',\n\t'could',\n\t'does',\n\t'done',\n\t'down',\n\t'each',\n\t'even',\n\t'every',\n\t'first',\n\t'following',\n\t'from',\n\t'have',\n\t'here',\n\t'into',\n\t'just',\n\t'know',\n\t'like',\n\t'make',\n\t'many',\n\t'might',\n\t'more',\n\t'most',\n\t'much',\n\t'must',\n\t'need',\n\t'only',\n\t'other',\n\t'over',\n\t'same',\n\t'should',\n\t'some',\n\t'such',\n\t'sure',\n\t'take',\n\t'than',\n\t'that',\n\t'them',\n\t'then',\n\t'there',\n\t'these',\n\t'they',\n\t'this',\n\t'those',\n\t'through',\n\t'under',\n\t'very',\n\t'want',\n\t'well',\n\t'were',\n\t'what',\n\t'when',\n\t'where',\n\t'which',\n\t'while',\n\t'will',\n\t'with',\n\t'would',\n\t'your',\n]);\n","import type { EmbeddingProvider } from './interface.js';\n\n/**\n * Local TF-IDF based embedding provider.\n *\n * Uses a deterministic hash-based approach to create sparse-then-dense\n * embeddings from text. No external API calls or model downloads needed.\n *\n * Quality is lower than neural embedding models, but sufficient for\n * keyword-heavy skill descriptions where exact word matching matters.\n * Ideal for catalogs under 500 skills.\n */\nexport class LocalEmbeddingProvider implements EmbeddingProvider {\n\treadonly name = 'local-tfidf';\n\treadonly dimensions: number;\n\n\tprivate vocabulary: Map<string, number> = new Map();\n\tprivate idfValues: Map<string, number> = new Map();\n\tprivate isBuilt = false;\n\n\tconstructor(dimensions = 256) {\n\t\tthis.dimensions = dimensions;\n\t}\n\n\t/**\n\t * Build the vocabulary and IDF values from a corpus.\n\t * Call this once after indexing all skill descriptions.\n\t */\n\tbuildVocabulary(texts: string[]): void {\n\t\tconst docCount = texts.length;\n\t\tconst termDocFreq = new Map<string, number>();\n\n\t\tfor (const text of texts) {\n\t\t\tconst tokens = new Set(tokenize(text));\n\t\t\tfor (const token of tokens) {\n\t\t\t\ttermDocFreq.set(token, (termDocFreq.get(token) ?? 0) + 1);\n\t\t\t}\n\t\t}\n\n\t\t// Compute IDF values\n\t\tfor (const [term, df] of termDocFreq) {\n\t\t\tthis.idfValues.set(term, Math.log((docCount + 1) / (df + 1)) + 1);\n\t\t}\n\n\t\t// Build vocabulary (top terms by IDF)\n\t\tconst sorted = Array.from(this.idfValues.entries()).sort((a, b) => b[1] - a[1]);\n\t\tlet idx = 0;\n\t\tfor (const [term] of sorted) {\n\t\t\tthis.vocabulary.set(term, idx % this.dimensions);\n\t\t\tidx++;\n\t\t}\n\n\t\tthis.isBuilt = true;\n\t}\n\n\tasync embed(texts: string[]): Promise<number[][]> {\n\t\treturn texts.map((text) => this.embedSingle(text));\n\t}\n\n\tprivate embedSingle(text: string): number[] {\n\t\tconst vector = new Float64Array(this.dimensions);\n\t\tconst tokens = tokenize(text);\n\t\tconst termFreq = new Map<string, number>();\n\n\t\tfor (const token of tokens) {\n\t\t\ttermFreq.set(token, (termFreq.get(token) ?? 0) + 1);\n\t\t}\n\n\t\tfor (const [term, tf] of termFreq) {\n\t\t\tconst idf = this.idfValues.get(term) ?? 1;\n\t\t\tconst tfidf = (1 + Math.log(tf)) * idf;\n\n\t\t\tif (this.isBuilt) {\n\t\t\t\t// Use vocabulary mapping if built\n\t\t\t\tconst idx = this.vocabulary.get(term);\n\t\t\t\tif (idx !== undefined) {\n\t\t\t\t\tvector[idx] = (vector[idx] ?? 0) + tfidf;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Fall back to hash-based indexing\n\t\t\t\tconst idx = hashToIndex(term, this.dimensions);\n\t\t\t\tvector[idx] = (vector[idx] ?? 0) + tfidf;\n\t\t\t}\n\t\t}\n\n\t\t// L2 normalize\n\t\treturn l2Normalize(Array.from(vector));\n\t}\n}\n\n/**\n * Tokenize text into lowercase tokens, removing stop words and punctuation.\n */\nfunction tokenize(text: string): string[] {\n\treturn text\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9\\s-]/g, ' ')\n\t\t.split(/\\s+/)\n\t\t.filter((t) => t.length > 1 && !STOP_WORDS.has(t));\n}\n\n/**\n * Deterministic hash of a string to an index in range [0, size).\n * Uses FNV-1a hash.\n */\nfunction hashToIndex(str: string, size: number): number {\n\tlet hash = 0x811c9dc5; // FNV offset basis\n\tfor (let i = 0; i < str.length; i++) {\n\t\thash ^= str.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193); // FNV prime\n\t}\n\treturn Math.abs(hash) % size;\n}\n\n/**\n * L2 normalize a vector.\n */\nfunction l2Normalize(vector: number[]): number[] {\n\tlet sum = 0;\n\tfor (const v of vector) {\n\t\tsum += v * v;\n\t}\n\tconst magnitude = Math.sqrt(sum);\n\tif (magnitude === 0) return vector;\n\treturn vector.map((v) => v / magnitude);\n}\n\n/**\n * Common English stop words.\n */\nconst STOP_WORDS = new Set([\n\t'a',\n\t'an',\n\t'the',\n\t'is',\n\t'are',\n\t'was',\n\t'were',\n\t'be',\n\t'been',\n\t'being',\n\t'have',\n\t'has',\n\t'had',\n\t'do',\n\t'does',\n\t'did',\n\t'will',\n\t'would',\n\t'could',\n\t'should',\n\t'may',\n\t'might',\n\t'shall',\n\t'can',\n\t'must',\n\t'to',\n\t'of',\n\t'in',\n\t'for',\n\t'on',\n\t'with',\n\t'at',\n\t'by',\n\t'from',\n\t'as',\n\t'into',\n\t'through',\n\t'during',\n\t'before',\n\t'after',\n\t'above',\n\t'below',\n\t'between',\n\t'out',\n\t'off',\n\t'over',\n\t'under',\n\t'again',\n\t'further',\n\t'then',\n\t'once',\n\t'here',\n\t'there',\n\t'when',\n\t'where',\n\t'why',\n\t'how',\n\t'all',\n\t'each',\n\t'every',\n\t'both',\n\t'few',\n\t'more',\n\t'most',\n\t'other',\n\t'some',\n\t'such',\n\t'no',\n\t'nor',\n\t'not',\n\t'only',\n\t'own',\n\t'same',\n\t'so',\n\t'than',\n\t'too',\n\t'very',\n\t'and',\n\t'but',\n\t'or',\n\t'if',\n\t'it',\n\t'its',\n\t'this',\n\t'that',\n\t'these',\n\t'those',\n\t'he',\n\t'she',\n\t'they',\n\t'we',\n\t'you',\n\t'i',\n\t'me',\n\t'my',\n\t'your',\n\t'his',\n\t'her',\n\t'their',\n\t'our',\n\t'what',\n\t'which',\n\t'who',\n\t'whom',\n]);\n","import type { SearchResult, VectorEntry, VectorStore } from './interface.js';\n\n/**\n * In-memory vector store using brute-force cosine similarity search.\n *\n * Suitable for catalogs of up to ~1,000 skills. For larger catalogs,\n * use the SQLite backend.\n *\n * At 1,000 entries with 256-dimensional vectors, search takes <5ms.\n */\nexport class MemoryVectorStore implements VectorStore {\n\tprivate entries: VectorEntry[] = [];\n\n\tasync add(entries: VectorEntry[]): Promise<void> {\n\t\tthis.entries.push(...entries);\n\t}\n\n\tasync search(queryVector: number[], topK: number, threshold = 0.0): Promise<SearchResult[]> {\n\t\tconst scored = this.entries.map((entry) => ({\n\t\t\tid: entry.id,\n\t\t\tscore: cosineSimilarity(queryVector, entry.vector),\n\t\t\tmetadata: entry.metadata,\n\t\t}));\n\n\t\treturn scored\n\t\t\t.filter((r) => r.score >= threshold)\n\t\t\t.sort((a, b) => b.score - a.score)\n\t\t\t.slice(0, topK);\n\t}\n\n\tasync remove(ids: string[]): Promise<void> {\n\t\tconst idSet = new Set(ids);\n\t\tthis.entries = this.entries.filter((e) => !idSet.has(e.id));\n\t}\n\n\tsize(): number {\n\t\treturn this.entries.length;\n\t}\n\n\tserialize(): unknown {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tentries: this.entries.map((e) => ({\n\t\t\t\tid: e.id,\n\t\t\t\tvector: e.vector,\n\t\t\t\tmetadata: e.metadata,\n\t\t\t})),\n\t\t};\n\t}\n\n\tdeserialize(data: unknown): void {\n\t\tif (!data || typeof data !== 'object') {\n\t\t\tthrow new Error('Invalid snapshot: expected an object');\n\t\t}\n\t\tconst obj = data as Record<string, unknown>;\n\t\tif (obj.version !== 1) {\n\t\t\tthrow new Error(`Unsupported vector store version: ${obj.version}`);\n\t\t}\n\t\tif (!Array.isArray(obj.entries)) {\n\t\t\tthrow new Error('Invalid snapshot: entries must be an array');\n\t\t}\n\t\tfor (const entry of obj.entries as unknown[]) {\n\t\t\tif (!entry || typeof entry !== 'object') {\n\t\t\t\tthrow new Error('Invalid snapshot: each entry must be an object');\n\t\t\t}\n\t\t\tconst e = entry as Record<string, unknown>;\n\t\t\tif (typeof e.id !== 'string') {\n\t\t\t\tthrow new Error('Invalid snapshot: entry id must be a string');\n\t\t\t}\n\t\t\tif (\n\t\t\t\t!Array.isArray(e.vector) ||\n\t\t\t\t!(e.vector as unknown[]).every((v) => typeof v === 'number')\n\t\t\t) {\n\t\t\t\tthrow new Error('Invalid snapshot: entry vector must be an array of numbers');\n\t\t\t}\n\t\t}\n\t\tthis.entries = obj.entries as VectorEntry[];\n\t}\n}\n\n/**\n * Compute cosine similarity between two vectors.\n * Returns a value between -1 and 1. For normalized vectors, this is equivalent\n * to the dot product.\n */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n\tif (a.length !== b.length) {\n\t\tthrow new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);\n\t}\n\n\tlet dotProduct = 0;\n\tlet normA = 0;\n\tlet normB = 0;\n\n\tfor (let i = 0; i < a.length; i++) {\n\t\tdotProduct += a[i]! * b[i]!;\n\t\tnormA += a[i]! * a[i]!;\n\t\tnormB += b[i]! * b[i]!;\n\t}\n\n\tconst denominator = Math.sqrt(normA) * Math.sqrt(normB);\n\tif (denominator === 0) return 0;\n\n\treturn dotProduct / denominator;\n}\n","import { parseSkill, resolveSkillFiles } from '@skill-tools/core';\nimport type { BM25Options } from './bm25/index.js';\nimport { BM25Index } from './bm25/index.js';\nimport { extractContext } from './context/extractor.js';\nimport type { EmbeddingConfig, EmbeddingProvider } from './embeddings/interface.js';\nimport { LocalEmbeddingProvider } from './embeddings/local.js';\nimport type { VectorStore } from './stores/interface.js';\nimport { MemoryVectorStore } from './stores/memory.js';\n\n/**\n * A skill entry prepared for indexing.\n */\nexport interface SkillEntry {\n\t/** Unique identifier (typically the skill name) */\n\treadonly name: string;\n\t/** The description text to embed */\n\treadonly description: string;\n\t/** Path to the SKILL.md file */\n\treadonly path?: string;\n\t/** Additional metadata to store alongside the embedding */\n\treadonly metadata?: Record<string, unknown>;\n\t/** Raw markdown body (used for contextual retrieval) */\n\treadonly body?: string;\n\t/** Parsed sections from the SKILL.md (used for contextual retrieval) */\n\treadonly sections?: ReadonlyArray<{\n\t\treadonly heading: string;\n\t\treadonly depth: number;\n\t\treadonly content: string;\n\t}>;\n}\n\n/**\n * Result of selecting a skill for a query.\n */\nexport interface SelectionResult {\n\t/** Skill name/ID */\n\treadonly skill: string;\n\t/** Similarity score (0-1) */\n\treadonly score: number;\n\t/** Metadata from the indexed skill */\n\treadonly metadata: Record<string, unknown>;\n}\n\n/**\n * Options for skill selection queries.\n */\nexport interface SelectOptions {\n\t/** Number of results to return (default: 5) */\n\treadonly topK?: number;\n\t/** Minimum similarity threshold (default: 0.0) */\n\treadonly threshold?: number;\n\t/** Skill names to boost in ranking */\n\treadonly boost?: string[];\n\t/** Skill name patterns to exclude */\n\treadonly exclude?: string[];\n}\n\n/**\n * Options for the SkillRouter constructor.\n */\nexport interface SkillRouterOptions {\n\t/** Embedding provider configuration. Defaults to BM25 ('local'). */\n\treadonly embedding?: EmbeddingConfig;\n\t/** BM25 tuning parameters (only used with the default BM25 engine) */\n\treadonly bm25?: BM25Options;\n\t/**\n\t * Enable contextual retrieval. When true (default), skills with\n\t * body or sections will have supplementary context extracted and\n\t * prepended to their description before indexing.\n\t * Only affects indexing — result descriptions stay unchanged.\n\t */\n\treadonly context?: boolean;\n}\n\n/**\n * SkillRouter — Skill selection middleware using BM25 full-text search.\n *\n * Indexes skill descriptions and enables fast, ranked search to find\n * the most relevant skills for a given query. Uses Okapi BM25 by default\n * with zero external dependencies.\n *\n * For neural/semantic embeddings, pass a custom embedding provider\n * via the `embedding` option.\n *\n * @example\n * ```ts\n * const router = new SkillRouter();\n * await router.indexSkills([\n * { name: 'deploy-vercel', description: 'Deploy apps to Vercel...' },\n * { name: 'run-tests', description: 'Execute test suites...' },\n * ]);\n *\n * const results = await router.select('deploy my app');\n * // => [{ skill: 'deploy-vercel', score: 0.89, ... }]\n * ```\n */\nexport class SkillRouter {\n\t/** BM25 index — used when no external embedding provider is configured */\n\tprivate readonly bm25: BM25Index | null;\n\n\t/** Embedding provider — used with custom/openai/ollama providers */\n\tprivate readonly embedding: EmbeddingProvider | null;\n\n\t/** Vector store — used alongside embedding provider */\n\tprivate readonly store: VectorStore | null;\n\n\t/** Whether the router uses the BM25 engine (true) or embedding+store (false) */\n\tprivate readonly usesBM25: boolean;\n\n\t/** Whether contextual retrieval is enabled */\n\tprivate readonly contextEnabled: boolean;\n\n\tprivate skillNames: Set<string> = new Set();\n\n\tconstructor(options?: SkillRouterOptions) {\n\t\tconst embeddingConfig = options?.embedding ?? 'local';\n\t\tthis.contextEnabled = options?.context !== false;\n\n\t\tif (embeddingConfig === 'local') {\n\t\t\t// Default: BM25 full-text search — fast, zero-dependency\n\t\t\tthis.bm25 = new BM25Index(options?.bm25);\n\t\t\tthis.embedding = null;\n\t\t\tthis.store = null;\n\t\t\tthis.usesBM25 = true;\n\t\t} else {\n\t\t\t// External embedding provider: use embedding + vector store\n\t\t\tthis.embedding = createEmbeddingProvider(embeddingConfig);\n\t\t\tthis.store = new MemoryVectorStore();\n\t\t\tthis.bm25 = null;\n\t\t\tthis.usesBM25 = false;\n\t\t}\n\t}\n\n\t/**\n\t * Index a list of skill entries.\n\t * With BM25 (default): indexes description text directly.\n\t * With embeddings: embeds descriptions and stores vectors.\n\t */\n\tasync indexSkills(skills: SkillEntry[]): Promise<void> {\n\t\tif (skills.length === 0) return;\n\n\t\tif (this.usesBM25 && this.bm25) {\n\t\t\t// BM25 path — direct text indexing, no vectors\n\t\t\tthis.bm25.add(\n\t\t\t\tskills.map((s) => ({\n\t\t\t\t\tid: s.name,\n\t\t\t\t\ttext: this.enrichText(s),\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tdescription: s.description,\n\t\t\t\t\t\tpath: s.path,\n\t\t\t\t\t\t...s.metadata,\n\t\t\t\t\t},\n\t\t\t\t})),\n\t\t\t);\n\t\t} else if (this.embedding && this.store) {\n\t\t\t// Embedding path — vectorize and store\n\t\t\tconst descriptions = skills.map((s) => this.enrichText(s));\n\n\t\t\tif (this.embedding instanceof LocalEmbeddingProvider) {\n\t\t\t\tthis.embedding.buildVocabulary(descriptions);\n\t\t\t}\n\n\t\t\tconst vectors = await this.embedding.embed(descriptions);\n\n\t\t\tconst entries = skills.map((skill, i) => ({\n\t\t\t\tid: skill.name,\n\t\t\t\tvector: vectors[i]!,\n\t\t\t\tmetadata: {\n\t\t\t\t\tdescription: skill.description,\n\t\t\t\t\tpath: skill.path,\n\t\t\t\t\t...skill.metadata,\n\t\t\t\t},\n\t\t\t}));\n\n\t\t\tawait this.store.add(entries);\n\t\t}\n\n\t\tfor (const skill of skills) {\n\t\t\tthis.skillNames.add(skill.name);\n\t\t}\n\t}\n\n\t/**\n\t * Index all SKILL.md files in a directory.\n\t * Parses each file and indexes its description.\n\t */\n\tasync indexDirectory(dirPath: string): Promise<number> {\n\t\tconst locations = await resolveSkillFiles(dirPath);\n\t\tconst skills: SkillEntry[] = [];\n\n\t\tfor (const location of locations) {\n\t\t\tconst result = await parseSkill(location.skillFile);\n\t\t\tif (result.ok && result.skill.metadata.description) {\n\t\t\t\tskills.push({\n\t\t\t\t\tname: result.skill.metadata.name ?? location.dirName,\n\t\t\t\t\tdescription: result.skill.metadata.description,\n\t\t\t\t\tpath: location.skillFile,\n\t\t\t\t\tbody: result.skill.body,\n\t\t\t\t\tsections: result.skill.sections,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tawait this.indexSkills(skills);\n\t\treturn skills.length;\n\t}\n\n\t/**\n\t * Select the most relevant skills for a query.\n\t */\n\tasync select(query: string, options?: SelectOptions): Promise<SelectionResult[]> {\n\t\tconst topK = options?.topK ?? 5;\n\t\tconst threshold = options?.threshold ?? 0.0;\n\t\tconst boost = new Set(options?.boost ?? []);\n\t\tconst exclude = options?.exclude ?? [];\n\n\t\tlet results: Array<{ id: string; score: number; metadata: Record<string, unknown> }>;\n\n\t\tif (this.usesBM25 && this.bm25) {\n\t\t\t// BM25 path — direct text scoring\n\t\t\tresults = this.bm25.search(query, topK * 2, threshold);\n\t\t} else if (this.embedding && this.store) {\n\t\t\t// Embedding path — vectorize query and search store\n\t\t\tconst [queryVector] = await this.embedding.embed([query]);\n\t\t\tif (!queryVector) {\n\t\t\t\tthrow new Error('Embedding provider returned empty result for query');\n\t\t\t}\n\t\t\tresults = await this.store.search(queryVector, topK * 2, threshold);\n\t\t} else {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Apply exclude filters\n\t\tif (exclude.length > 0) {\n\t\t\tresults = results.filter((r) => {\n\t\t\t\treturn !exclude.some((pattern) => {\n\t\t\t\t\tif (pattern.endsWith('*')) {\n\t\t\t\t\t\treturn r.id.startsWith(pattern.slice(0, -1));\n\t\t\t\t\t}\n\t\t\t\t\treturn r.id === pattern;\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t// Apply boost\n\t\tif (boost.size > 0) {\n\t\t\tresults = results.map((r) => ({\n\t\t\t\t...r,\n\t\t\t\tscore: boost.has(r.id) ? r.score * 1.2 : r.score,\n\t\t\t}));\n\t\t\tresults.sort((a, b) => b.score - a.score);\n\t\t}\n\n\t\treturn results.slice(0, topK).map((r) => ({\n\t\t\tskill: r.id,\n\t\t\tscore: r.score,\n\t\t\tmetadata: r.metadata,\n\t\t}));\n\t}\n\n\t/**\n\t * Detect skills with overlapping descriptions.\n\t */\n\tasync detectConflicts(threshold = 0.85): Promise<ConflictGroup[]> {\n\t\tconst conflicts: ConflictGroup[] = [];\n\t\tconst names = Array.from(this.skillNames);\n\n\t\tif (this.usesBM25 && this.bm25) {\n\t\t\t// BM25 path — search each skill name against descriptions\n\t\t\tfor (const name of names) {\n\t\t\t\tconst results = this.bm25.search(name, names.length, threshold);\n\t\t\t\tconst similar = results\n\t\t\t\t\t.filter((r) => r.id !== name && r.score >= threshold)\n\t\t\t\t\t.map((r) => r.id);\n\n\t\t\t\tif (similar.length > 0) {\n\t\t\t\t\tconst existing = conflicts.find(\n\t\t\t\t\t\t(c) => c.skills.includes(name) || similar.some((s) => c.skills.includes(s)),\n\t\t\t\t\t);\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\t\tskills: [name, ...similar],\n\t\t\t\t\t\t\tsimilarity: results.find((r) => r.id !== name)?.score ?? threshold,\n\t\t\t\t\t\t\tsuggestion:\n\t\t\t\t\t\t\t\t'These skills have highly similar descriptions. Consider differentiating their trigger contexts.',\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.embedding && this.store) {\n\t\t\t// Embedding path — embed skill names and search store\n\t\t\tfor (const name of names) {\n\t\t\t\tconst results = await this.store.search(\n\t\t\t\t\t(await this.embedding.embed([name]))[0]!,\n\t\t\t\t\tnames.length,\n\t\t\t\t\tthreshold,\n\t\t\t\t);\n\n\t\t\t\tconst similar = results\n\t\t\t\t\t.filter((r) => r.id !== name && r.score >= threshold)\n\t\t\t\t\t.map((r) => r.id);\n\n\t\t\t\tif (similar.length > 0) {\n\t\t\t\t\tconst existing = conflicts.find(\n\t\t\t\t\t\t(c) => c.skills.includes(name) || similar.some((s) => c.skills.includes(s)),\n\t\t\t\t\t);\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\t\tskills: [name, ...similar],\n\t\t\t\t\t\t\tsimilarity: results.find((r) => r.id !== name)?.score ?? threshold,\n\t\t\t\t\t\t\tsuggestion:\n\t\t\t\t\t\t\t\t'These skills have highly similar descriptions. Consider differentiating their trigger contexts.',\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn conflicts;\n\t}\n\n\t/**\n\t * Build the text to index for a skill entry.\n\t * When contextual retrieval is enabled and the skill has body/sections,\n\t * prepends extracted context to the description.\n\t */\n\tprivate enrichText(skill: SkillEntry): string {\n\t\tif (this.contextEnabled && (skill.body || skill.sections)) {\n\t\t\tconst ctx = extractContext(skill);\n\t\t\tif (ctx) return `${ctx} ${skill.description}`;\n\t\t}\n\t\treturn skill.description;\n\t}\n\n\t/**\n\t * Get the number of indexed skills.\n\t */\n\tget count(): number {\n\t\tif (this.usesBM25 && this.bm25) {\n\t\t\treturn this.bm25.size();\n\t\t}\n\t\treturn this.store?.size() ?? 0;\n\t}\n\n\t/**\n\t * Save the index to a JSON-serializable object.\n\t */\n\tsave(): SkillRouterSnapshot {\n\t\tif (this.usesBM25 && this.bm25) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tembeddingProvider: 'bm25',\n\t\t\t\tdimensions: 0,\n\t\t\t\tstore: this.bm25.serialize(),\n\t\t\t\tskillNames: Array.from(this.skillNames),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tembeddingProvider: this.embedding!.name,\n\t\t\tdimensions: this.embedding!.dimensions,\n\t\t\tstore: this.store!.serialize(),\n\t\t\tskillNames: Array.from(this.skillNames),\n\t\t};\n\t}\n\n\t/**\n\t * Load a previously saved index.\n\t * Validates that the snapshot format matches the current engine.\n\t */\n\tload(snapshot: SkillRouterSnapshot): void {\n\t\tif (this.usesBM25 && this.bm25) {\n\t\t\tif (snapshot.embeddingProvider !== 'bm25') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Cannot load snapshot from provider \"${snapshot.embeddingProvider}\" into BM25 router. ` +\n\t\t\t\t\t\t'Create the router with a matching embedding config.',\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.bm25.deserialize(snapshot.store);\n\t\t} else if (this.embedding && this.store) {\n\t\t\tif (snapshot.dimensions !== this.embedding.dimensions) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Snapshot dimensions (${snapshot.dimensions}) don't match current provider dimensions (${this.embedding.dimensions})`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.store.deserialize(snapshot.store);\n\t\t}\n\n\t\tthis.skillNames = new Set(snapshot.skillNames);\n\t}\n\n\t/**\n\t * Create a SkillRouter from a saved snapshot.\n\t */\n\tstatic fromSnapshot(snapshot: SkillRouterSnapshot, options?: SkillRouterOptions): SkillRouter {\n\t\tconst router = new SkillRouter(options);\n\t\trouter.load(snapshot);\n\t\treturn router;\n\t}\n}\n\n/**\n * A group of conflicting (highly similar) skills.\n */\nexport interface ConflictGroup {\n\treadonly skills: string[];\n\treadonly similarity: number;\n\treadonly suggestion: string;\n}\n\n/**\n * Serialized snapshot of a SkillRouter state.\n */\nexport interface SkillRouterSnapshot {\n\treadonly version: number;\n\treadonly embeddingProvider: string;\n\treadonly dimensions: number;\n\treadonly store: unknown;\n\treadonly skillNames: string[];\n}\n\n/**\n * Create an embedding provider from config.\n */\nfunction createEmbeddingProvider(config: EmbeddingConfig): EmbeddingProvider {\n\tif (config === 'local') {\n\t\treturn new LocalEmbeddingProvider();\n\t}\n\n\tif (config.provider === 'custom') {\n\t\treturn {\n\t\t\tname: 'custom',\n\t\t\tdimensions: config.dimensions,\n\t\t\tembed: config.embed,\n\t\t};\n\t}\n\n\t// For openai/ollama, we'd need their SDKs as optional deps.\n\t// For now, throw a helpful error.\n\tthrow new Error(\n\t\t`Embedding provider \"${config.provider}\" requires additional setup. ` +\n\t\t\t'Install the appropriate SDK and configure an API key. ' +\n\t\t\t'See: https://github.com/skill-tools/skill-tools#embedding-providers',\n\t);\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Okapi BM25 — Zero-dependency, optimized text search index.
|
|
3
|
+
*
|
|
4
|
+
* Builds an inverted index from document text and scores queries
|
|
5
|
+
* using the BM25 ranking function. Designed for fast skill routing
|
|
6
|
+
* with catalogs up to ~10,000 entries.
|
|
7
|
+
*
|
|
8
|
+
* Performance:
|
|
9
|
+
* - Index build: O(n * avg_doc_len)
|
|
10
|
+
* - Query: O(q * avg_posting_len) — only visits docs containing query terms
|
|
11
|
+
* - Memory: O(vocabulary_size * avg_posting_len)
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
/** A posting list entry: document index + term frequency */
|
|
16
|
+
interface Posting {
|
|
17
|
+
readonly docIdx: number;
|
|
18
|
+
readonly tf: number;
|
|
19
|
+
}
|
|
20
|
+
/** Serialized snapshot of BM25Index state */
|
|
21
|
+
interface BM25Snapshot {
|
|
22
|
+
readonly version: 2;
|
|
23
|
+
readonly documents: ReadonlyArray<{
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly length: number;
|
|
26
|
+
readonly metadata: Record<string, unknown>;
|
|
27
|
+
}>;
|
|
28
|
+
readonly invertedIndex: ReadonlyArray<[string, Posting[]]>;
|
|
29
|
+
readonly idf: ReadonlyArray<[string, number]>;
|
|
30
|
+
readonly avgdl: number;
|
|
31
|
+
readonly k1: number;
|
|
32
|
+
readonly b: number;
|
|
33
|
+
}
|
|
34
|
+
/** Options for creating a BM25Index */
|
|
35
|
+
interface BM25Options {
|
|
36
|
+
/** Term frequency saturation parameter (default: 1.2) */
|
|
37
|
+
readonly k1?: number;
|
|
38
|
+
/** Document length normalization parameter (default: 0.75) */
|
|
39
|
+
readonly b?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* BM25Index — Fast, zero-dependency full-text search using Okapi BM25.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const idx = new BM25Index();
|
|
47
|
+
* idx.add([
|
|
48
|
+
* { id: 'deploy', text: 'Deploy apps to Vercel production', metadata: {} },
|
|
49
|
+
* { id: 'test', text: 'Run unit tests with coverage', metadata: {} },
|
|
50
|
+
* ]);
|
|
51
|
+
* const results = idx.search('deploy production', 5);
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare class BM25Index {
|
|
55
|
+
private readonly k1;
|
|
56
|
+
private readonly b;
|
|
57
|
+
private documents;
|
|
58
|
+
private invertedIndex;
|
|
59
|
+
private idfCache;
|
|
60
|
+
private avgdl;
|
|
61
|
+
private totalDocLength;
|
|
62
|
+
constructor(options?: BM25Options);
|
|
63
|
+
/**
|
|
64
|
+
* Add documents to the index.
|
|
65
|
+
* Batch operation — IDF is recomputed once after all documents are added.
|
|
66
|
+
*/
|
|
67
|
+
add(entries: ReadonlyArray<{
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly text: string;
|
|
70
|
+
readonly metadata: Record<string, unknown>;
|
|
71
|
+
}>): void;
|
|
72
|
+
/**
|
|
73
|
+
* Remove documents by ID.
|
|
74
|
+
* Rebuilds internal index mappings after removal.
|
|
75
|
+
*/
|
|
76
|
+
remove(ids: readonly string[]): void;
|
|
77
|
+
/**
|
|
78
|
+
* Search the index with a query string.
|
|
79
|
+
*
|
|
80
|
+
* Returns results sorted by BM25 score (highest first).
|
|
81
|
+
* Scores are normalized to [0, 1] — the top result gets 1.0.
|
|
82
|
+
*
|
|
83
|
+
* Only documents containing at least one query term are scored,
|
|
84
|
+
* making queries fast even on large indexes.
|
|
85
|
+
*/
|
|
86
|
+
search(query: string, topK: number, threshold?: number): Array<{
|
|
87
|
+
readonly id: string;
|
|
88
|
+
readonly score: number;
|
|
89
|
+
readonly metadata: Record<string, unknown>;
|
|
90
|
+
}>;
|
|
91
|
+
/** Number of indexed documents */
|
|
92
|
+
size(): number;
|
|
93
|
+
/** Serialize to a JSON-compatible snapshot */
|
|
94
|
+
serialize(): BM25Snapshot;
|
|
95
|
+
/** Restore from a serialized snapshot */
|
|
96
|
+
deserialize(data: unknown): void;
|
|
97
|
+
/** Recompute IDF values for all terms in the inverted index */
|
|
98
|
+
private recomputeIDF;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Context extractor for contextual retrieval.
|
|
103
|
+
*
|
|
104
|
+
* Extracts supplementary terms from a skill's body and sections
|
|
105
|
+
* to enrich the description before BM25 indexing. This is a
|
|
106
|
+
* deterministic, zero-dependency alternative to LLM-generated
|
|
107
|
+
* chunk context (see: Anthropic's contextual retrieval paper).
|
|
108
|
+
*
|
|
109
|
+
* @packageDocumentation
|
|
110
|
+
*/
|
|
111
|
+
/** Minimal skill shape required for context extraction */
|
|
112
|
+
interface ContextInput {
|
|
113
|
+
readonly name: string;
|
|
114
|
+
readonly description: string;
|
|
115
|
+
readonly body?: string;
|
|
116
|
+
readonly sections?: ReadonlyArray<{
|
|
117
|
+
readonly heading: string;
|
|
118
|
+
readonly depth: number;
|
|
119
|
+
readonly content: string;
|
|
120
|
+
}>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Extract supplementary context from a skill's body and structure.
|
|
124
|
+
*
|
|
125
|
+
* Returns a space-separated string of unique terms derived from:
|
|
126
|
+
* 1. Skill name parts (split on `-` and `_`)
|
|
127
|
+
* 2. Section headings
|
|
128
|
+
* 3. Inline code references (backtick-wrapped)
|
|
129
|
+
* 4. Key terms from body text
|
|
130
|
+
*
|
|
131
|
+
* Terms already present in the description are omitted.
|
|
132
|
+
* Result is truncated to ~80 tokens.
|
|
133
|
+
*
|
|
134
|
+
* Returns empty string if no useful context can be extracted.
|
|
135
|
+
*/
|
|
136
|
+
declare function extractContext(skill: ContextInput): string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Interface for embedding providers.
|
|
140
|
+
* Implementations convert text into dense vector representations
|
|
141
|
+
* for semantic similarity comparison.
|
|
142
|
+
*/
|
|
143
|
+
interface EmbeddingProvider {
|
|
144
|
+
/** Human-readable name of the provider */
|
|
145
|
+
readonly name: string;
|
|
146
|
+
/** Dimensionality of the output vectors */
|
|
147
|
+
readonly dimensions: number;
|
|
148
|
+
/**
|
|
149
|
+
* Generate embeddings for a batch of texts.
|
|
150
|
+
* @param texts - Array of text strings to embed
|
|
151
|
+
* @returns Array of embedding vectors (same order as input)
|
|
152
|
+
*/
|
|
153
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Configuration for embedding providers.
|
|
157
|
+
*/
|
|
158
|
+
type EmbeddingConfig = 'local' | {
|
|
159
|
+
provider: 'openai';
|
|
160
|
+
model?: string;
|
|
161
|
+
apiKey?: string;
|
|
162
|
+
} | {
|
|
163
|
+
provider: 'ollama';
|
|
164
|
+
model?: string;
|
|
165
|
+
baseUrl?: string;
|
|
166
|
+
} | {
|
|
167
|
+
provider: 'custom';
|
|
168
|
+
embed: (texts: string[]) => Promise<number[][]>;
|
|
169
|
+
dimensions: number;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Local TF-IDF based embedding provider.
|
|
174
|
+
*
|
|
175
|
+
* Uses a deterministic hash-based approach to create sparse-then-dense
|
|
176
|
+
* embeddings from text. No external API calls or model downloads needed.
|
|
177
|
+
*
|
|
178
|
+
* Quality is lower than neural embedding models, but sufficient for
|
|
179
|
+
* keyword-heavy skill descriptions where exact word matching matters.
|
|
180
|
+
* Ideal for catalogs under 500 skills.
|
|
181
|
+
*/
|
|
182
|
+
declare class LocalEmbeddingProvider implements EmbeddingProvider {
|
|
183
|
+
readonly name = "local-tfidf";
|
|
184
|
+
readonly dimensions: number;
|
|
185
|
+
private vocabulary;
|
|
186
|
+
private idfValues;
|
|
187
|
+
private isBuilt;
|
|
188
|
+
constructor(dimensions?: number);
|
|
189
|
+
/**
|
|
190
|
+
* Build the vocabulary and IDF values from a corpus.
|
|
191
|
+
* Call this once after indexing all skill descriptions.
|
|
192
|
+
*/
|
|
193
|
+
buildVocabulary(texts: string[]): void;
|
|
194
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
195
|
+
private embedSingle;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* A skill entry prepared for indexing.
|
|
200
|
+
*/
|
|
201
|
+
interface SkillEntry {
|
|
202
|
+
/** Unique identifier (typically the skill name) */
|
|
203
|
+
readonly name: string;
|
|
204
|
+
/** The description text to embed */
|
|
205
|
+
readonly description: string;
|
|
206
|
+
/** Path to the SKILL.md file */
|
|
207
|
+
readonly path?: string;
|
|
208
|
+
/** Additional metadata to store alongside the embedding */
|
|
209
|
+
readonly metadata?: Record<string, unknown>;
|
|
210
|
+
/** Raw markdown body (used for contextual retrieval) */
|
|
211
|
+
readonly body?: string;
|
|
212
|
+
/** Parsed sections from the SKILL.md (used for contextual retrieval) */
|
|
213
|
+
readonly sections?: ReadonlyArray<{
|
|
214
|
+
readonly heading: string;
|
|
215
|
+
readonly depth: number;
|
|
216
|
+
readonly content: string;
|
|
217
|
+
}>;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Result of selecting a skill for a query.
|
|
221
|
+
*/
|
|
222
|
+
interface SelectionResult {
|
|
223
|
+
/** Skill name/ID */
|
|
224
|
+
readonly skill: string;
|
|
225
|
+
/** Similarity score (0-1) */
|
|
226
|
+
readonly score: number;
|
|
227
|
+
/** Metadata from the indexed skill */
|
|
228
|
+
readonly metadata: Record<string, unknown>;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Options for skill selection queries.
|
|
232
|
+
*/
|
|
233
|
+
interface SelectOptions {
|
|
234
|
+
/** Number of results to return (default: 5) */
|
|
235
|
+
readonly topK?: number;
|
|
236
|
+
/** Minimum similarity threshold (default: 0.0) */
|
|
237
|
+
readonly threshold?: number;
|
|
238
|
+
/** Skill names to boost in ranking */
|
|
239
|
+
readonly boost?: string[];
|
|
240
|
+
/** Skill name patterns to exclude */
|
|
241
|
+
readonly exclude?: string[];
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Options for the SkillRouter constructor.
|
|
245
|
+
*/
|
|
246
|
+
interface SkillRouterOptions {
|
|
247
|
+
/** Embedding provider configuration. Defaults to BM25 ('local'). */
|
|
248
|
+
readonly embedding?: EmbeddingConfig;
|
|
249
|
+
/** BM25 tuning parameters (only used with the default BM25 engine) */
|
|
250
|
+
readonly bm25?: BM25Options;
|
|
251
|
+
/**
|
|
252
|
+
* Enable contextual retrieval. When true (default), skills with
|
|
253
|
+
* body or sections will have supplementary context extracted and
|
|
254
|
+
* prepended to their description before indexing.
|
|
255
|
+
* Only affects indexing — result descriptions stay unchanged.
|
|
256
|
+
*/
|
|
257
|
+
readonly context?: boolean;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* SkillRouter — Skill selection middleware using BM25 full-text search.
|
|
261
|
+
*
|
|
262
|
+
* Indexes skill descriptions and enables fast, ranked search to find
|
|
263
|
+
* the most relevant skills for a given query. Uses Okapi BM25 by default
|
|
264
|
+
* with zero external dependencies.
|
|
265
|
+
*
|
|
266
|
+
* For neural/semantic embeddings, pass a custom embedding provider
|
|
267
|
+
* via the `embedding` option.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* const router = new SkillRouter();
|
|
272
|
+
* await router.indexSkills([
|
|
273
|
+
* { name: 'deploy-vercel', description: 'Deploy apps to Vercel...' },
|
|
274
|
+
* { name: 'run-tests', description: 'Execute test suites...' },
|
|
275
|
+
* ]);
|
|
276
|
+
*
|
|
277
|
+
* const results = await router.select('deploy my app');
|
|
278
|
+
* // => [{ skill: 'deploy-vercel', score: 0.89, ... }]
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
declare class SkillRouter {
|
|
282
|
+
/** BM25 index — used when no external embedding provider is configured */
|
|
283
|
+
private readonly bm25;
|
|
284
|
+
/** Embedding provider — used with custom/openai/ollama providers */
|
|
285
|
+
private readonly embedding;
|
|
286
|
+
/** Vector store — used alongside embedding provider */
|
|
287
|
+
private readonly store;
|
|
288
|
+
/** Whether the router uses the BM25 engine (true) or embedding+store (false) */
|
|
289
|
+
private readonly usesBM25;
|
|
290
|
+
/** Whether contextual retrieval is enabled */
|
|
291
|
+
private readonly contextEnabled;
|
|
292
|
+
private skillNames;
|
|
293
|
+
constructor(options?: SkillRouterOptions);
|
|
294
|
+
/**
|
|
295
|
+
* Index a list of skill entries.
|
|
296
|
+
* With BM25 (default): indexes description text directly.
|
|
297
|
+
* With embeddings: embeds descriptions and stores vectors.
|
|
298
|
+
*/
|
|
299
|
+
indexSkills(skills: SkillEntry[]): Promise<void>;
|
|
300
|
+
/**
|
|
301
|
+
* Index all SKILL.md files in a directory.
|
|
302
|
+
* Parses each file and indexes its description.
|
|
303
|
+
*/
|
|
304
|
+
indexDirectory(dirPath: string): Promise<number>;
|
|
305
|
+
/**
|
|
306
|
+
* Select the most relevant skills for a query.
|
|
307
|
+
*/
|
|
308
|
+
select(query: string, options?: SelectOptions): Promise<SelectionResult[]>;
|
|
309
|
+
/**
|
|
310
|
+
* Detect skills with overlapping descriptions.
|
|
311
|
+
*/
|
|
312
|
+
detectConflicts(threshold?: number): Promise<ConflictGroup[]>;
|
|
313
|
+
/**
|
|
314
|
+
* Build the text to index for a skill entry.
|
|
315
|
+
* When contextual retrieval is enabled and the skill has body/sections,
|
|
316
|
+
* prepends extracted context to the description.
|
|
317
|
+
*/
|
|
318
|
+
private enrichText;
|
|
319
|
+
/**
|
|
320
|
+
* Get the number of indexed skills.
|
|
321
|
+
*/
|
|
322
|
+
get count(): number;
|
|
323
|
+
/**
|
|
324
|
+
* Save the index to a JSON-serializable object.
|
|
325
|
+
*/
|
|
326
|
+
save(): SkillRouterSnapshot;
|
|
327
|
+
/**
|
|
328
|
+
* Load a previously saved index.
|
|
329
|
+
* Validates that the snapshot format matches the current engine.
|
|
330
|
+
*/
|
|
331
|
+
load(snapshot: SkillRouterSnapshot): void;
|
|
332
|
+
/**
|
|
333
|
+
* Create a SkillRouter from a saved snapshot.
|
|
334
|
+
*/
|
|
335
|
+
static fromSnapshot(snapshot: SkillRouterSnapshot, options?: SkillRouterOptions): SkillRouter;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* A group of conflicting (highly similar) skills.
|
|
339
|
+
*/
|
|
340
|
+
interface ConflictGroup {
|
|
341
|
+
readonly skills: string[];
|
|
342
|
+
readonly similarity: number;
|
|
343
|
+
readonly suggestion: string;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Serialized snapshot of a SkillRouter state.
|
|
347
|
+
*/
|
|
348
|
+
interface SkillRouterSnapshot {
|
|
349
|
+
readonly version: number;
|
|
350
|
+
readonly embeddingProvider: string;
|
|
351
|
+
readonly dimensions: number;
|
|
352
|
+
readonly store: unknown;
|
|
353
|
+
readonly skillNames: string[];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* A single entry in the vector store.
|
|
358
|
+
*/
|
|
359
|
+
interface VectorEntry {
|
|
360
|
+
/** Unique identifier (skill name or path) */
|
|
361
|
+
readonly id: string;
|
|
362
|
+
/** The embedding vector */
|
|
363
|
+
readonly vector: number[];
|
|
364
|
+
/** Metadata associated with this entry */
|
|
365
|
+
readonly metadata: Record<string, unknown>;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Result of a similarity search.
|
|
369
|
+
*/
|
|
370
|
+
interface SearchResult {
|
|
371
|
+
/** Identifier of the matched entry */
|
|
372
|
+
readonly id: string;
|
|
373
|
+
/** Cosine similarity score (0-1, higher is more similar) */
|
|
374
|
+
readonly score: number;
|
|
375
|
+
/** Metadata from the matched entry */
|
|
376
|
+
readonly metadata: Record<string, unknown>;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Interface for vector storage backends.
|
|
380
|
+
*/
|
|
381
|
+
interface VectorStore {
|
|
382
|
+
/**
|
|
383
|
+
* Add entries to the store.
|
|
384
|
+
*/
|
|
385
|
+
add(entries: VectorEntry[]): Promise<void>;
|
|
386
|
+
/**
|
|
387
|
+
* Search for the top-K most similar entries to a query vector.
|
|
388
|
+
*/
|
|
389
|
+
search(queryVector: number[], topK: number, threshold?: number): Promise<SearchResult[]>;
|
|
390
|
+
/**
|
|
391
|
+
* Remove entries by ID.
|
|
392
|
+
*/
|
|
393
|
+
remove(ids: string[]): Promise<void>;
|
|
394
|
+
/**
|
|
395
|
+
* Get the number of entries in the store.
|
|
396
|
+
*/
|
|
397
|
+
size(): number;
|
|
398
|
+
/**
|
|
399
|
+
* Serialize the store to a JSON-compatible object.
|
|
400
|
+
*/
|
|
401
|
+
serialize(): unknown;
|
|
402
|
+
/**
|
|
403
|
+
* Load from a serialized object.
|
|
404
|
+
*/
|
|
405
|
+
deserialize(data: unknown): void;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* In-memory vector store using brute-force cosine similarity search.
|
|
410
|
+
*
|
|
411
|
+
* Suitable for catalogs of up to ~1,000 skills. For larger catalogs,
|
|
412
|
+
* use the SQLite backend.
|
|
413
|
+
*
|
|
414
|
+
* At 1,000 entries with 256-dimensional vectors, search takes <5ms.
|
|
415
|
+
*/
|
|
416
|
+
declare class MemoryVectorStore implements VectorStore {
|
|
417
|
+
private entries;
|
|
418
|
+
add(entries: VectorEntry[]): Promise<void>;
|
|
419
|
+
search(queryVector: number[], topK: number, threshold?: number): Promise<SearchResult[]>;
|
|
420
|
+
remove(ids: string[]): Promise<void>;
|
|
421
|
+
size(): number;
|
|
422
|
+
serialize(): unknown;
|
|
423
|
+
deserialize(data: unknown): void;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export { BM25Index, type BM25Options, type BM25Snapshot, type ConflictGroup, type ContextInput, type EmbeddingConfig, type EmbeddingProvider, LocalEmbeddingProvider, MemoryVectorStore, type SearchResult, type SelectOptions, type SelectionResult, type SkillEntry, SkillRouter, type SkillRouterOptions, type SkillRouterSnapshot, type VectorEntry, type VectorStore, extractContext };
|