Help us improve
Share bugs, ideas, or general feedback.
From claude-obsidian
Ingests files and URLs into an Obsidian vault by extracting entities and concepts, creating or updating wiki pages with cross-references. Supports batch mode and multiple transport methods (CLI, MCP, filesystem).
npx claudepluginhub agricidaniel/claude-obsidian --plugin claude-obsidianHow this skill is triggered — by the user, by Claude, or both
Slash command
/claude-obsidian:wiki-ingestThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Read the source. Write the wiki. Cross-reference everything. A single source typically touches 8-15 wiki pages.
Build, maintain, and query a personal LLM-managed markdown wiki where the LLM owns all writing, cross-referencing, and bookkeeping while the user curates sources. Includes idempotent scripts for ingest, query, and lint.
Maintains Obsidian-based LLM-driven wiki: ingests research papers/sources, compiles knowledge, manages topics/milestones/cross-references, queries wiki, runs lint checks.
Share bugs, ideas, or general feedback.
Read the source. Write the wiki. Cross-reference everything. A single source typically touches 8-15 wiki pages.
Syntax standard: Write all Obsidian Markdown using proper Obsidian Flavored Markdown. Wikilinks as [[Note Name]], callouts as > [!type] Title, embeds as ![[file]], properties as YAML frontmatter. If the kepano/obsidian-skills plugin is installed, prefer its canonical obsidian-markdown skill for Obsidian syntax reference. Otherwise, follow the guidance in this skill.
Before mutating any vault file, consult .vault-meta/transport.json (auto-created by bash scripts/detect-transport.sh). Use the preferred transport per the fallback chain:
obsidian-cli write "$VAULT" "$NOTE" < content.md (or append, property:set); see skills/wiki-cli/SKILL.mdmcp__obsidian-vault__write_note and friends; see skills/wiki/references/mcp-setup.mdWrite/Edit tools with absolute vault-rooted paths (final floor; always works)Full decision tree: wiki/references/transport-fallback.md.
Before creating any new wiki page, consult the vault's methodology mode via python3 scripts/wiki-mode.py route <type> "<name>". The router returns the vault-relative path where the page should be filed.
SRC_PATH=$(python3 scripts/wiki-mode.py route source "Karpathy 2025 LLM Wiki essay")
# generic: wiki/sources/Karpathy-2025-LLM-Wiki-essay.md
# lyt: wiki/notes/Karpathy-2025-LLM-Wiki-essay.md (also update relevant MOC)
# para: wiki/resources/incoming/Karpathy-2025-LLM-Wiki-essay.md
# zettelkasten: wiki/20260517123456-Karpathy-2025-LLM-Wiki-essay.md
ENT_PATH=$(python3 scripts/wiki-mode.py route entity "Andrej Karpathy")
CON_PATH=$(python3 scripts/wiki-mode.py route concept "Compounding Vault Pattern")
If .vault-meta/mode.json is absent, the router returns mode=generic paths (identical to v1.7 behavior). No special-casing needed in this skill.
Mode-specific follow-up:
wiki/mocs/<topic>-moc.md) to link the new note. If no MOC exists for the topic, create one using skills/wiki-mode/templates/lyt/moc-template.md.id: frontmatter field to match.wiki/resources/incoming/ by default. Do NOT auto-guess the topic; leave in incoming/ for user review.Multi-writer is safe in v1.7. The latent corruption bug from v1.6 — where two parallel sub-agents writing to the same page could silently trample each other — is closed by per-file advisory locking. Every wiki page write MUST be preceded by wiki-lock acquire <path>.
# Acquire — blocks (returns 75 EX_TEMPFAIL) if another writer holds the lock
if bash scripts/wiki-lock.sh acquire wiki/concepts/Foo.md; then
# ... do the write via the §Transport-selected method ...
bash scripts/wiki-lock.sh release wiki/concepts/Foo.md
else
# rc=75: another writer is in flight. Retry once after 2s; if still held,
# log to wiki/log.md and skip this page rather than overwrite.
sleep 2
bash scripts/wiki-lock.sh acquire wiki/concepts/Foo.md && {
# write …
bash scripts/wiki-lock.sh release wiki/concepts/Foo.md
} || echo "skipped wiki/concepts/Foo.md (locked); logged to wiki/log.md"
fi
Properties:
sha1(<vault-relative-path>); concurrent writes to DIFFERENT pages run in parallel.STALE_AFTER_SEC=60. A crashed holder unblocks in ≤60 seconds without manual intervention. See scripts/wiki-lock.sh header for the full semantics.rm -f (no PID match required). Skill authors are trusted to release locks they acquire; cross-skill release is allowed by design (a janitor running wiki-lock clear-stale --max-age 0 is the canonical recovery path).git add if any locks are currently held, so the auto-commit doesn't fire mid-ingest and produce torn commits. See hooks/hooks.json.wiki-lock is unconditional in v1.7+ — there is no feature gate, no fallback. Skills that don't acquire locks are racing against any other writer. The script is in core, not opt-in.
Sub-agent rule from v1.6 — "Sub-agents MUST NOT call scripts/allocate-address.sh" — is preserved (orchestrator still backfills addresses to keep the counter monotonic). The NEW rule is: sub-agents MAY now write pages, but MUST acquire locks first. See agents/wiki-ingest.md.
Before ingesting any file, check .raw/.manifest.json to avoid re-processing unchanged sources.
# Check if manifest exists
[ -f .raw/.manifest.json ] && echo "exists" || echo "no manifest yet"
Manifest format (create if missing):
{
"sources": {
".raw/articles/article-slug-2026-04-08.md": {
"hash": "abc123",
"ingested_at": "2026-04-08",
"pages_created": ["wiki/sources/article-slug.md", "wiki/entities/Person.md"],
"pages_updated": ["wiki/index.md"]
}
}
}
Before ingesting a file:
md5sum [file] | cut -d' ' -f1 (or sha256sum on Linux)..manifest.json with the same hash.force to re-ingest."After ingesting a file:
{hash, ingested_at, pages_created, pages_updated} in .manifest.json.Skip delta checking if the user says "force ingest" or "re-ingest".
Trigger: user passes a URL starting with https://.
Steps:
defuddle is available (which defuddle 2>/dev/null), run defuddle [url] to strip ads, nav, and clutter. Typically saves 40-60% tokens. Fall back to raw WebFetch output if not installed..raw/articles/[slug]-[YYYY-MM-DD].md with a frontmatter header:
---
source_url: [url]
fetched: [YYYY-MM-DD]
---
.raw/).Trigger: user passes an image file path (.png, .jpg, .jpeg, .gif, .webp, .svg, .avif).
Steps:
.raw/images/[slug]-[YYYY-MM-DD].md:
---
source_type: image
original_file: [original path]
fetched: YYYY-MM-DD
---
# Image: [slug]
[Full description of image contents, transcribed text, entities visible, etc.]
_attachments/images/[slug].[ext] if it's not already in the vault.Use cases: whiteboard photos, screenshots, diagrams, infographics, document scans.
Trigger: user drops a file into .raw/ or pastes content.
Steps:
wiki/sources/. Use the source frontmatter schema from references/frontmatter.md. Assign an address per the Address Assignment section below._index.md sub-indexes.wiki/overview.md if the big picture changed.wiki/index.md. Add entries for all new pages.wiki/hot.md with this ingest's context.wiki/log.md (new entries at the TOP):
## [YYYY-MM-DD] ingest | Source Title
- Source: `.raw/articles/filename.md`
- Summary: [[Source Title]]
- Pages created: [[Page 1]], [[Page 2]]
- Pages updated: [[Page 3]], [[Page 4]]
- Key insight: One sentence on what is new.
> [!contradiction] callouts on both pages.Trigger: user drops multiple files or says "ingest all of these."
Steps:
Batch ingest is less interactive. For 30+ sources, expect significant processing time. Check in with the user after every 10 sources.
Token budget matters. Follow these rules during ingest:
wiki/hot.md first. If it contains the relevant context, don't re-read full pages.wiki/index.md to find existing pages before creating new ones./search/simple/) to find specific content without reading full pages.[!note] Custom callout dependency The
[!contradiction]callout type used below is a custom callout defined in.obsidian/snippets/vault-colors.css(auto-installed by/wikiscaffold). It renders with reddish-brown styling and an alert-triangle icon when the snippet is enabled. If the snippet is missing, Obsidian falls back to default callout styling, so the page still works without the visual flourish. See [[skills/wiki/references/css-snippets.md]] for the four custom callouts (contradiction,gap,key-insight,stale).
When new info contradicts an existing wiki page:
On the existing page, add:
> [!contradiction] Conflict with [[New Source]]
> [[Existing Page]] claims X. [[New Source]] says Y.
> Needs resolution. Check dates, context, and primary sources.
On the new source summary, reference it:
> [!contradiction] Contradicts [[Existing Page]]
> This source says Y, but existing wiki says X. See [[Existing Page]] for details.
Do not silently overwrite old claims. Flag and let the user decide.
.raw/ are immutable. Do not modify the files that users drop there (articles, transcripts, images). The .raw/.manifest.json delta tracker and its address_map (DragonScale Mechanism 2) are the only files under .raw/ that wiki-ingest itself maintains. Treat every other file under .raw/ as read-only source content.Opt-in feature. DragonScale address assignment runs only if scripts/allocate-address.sh is present AND .vault-meta/ exists. Otherwise, skip this entire section and proceed with ingest normally.
Feature detection (run at start of every ingest):
if [ -x ./scripts/allocate-address.sh ] && [ -d ./.vault-meta ]; then
DRAGONSCALE_ADDRESSES=1
else
DRAGONSCALE_ADDRESSES=0
fi
When DRAGONSCALE_ADDRESSES=0, pages are created without an address: frontmatter field, and wiki-lint's Address Validation section is skipped entirely (missing addresses are not flagged in any severity). This preserves default plugin behavior for vaults that have not adopted DragonScale.
When DRAGONSCALE_ADDRESSES=1, proceed with the rest of this section.
Every newly created non-meta wiki page gets a stable address in its frontmatter:
address: c-000042
Format: c-<6-digit-counter>. The c- prefix stands for "creation-order counter." Zero-padded.
Rollout baseline: 2026-04-23 (Phase 2 ship date). Pages with created: >= this date are post-rollout and MUST have an address (unless excluded below). Pages with created: earlier are legacy-exempt until a deliberate backfill pass assigns l-NNNNNN addresses.
scripts/allocate-address.shAddress allocation is delegated to an atomic Bash helper. The helper uses flock on .vault-meta/.address.lock to prevent read-use-increment races and recovers the counter by scanning existing frontmatter if the counter file is missing.
ADDR=$(./scripts/allocate-address.sh)
# ADDR is now e.g. "c-000042"; counter is already incremented
CRITICAL: never use the Write or Edit tool on .vault-meta/address-counter.txt. That would fire the PostToolUse hook, which runs git add wiki/ .raw/ and can accidentally commit unrelated pending wiki changes under a generic message. Counter mutation is only permitted through the helper script (Bash tool).
./scripts/allocate-address.sh — atomically reserves and returns the next address../scripts/allocate-address.sh --peek — prints the next value without reserving (safe, read-only)../scripts/allocate-address.sh --rebuild — recomputes the counter from the highest observed c-NNNNNN in existing frontmatter. Never resets to 1 silently if pages already have addresses. Run this if the counter file is suspected corrupt../scripts/allocate-address.sh and capture the output.address: c-XXXXXX in the page's frontmatter..raw/.manifest.json under a new top-level key address_map (see schema below).address_map in .raw/.manifest.json{
"sources": { ... },
"address_map": {
"wiki/concepts/Example.md": "c-000042",
"wiki/entities/Another.md": "c-000043"
}
}
On re-ingest of the same source (whether by --force or a changed hash), always consult address_map first. If the target page path has a prior address, REUSE it. Do not allocate a new one.
On a page rename, the skill must update the address_map key (old path -> new path) while preserving the address value.
_index.md, index.md, log.md, hot.md, overview.md, dashboard.md, dashboard.base, Wiki Map.md, getting-started.md.wiki/folds/ (they use their own deterministic fold_id).created: < 2026-04-23). Legacy pages get l-NNNNNN addresses only via a deliberate backfill operation.address: field in its current content, REUSE it. Do not allocate a new one.address_map has a mapping for the target path, reuse that mapping.created: date is post-rollout, allocate an address and record it. This covers the case where an older ingest produced a page before Phase 2 rollout; the rollout cutoff still applies (pages dated pre-2026-04-23 stay legacy).flock in the helper prevents counter corruption but does not serialize page writes themselves.Assign addresses sequentially during single-source-ingest for each source. Do not pre-reserve a block of counter values. The helper is cheap (one lock, one integer read/write).
When working on this skill, apply the 10-principle loop. See skills/think/SKILL.md for the canonical framework.
| # | Principle | Application here |
|---|---|---|
| 1 | OBSERVE (ext) | Read the source file completely before extracting anything. No shortcuts on long sources. |
| 2 | OBSERVE (int) | Am I biased toward the source's framing? Where do my disagreements live? Note them as contradiction callouts. |
| 3 | LISTEN | The user's source-selection intent — what made THIS source worth ingesting, and what is the user hoping to extract? |
| 4 | THINK | Which entities deserve pages? Which concepts? What cross-references? What contradictions with existing pages? |
| 5 | CONNECT (lat) | This source's claims vs other sources already in the wiki. Contradictions are the highest-signal finding. |
| 6 | CONNECT (sys) | wiki-mode.py route for paths + wiki-lock.sh for safety + index/log/hot for consumer visibility. |
| 7 | FEEL | A page that compounds — useful in 6 months, not just today. Skip filler; favor synthesis over transcription. |
| 8 | ACCEPT | Not every claim is wiki-worthy. Editorial judgment is part of ingest, not a bug to remove. |
| 9 | CREATE | Source + entity + concept pages with full frontmatter; cross-references; contradiction callouts where needed. |
| 10 | GROW | Contradictions found mid-ingest are the most valuable wiki signal. File them as questions for follow-up, not silently. |