docs(kb): handoff for the remaining knowledge-base v2 work (K-D … K-I)#281
Conversation
Design of record for three KB capabilities on top of v1.0 (#235-#242): - K1 paste any link (web / 公众号 / B站 / YouTube) -> Markdown source - K2 a watchlist -> daily fetched, classified, summarized brief - K3 [[slug]] parsed into a real link graph; index.md becomes a ranked map-of-content; memory stores [[kb:slug]] links instead of content Review of the shipped v1.0 code found five gaps this plan closes, one of them a confirmed bug: both TF-IDF tokenizers (kb/search.ts:45 and memory/vector.ts:212) split on whitespace, so a whole CJK run collapses into a single token and Chinese content only matches an exact whole-string query. Also: normalizeSlug strips non-ASCII so every Chinese title falls back to entry-<timestamp>; web_fetch is lossy plain text with no metadata and no store; [[slug]] is documented in SCHEMA.md but nothing parses it; and there is no KB scheduler at all. Includes the pro/con debate for each decision (self-built extractor vs deps vs remote reader, immutable sources vs re-ingest, autonomous ingest as a prompt-injection path, consent, ASCII vs CJK slugs, brief storage, and RSSHub for feeds without one) and a nine-PR ship order. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three foundation fixes the rest of the KB v2 work stands on
(docs/PLAN_KNOWLEDGE_BASE_v2.0.md K-B). All three are things that would
have corrupted or lost data once link ingest starts pouring Chinese
content in, so they land before the firehose.
1. CJK search was broken. Both TF-IDF tokenizers (kb/search.ts,
memory/vector.ts) carried the same three lines: lowercase, strip
non-word chars, split on whitespace. Chinese has no whitespace, so a
whole clause survived as ONE token:
doc "这篇公众号文章讲的是知识库的设计" -> one token
query "知识库" -> one token
intersection = empty -> zero hits
i.e. CJK content was only findable by an exact whole-clause query.
Replaced by a shared src/tokenize.ts that emits character bigrams for
CJK runs (plus the run itself, so an exact phrase still ranks
highest), segments at latin/CJK boundaries so "gpt模型" indexes as
"gpt" + CJK bigrams, and now keeps kana and CJK Ext-A, which the old
character class dropped entirely. Latin tokenization is unchanged.
2. Chinese titles produced throwaway slugs. normalizeSlug strips every
non-[a-z0-9] char, so a CJK title normalized to "" and addSource fell
back to entry-<Date.now()> — opaque, unstable, un-dedupable. New
kb/slug.ts mints <date>-<sha256/8> for titles with no usable latin,
keeping slugs ASCII on purpose (macOS NFD vs git NFC filenames, URLs,
git paths) with the real title in frontmatter. canonicalUrl() drops
utm_*/known tracking params so the same article shared twice
canonicalizes — and hashes — identically.
3. Entries can now carry arbitrary frontmatter via KbEntry.extra,
round-tripped on read/write. This is where ingest provenance goes
(url/site/author/published/hash/via) without the store knowing about
any of it, and it means keys a future version adds survive a
round-trip through an older one. Values are whitespace-collapsed on
write so a newline can't forge frontmatter lines on the next read.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d MOC v1.0 told Lisa to cross-link wiki pages with [[slug]] (SCHEMA.md) and recorded each page's sources: in frontmatter — but nothing ever read either one. The KB was a pile of files with decorative links. src/kb/links.ts builds the graph from three edge sources ([[slug]], [[slug|alias]], [[kb:slug]] in bodies; a wiki page's sources: frontmatter) and derives what you can't get from search: backlinks, hubs, orphans, and links pointing at pages that don't exist yet. index.md is now a ranked map of content rather than a flat listing. It is injected into EVERY system prompt and hard-capped there (prompt.ts:165), so with a flat list the truncation lands at an arbitrary point once the KB grows and whatever fell below the line stops existing as far as Lisa is concerned. Ordering wiki pages by (1 + backlinks) x recency means the cut always eats the least-connected tail. Orphans and broken links are listed as the tending to-do list, and every truncation says how much it dropped instead of trailing off. Sources are now listed by TITLE ONLY. Layer 1 is raw captured material — after link ingest lands that means whole web pages — and index.md is always-on prompt text, so an excerpt there is a direct "any page on the internet -> Lisa's system prompt" path. This is a stronger form of the containment the plan assigned to K-I (it covers every source, not just origin: web), and it costs nothing: a title is all a map needs, and the body is one kb_read away. Wiki gists stay — Lisa wrote those herself. Also: kb/index.json (the machine-readable graph, for the web UI and tools), a kb_links tool (links to / linked from / shares tags with), and kb_read now takes a bare slug or a [[wikilink]] with the layer optional, and appends the pages that link to what you just read. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
K-A/K-B/K-C are open as #278/#279/#280. This is the pickup document for whoever continues: the branch stack and how not to break it, the APIs the first three PRs landed, a file-by-file spec for each remaining PR, the repo conventions that are easy to trip over (LISA_HOME must be set before import, the tool-subset triple, the giant template literals in lisa-client.ts), and the eight decisions that are already settled so they don't get re-litigated. Two things it corrects against the plan: the index-side injection containment assigned to K-I already shipped in K-C, and in a stronger form (every source is title-only in index.md, not just origin: web), so K-I is down to the watchlist restriction and the kb_read fence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Docs-only handoff; no code to break. Accurate against the current stack — spot-checked the "sources are title-only in index.md" claim against |
|
Docs-only handoff, accurate against the shipped stack — merging. (Reminder tracked: this doc plans its own removal in K-I.) |
|
Reconciled onto main (docs-only, no code delta) and merging. Reminder tracked: this handoff plans its own removal in K-I. |
…#281 A tooling worktree symlinked node_modules to share deps; `.gitignore` matched only the directory (`node_modules/`), so the symlink file slipped past ignore and into #280/#281. Remove it from tracking and tighten the ignore to `node_modules` (matches dir and symlink). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pickup document for whoever continues the KB v2 stack. Stacked on #280.
K-A/K-B/K-C are open as #278 / #279 / #280. This doc covers everything a fresh session needs to continue without re-deriving anything:
gh pr merge --delete-branchon a middle branch auto-closes every downstream PR based on it.listFullEntries,renderIndex,buildGraph,resolveRef,kbSlug,canonicalUrl,KbEntry.extra) so K-D doesn't rebuild any of it.fetchFollowingSafeRedirectsfor every outbound fetch,heartbeat/config.ts's budget gate.LISA_HOMEmust be set before importing anything (paths resolve at import time), the tool-subset triple inregistry.ts, and the fact thatlisa-client.ts/island.tsare one giant template literal where a stray backtick — even inside a JS comment — produces a cryptic TS1005.One correction against the plan
The plan assigned index-side injection containment to K-I. It already shipped in K-C, and in a stronger form: every source is title-only in
index.md, not justorigin: webones. So K-I is down to two prongs — the watchlist restriction on autonomous ingest, and the untrusted-content fence inkb_read.🤖 Generated with Claude Code