Skip to content

#635 Add the kb-core foundation library - #641

Merged
williamthorsen merged 11 commits into
mainfrom
635
May 22, 2026
Merged

#635 Add the kb-core foundation library#641
williamthorsen merged 11 commits into
mainfrom
635

Conversation

@williamthorsen

Copy link
Copy Markdown
Owner

What

Adds @codeassembly/kb-core, a package that gives knowledge-base skills a shared, typed foundation for the work they previously each implemented on their own: locating a knowledge base, loading its schema and tag aliases, reading and writing note frontmatter, and validating notes against composable rules.

Why

Three planned knowledge-base skills (kb-retrieve, kb-add, kb-curate) and a future MCP server all need the same plumbing: finding a KB on disk, loading its schema and aliases, parsing note frontmatter, and validating notes. Without a shared library each would reimplement that plumbing or shell out to vault-local scripts, drifting apart with no typed contract and no single place to evolve the schema.

Details

🎉 Features

New package @codeassembly/kb-core at packages/kb-core/, mirroring the @codeassembly/run-core shape, with five tree-shakeable subpath exports:

  • discovery: findKbRoot walks ancestor directories for a .kb/ marker; loadKbConfig reads and merges the user-global (~/.claude/kb.yaml) and project-local (.agents/kb.yaml) registries, project entries overriding user entries by name, with tilde and relative-path resolution.
  • schema: a deep-frozen defaultSchema constant (the Diátaxis four types plus the canonical field set) and loadSchema, which merges a per-KB .kb/schema.yaml under narrow-only rules and rejects illegal overrides at load time.
  • frontmatter: parseNote / parseNoteContent produce a typed ParsedNote; writeFrontmatter renders it back to YAML with a guaranteed-idempotent round trip.
  • tags: loadAliases reads .kb/tag-aliases.yaml into an AliasMap; canonicalize / findAliasFor resolve tags.
  • rules: frontmatterRule and tagAliasRule as composable KbRule objects plus a runRules aggregator, ported from the coding vault's check-notes and verified by a parity test against that tool's real output.

All public functions take single plain-object inputs, so a future MCP server can bind Zod-validated payloads mechanically. The package README and a new .agents/PROJECT.md section document the kb.yaml registry shape, merge semantics, and the default schema. The package ships 149 behavioral tests in sibling __tests__/ directories with co-located fixtures.

📦 Dependencies

Adds yaml (eemeli) 2.9.0. Its CST/Document API gives rule findings accurate source line numbers and has no equivalent in the monorepo's existing js-yaml; the second YAML library is a deliberate, package-scoped choice.

Closes #635

Adds the `@codeassembly/kb-core` workspace package, a knowledge-base foundation library that underpins the planned `kb-mcp` server and the `kb-retrieve`, `kb-add`, and `kb-curate` skills. It ships five subpath exports.

`@codeassembly/kb-core/discovery` finds a knowledge base by walking ancestor directories for a `.kb/` folder and loads and merges the user-global (`~/.claude/kb.yaml`) and project-local (`.agents/kb.yaml`) registries; project entries replace user entries by name, tilde paths expand to `$HOME`, and relative paths resolve against the registry file's directory.

`@codeassembly/kb-core/schema` exposes a deep-frozen default schema covering the Diátaxis four types and the canonical required and optional frontmatter fields, reads per-knowledge-base `.kb/schema.yaml`, and merges it under narrow-only rules that reject new types and demoted required fields at load time.

`@codeassembly/kb-core/frontmatter` parses a note from a path or literal string into a typed `ParsedNote` and writes frontmatter plus body back to YAML with an idempotent round trip; date fields surface as strings.

`@codeassembly/kb-core/tags` loads `.kb/tag-aliases.yaml` into an alias map and resolves tags to their canonical form.

`@codeassembly/kb-core/rules` ships the `frontmatterRule` and `tagAliasRule` validators plus a `runRules` aggregator, verified against a checked-in parity golden representative of every `frontmatter.*` finding code.

The package README documents the `kb.yaml` configuration schema, merge semantics, default schema, and error model.
The frontmatter writer now single-quotes string fields whose value is a YAML core-schema reserved keyword (`null`, `~`, `true`, `false`). Previously such a value was emitted unquoted, so a string field literally equal to `null` parsed back as a YAML null and lost its value on a write-then-parse round trip.

A syntactically malformed `.kb/tag-aliases.yaml` now throws an error prefixed with its source path, matching the labelled errors `loadKbConfig` and `loadSchema` already produce; previously the raw `yaml` parser error surfaced with no file context.

`parseNoteContent` now takes a single object input `{ content, path? }`, consistent with the package's MCP-wrappability requirement that public functions accept object payloads.

