* chore(sync): ignore the regenerable graph cache
New vaults get `.graph-cache.json` in their .gitignore — it is derived data
that churns on every graph change and should never enter history.
An existing .gitignore is the user's file and is left byte-identical; the
command prints a tip instead of editing it.
* feat(graph): bridges, cohesion, paths, neighbourhoods and a betweenness cache
Brings the graph analyser up to the algorithm family graphify runs on code
graphs, in pure Python (no networkx/numpy — the package stays dependency-free):
- `bridges` — exact Brandes betweenness centrality. The pages that sit on the
most shortest paths; removing one fragments the vault.
- `cohesion` per community — intra-cluster edge density; < 0.15 with 5+ pages
marks a fragmented cluster.
- `--path A B` — shortest wikilink chain between two pages.
- `--around PAGE --depth N --direction in|out|both` — N-hop neighbourhood;
`--direction in` is the blast radius if the page were renamed or removed.
- `--diff-against` / `--snapshot` — compare against a previous `_insights.md`
snapshot (added/removed pages and links, newly connected, lost incoming).
- `suggested_questions` — derived from bridges, sink hubs, isolates and
low-cohesion clusters.
Two correctness fixes fall out of this:
- Vault bookkeeping files (`index`, `log`, `hot`, `_insights` at the vault
root, plus `_meta/` and `_readouts/`) are no longer graph nodes. They link
to nearly every page, so they dominated every degree and centrality ranking.
A nested `concepts/index.md` is still an ordinary page.
- Pages are now iterated in sorted order rather than filesystem order, so
results are reproducible across machines. That exposed a latent flaw in the
label-propagation fallback: a sequential sweep cascades one label along
chains and collapsed a barbell graph into a single community. Nodes are now
visited in a seeded shuffle — deterministic, and clusters survive.
Betweenness is O(V*E) and the only expensive step in the module (~43s on a
5,000-page vault, versus 0.13s to parse it and 0.03s for communities), so it
is memoised in `.graph-cache.json`. The cache key is a hash of the graph
topology rather than file mtimes, which makes a stale hit impossible by
construction: retargeting any link changes the key, while editing prose
without touching links keeps the cached result valid. The file is only
written when the computation actually exceeded 0.5s, so small and medium
vaults never accumulate one; it is bounded to 3 keys, written atomically,
and ignored if corrupt.
Sampled betweenness (k pivots) was measured as an alternative and rejected:
25-50x faster but only 60-90% top-10 overlap and wrong ordering even on a
38-page vault, which is not acceptable for a ranked list a human reads.
* fix(query): stop routing paths through index.md; answer structural questions
`graph-query` is the front door `wiki-query` calls first, and it built its
own graph in graphrag.py with its own page-selection rules, its own slug
helper and its own BFS. Those rules did not exclude the vault bookkeeping
files, and `index.md` links to every page — so almost any two pages looked
about two hops apart and the returned "connection" was meaningless:
owasp-agentic-quotes -> prismor-overview
before: owasp-agentic-quotes -> index -> arnav -> prismor-overview
after: owasp-agentic-quotes -> agentic-governance-maturity
-> prismor -> arnav -> prismor-overview
On a 38-page vault, 52 of 630 page pairs (8%) routed through a bookkeeping
file, and 51 of those were shorter than the real semantic path, so BFS
preferred them. The share grows with vault size.
graphrag now uses the shared page selection from graph_analysis and delegates
to its `shortest_path`, so there is one graph and one BFS in the codebase.
On top of that, `graph-query` now answers questions about the shape of the
vault, phrased in plain English, and returns them fully computed in a new
`graph` field (null for ordinary queries, so the response stays backwards
compatible):
impact "what breaks if I delete X" / "what depends on X"
bridges "which pages bridge my clusters" / "what would fragment my vault"
hubs "what's central" / "top hubs" / "my main topics"
clusters "what clusters do I have" / "how is my wiki organised"
surprising "surprising connections" / "unexpected links"
These are computed lazily: only `bridges` needs betweenness, so every other
intent stays as cheap as an ordinary lookup even on a 5,000-page vault
(<= 0.71s measured). A structural question that names an unresolvable page
falls back to ordinary retrieval rather than guessing.
* docs: document the structural intents, query modes and graph cache
- docs/cli.md: the new graph-analyse flags, the graph-query structural
questions, and a section on the cache (what it stores, why the key is the
graph topology, and when the file is written).
- wiki-query: route structural questions to graph-query and report the
`graph` payload directly instead of re-deriving it from page reads.
- wiki-status: insights mode now reads bridges, cohesion, suggested questions
and the graph delta straight from the analyser rather than instructing the
model to compute them by hand.
* docs(readme): add the structural-query benchmark vs a plain agent
Same model, same vault, same plain-English questions — the only difference is
whether obsidian-wiki is installed: 81s -> 19s, 44% -> 83% correct, at
unchanged API cost.
Includes the per-question breakdown and, in a collapsed block, the method and
its limits: ground truth verified against networkx rather than this project's
own code, n=2 per cell on a single 38-page vault, and the one run in the
"with" column that failed outright. The accuracy gap is attributed to its
actual cause — the plain agent routing through index.md, which links to every
page.
Both READMEs updated together per the translation-parity rule.
12 KiB
CLI Reference
The obsidian-wiki Python package ships a CLI for setup, inspection, and the deterministic parts of the workflow — the things that don't need an LLM. Everything else is a skill your agent runs.
pip install obsidian-wiki
obsidian-wiki --help
obsidian-wiki --version
Running obsidian-wiki with no subcommand defaults to setup.
Setup & inspection
| Command | What it does |
|---|---|
setup |
Install skills into your agents and write the global config |
info |
Show install paths, version, and resolved config |
list |
List the bundled skills |
doctor |
Health-check config, vault shape, bootstrap assets, and installed skills; with --project, also reports the code-understanding capability section |
obsidian-wiki setup --vault ~/brain
obsidian-wiki setup --project . # also install project-local skills + bootstrap files
obsidian-wiki setup --project-only # skip the global install (use with --project)
obsidian-wiki setup --copy # copy skill files instead of symlinking
obsidian-wiki setup --remote https://github.com/you/my-wiki.git # configure sync non-interactively
obsidian-wiki doctor --json --pretty
obsidian-wiki doctor --vault /other/vault --project .
obsidian-wiki doctor --strict # exit non-zero on warnings too
Commands other than setup, info, and doctor warn you when the install has gone stale (the package upgraded but skills weren't re-linked). Re-run obsidian-wiki setup to fix.
Querying & linting
| Command | What it does |
|---|---|
query <question> |
Answer a question from the configured vault's index |
lint [vault] |
Find missing frontmatter, broken links, duplicates, and orphans |
obsidian-wiki query "what do I know about MCP security?"
obsidian-wiki query "rate limiting" --top 12 --max-read 5 --json
obsidian-wiki lint # uses the configured vault
obsidian-wiki lint /path/to/vault --strict
obsidian-wiki lint @research --json # uses <config dir>/config.research only
obsidian-wiki lint --strict-trust # fail on trust-ledger problems, not just warn
obsidian-wiki lint --allow-lifecycle active --allow-relationship-type synthesizes \
--required-trust-field updated --schema-source /path/to/vault/AGENTS.md
Lint resolves its vault and schema together: explicit path (no config inheritance), positional @name, nearest CWD .env, then global config. CLI schema flags extend/replace that resolved vault's settings and are recorded in the JSON schema block.
Context packs
wiki-context-pack compiles a task-scoped snapshot from existing Markdown.
Notes do not need to be moved into wiki-generated folders or migrated to the
full frontmatter schema. The command is read-only.
obsidian-wiki context-pack "authentication architecture" --budget 8000
obsidian-wiki context-pack --recent --budget 4000
obsidian-wiki context-pack "release notes" --budget 8000 --public-only
Omitting --budget uses the default of 8000 estimated tokens.
The output includes source paths, summaries, selected excerpts, and a hard
estimated-token ceiling. Vault excerpts are explicitly marked as untrusted
reference data: downstream agents may use their facts but must not execute
instructions embedded in notes. Use --metadata-only for the smallest pack,
or --json for tool-to-tool integration.
| Flag | Effect |
|---|---|
--budget N |
Maximum estimated output tokens, 256–100000 (default 8000) |
--recent |
Select recently updated notes — the only way to omit the topic |
--public-only |
Exclude visibility/internal and visibility/pii notes |
--metadata-only |
Titles, provenance, and summaries with no body excerpts |
--json |
Structured output for tool-to-tool integration |
--vault PATH |
Override OBSIDIAN_VAULT_PATH |
context is an accepted alias for context-pack.
Session brain
Builds a topic graph over your agent session history. Output is a sidecar at ~/.claude/session-brain/ — the vault is never written to. Full detail in Session Brain.
| Command | What it does |
|---|---|
sessions-build |
Build (or incrementally update) the topic graph |
sessions-query <topic> |
Find the sessions most relevant to a topic |
sessions-show <id> |
Show one session's node and its nearest neighbours |
sessions-clusters |
List the discovered topic clusters |
sessions-name --from FILE |
Assign durable names to clusters, surviving rebuilds |
obsidian-wiki sessions-build # ~3s cold, under a second incrementally
obsidian-wiki sessions-build --full --verbose # ignore caches, re-read everything
obsidian-wiki sessions-build --since 2026-01-01 --skip archived,scratch
obsidian-wiki sessions-build --k 12 --min-sim 0.12 --mutual --half-life 60
obsidian-wiki sessions-query "prismor telemetry"
obsidian-wiki sessions-query "auth bug" --project my-app --cluster 3 --json
obsidian-wiki sessions-show 01935a40 --neighbors 12
obsidian-wiki sessions-clusters --unnamed
obsidian-wiki sessions-name --from names.json # or - for stdin
sessions-name takes a JSON array of {"id": N, "name": "...", "summary": "..."}. The /session-brain skill generates this for you.
Vault syncing
| Command | What it does |
|---|---|
sync |
Stage, commit, and push pending vault changes |
sync-setup <remote> |
Configure GitHub sync (git init, .gitignore, remote) |
obsidian-wiki sync
obsidian-wiki sync-setup https://github.com/you/my-wiki.git
See Configuration → Syncing your vault to GitHub.
Trust ledger
Records and validates human-approved confidence reviews, so you can gate on "a person actually checked these pages" in CI.
| Command | What it does |
|---|---|
trust-record |
Record explicitly approved manual confidence reviews |
trust-check |
Validate confidence values and material fingerprints against the ledger |
obsidian-wiki trust-record --all --reviewed-at 2026-07-30T10:00:00+00:00 --approved
obsidian-wiki trust-record --page concepts/rate-limiting.md --reviewed-at <ISO> --approved
obsidian-wiki trust-check --strict
obsidian-wiki trust-record @research --all --reviewed-at <ISO> --approved --allow-lifecycle active
obsidian-wiki trust-check @research --allow-lifecycle active --schema-source /vault/AGENTS.md
--reviewed-at needs a timezone. --approved is required and mandatory — it's your assertion that a human approved every confidence value being recorded. trust-check --strict is the CI/scheduled gate. trust-record and trust-check resolve the same vault-scoped schema as lint; pass the same lifecycle and required-field overrides to record and check. If the owner schema does not require base_confidence, pages without it are reported as not_applicable, excluded by trust-record --all, and any obsolete ledger entry is warned by trust-check then removed by trust-record --page or a rebuild. Both JSON and human-readable record output list excluded pages and removed obsolete entries; human output also emits a stderr warning when removal occurs. Required-field config accepts only base_confidence, lifecycle, lifecycle_changed, and updated; typos fail closed. Lifecycle, relationship-type, and required-field override values are stripped and empty or whitespace-only entries are rejected rather than added to an allowlist. Without an explicit --schema-source, CLI overrides on an explicit vault are labeled cli:explicit-vault; combined CLI and config overrides use cli+config:<resolved-config-path>.
Lower-level commands
Available for automation, scripting, and debugging. Skills call some of these internally.
| Command | What it does |
|---|---|
graph-query <vault> <question> |
Answer from the wikilink index without reading page bodies. Plain-English structural questions are answered from the graph and returned in a graph field: "what breaks if I delete X" (impact/blast radius), "which pages bridge my clusters" (betweenness), "what's central" (hubs), "what clusters do I have" (communities + cohesion), "surprising connections". |
graph-analyse <vault> [--top N] [--snapshot] [--diff-against FILE] |
Graph analysis in pure Python (the graphify algorithm family): god nodes (degree), bridge pages (Brandes betweenness centrality), communities with cohesion scores, cross-community surprising connections, suggested questions, and — with --diff-against a previous _insights.md — a graph diff. Vault bookkeeping files (index, log, hot, _insights) are excluded. |
graph-analyse <vault> --path A B / --around PAGE --depth N [--direction in|out|both] |
Query modes: shortest link path between two pages; N-hop neighbourhood of a page (--direction in = blast radius) |
batch-plan <vault> <source_dir> |
Split a source directory into parallel-ingest batches, skipping unchanged files |
cache-check <vault> <sources...> |
Which sources are new / modified / unchanged vs. .manifest.json |
cache-update <vault> <source> |
Record a source's SHA-256 in .manifest.json after ingest |
cache-hash <path> |
Compute a file or directory hash (no manifest I/O) |
ast-extract <path> |
Extract classes, functions, and imports from code — no LLM, no API calls |
code-understand --project <dir> [--backend auto|builtin|codegraph] [--since <sha>] [--changed <file>...] [--max-symbols N] [--pretty] |
Emit a ranked code-understanding focus map (symbols + file:line citations) for a project; CodeGraph when available, built-in AST + rg otherwise. Used by wiki-update Step 3b. |
obsidian-wiki graph-query /path/to/vault "transformer architecture" --pretty
obsidian-wiki graph-query /path/to/vault "what breaks if I delete tool-call-interception"
obsidian-wiki graph-query /path/to/vault "which pages bridge my clusters"
obsidian-wiki graph-query /path/to/vault "what clusters do I have"
obsidian-wiki graph-analyse /path/to/vault --top 30 --pretty
obsidian-wiki graph-analyse /path/to/vault --snapshot --diff-against /path/to/vault/_insights.md
obsidian-wiki graph-analyse /path/to/vault --path transformers lstm
obsidian-wiki graph-analyse /path/to/vault --around attention --depth 2 --direction in
obsidian-wiki batch-plan /path/to/vault ~/research --max-mb 4 --max-files 30
obsidian-wiki cache-check /path/to/vault ~/research/*.pdf
obsidian-wiki cache-update /path/to/vault ~/research/paper.pdf --pages concepts/attention.md
obsidian-wiki ast-extract ./src --pretty
obsidian-wiki code-understand --project . --since <last_commit_synced> --pretty
Most commands accept --json and/or --pretty for machine-readable output.
Graph cache
Betweenness centrality (the bridges metric) is the only expensive computation in the
graph layer — O(V·E), roughly 0.3s on a 500-page vault but ~43s at 5 000 pages. Every
other metric stays under a second even on the largest vaults.
It is therefore memoised in .graph-cache.json at the vault root. The cache key is a
hash of the graph topology itself, not file timestamps, which has two consequences:
- Editing a page's prose without changing its links keeps the cache valid.
- Adding, removing, or retargeting any link changes the key, so a stale hit is impossible.
The file is only written when the computation actually took longer than 0.5s, so small
and medium vaults never accumulate one. It is bounded to the 3 most recent keys, written
atomically (safe under concurrent runs), and ignored if corrupt — deleting it is always
safe. Running graph-analyse warms the same cache that graph-query reads, so a nightly
daily-update removes the first-query cost entirely.