#635 Add the kb-core foundation library - #641
Merged
Merged
Conversation
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.
Dependency auditProduction dependency audit passed. |
williamthorsen
marked this pull request as ready for review
May 22, 2026 02:30
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-coreatpackages/kb-core/, mirroring the@codeassembly/run-coreshape, with five tree-shakeable subpath exports:discovery:findKbRootwalks ancestor directories for a.kb/marker;loadKbConfigreads 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-frozendefaultSchemaconstant (the Diátaxis four types plus the canonical field set) andloadSchema, which merges a per-KB.kb/schema.yamlunder narrow-only rules and rejects illegal overrides at load time.frontmatter:parseNote/parseNoteContentproduce a typedParsedNote;writeFrontmatterrenders it back to YAML with a guaranteed-idempotent round trip.tags:loadAliasesreads.kb/tag-aliases.yamlinto anAliasMap;canonicalize/findAliasForresolve tags.rules:frontmatterRuleandtagAliasRuleas composableKbRuleobjects plus arunRulesaggregator, ported from the coding vault'scheck-notesand 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.mdsection document thekb.yamlregistry 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 existingjs-yaml; the second YAML library is a deliberate, package-scoped choice.Closes #635