Copyright (C) 2026, Rijn Buve
This repository started as an implementation of Andrej Karpathy's idea for an LLM-maintained knowledge base: keep source notes in a folder, let an LLM maintain a Wiki on top of them, and use the Wiki as the practical interface for remembering and reasoning. This implementation keeps that core idea, but goes considerably further. It is designed for long-lived work knowledge, where old notes do not merely accumulate; they need to be curated, checked, superseded, and sometimes deliberately ranked lower when answering current questions.
The knowledge base is structured as an Obsidian vault, assisted by the semantic database QMD. Raw evidence lives in raw/; canonical pages live in wiki/. Ingestion adds and updates canonical pages, while curation improves selected pages over time using source evidence, page-level provenance, and freshness metadata. Provenance-aware query tooling can explain when a page is current, stale, unverified, or superseded, so outdated information is not silently treated as equally reliable.
The primary goal is efficient decision intelligence: understanding why decisions were taken, on what basis, by whom, and when. Secondary goals include mapping how technologies and systems relate, who is involved in what, and how competitors compare. And 'efficient', because the mechanism needs to be token (and environmentally) efficient.
Division of labor:
- The user curates source files in
raw/. - LLM does all writing, cross-referencing, and bookkeeping in
wiki/. - Obsidian is the UI for entering/accessing notes and asking questions (e.g. through
Claudian).
# 1. Clone this repo
git clone <repo-url> ~/my-knowledge-base
# 2. Create the raw/ subdirectories (raw/ is not stored in git; wiki/ is created empty by the clone)
cd ~/my-knowledge-base
mkdir -p raw/{notes,clips,confluence,diary,emails,transcripts,scans,slack}
# 3. Install QMD (the semantic search engine; it runs on the Bun runtime)
npm install -g bun
npm install -g @tobilu/qmd
# 4. Register the vault as a QMD collection and build the index
./scripts/qmd-full-reindex.sh
# 5. Register QMD as an MCP server for Claude Code (user scope, so it works in every project)
claude mcp add --scope user qmd -- qmd mcp
# Or just ask Claude: "read this README.md and install QMD as an MCP server"
# 6. (Optional) Install the QMD skill globally, for use in other projects.
# This repo already ships the skill in .claude/skills/qmd, so this step is not needed here.
qmd skill install --global --yes
# 7. Open this directory as an Obsidian vault: File → Open Folder as VaultIf you use the Obsidian Web Clipper, import config/obsidian_webclipper_template.json as a clipper template so clips land in raw/clips/ with the expected frontmatter.
After setup, put your notes in raw/ and tell Claude: "ingest new notes".
Alternatively, run:
./scripts/wiki-ingest.shAfter ingesting notes, run the doctor regularly to keep your knowledge base clean by telling Claude "health check" (or "lint"):
./scripts/wiki-doctor.pyFreshness checks are the third regular maintenance action, alongside ingest and doctor. This check validates the provenance frontmatter of generated notes and builds the curation queues that improve search relevance later. You can ask for a "freshness check", or run:
./scripts/wiki-freshness.shYou can keep notes that you do not want to be ingested yet (like drafts) in INBOX. The inbox is not part of the ingestion process, and (apart from INBOX/RELEASE-NOTES.md) it is not stored in git.
For sensitive Markdown notes that should remain in raw/ but never be ingested, add frontmatter:
---
ingest: false
---The batch importer skips that note and any local raw/ files explicitly linked from it with wikilinks, embeds, or Markdown links/images. It prints the skipped note basename with the number of linked files skipped, and it does not write a skip entry to wiki/log.jsonl. Remove the field later to make the note eligible for ingestion again.
Provenance data is used to better establish the relevance of a page with respect to a query. It is more powerful than just looking at the "note creation date". It records, per canonical page, which raw notes the page was built from, who (or which agent) generated it and when, who verified it since, and optionally when it should be considered stale. Using this data, the wiki-query skill is much better able to establish the relevance of pages for a specific query.
The provenance data is stored directly in the YAML frontmatter of generated canonical notes under wiki/, not in a hidden sidecar database. That keeps each page readable in Obsidian, reviewable in Git, and usable by LLMs even when only the Markdown file is available. The format is called OKF v0.2.
A canonical page with full provenance has:
- a
sources:list in the frontmatter, one entry per raw note the page draws on, each with anid(s1,s2, ...) and aresourcepath; - a
generated:mapping (by,at) recording who created the content and when; - an append-only
verified:list of{by, at}entries recording later checks; - an optional
stale_after:date, after which the page is treated as stale; - per-claim attribution in the body via Markdown footnotes
[^s1], keyed tosources[].id, with the footnote definitions at the end of the page.
Example:
---
type: system
description: "Ownership and status of the map enrichment flow."
sources:
- id: s1
resource: "raw/notes/2026-06-02 Meeting map enrichment.md"
generated:
by: "agent:wiki-ingest"
at: 2026-06-02
verified:
- by: "human:rijn.buve"
at: 2026-06-20
---
Current ownership sits with the map enrichment flow.[^s1] ^claim-owner-01
[^s1]: [[raw/notes/2026-06-02 Meeting map enrichment.md]]Trust tiers come from the by: prefix of verified: entries: human:* is the human-reviewed tier (strongest); agent:* is the machine tier; a page with only generated: is unverified (weakest). Freshness is derived from generated.at and the latest verified.at. Query tooling derives a page status (current, stale, or unknown when no provenance is present) and a confidence (high for human-verified, medium otherwise) and uses these to rank current, verified evidence higher and to explain when older or unverified evidence is being demoted rather than silently ignored.
Pages that are explicitly replaced carry superseded_by: (and the replacement carries supersedes:) in their frontmatter; a superseded page is never used as the main current answer unless you ask for history.
Stable Obsidian block IDs such as ^claim-owner-01 remain allowed as plain anchors for citation, but they carry no metadata. The earlier kb-prov-v1 provenance callout format is abolished; the linter flags any page that still contains it. Validate provenance with:
python3 scripts/system/wiki-provenance-lint.py --root .If you already have a large raw/ corpus and existing generated or curated wiki/ pages, migrate it instead of bulk re-ingesting it.
Start with a dry-run:
scripts/wiki-migrate-existing.sh --root .Then apply the migration:
scripts/wiki-migrate-existing.sh --root . --applyThe migration script defaults to the safe behavior: existing raw files are baselined in wiki/log.jsonl, so a later scripts/wiki-ingest.sh run will not re-ingest the whole historical corpus. It still respects ingest: false protected notes and their explicitly linked local raw files, so private opt-out material is not logged.
Use the full re-ingestion option only when you intentionally want old raw files to be eligible for fresh ingestion - this may be expensive and take a long time:
scripts/wiki-migrate-existing.sh --root . --apply --allow-reingest-existingThe migration flow checks structural health, optionally migrates legacy converted/ layouts, assigns freshness dates, rebuilds index pages, syncs QMD (text index only; add --qmd-embed for vector embeddings), runs the freshness/provenance queues, and writes .wiki-scratch/migration-report.md. Use --help for the remaining options (--skip-legacy-layout, --skip-qmd, --limit, --no-report, --strict).
The framework is updated regularly, so it's wise to git pull every now and then:
cd ~/my-knowledge-base && git pullRequired:
- Claude Code (CLI) — or Codex, Vibe, Junie (experimental)
- Node.js / npm — for installing bun and qmd
- QMD — local semantic search engine (
npm install -g @tobilu/qmd) - Obsidian — vault UI (free, Mac/Windows/Linux)
git- Python 3 — for the scripts under
scripts/
Optional:
- pdftotext — faster/cheaper PDF extraction (
brew install poppler); LLM vision is the fallback - Obsidian Web Clipper — one-click web article saving to
raw/clips/(template inconfig/obsidian_webclipper_template.json) - Claudian — run Claude from within Obsidian (ask Claude to install it safely)
- Amphetamine (Mac App Store) — prevents Mac sleep during long overnight ingests
Register QMD as an MCP server for Claude Code (or ask Claude to do it):
claude mcp add --scope user qmd -- qmd mcpThis writes the server into ~/.claude.json. If you prefer a project-scoped server, create a .mcp.json in the vault root instead:
{
"mcpServers": {
"qmd": {
"command": "qmd",
"args": ["mcp"]
}
}
}Claude Desktop uses a different file (~/Library/Application Support/Claude/claude_desktop_config.json) with the same JSON shape.
The Slack integration is managed via your claude.ai organization. Authorize it yourself at claude.ai → Settings → Connectors. Once authorized, the Slack tools are available automatically in all Claude sessions — no local configuration needed.
The email integration uses Microsoft Power Automate to save emails to a OneDrive folder, which syncs to your local disk. Ask "fetch mail" to copy files from that folder into raw/emails/ and queue them for ingestion.
-
Create and collect notes:
- User produces raw notes and stores them in the
raw/notesdirectory. - User uses the Obsidian Web Clipper to store notes in
raw/clips. - User stores
.vttmeeting transcripts inraw/transcripts. - User asks "fetch mail" to copy emails from the configured inbox to
raw/emails/, or drags.eml/.htmlfiles there manually. - User stores handwritten notes or scanned pages (PDF, JPG) in
raw/scans. - User fetches Slack channels and DMs by asking "fetch slack" — messages are written to
raw/slack/<channel>/andraw/slack/DM-<Name>/.
- User produces raw notes and stores them in the
-
Ingest notes:
- User asks to "ingest new notes", "ingest Confluence page
<URL>" or runswiki-ingest.sh. - Non-Markdown inputs are converted first:
.vtttranscripts and.eml/.htmlemails deterministically by the ingest script,.pdf/.jpgscans by the LLM. The original file is moved into a_resources/subdirectory of its directory, and a companion.mdnote is written next to it (in the same directory as the original was). - Markdown notes with
ingest: falsefrontmatter, plus localraw/files explicitly linked from them, are excluded from batch ingestion and are not logged. - LLM partitions files into batches and processes them (large ingests use N parallel LLM sessions; a single batch is handled in one session).
- After all batches are done, the coordinator session finalizes (or you say "finalize ingest"): merge session logs, assign freshness dates, rebuild
index.mdfiles, and run post-processing (QMD re-index, health check, freshness check).
- User asks to "ingest new notes", "ingest Confluence page
-
Query wiki:
- User asks a high-level question.
- LLM queries the semantic database (QMD, via MCP or CLI) for relevant page links (fast/token-efficient).
- LLM builds a freshness packet over the retrieved pages, processes them, and produces an answer to the user.
- LLM proposes to store valuable conversations in
wiki/conversations/to extend the knowledge base.
-
Maintenance:
- Every now and then ask for a "health check" and a "freshness check" to fix broken links and validate provenance, which increases search relevance over time.
The combination of using a semantic database to fetch relevant pages before analyzing documents and reasoning about them makes this implementation of a knowledge base significantly faster and more token efficient than one that uses Markdown files only.
The skills live in .claude/skills/ (one SKILL.md per skill) and are mirrored to .agents/, .codex/ and .junie/ for other agents (see Development). These skill commands and natural-language triggers are available.
Commonly used:
| Command / phrase | Skill | Description |
|---|---|---|
| ask any question | wiki-query |
Query the knowledge base (default behavior) |
| "ingest new notes" | wiki-ingest |
Start a new ingest of raw notes or a Confluence page (Session 1 — coordinator flow) |
| "fetch slack" | wiki-fetch-slack |
Fetch Slack channels and DMs into raw/slack/; ingest afterwards |
| "fetch mail" | wiki-fetch-mail |
Copy emails from the configured inbox to raw/emails/; ingest afterwards |
| "health check" or "lint" | wiki-doctor |
Check for broken links, orphaned pages, stubs, loose files, and frontmatter problems |
| "freshness check" | wiki-freshness |
Run the one-command provenance lint / drift queue / coverage backlog check |
| "curate page" or "refresh this page" | wiki-curate-page |
Clean up one canonical page using raw evidence and freshness/drift signals |
| "add missing [topic]" | wiki-add-missing |
Create a new Wiki page for a missing concept, person, system, etc. |
| "write an article about [topic]" | write-article |
Draft a footnoted, publication-quality article from vault knowledge plus web research |
Less common / maintenance:
| Command / phrase | Skill | Description |
|---|---|---|
| "ingest next batch" | wiki-ingest-next-batch |
Continue ingesting the next batch (Sessions 2–N flow) |
| "finalize ingest" | wiki-finalize-ingest |
Finalize the ingest: merge logs, assign dates, rebuild indexes, run post-processing |
| "clear ingest batches" | wiki-clear-ingest-batches |
Remove incomplete batch files to restart a failed ingest |
| "migrate existing knowledge base" | wiki-migrate-existing |
Prepare an existing raw/ + wiki/ corpus without bulk re-ingesting historical raw notes |
| "ground this conversation" or "wiki-ground [topic]" | wiki-ground |
Treat the KB as source of truth for this conversation; optionally front-load a topic |
Background skills (loaded by other skills, not invoked directly):
| Skill | Description |
|---|---|
wiki-ingest-per-note |
Per-note processing rules: file conversion, topic assignment, wikilinks, provenance frontmatter |
wiki-templates |
Page structure and frontmatter for each topic type; used whenever a Wiki page is created |
qmd |
How to search the QMD semantic database (upstream skill, with a Codex sandbox note added) |
.claude/agents/ holds two sub-agent definitions (wiki-ingest-next-batch, wiki-finalize-ingest) that the wiki-ingest skill dispatches during large imports.
The ingest next batch and finalize ingest commands are only needed for importing large amounts of notes. The LLM will notify you when you ingest new notes and it sees that batched importing is required.
You can use the script scripts/wiki-ingest.sh to start ingesting new notes. The advantage of this script is that it runs unattended: it converts raw files, partitions new notes into batches (without calling the LLM), runs one ingest next batch session per batch, runs finalize ingest, and finishes with the doctor, a QMD re-sync, and a freshness check. When the backend is Claude, it pauses for 30 minutes whenever your 5-hour usage window is at or above the threshold (default 85%) and then resumes.
By default, the script reads config/settings.md and uses its ai_backend
frontmatter value. You can override it for a single run like this:
scripts/wiki-ingest.sh [--agent claude|vibe|codex|junie]
Other options include --threshold, --max-batches, --max-files-per-batch, --wait-between-batches, --max-errors, and --dry-run. Use wiki-ingest.sh --help for details.
The ingest script runs the doctor automatically at the end of each run. The doctor itself never calls an LLM; it is a fast, deterministic check for:
- broken internal links and embeds (optionally external HTTP links with
--external); - orphaned pages (no incoming links) and stub pages (identified by the LLM but never filled in);
- frontmatter problems, such as a missing
description:or leftover legacy provenance markup; - files whose names differ only in accents (duplicates created by syncing between file systems);
- loose non-Markdown files and misplaced or orphaned attachments;
- footnote references without a definition (and vice versa);
- leftover legacy
converted/directories.
You can run it manually by simply executing:
scripts/wiki-doctor.py
This opens an interactive TUI to deal with:
- Broken links: these can be removed, flagged, replaced with plain text, or retargeted.
- Orphaned pages: these can be deleted, or kept (marked with
orphan: false). - Stub pages: these can be deleted, or kept and acknowledged in their frontmatter.
- Orphaned attachments: these can be previewed and deleted.
Using this interactive mode, you should be able to keep your knowledge base 100% free of false positive alerts so it's easy to see if the knowledge base is still sound or not. Use --batch-mode to suppress the TUI and get text/JSON output only (--format text|json). --fix-simple-errors repairs broken links that have a unique normalized match, prunes wiki/log.jsonl, and relocates loose files into _resources/ using the Obsidian CLI (Obsidian must be running). --fix-orphans turns plain-text mentions of orphaned pages into wikilinks. See --help for the full list.
After running ingestion of notes (e.g. by scripts/wiki-ingest.sh), you are advised to run:
scripts/qmd-full-reindex.sh
This registers the vault root as a single QMD collection (named tomtom; the name is hard-coded in scripts/system/qmd-sync-collections.sh), removes any stale per-subdirectory collections, runs qmd update (text index), and then qmd embed (vector embeddings). Pass --skip-embed to skip the slow embedding step, or --reset to drop all collections and the index database first. The LLM skill wiki-query makes use of the semantic database, so make sure it's up-to-date.
Instead of running a full re-index, you can also execute qmd embed. This is useful if you only ingested a couple of new notes, for example.
Canonical pages carry OKF v0.2 provenance frontmatter (see Some background on provenance). The easy command is:
scripts/wiki-freshness.sh --root .
It runs the provenance lint, builds a freshness inventory, and writes two queues under .wiki-scratch/ (generated local working state, ignored by Git):
freshness-curation-candidates.md, produced bywiki-drift-detect.py: pages where related raw evidence, newer raw notes, or freshness statuses suggest a real query-time risk.provenance-coverage-backlog.md, produced bywiki-provenance-coverage.py: every canonical Wiki page whose provenance is missing (no-provenance) or invalid (invalid-provenance), even when no freshness risk has been detected yet.
Use the drift queue first for answer-quality improvements. Use the coverage backlog for gradual migration planning. The ingest script runs this automatically after finalization, and wiki-doctor.py reminds you about it in its recommendations. You can run it manually any time, especially after fixing doctor findings or before freshness-sensitive query work.
Classifier-reviewed legacy pages can receive a minimal provenance stamp instead of a full rewrite. This writes sources:, generated:, and (optionally) verified: frontmatter from a reviewed JSON manifest, which moves the page from the backlog to covered:
python3 scripts/system/wiki-provenance-stamp-status.py \
--root . \
.wiki-scratch/freshness-auto-ok.json
Use this only with a reviewed manifest. It is not a substitute for detailed per-claim curation.
Use wiki-curate-page for one-page cleanup when drift detection shows that newer raw notes may affect a canonical page. Just ask "curate this page". Under the hood, the skill prepares a read-only packet for the target page:
python3 scripts/system/wiki-curate-page.py --page "wiki/concepts/Some Concept.md" --format json
The wiki-query skill uses a related helper to rank retrieved pages by provenance freshness before answering. With --qmd it runs QMD candidate discovery itself:
python3 scripts/system/wiki-freshness-query.py \
--query "What is current?" \
--qmd \
--format text
With --qmd, raw-note hits are preserved too: raw hits that link to or title-match a canonical page are mapped into raw_mappings and can pull that canonical page into the ranked packet; raw hits without a canonical match remain visible as raw_evidence. If candidate pages were already retrieved by another search path, pass them explicitly with repeated --page arguments instead of --qmd.
None of these tools bulk-rewrite wiki/.
You can store notes in INBOX while you're working on them and you don't want them ingested yet. Move them manually to raw/notes once you think they are ready for ingestion. Then run scripts/wiki-ingest.sh.
You can use the Obsidian CLI to interact with Obsidian on the command-line. Or, even better, have Claude interact with Obsidian using the CLI: the skills use it to move, rename, and delete notes so that Obsidian updates all internal links. Take a look at Obsidian skills for Claude to install skills for Claude on how to use Obsidian.
Provide personal info on who you are, what you do, and what your focus is, in config/personal_info.md (this file is gitignored):
# Personal Info
My name is ...
I am ...
# My Main Focus
- Strategic decision making on technology choices.
- ...If the file is missing, or it contains no info topics, default topics will be used. The same file holds the # Slack and # Email sections described below.
Set the local LLM CLI used by scripts/wiki-ingest.sh in config/settings.md:
---
ai_backend: claude
---Supported values are claude (claude -p ...), vibe (vibe -p ...),
codex (codex exec ...), and junie (experimental; the script asks for
confirmation and uses smaller batches). Usage throttling only works for
claude. If the selected CLI is missing or fails, the script keeps
deterministic state intact and stops before consuming LLM-backed batches.
Add a # Slack section to config/personal_info.md to configure which channels and DMs to fetch:
| Channel / DM | Days | Mode |
|---|---|---|
| #architecture-decisions | 14 | signal |
| #team-platform | all | |
| @Alice van Dijk | 7 | software design decisions |
#channel-name— a public or private Slack channel@Person Name— a direct message thread with that person- Days — how many calendar days back to fetch conversation updates (default: 7; say "fetch slack last N days" to override once)
- Mode —
signalfilters out noise (absences, bot messages, bare acks);allincludes everything; any other text is treated as a topic filter (only threads directly about that topic are included)
You can use Microsoft Power Automate to automatically save incoming emails as .html files so they are picked up by the ingestion pipeline.
Create a flow with these steps:
- Trigger: When a new email arrives (V3)
- Action: Get email (V2) — to get the full email details and body
- Action: Create file (OneDrive for Business) — save to a dedicated OneDrive inbox folder (e.g.
KnowledgeSystem/inbox). This folder syncs to your local disk; configure its local path inconfig/personal_info.mdand ask "fetch mail" to copy files toraw/emails/.- File name:
@{outputs('Get_email_(V2)')?['body/receivedDateTime']}.html - File content:
FROM:@{outputs('Get_email_(V2)')?['body/from']},TO:@{outputs('Get_email_(V2)')?['body/toRecipients']},CC:@{outputs('Get_email_(V2)')?['body/ccRecipients']},BCC:@{outputs('Get_email_(V2)')?['body/bccRecipients']},SUBJECT:@{outputs('Get_email_(V2)')?['body/subject']},BODY:@{outputs('Get_email_(V2)')?['body/body']}
- File name:
The Get_email_(V2) in the expressions must match the name of the action in your flow. The resulting filename looks like 2026-05-13T08_32_05+00_00.html — the date is extracted from it automatically. The FROM, TO, CC, BCC, and SUBJECT fields become YAML frontmatter; BODY is converted from HTML to Markdown. Once the OneDrive folder syncs to your local disk, ask "fetch mail" to pull the files into raw/emails/ and drain the inbox.
Add an # Email section to config/personal_info.md to configure where "fetch mail" copies files from:
# Email
| Setting | Value |
|---------|------------------------------|
| Inbox | /path/to/your/onedrive/inbox |Set Inbox to the local path of the folder that contains your exported email files (.html and .eml) from, for example, the Power Automate flow. Files are copied to raw/emails/ (duplicates already present are skipped) and deleted from the inbox on each fetch.
You can run Claude from within Obsidian using the Claudian plugin. Install it by asking Claude:
Claude, I want you to install the following Obsidian plugin from Github. First, I want you to review
the plugin and make sure it is safe to install. And if it is safe, install it.
This is the repo: https://github.com/YishenTu/claudian
To re-create the entire Wiki, remove the wiki/ directory, /clear the LLM conversation and ask it to ingest new notes. Note that for large amounts of notes this may be expensive and take a long time.
Note: The wiki/log.jsonl file tracks which notes have already been ingested, including a content hash so that renamed raw notes are recognized as already ingested. If you share the wiki/ directory across machines, any client can run incremental ingestions without re-processing everything.
The database is automatically checked for errors at the end of each wiki-ingest.sh run. To check manually:
# Basic check (fast, no TUI):
./scripts/wiki-doctor.py --batch-mode --format text
# Interactive TUI (deal with broken links, orphans, stubs, attachments):
./scripts/wiki-doctor.py<root>/
├── .claude/
│ ├── skills/ ← the wiki skills (source of truth; one SKILL.md per skill)
│ └── agents/ ← sub-agent definitions used by wiki-ingest for large imports
├── .agents/, .codex/, .junie/ ← mirrors of the skills for other agents (generated; .agents and .junie gitignored)
├── .import/ ← in-progress batch import state (gitignored)
├── .wiki-scratch/ ← freshness queues and migration report (gitignored)
├── _resources/ ← Obsidian's default paste folder for attachments (gitignored)
├── config/ ← settings.md, personal_info.md (gitignored), web clipper template
├── templates/ ← Obsidian note templates (daily note, broken-link marker)
├── scripts/ ← helper scripts (see Scripts section)
│ ├── lib/ ← shared Python package: doctor checks/fixers/TUI, provenance, freshness, drift, curation
│ ├── system/ ← scripts invoked by skills and wrapper scripts (not normally run directly)
│ └── tests/ ← unit tests
├── INBOX/ ← draft notes (review/finish before ingestion); gitignored except RELEASE-NOTES.md
│ └── RELEASE-NOTES.md ← changelog of script and skill changes
├── raw/ ← not stored in git
│ ├── clips/ ← web articles and saved pages (web clipper)
│ ├── confluence/ ← pages fetched from Atlassian Confluence (fetch cache)
│ ├── diary/ ← dated personal/work diary notes
│ ├── emails/ ← email threads (.eml/.html → .md, originals in _resources/)
│ ├── notes/ ← notes, 1:1s, and people-specific files
│ ├── scans/ ← handwritten pages, whiteboards (→ .md, originals in _resources/)
│ ├── slack/ ← Slack channel and DM threads (fetched by "fetch slack")
│ └── transcripts/ ← meeting transcripts (.vtt → .md, originals in _resources/)
├── wiki/ ← not stored in git (only an empty placeholder)
│ ├── index.md ← top-level navigation to section indexes
│ ├── log.jsonl ← append-only ingest log (JSON Lines)
│ ├── concepts/ ← mental models and domain concepts
│ │ └── index.md ← alphabetical index of concept pages
│ ├── competition/ ← competitor profiles
│ ├── conversations/ ← interesting and valuable conversations (query results)
│ ├── decisions/ ← decision records
│ ├── people/ ← people and team pages
│ ├── problems/ ← living problem tracking pages
│ ├── projects/ ← living project tracking pages
│ └── systems/ ← living system reference pages
├── AGENTS.md ← workflow and rules for all agents (skills, topic types, naming, linking)
├── CLAUDE.md ← one-liner pointing Claude Code at AGENTS.md
├── index.md ← vault entry point
├── LICENSE
└── README.md ← this file
When a non-Markdown file is converted, the original is moved into a _resources/
subdirectory of its directory and a companion .md note is written alongside it
(in the directory the original came from). For example, raw/transcripts/foo.vtt
becomes raw/transcripts/_resources/foo.vtt plus raw/transcripts/foo.md.
The raw/ directory is not stored in Git; create it (and its subdirectories) before first use.
| Type | Purpose |
|---|---|
| competition | Competing companies, products, and approaches |
| concepts | Technologies, standards, mental models, domain vocabulary |
| conversations | Valuable results of earlier queries/conversations |
| decisions | Why decisions were taken, on what basis, by whom, and when |
| people | Colleagues, contacts, external stakeholders, teams |
| problems | Active and past problems |
| projects | Active and past initiatives |
| systems | Our products, platforms, and services |
raw/is human territory — the LLM never edits the content of your notes. The only writes are the deterministic conversions described above (moving originals into_resources/, writing companion.mdnotes, adding adateprefix/frontmatter) and theraw/confluence/fetch cache.wiki/is LLM-owned — LLM writes, the user reads. Hand-curated content in Wiki pages is never deleted or overwritten.- Pages live exactly one level deep:
wiki/<topic>/<page>.md, each withtype:anddescription:frontmatter. - The relevant
wiki/<type>/index.mdfiles are rebuilt andwiki/log.jsonlis updated on every finalized ingest. - File names are plain readable text: no slugs (spaces, not hyphens), no accents or diacritics, no characters with file-system meaning (
:,/,\,*,?,",<,>,|). - Wikilinks target the exact filename, with spaces:
[[Real-Time Map]], never[[Real-Time-Map]]. - Moving, renaming, or deleting notes goes through the Obsidian CLI so that Obsidian updates all internal links.
- Every change to
scripts/or the skills gets an entry inINBOX/RELEASE-NOTES.md.
| Script | Purpose |
|---|---|
wiki-ingest.sh |
Main ingestion pipeline: converts raw files (VTT, EML, HTML), partitions new notes into batches, runs one LLM session per batch, finalizes, and then runs the doctor, QMD sync, and freshness check. The normal way to ingest new notes. |
wiki-migrate-existing.sh |
Safe migration wrapper for existing raw/ + wiki/ corpora. Dry-run by default; with --apply, baselines existing raw files so they are not re-ingested wholesale. |
wiki-doctor.py |
Deterministic health check for the vault: broken links, orphans, stubs, frontmatter, accent duplicates, loose files, attachments, footnotes. Runs as an interactive TUI by default, or in --batch-mode for text/JSON output. |
wiki-freshness.sh |
One-command freshness check: provenance lint, freshness inventory, drift queue, and provenance coverage backlog. Run after ingest/finalize, or ask "freshness check". |
| Script | Purpose |
|---|---|
qmd-full-reindex.sh |
Register the vault as a QMD collection, run the text re-index and vector embeddings. --skip-embed for text only; --reset to wipe and rebuild from scratch. |
| Script | Purpose |
|---|---|
system/wiki-create-import-batches.sh |
Partitions un-ingested notes into batch files for parallel import sessions, honouring ingest: false. Called by wiki-ingest.sh and the wiki-ingest skill. |
system/wiki-merge-batch-logs.py |
Merges .import/batch-log-*.jsonl into wiki/log.jsonl, validating every line and quarantining malformed ones. Called by wiki-finalize-ingest. |
system/wiki-stamp-log-hashes.py |
Stamps a content hash and mtime onto every wiki/log.jsonl entry so renamed raw notes are recognized as already ingested. Called by wiki-finalize-ingest. |
system/wiki-relink-log-renames.py |
Repoints wiki/log.jsonl entries whose source note was renamed. Called by wiki-finalize-ingest and wiki-doctor. |
system/wiki-clear-ingest-batches.py |
Lists (--list) or deletes (--apply) the batch files under .import/. Called by the wiki-clear-ingest-batches skill. |
system/wiki-create-index-pages.py |
Rebuilds index.md files for each wiki section from the pages' description: frontmatter. Called by wiki-finalize-ingest and wiki-migrate-existing.sh. |
system/wiki-backfill-descriptions.py |
Adds a derived description: to wiki pages that lack one. |
system/wiki-assign-dates.py |
Deterministically infers date, date_span, and date_confidence frontmatter for raw and wiki pages from filenames, folders, and existing frontmatter. --apply to write, --revert to undo. Called by wiki-finalize-ingest and wiki-migrate-existing.sh. |
system/wiki-baseline-raw-log.py |
Adds migration-baseline entries to wiki/log.jsonl for existing raw files, while respecting ingest: false. Called by wiki-migrate-existing.sh. |
system/wiki-provenance-lint.py |
Validates OKF v0.2 provenance frontmatter and [^sN] footnotes; flags legacy kb-prov-v1 markup. Called by wiki-freshness.sh and wiki-curate-page. |
system/wiki-freshness-inventory.py |
Builds a read-only freshness inventory over raw/ and wiki/. Called by wiki-freshness.sh. |
system/wiki-drift-detect.py |
Finds canonical pages that deserve one-page curation because newer raw evidence may affect them. Called by wiki-freshness.sh. |
system/wiki-provenance-coverage.py |
Lists every canonical page with missing or invalid provenance (no-provenance, invalid-provenance). Called by wiki-freshness.sh. |
system/wiki-freshness-query.py |
Builds a query-time packet from retrieved pages, ranking them by provenance freshness and explaining demoted legacy evidence. --qmd runs candidate discovery first. Called by wiki-query. |
system/wiki-curate-page.py |
Prepares a read-only curation packet for one canonical page. Called by wiki-curate-page. |
system/wiki-provenance-stamp-status.py |
Writes minimal provenance frontmatter to reviewed legacy pages from a JSON manifest. |
system/wiki-restore-source-footnotes.py |
One-time helper: restores [^sN] footnotes from a pre-OKF vault backup. |
system/wiki-supersession-lint.py |
Checks that superseded_by / supersedes frontmatter pairs are consistent. |
system/convert-eml-to-md.py |
Converts .eml email files to Markdown with YAML frontmatter. Called by wiki-ingest.sh before ingestion. |
system/convert-html-to-md.py |
Converts .html email exports (e.g. from Microsoft Power Automate) to Markdown with YAML frontmatter. Called by wiki-ingest.sh before ingestion. |
system/convert-vtt-to-md.py |
Converts .vtt transcript files to readable Markdown with YAML frontmatter. Called by wiki-ingest.sh before ingestion. |
system/migrate-converted-to-resources.py |
One-time migration from the legacy converted/ layout to the current _resources/ layout. Dry-run by default; pass --apply to modify files. |
system/copy-claude-skills-to-other-agents.sh |
Copies .claude/skills/ and .claude/agents/ to .agents/, .codex/, and .junie/, and generates Codex TOML agent definitions, so all agents share the same skill set. |
system/qmd-reset-collections.sh |
Removes all QMD collections and wipes the search index database. Used by qmd-full-reindex.sh --reset. |
system/qmd-sync-collections.sh |
Registers the vault root as the QMD collection tomtom (idempotent), removes stale per-subdirectory collections, and re-indexes. Called by qmd-full-reindex.sh, wiki-ingest.sh, and wiki-finalize-ingest. |
- Tests live in
scripts/tests/. Run them with:python3 -m unittest discover -s scripts/tests -v
- Skills are edited in
.claude/skills/only. After changing a skill or agent definition, runbash scripts/system/copy-claude-skills-to-other-agents.shfrom the vault root to refresh the.agents/,.codex/, and.junie/mirrors;test_skill_mirrors.pyfails while they are out of sync. - Release notes: append a short entry to
INBOX/RELEASE-NOTES.mdafter any change toscripts/or the skills.
- Andrej Karpathy - for his original idea for the LLM Wiki.
- Rob van der Most - for brainstorming and experimenting with this idea.
- Christian Rexwinkel - for creating the Slack and Outlook extensions.