{"version":3,"file":"procedural-skill-store.mjs","names":[],"sources":["../../../../../../../../ai/src/skills/store/procedural-skill-store.ts"],"sourcesContent":["import type { MemoryContract } from \"../../contracts/memory/memory.contract\";\nimport type { ProceduralMemoryConfig } from \"../../contracts/memory/memory-config.type\";\nimport { memory } from \"../../memory\";\nimport type {\n  SkillCatalogEntry,\n  SkillRecord,\n} from \"../contracts/skill-record.type\";\nimport type { SkillsStoreContract } from \"../contracts/skills-store.contract\";\n\n/**\n * Metadata a procedural memory carries to round-trip a skill. `recall()`\n * surfaces `metadata` verbatim, so the skill's identity (name, version,\n * provenance, description, tags) rides here while the procedure body lives\n * in the memory's `text`.\n */\ntype ProceduralSkillMeta = {\n  /** Marks the record as a skill (vs. a plain procedure) so `list` can scope. */\n  skill: true;\n  /** Skill name — the catalog key (also the memory `id`). */\n  name: string;\n  /** Provenance flag round-tripped onto the SkillRecord. */\n  type: \"candidate\" | \"promoted\";\n  /** Catalog line. */\n  description: string;\n  /** Monotonic version — bumped on promote. */\n  version: number;\n  /** Optional scope tags. */\n  tags?: string[];\n};\n\n/** A recalled skill entry — the procedure body plus its skill metadata. */\ntype ProceduralSkillEntry = { body: string; meta: ProceduralSkillMeta };\n\nconst RECALL_K = 1000;\n\n/**\n * {@link SkillsStoreContract} backed by the procedural memory tier\n * (`ai.memory({ procedural })`). **The unification** the design calls for:\n * \"promote a proven procedural memory to a named skill\" and \"save a\n * self-authored skill\" are the SAME machinery — one store, two entry\n * points. No fifth `MemoryTier` is added; the existing `\"procedural\"` tier\n * is reused verbatim.\n *\n * - `saveCandidate` ⇒ `memory.remember({ tier: \"procedural\", metadata: { type: \"candidate\" } })`.\n * - `promote` ⇒ re-remembers the same id with `type: \"promoted\"` and\n *   `version + 1`, which the procedural tier reinforces (increments `uses`).\n * - `list` / `load` map `memory.recall(..., { tier: \"procedural\" })` ⇒\n *   `RecalledMemory[]` ⇒ `SkillCatalogEntry[]` / `SkillRecord`, filtering\n *   out inert candidates so they can never be catalogued or injected.\n *\n * @example\n * const store = proceduralSkillStore({ embedder, store: cacheDriver });\n * const lib = skills({ name: \"learned\", sources: [{ type: \"store\", store }], review: gate });\n */\nexport function proceduralSkillStore(\n  config: ProceduralMemoryConfig & { name?: string; recallQuery?: string },\n): SkillsStoreContract {\n  const store: MemoryContract = memory({\n    name: config.name ?? \"skills.procedural\",\n    working: false,\n    defaultTier: \"procedural\",\n    procedural: {\n      embedder: config.embedder,\n      store: config.store,\n      namespace: config.namespace,\n      reinforcementWeight: config.reinforcementWeight,\n    },\n  });\n\n  // The procedural tier recalls by similarity to a query; for a full\n  // catalog listing we recall against a broad seed with a large `k` and a\n  // zero floor so every stored skill comes back.\n  const recallQuery = config.recallQuery ?? \"skill procedure how-to\";\n\n  const recallAll = async (): Promise<ProceduralSkillEntry[]> => {\n    const hits = await store.recall(recallQuery, {\n      tier: \"procedural\",\n      k: RECALL_K,\n      threshold: 0,\n    });\n\n    return hits\n      .map((hit) => ({ body: hit.text, meta: hit.metadata as ProceduralSkillMeta | undefined }))\n      .filter((entry): entry is ProceduralSkillEntry => Boolean(entry.meta?.skill));\n  };\n\n  return {\n    async list(scope?: { tags?: string[] }): Promise<SkillCatalogEntry[]> {\n      const all = await recallAll();\n      const wanted = scope?.tags;\n\n      return all\n        .filter((entry) => entry.meta.type !== \"candidate\")\n        .filter((entry) => intersects(entry.meta.tags, wanted))\n        .map((entry) => toCatalogEntry(entry.meta));\n    },\n    async load(name: string, version?: number): Promise<SkillRecord | undefined> {\n      const all = await recallAll();\n      const match = all.find((entry) => entry.meta.name === name);\n\n      if (!match || match.meta.type === \"candidate\") {\n        return undefined;\n      }\n\n      if (version !== undefined && match.meta.version !== version) {\n        return undefined;\n      }\n\n      return toRecord(match.body, match.meta);\n    },\n    async saveCandidate(record: Omit<SkillRecord, \"version\" | \"type\">): Promise<SkillRecord> {\n      const meta: ProceduralSkillMeta = {\n        skill: true,\n        name: record.name,\n        type: \"candidate\",\n        description: record.description,\n        version: 0,\n        tags: record.tags,\n      };\n\n      await store.remember({\n        id: record.name,\n        text: record.body,\n        tier: \"procedural\",\n        metadata: meta,\n      });\n\n      return { ...record, version: 0, type: \"candidate\" };\n    },\n    async promote(name: string): Promise<SkillRecord> {\n      const all = await recallAll();\n      const match = all.find((entry) => entry.meta.name === name);\n\n      if (!match) {\n        throw new Error(`proceduralSkillStore.promote: no skill named \"${name}\" to promote`);\n      }\n\n      const meta: ProceduralSkillMeta = {\n        ...match.meta,\n        type: \"promoted\",\n        version: match.meta.version + 1,\n      };\n\n      // Re-remembering the same id reinforces (uses++) AND flips the\n      // metadata — the procedural tier's reinforcement IS the promotion.\n      await store.remember({\n        id: name,\n        text: match.body,\n        tier: \"procedural\",\n        metadata: meta,\n      });\n\n      return toRecord(match.body, meta);\n    },\n  };\n}\n\nfunction toCatalogEntry(meta: ProceduralSkillMeta): SkillCatalogEntry {\n  return {\n    name: meta.name,\n    description: meta.description,\n    version: meta.version,\n    tags: meta.tags,\n    type: meta.type,\n  };\n}\n\nfunction toRecord(body: string, meta: ProceduralSkillMeta): SkillRecord {\n  return {\n    name: meta.name,\n    description: meta.description,\n    version: meta.version,\n    body,\n    tags: meta.tags,\n    type: meta.type,\n  };\n}\n\nfunction intersects(recordTags: string[] | undefined, wanted: string[] | undefined): boolean {\n  if (!wanted || wanted.length === 0) {\n    return true;\n  }\n\n  if (!recordTags || recordTags.length === 0) {\n    return false;\n  }\n\n  return recordTags.some((tag) => wanted.includes(tag));\n}\n"],"mappings":";;;;AAiCA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;AAqBjB,SAAgB,qBACd,QACqB;CACrB,MAAM,QAAwB,OAAO;EACnC,MAAM,OAAO,QAAQ;EACrB,SAAS;EACT,aAAa;EACb,YAAY;GACV,UAAU,OAAO;GACjB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,qBAAqB,OAAO;EAC9B;CACF,CAAC;CAKD,MAAM,cAAc,OAAO,eAAe;CAE1C,MAAM,YAAY,YAA6C;EAO7D,QAAO,MANY,MAAM,OAAO,aAAa;GAC3C,MAAM;GACN,GAAG;GACH,WAAW;EACb,CAAC,EAEU,CACR,KAAK,SAAS;GAAE,MAAM,IAAI;GAAM,MAAM,IAAI;EAA4C,EAAE,CAAC,CACzF,QAAQ,UAAyC,QAAQ,MAAM,MAAM,KAAK,CAAC;CAChF;CAEA,OAAO;EACL,MAAM,KAAK,OAA2D;GACpE,MAAM,MAAM,MAAM,UAAU;GAC5B,MAAM,SAAS,OAAO;GAEtB,OAAO,IACJ,QAAQ,UAAU,MAAM,KAAK,SAAS,WAAW,CAAC,CAClD,QAAQ,UAAU,WAAW,MAAM,KAAK,MAAM,MAAM,CAAC,CAAC,CACtD,KAAK,UAAU,eAAe,MAAM,IAAI,CAAC;EAC9C;EACA,MAAM,KAAK,MAAc,SAAoD;GAE3E,MAAM,SAAQ,MADI,UAAU,EACX,CAAC,MAAM,UAAU,MAAM,KAAK,SAAS,IAAI;GAE1D,IAAI,CAAC,SAAS,MAAM,KAAK,SAAS,aAChC;GAGF,IAAI,YAAY,UAAa,MAAM,KAAK,YAAY,SAClD;GAGF,OAAO,SAAS,MAAM,MAAM,MAAM,IAAI;EACxC;EACA,MAAM,cAAc,QAAqE;GACvF,MAAM,OAA4B;IAChC,OAAO;IACP,MAAM,OAAO;IACb,MAAM;IACN,aAAa,OAAO;IACpB,SAAS;IACT,MAAM,OAAO;GACf;GAEA,MAAM,MAAM,SAAS;IACnB,IAAI,OAAO;IACX,MAAM,OAAO;IACb,MAAM;IACN,UAAU;GACZ,CAAC;GAED,OAAO;IAAE,GAAG;IAAQ,SAAS;IAAG,MAAM;GAAY;EACpD;EACA,MAAM,QAAQ,MAAoC;GAEhD,MAAM,SAAQ,MADI,UAAU,EACX,CAAC,MAAM,UAAU,MAAM,KAAK,SAAS,IAAI;GAE1D,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,iDAAiD,KAAK,aAAa;GAGrF,MAAM,OAA4B;IAChC,GAAG,MAAM;IACT,MAAM;IACN,SAAS,MAAM,KAAK,UAAU;GAChC;GAIA,MAAM,MAAM,SAAS;IACnB,IAAI;IACJ,MAAM,MAAM;IACZ,MAAM;IACN,UAAU;GACZ,CAAC;GAED,OAAO,SAAS,MAAM,MAAM,IAAI;EAClC;CACF;AACF;AAEA,SAAS,eAAe,MAA8C;CACpE,OAAO;EACL,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,SAAS,KAAK;EACd,MAAM,KAAK;EACX,MAAM,KAAK;CACb;AACF;AAEA,SAAS,SAAS,MAAc,MAAwC;CACtE,OAAO;EACL,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,SAAS,KAAK;EACd;EACA,MAAM,KAAK;EACX,MAAM,KAAK;CACb;AACF;AAEA,SAAS,WAAW,YAAkC,QAAuC;CAC3F,IAAI,CAAC,UAAU,OAAO,WAAW,GAC/B,OAAO;CAGT,IAAI,CAAC,cAAc,WAAW,WAAW,GACvC,OAAO;CAGT,OAAO,WAAW,MAAM,QAAQ,OAAO,SAAS,GAAG,CAAC;AACtD"}