Personal knowledge management powered by LLM agents. Turns raw documents into a structured, searchable, git-backed markdown wiki vault — then answers questions over it with typed-edge graph traversal and cited synthesis.
Follows the Karpathy LLM-wiki pattern.
npx skills add dhague/wiki-knowledge --all
Installs all eight skills into the host’s own skill directory — .agents/skills/ for OpenCode and DeepSeek Harness (which reads <projectRoot>/.agents/skills natively), .claude/skills/ for Claude Code, and the equivalent for the other hosts the skills CLI supports. Add -g to install for your user rather than the current project.
The package is host-neutral, so it carries no model tiers and no hooks: every host runs the procedures on its own session model. For the model-pinned install, use the Claude Code plugin below.
/plugin marketplace add dhague/wiki-knowledge
/plugin install wiki-knowledge
/wiki-init . inside a project to keep the vault alongside your codebase./wiki-init /some/remote/path then set WIKI_ROOT to query it from anywhere. Useful when a wiki spans multiple projects or lives on a shared drive.The plugin is the fuller install: the same eight skills, plus the three model-pinned subagents and the session hooks.
Download the per-skill ZIP files from the latest GitHub Release — one per skill — and install each via Joule Desktop’s “Install from file” option (Extensions > Add Skill > Upload).
The script layer is a TypeScript bundle shipped inside each skill’s scripts/
directory, run by node (or bun where that is the only runtime available).
In a checkout, wiki-plugin/bin/enchiridion is a thin shim that execs node
against the plugin’s own copy of the bundle.
Cost-optimised by design. Ingestion and retrieval run as subagents with model selection tuned to task. Sonnet handles the expensive judgment work (semantic chunking, edge typing); Haiku handles high-volume retrieval at a fraction of the cost. Each query only explores the frontier it needs — no expensive vector re-ranking, no full-graph traversal.
Predictability through scripts, not prompts. Everything that can be deterministic is. Page placement, frontmatter parsing, link rewriting, search indexing, and commit construction run as subcommands of a single CLI (the enchiridion bundle, run on Node or Bun) — no model in the loop. The agents call it for side effects and read its output; they never generate file paths, YAML, or git operations from a prompt.
No new infrastructure. SQLite FTS5 search runs in-process with zero extra dependencies. No additional runtime to install — the script layer runs on the Node or Bun the host already has. No vector database, no MCP server, no background daemons. The vault is just a git repo of markdown files — portable, diffable, and backup-friendly.
Trust and provenance. Every derived page traces back to its raw source through a chain of evidence. Bitemporal metadata (when the knowledge is from vs. when it was written) and explicit volatility annotations make staleness visible, not hidden.
Agent-native, not API-native. Ingestion and retrieval are skills that the host’s agent executes by reading instructions and running scripts. This means the full context window, tool use, and reasoning of frontier models are available — not limited by a fixed RAG pipeline or a hardcoded prompt template.
| Command | Purpose |
|---|---|
/wiki-init [path] |
Scaffold a new vault (folders, git repo, index) |
/wiki-ingest <path> |
Ingest one file, a folder, or sweep raw/ |
/wiki-watch |
Long-running auto-ingest watcher for raw/ |
/wiki-ask <question> |
Grounded, cited answer from the vault |
/wiki-lint [vault-path] |
Vault health check — prioritised findings against the conventions contract, plus mechanical auto-fixes |
/wiki-export ["Title"] |
Render the vault as a static HTML site — multi-page, or one self-contained file |
/save-conversation |
Capture and ingest the current session |
/wiki-lint runs 20 checks in two dimensions — structural (kind-folder
conformance, frontmatter link format, split links, duplicate frontmatter,
orphans, concept fragmentation, Consolidation-exclusion registry integrity) and
retrievability (missing volatility/source_date, unresolved supersession, data
gaps, summary quality, under-typed edges, over-typed and stale edges) — and
reports what it finds ordered HIGH, MEDIUM, LOW.
Thirteen of the checks are mechanical, run through enchiridion check <name> --json;
the other seven need page judgment. Every finding carries a fix level:
auto-fix (applied without asking — link format, unambiguous raw_source
and cross-reference repairs, folded frontmatter links, redundant frontmatter
blocks), report-only (the fix
needs author judgment), or confirm-first (a proposed change you approve or
skip — a page move, an edge retype, an orphan delete, or a concept
consolidation).
A concept consolidation (concept-fragmentation check) is proposed one cluster at a time and never
batched, because it deletes committed pages. Each cluster is first assessed:
enchiridion assess <refs...> reads every member in full from one committed
snapshot, and the assessment returns one of three dispositions — consolidate
(one concept: recommend a survivor), relate (distinct but related: recommend
typed edges), or conflict (the claims disagree: recommend the supersession
flow) — with a short rationale. Assessment writes nothing; on yes to a
consolidate it hands off to the wiki-ingest procedure, which reads the same
members, authors the merged survivor, and pins the plan to the assessed snapshot
so a cluster that changed before the write is refused rather than merged stale.
On no it offers to remember the decision as a Consolidation exclusion.
A remembered decline lives in CONSOLIDATION_EXCLUSIONS.yaml in the pages’
kind-folder — committed with the vault, and safe to inspect or edit by hand:
exclusions:
- members:
- page_ref: wiki/concepts/authentication.md
blob_oid: 9574fbc08f # the blob object ID at HEAD
fingerprint: sha256:0123456789abcdef # the semantic fingerprint
- page_ref: wiki/concepts/authorization.md
blob_oid: 53a7d7365a
fingerprint: sha256:fedcba9876543210
reason: "Distinct concepts: authentication establishes identity; authorization grants access."
The object IDs shown are illustrative; the plugin writes their full Git value
and the full semantic digest. A matching blob_oid is the fast path. If the
blob changed, the plugin compares the semantic fingerprint, which covers the
title, summary, typed relationships, supersedes and body while ignoring tags,
source date, volatility, YAML formatting and field order. An irrelevant edit
therefore preserves the decision and refreshes the cached object ID, while a
content change removes that member from the effective exclusion — unchanged
members remain excluded while at least two remain, and only a matching member
set suppresses a proposal.
On yes to the offer it runs
enchiridion exclusion add wiki/concepts/a.md wiki/concepts/b.md --reason "Distinct concepts: …"
which records the members’ committed revisions and commits the registry on its
own; deleting a record by hand lets its cluster be proposed again. Cached values
need no hand maintenance — enchiridion fix consolidation-exclusions refreshes
a blob ID whose page changed only in ways outside the fingerprint and collapses
identical records, while --prune drops members that no longer match HEAD and
deletes a record left with fewer than two. A malformed registry never suppresses
a candidate and is reported as a HIGH finding. Any contradicts: or
supersedes: edge between two candidate pages excludes them from a proposal
automatically, with no registry record.
A term that recurs across three or more pages without a page of its own is
proposed as a candidate for extraction. Recurrence is judged from page bodies,
never from titles, summaries, tags or search snippets: enchiridion read-pages
reads them from one committed snapshot in bounded batches (12 pages or 48 KiB at
a time), and every body a lint run reads is remembered for that run, so a page
another body-reading check already read is not read twice. A run’s sweep reads at
most 60 new bodies by default. A proposal names the term and at least three
supporting pages with the revision each was read at, so anyone can re-run the
read and check it; when a large vault leaves pages unread, the report says so
rather than implying full coverage.
Invocation follows the usual vault-root resolution: $WIKI_ROOT if set, else a
path argument, else the current directory.
/wiki-export asks whether you want multi-page or a single file, then passes
these through to enchiridion export. Multi-page is the default: a directory
of linked HTML pages under web/. --single-file writes one self-contained
HTML file instead — every page a section, navigated by hash — which is the
form to attach to an email: the recipient opens it straight from the
attachment, with no unzip, no server and no network.
| Flag | Purpose |
|---|---|
--single-file |
Write one self-contained HTML file instead of a directory tree |
--out <path> |
Output directory — or, with --single-file, the output file (defaults: web/, or wiki.html at the vault root) |
--raw |
Include raw/ pages in the site |
--force |
Overwrite a non-empty output directory (multi-page only) |
--allow-dirty |
Export with uncommitted changes in the exported subtree |
--title <title> |
Wiki title for this run only |
--save-title <title> |
Save the title as the persistent default (exports nothing) |
--start-page <ref> |
Export this page as the site’s landing page for this run only |
--save-start-page <ref> |
Save the ref as the vault’s persistent start page (a blank ref clears it; exports nothing) |
--candidates |
Emit the ranked entry-point candidates as one JSON line, then exit (writes nothing) |
--starters <refs...> |
The entry-point pages for the front page, optionally as ref=annotation |
A single-file export over 5 MB is still written, with a note on stderr suggesting multi-page mode.
The site’s title — the sticky nav bar and the front page heading — resolves
--title → the saved title → the vault root directory name. /wiki-export
"My Knowledge Base" sets it for one run and then offers to save it; the saved
title lives in .wiki-knowledge/config.json at the vault root, gitignored
beside the search index (ADR-0023).
--start-page nominates a page as the site’s landing page. That page is
exported at the front page’s path instead of its own — so it appears exactly
once, and every link to it resolves to the landing page — the generated
title/page-count/get-started blocks are dropped, and a list of the kind index
pages is added at the foot of the page. The choice resolves the same way as
the title: --start-page → the saved startPage → the generated front page.
--save-start-page persists it (a blank value clears it). A nominated page
the export does not carry is a hard error rather than a quiet fallback to the
generated front page (ADR-0022).
raw/ # Inbox — drop documents here for ingestion
wiki/
concepts/ # Abstract ideas, frameworks, definitions
entities/ # Concrete people, tools, projects, organizations
sources/ # Provenance stubs (one per raw artifact)
synthesis/ # Cross-cutting analysis and summaries
Every page has YAML frontmatter with a typed edge graph (refines, contradicts, example-of, source, related, supersedes) and bitemporal metadata (source_date, volatility).
The script layer is a single TypeScript implementation
(ADR-0017),
bundled by esbuild and invoked via wiki-plugin/bin/enchiridion — a thin shim
that execs node against the bundle. ENCHIRIDION_BIN points that entrypoint
at a local build or alternate runtime instead.
cd enchiridion-ts
npm ci
npm run typecheck && npm run lint && npm run format:check
npm run build # esbuild bundle to dist/cli.cjs + wasm sidecar
npm test
# Run any subcommand against the built bundle
WIKI_ROOT=<path_to_vault> node dist/cli.cjs search "connection pooling" --limit 10
WIKI_ROOT=<path_to_vault> node dist/cli.cjs ingest-scan --json
wiki-plugin/skills/ is the canonical, hand-edited skill tree; repo-root
skills/ is a generated copy of it, kept in step by scripts/sync-skills.sh —
which scripts/release.sh calls too, alongside refreshing the bundle inside
every skill. CI fails a PR if the two trees diverge.
wiki-plugin/tests/ holds the shim’s bats suite (brew install bats-core).
The skill tree’s structural checks — frontmatter name matches the directory,
descriptions are non-empty, no host-specific spelling in portable prose, and
cut-release carries a real metadata.internal: true — live in the TypeScript
suite (enchiridion-ts/src/skills.test.ts) and run with npm test.

Key decisions are documented in docs/adr/:
See CONTEXT.md for the domain glossary.
Full architecture documentation is here.