{"version":3,"file":"skill-registry.mjs","names":[],"sources":["../../../src/services/skill-registry.ts"],"sourcesContent":["/**\n * Skill Registry — unified index of static + learned skills with keyword matching.\n *\n * Solves the \"LLM didn't load the right skill\" problem by:\n * 1. Indexing ALL skills (42 static + N learned) with name, description, keywords\n * 2. Matching user messages against keywords → returning full skill content\n * 3. Providing a single lookup surface for /skills command + prompt-builder\n *\n * Static skills:  extensions/crypto/skills/<name>/SKILL.md\n * Learned skills: ~/.openclawnch/learned-skills/<name>/SKILL.md\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// ─── ESM path resolution ────────────────────────────────────────────────\n// __dirname is undefined in ESM. Resolve from import.meta.url instead.\n// Compiled: extensions/crypto/dist/src/services/ → 3 levels up → extensions/crypto/\n// Source:   extensions/crypto/src/services/       → 2 levels up → extensions/crypto/\n\nconst __selfDir = dirname(fileURLToPath(import.meta.url));\nconst EXTENSION_ROOT = existsSync(join(__selfDir, '..', '..', '..', 'skills'))\n  ? join(__selfDir, '..', '..', '..')       // from dist/src/services\n  : join(__selfDir, '..', '..');            // from src/services (dev/test)\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface SkillEntry {\n  /** Skill name (kebab-case). */\n  name: string;\n  /** Short description from frontmatter or first paragraph. */\n  description: string;\n  /** Source: 'static' (shipped) or 'learned' (agent-created). */\n  source: 'static' | 'learned';\n  /** Absolute path to SKILL.md. */\n  path: string;\n  /** Keywords for matching (derived from name + description + frontmatter). */\n  keywords: string[];\n  /** Environment variables required by this skill (from frontmatter metadata). */\n  requiresEnv: string[];\n  /** Whether the user has disabled this skill. */\n  disabled: boolean;\n  /** Full file content (lazy-loaded, cached). */\n  _content?: string;\n}\n\nexport interface SkillMatch {\n  skill: SkillEntry;\n  /** Number of keyword hits. */\n  score: number;\n}\n\n// ─── Disabled Skills Persistence ────────────────────────────────────────\n\nconst OPENCLAWNCH_DIR = join(process.env.HOME ?? '', '.openclawnch');\nconst DISABLED_FILE = join(OPENCLAWNCH_DIR, 'disabled-skills.json');\n\nfunction loadDisabledSet(): Set<string> {\n  try {\n    if (existsSync(DISABLED_FILE)) {\n      const data = JSON.parse(readFileSync(DISABLED_FILE, 'utf8'));\n      if (Array.isArray(data)) return new Set(data as string[]);\n    }\n  } catch { /* corrupt file — start fresh */ }\n  return new Set();\n}\n\nfunction saveDisabledSet(set: Set<string>): void {\n  try {\n    if (!existsSync(OPENCLAWNCH_DIR)) mkdirSync(OPENCLAWNCH_DIR, { recursive: true });\n    writeFileSync(DISABLED_FILE, JSON.stringify([...set], null, 2), 'utf8');\n  } catch { /* best-effort */ }\n}\n\n// ─── Frontmatter Parsing ────────────────────────────────────────────────\n\ninterface FrontmatterResult {\n  name?: string;\n  description?: string;\n  requiresEnv: string[];\n  rest: string;\n}\n\nfunction parseFrontmatter(content: string): FrontmatterResult {\n  if (!content.startsWith('---')) {\n    // No frontmatter — extract from markdown heading + first paragraph\n    const lines = content.split('\\n');\n    let name: string | undefined;\n    let description: string | undefined;\n\n    for (const line of lines) {\n      if (!name && line.startsWith('# ')) {\n        name = line.slice(2).trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n      } else if (!description && name && line.trim().length > 0 && !line.startsWith('#')) {\n        description = line.trim().slice(0, 200);\n        break;\n      }\n    }\n    return { name, description, requiresEnv: [], rest: content };\n  }\n\n  const endIdx = content.indexOf('---', 3);\n  if (endIdx === -1) return { requiresEnv: [], rest: content };\n\n  const yaml = content.slice(3, endIdx).trim();\n  const rest = content.slice(endIdx + 3).trim();\n\n  let name: string | undefined;\n  let description: string | undefined;\n  let requiresEnv: string[] = [];\n\n  for (const line of yaml.split('\\n')) {\n    const nameMatch = line.match(/^name:\\s*[\"']?(.+?)[\"']?\\s*$/);\n    if (nameMatch) name = nameMatch[1];\n\n    const descMatch = line.match(/^description:\\s*[\"']?(.+?)[\"']?\\s*$/);\n    if (descMatch) description = descMatch[1];\n\n    // Parse metadata JSON: metadata: { \"openclaw\": { \"requires\": { \"env\": [...] } } }\n    const metaMatch = line.match(/^metadata:\\s*(\\{.+\\})\\s*$/);\n    if (metaMatch) {\n      try {\n        const meta = JSON.parse(metaMatch[1]!);\n        const envArr = meta?.openclaw?.requires?.env;\n        if (Array.isArray(envArr)) {\n          requiresEnv = envArr.filter((v: unknown): v is string => typeof v === 'string');\n        }\n      } catch { /* malformed JSON — skip */ }\n    }\n  }\n\n  return { name, description, requiresEnv, rest };\n}\n\n/** Extract keywords from name + description for matching. */\nfunction extractKeywords(name: string, description: string): string[] {\n  const text = `${name.replace(/-/g, ' ')} ${description}`.toLowerCase();\n  // Split on non-alphanumeric, filter short/stopwords\n  const STOPWORDS = new Set([\n    'a', 'an', 'the', 'is', 'are', 'was', 'be', 'to', 'of', 'and', 'or', 'in',\n    'on', 'for', 'with', 'by', 'from', 'at', 'as', 'it', 'its', 'this', 'that',\n    'use', 'via', 'any', 'all', 'can', 'your', 'you', 'more', 'also', 'into',\n  ]);\n  const words = text.split(/[^a-z0-9]+/).filter(w => w.length > 2 && !STOPWORDS.has(w));\n  return [...new Set(words)];\n}\n\n// ─── Directory Scanning ─────────────────────────────────────────────────\n\nfunction scanSkillDir(\n  dir: string,\n  source: 'static' | 'learned',\n  disabledSet: Set<string>,\n): SkillEntry[] {\n  const entries: SkillEntry[] = [];\n  if (!existsSync(dir)) return entries;\n\n  try {\n    for (const entry of readdirSync(dir)) {\n      const entryPath = join(dir, entry);\n      if (!statSync(entryPath).isDirectory()) continue;\n\n      const skillPath = join(entryPath, 'SKILL.md');\n      if (!existsSync(skillPath)) continue;\n\n      try {\n        const content = readFileSync(skillPath, 'utf8');\n        if (content.length > 256_000) continue; // skip oversized\n\n        const fm = parseFrontmatter(content);\n        const name = fm.name ?? entry;\n        const description = fm.description ?? '';\n\n        entries.push({\n          name,\n          description,\n          source,\n          path: skillPath,\n          keywords: extractKeywords(name, description),\n          requiresEnv: fm.requiresEnv,\n          disabled: disabledSet.has(name),\n        });\n      } catch { /* skip unreadable */ }\n    }\n  } catch { /* dir not readable */ }\n\n  return entries;\n}\n\n// ─── Registry ───────────────────────────────────────────────────────────\n\nexport class SkillRegistry {\n  private skills = new Map<string, SkillEntry>();\n  private disabledSet: Set<string>;\n  private staticDir: string;\n  private learnedDir: string;\n  private lastScan = 0;\n  private scanIntervalMs = 60_000; // re-scan every 60s\n\n  constructor(opts?: { staticDir?: string; learnedDir?: string }) {\n    this.staticDir = opts?.staticDir ?? join(EXTENSION_ROOT, 'skills');\n    this.learnedDir = opts?.learnedDir ?? join(\n      process.env.HOME ?? '', '.openclawnch', 'learned-skills'\n    );\n    this.disabledSet = loadDisabledSet();\n    this.scan();\n  }\n\n  /** Re-scan both directories for skills. */\n  scan(): void {\n    this.skills.clear();\n    this.disabledSet = loadDisabledSet();\n\n    const staticSkills = scanSkillDir(this.staticDir, 'static', this.disabledSet);\n    const learnedSkills = scanSkillDir(this.learnedDir, 'learned', this.disabledSet);\n\n    // Learned skills override static if name conflicts\n    for (const s of staticSkills) this.skills.set(s.name, s);\n    for (const s of learnedSkills) this.skills.set(s.name, s);\n\n    this.lastScan = Date.now();\n  }\n\n  private ensureFresh(): void {\n    if (Date.now() - this.lastScan > this.scanIntervalMs) {\n      this.scan();\n    }\n  }\n\n  /** Get a skill by exact name. */\n  get(name: string): SkillEntry | null {\n    this.ensureFresh();\n    return this.skills.get(name) ?? null;\n  }\n\n  /** List all skills sorted by name. */\n  list(opts?: { source?: 'static' | 'learned'; includeDisabled?: boolean }): SkillEntry[] {\n    this.ensureFresh();\n    let all = Array.from(this.skills.values());\n    if (opts?.source) all = all.filter(s => s.source === opts.source);\n    if (!opts?.includeDisabled) all = all.filter(s => !s.disabled);\n    return all.sort((a, b) => a.name.localeCompare(b.name));\n  }\n\n  /** List all skills including disabled (for /skills command). */\n  listAll(): SkillEntry[] {\n    this.ensureFresh();\n    return Array.from(this.skills.values()).sort((a, b) => a.name.localeCompare(b.name));\n  }\n\n  /** Read the full SKILL.md content for a skill. */\n  readContent(name: string): string | null {\n    const skill = this.get(name);\n    if (!skill) return null;\n\n    // Cache content\n    if (skill._content === undefined) {\n      try {\n        skill._content = readFileSync(skill.path, 'utf8');\n      } catch {\n        return null;\n      }\n    }\n    return skill._content;\n  }\n\n  /**\n   * Match user message against skill keywords.\n   * Returns skills sorted by match score (descending), filtered to score >= minScore.\n   * Only matches enabled skills.\n   */\n  match(message: string, opts?: { minScore?: number; maxResults?: number }): SkillMatch[] {\n    this.ensureFresh();\n    const minScore = opts?.minScore ?? 2;\n    const maxResults = opts?.maxResults ?? 3;\n\n    const msgLower = message.toLowerCase();\n    const msgTokens = msgLower.split(/[^a-z0-9]+/).filter(w => w.length > 2);\n    const msgTokenSet = new Set(msgTokens);\n\n    const matches: SkillMatch[] = [];\n\n    for (const skill of this.skills.values()) {\n      // Skip disabled skills in matching\n      if (skill.disabled) continue;\n\n      let score = 0;\n\n      // Exact name match in message (strongest signal)\n      if (msgLower.includes(skill.name.replace(/-/g, ' ')) || msgLower.includes(skill.name)) {\n        score += 5;\n      }\n\n      // Keyword overlap\n      for (const kw of skill.keywords) {\n        if (msgTokenSet.has(kw)) {\n          score += 1;\n        }\n        // Substring match for compound words (e.g. \"botcoin\" matches \"botcoin-mining\")\n        if (kw.length > 3 && msgLower.includes(kw)) {\n          score += 1;\n        }\n      }\n\n      if (score >= minScore) {\n        matches.push({ skill, score });\n      }\n    }\n\n    matches.sort((a, b) => b.score - a.score);\n    return matches.slice(0, maxResults);\n  }\n\n  /** Build a compact index for prompt injection (name + description, one line each). */\n  buildIndex(): string {\n    // Only include enabled skills in the prompt index\n    const skills = this.list();\n    if (skills.length === 0) return '';\n\n    const lines = [\n      `## Crypto Skills (${skills.length} available)`,\n      'If a user request matches a skill below, load it with `/skills <name>` or `skill_evolve(action: \"view\", name: \"...\")` for full instructions.',\n      '',\n    ];\n    for (const s of skills) {\n      const tag = s.source === 'learned' ? ' (learned)' : '';\n      lines.push(`- **${s.name}**${tag}: ${s.description.slice(0, 120)}`);\n    }\n    return lines.join('\\n');\n  }\n\n  // ─── Enable / Disable ───────────────────────────────────────────────\n\n  /** Disable a skill (won't appear in prompts or matching). */\n  disable(name: string): boolean {\n    const skill = this.skills.get(name);\n    if (!skill) return false;\n    skill.disabled = true;\n    this.disabledSet.add(name);\n    saveDisabledSet(this.disabledSet);\n    return true;\n  }\n\n  /** Re-enable a disabled skill. */\n  enable(name: string): boolean {\n    const skill = this.skills.get(name);\n    if (!skill) return false;\n    skill.disabled = false;\n    this.disabledSet.delete(name);\n    saveDisabledSet(this.disabledSet);\n    return true;\n  }\n\n  /** Check which required env vars are missing for a skill. */\n  missingEnv(skill: SkillEntry): string[] {\n    return skill.requiresEnv.filter(key => !process.env[key]);\n  }\n\n  /** Get total count (enabled only by default). */\n  get size(): number {\n    this.ensureFresh();\n    return this.list().length;\n  }\n\n  /** Get total count including disabled. */\n  get totalSize(): number {\n    this.ensureFresh();\n    return this.skills.size;\n  }\n}\n\n// ─── Singleton ──────────────────────────────────────────────────────────\n\nlet instance: SkillRegistry | null = null;\n\nexport function getSkillRegistry(opts?: { staticDir?: string; learnedDir?: string }): SkillRegistry {\n  if (!instance) {\n    instance = new SkillRegistry(opts);\n  }\n  return instance;\n}\n\nexport function resetSkillRegistry(): void {\n  instance = null;\n}\n"],"mappings":"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"}