Adds tests covering the malformed-YAML and invalid-structure rejection paths in `loadSchema` and `loadKbConfig`, the parse-error early exit in `tagAliasRule`, non-ENOENT I/O error propagation from `loadAliases`, and frontmatter writer boundaries for empty tag lists and leading-hyphen titles. The parity golden's provenance documentation is corrected to describe it accurately as a regression guard over ported rules rather than a vault-verified correctness proof.
String frontmatter and `extra` field values that look like YAML core-schema numbers, special floats (`.inf`, `.nan` and case variants), booleans, or `null`/`~` are now preserved on round-trip. Previously such values were emitted unquoted and silently re-parsed as a number, boolean, or null, breaking the documented promise that re-parsing `writeFrontmatter` output yields a structurally equal `Frontmatter`.

`renderScalar` now delegates the quoting decision to the `yaml` library's own `core`-schema stringifier instead of a hand-rolled character-class and keyword-set guard, so the renderer and parser agree on exactly which strings need quoting to survive a write-then-parse cycle.
A multi-line YAML single-quoted scalar folds embedded newlines, so a frontmatter field whose string value contains a literal newline previously serialized and then re-parsed to a different string, breaking `writeFrontmatter`'s structural-equality guarantee. Multi-line string values now render as double-quoted scalars, whose `\n` escapes keep the value on one line and round-trip the embedded newlines exactly.
Removes unreachable `undefined`-part checks in `validateDate` that followed a passing `YYYY-MM-DD` regex match, computing the year, month, and day from fixed-width slices instead. Hoists the duplicated non-null-non-array type guard into `type-guards.ts` as the shared `isRecord`, replacing the two private copies in `load-aliases.ts` and `parse-note.ts`. Reorders the `sources.project` assignment guard so the path-defined condition leads, dropping the dead half of the prior compound check.
The parity golden was previously captured by running the ported rules
themselves, so the parity test was circular: it graded the port against
an answer key the port produced, catching future drift but not a day-one
porting mistake.

Recapture the golden from the vault's real check-notes rules
(github.com/williamthorsen/vaults.coding, commit 128ce97), feeding them
inputs equivalent to kb-core's so only rule logic is compared. The real
vault output is byte-identical to the existing golden, confirming the
port is faithful, so expected-findings.json is unchanged. The provenance
docs in the parity README and the parity.test.ts header are rewritten to
state the golden is now a genuine parity proof and to document the real
capture procedure.
Annotate the package-internal exports — exported for cross-module use but
deliberately kept out of the subpath barrels — with the `@internal` TSDoc tag,
so their non-public status is explicit rather than left to prose.

`parseNoteWithDocument` is imported only by rule tests, never by production
code, so it carries `@internal - Exported to allow testing`; the cross-module
helpers get a bare `@internal`.

`noteLineOf` had no importers outside its own module, so its `export` is
dropped, making it module-private.
Separate the two concerns findKbRoot interleaved — walking up the directory
tree, and searching each ancestor for a `.kb/`. The walk moves into a private
`ancestorDirs` generator; findKbRoot becomes a single-exit `for...of` over it.

`ancestorDirs` terminates on a real condition (`parent !== current`), replacing
the `for (;;)` infinite loop and its mid-body exit.
`loadAliases` threw on a missing `.kb/tag-aliases.yaml`, unlike its sibling loaders — `loadSchema` and `loadKbConfig` both absorb a missing optional file — and contrary to the README's stated error model. A tag-aliases file is optional; `loadAliases` now returns an empty `AliasMap` on ENOENT, which `tagAliasRule` already treats as a no-op.
`renderExtraEntry` rendered numeric `extra` values with `String(value)`, so a non-finite number emitted `Infinity`/`NaN` — neither a YAML core float literal — and re-parsed as a string, breaking the documented round-trip contract. The numeric and boolean branch now routes through `yaml.stringify` under the same `core` schema as `renderScalar`, emitting `.inf`/`.nan`. Adds round-trip coverage for numeric, boolean, null, and structured `extra` values, which previously had none.
…branch

`REQUIRED_FRONTMATTER_KEYS` was an exported constant with no consumer — a third copy of the required-key list, backed by a private `REQUIRED_KEYS` that existed only to feed it. Both are removed; `toFrontmatter`'s switch is the only required-key logic and is unchanged.

`toDateString`'s `value instanceof Date` branch was unreachable: `documentFor` parses with `schema: 'core'`, which omits the YAML 1.1 timestamp tag, so a scalar value is never a `Date`.

Adds an accept/reject test for the exported `frontmatterSchema`, which previously had no coverage.
@williamthorsen williamthorsen added the feature Added or improved external functionality label May 22, 2026
@github-actions

Copy link
Copy Markdown

Dependency audit

Production dependency audit passed.

@williamthorsen williamthorsen self-assigned this May 22, 2026
@williamthorsen williamthorsen changed the title #635 Add the @codeassembly/kb-core foundation library #635 Add the kb-core foundation library May 22, 2026
@williamthorsen
williamthorsen marked this pull request as ready for review May 22, 2026 02:30
@williamthorsen
williamthorsen merged commit 4fe35d1 into main May 22, 2026
3 checks passed
@williamthorsen
williamthorsen deleted the 635 branch May 22, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Added or improved external functionality scope:kb

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build KB foundation library: discovery, config, schema, frontmatter parsing

1 participant