You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The three planned KB skills (kb-retrieve, kb-add, kb-curate) all need the same plumbing: a way to find a knowledge base on disk, load its schema and aliases, parse and write note frontmatter, and validate notes against the schema. Without a shared library, each skill would reimplement that plumbing or shell out to vault-local scripts, leading to drift between skills, no typed contract, and no single place to evolve the schema. A future MCP server wrapping these operations needs a clean library surface to wrap.
Context
The vault at github.com/williamthorsen/vaults.coding already implements equivalent logic in bash and TypeScript: scripts/add-frontmatter.sh for frontmatter generation, scripts/check-notes/ for validation, and .kb/schema.yaml plus .kb/tag-aliases.yaml as data. Those implementations are vault-local. The goal here is to extract the durable concepts (not literal code) into a typed monorepo library with an ergonomic, MCP-wrappable surface that any KB-aware skill can consume.
Monorepo conventions to mirror: async I/O via node:fs/promises, Zod 4 for input validation at API boundaries, subpath exports for tree-shaking, sibling __tests__/ directories per module. The reference shape is @codeassembly/run-core.
A separate follow-up ticket will deliver an MCP server (@codeassembly/kb-mcp) that wraps this library; the skill tickets (kb-retrieve, kb-add, kb-curate) depend on both.
Proposed solution
Ship @codeassembly/kb-core at packages/kb-core/, with five subpath exports addressing distinct concerns:
discovery: finds the active KB by ancestor walk for .kb/, and loads and merges the user-global (~/.claude/kb.yaml) and project-local (.agents/kb.yaml) registries. The on-disk config is a kbs: { name: { path, description?, default?, readonly? } } map; project entries replace user entries by name on collision; relative paths resolve against the config file's directory; tilde expands to $HOME. Path existence is not checked at load time.
schema: exposes a bundled default schema (Diátaxis four types plus the canonical required and optional field set), reads per-KB .kb/schema.yaml, and merges them under narrow-only rules. Types may be narrowed (subset of default), required and optional may be extended, but a default-required field cannot be demoted to optional. Violations of these rules are rejected at load time.
frontmatter: parses a note (from path or literal string) into a ParsedNote carrying typed Frontmatter (required fields strongly typed; optional fields preserved through an extra map), and writes a Frontmatter plus body back to YAML. Round-trip is idempotent.
tags: loads .kb/tag-aliases.yaml into a typed AliasMap and exposes canonicalize / findAliasFor for tag resolution.
rules: exposes frontmatterRule and tagAliasRule as KbRule objects ({ name, check(input): Finding[] }), plus a runRules aggregator. Skills compose whichever rules they need; the interface is the extension point for future rules.
All public functions take single-object inputs so a future MCP wrapper can mechanically bind Zod-validated payloads. The library throws on errors; success/failure shaping is left to consumers like the future kb-mcp server.
Out of scope for this ticket: the vault index, content-shape rules (wikilinks, hardcoded paths), the MCP server, and any user-facing skill.
Acceptance criteria
@codeassembly/kb-core is importable by other monorepo packages via five subpath exports (./discovery, ./schema, ./frontmatter, ./tags, ./rules).
findKbRoot walks ancestor directories for .kb/ and returns the discovered root or null.
loadKbConfig reads ~/.claude/kb.yaml and .agents/kb.yaml, merges with project entries overriding user entries by name, expands tilde paths, and resolves relative paths against the source file's directory.
The bundled default schema is exported as a typed constant exposing the Diátaxis four types and the documented required and optional fields.
loadSchema returns the default verbatim when no .kb/schema.yaml exists, merges per-KB overrides under narrow-only rules, and rejects illegal overrides (new types, demoted required fields, fields in both required and optional) at load time.
parseNote and writeFrontmatter round-trip the vault's frontmatter shape exactly: required fields, optional fields preserved, UTC dates, list-form tags.
loadAliases reads .kb/tag-aliases.yaml, rejects collisions and self-aliases at load time, and produces an AliasMap consumed by canonicalize and findAliasFor.
frontmatterRule and tagAliasRule produce findings whose rule codes (frontmatter.*) and severities match the vault's check-notes output on a vendored fixture set representative of each finding code; verified by a parity test against a checked-in golden file.
All new behavior is covered by tests; tests live in sibling __tests__/ directories per module with co-located fixtures.
The kb.yaml configuration schema is documented (package README and/or .agents/PROJECT.md entry) so a future project can declare a KB without reading source.
Dependencies
None. Blocks the planned @codeassembly/kb-mcp package ticket and the three skill tickets (kb-retrieve, kb-add, kb-curate).
Problem
The three planned KB skills (
kb-retrieve,kb-add,kb-curate) all need the same plumbing: a way to find a knowledge base on disk, load its schema and aliases, parse and write note frontmatter, and validate notes against the schema. Without a shared library, each skill would reimplement that plumbing or shell out to vault-local scripts, leading to drift between skills, no typed contract, and no single place to evolve the schema. A future MCP server wrapping these operations needs a clean library surface to wrap.Context
The vault at
github.com/williamthorsen/vaults.codingalready implements equivalent logic in bash and TypeScript:scripts/add-frontmatter.shfor frontmatter generation,scripts/check-notes/for validation, and.kb/schema.yamlplus.kb/tag-aliases.yamlas data. Those implementations are vault-local. The goal here is to extract the durable concepts (not literal code) into a typed monorepo library with an ergonomic, MCP-wrappable surface that any KB-aware skill can consume.Monorepo conventions to mirror: async I/O via
node:fs/promises, Zod 4 for input validation at API boundaries, subpath exports for tree-shaking, sibling__tests__/directories per module. The reference shape is@codeassembly/run-core.A separate follow-up ticket will deliver an MCP server (
@codeassembly/kb-mcp) that wraps this library; the skill tickets (kb-retrieve,kb-add,kb-curate) depend on both.Proposed solution
Ship
@codeassembly/kb-coreatpackages/kb-core/, with five subpath exports addressing distinct concerns:discovery: finds the active KB by ancestor walk for.kb/, and loads and merges the user-global (~/.claude/kb.yaml) and project-local (.agents/kb.yaml) registries. The on-disk config is akbs: { name: { path, description?, default?, readonly? } }map; project entries replace user entries by name on collision; relative paths resolve against the config file's directory; tilde expands to$HOME. Path existence is not checked at load time.schema: exposes a bundled default schema (Diátaxis four types plus the canonical required and optional field set), reads per-KB.kb/schema.yaml, and merges them under narrow-only rules. Types may be narrowed (subset of default), required and optional may be extended, but a default-required field cannot be demoted to optional. Violations of these rules are rejected at load time.frontmatter: parses a note (from path or literal string) into aParsedNotecarrying typedFrontmatter(required fields strongly typed; optional fields preserved through anextramap), and writes aFrontmatterplus body back to YAML. Round-trip is idempotent.tags: loads.kb/tag-aliases.yamlinto a typedAliasMapand exposescanonicalize/findAliasForfor tag resolution.rules: exposesfrontmatterRuleandtagAliasRuleasKbRuleobjects ({ name, check(input): Finding[] }), plus arunRulesaggregator. Skills compose whichever rules they need; the interface is the extension point for future rules.All public functions take single-object inputs so a future MCP wrapper can mechanically bind Zod-validated payloads. The library throws on errors; success/failure shaping is left to consumers like the future
kb-mcpserver.Out of scope for this ticket: the vault index, content-shape rules (wikilinks, hardcoded paths), the MCP server, and any user-facing skill.
Acceptance criteria
@codeassembly/kb-coreis importable by other monorepo packages via five subpath exports (./discovery,./schema,./frontmatter,./tags,./rules).findKbRootwalks ancestor directories for.kb/and returns the discovered root ornull.loadKbConfigreads~/.claude/kb.yamland.agents/kb.yaml, merges with project entries overriding user entries by name, expands tilde paths, and resolves relative paths against the source file's directory.loadSchemareturns the default verbatim when no.kb/schema.yamlexists, merges per-KB overrides under narrow-only rules, and rejects illegal overrides (new types, demoted required fields, fields in both required and optional) at load time.parseNoteandwriteFrontmatterround-trip the vault's frontmatter shape exactly: required fields, optional fields preserved, UTC dates, list-form tags.loadAliasesreads.kb/tag-aliases.yaml, rejects collisions and self-aliases at load time, and produces anAliasMapconsumed bycanonicalizeandfindAliasFor.frontmatterRuleandtagAliasRuleproduce findings whose rule codes (frontmatter.*) and severities match the vault'scheck-notesoutput on a vendored fixture set representative of each finding code; verified by a parity test against a checked-in golden file.__tests__/directories per module with co-located fixtures.kb.yamlconfiguration schema is documented (package README and/or.agents/PROJECT.mdentry) so a future project can declare a KB without reading source.Dependencies
None. Blocks the planned
@codeassembly/kb-mcppackage ticket and the three skill tickets (kb-retrieve,kb-add,kb-curate).