add claude-obsidian
Tests / Hermetic test suite (push) Has been cancelled
Tests / Skill frontmatter validation (push) Has been cancelled

This commit is contained in:
김경종
2026-05-28 10:57:16 +09:00
parent 1b07531a45
commit 72dad72703
205 changed files with 41703 additions and 80 deletions
+298
View File
@@ -0,0 +1,298 @@
---
name: autoresearch
description: >
Autonomous iterative research loop. Takes a topic, runs web searches, fetches sources,
synthesizes findings, and files everything into the wiki as structured pages.
Based on Karpathy's autoresearch pattern: program.md configures objectives and constraints,
the loop runs until depth is reached, output goes directly into the knowledge base.
Triggers on: "/autoresearch", "autoresearch", "research [topic]", "deep dive into [topic]",
"investigate [topic]", "find everything about [topic]", "research and file",
"go research", "build a wiki on".
allowed-tools: Read Write Edit Glob Grep WebFetch WebSearch
---
# autoresearch: Autonomous Research Loop
You are a research agent. You take a topic, run iterative web searches, synthesize findings, and file everything into the wiki. The user gets wiki pages, not a chat response.
This is based on Karpathy's autoresearch pattern: a configurable program defines your objectives. You run the loop until depth is reached. Output goes into the knowledge base.
---
## Transport (v1.7+)
The research loop writes a lot — source pages, concept pages, entity pages, manifest updates. All writes follow the standard transport policy. Read `.vault-meta/transport.json` (auto-created by `bash scripts/detect-transport.sh`):
- **cli** — `obsidian-cli write "$VAULT" "$NOTE" < content.md`; see [`skills/wiki-cli/SKILL.md`](../wiki-cli/SKILL.md)
- **mcp-obsidian** / **mcpvault**`mcp__obsidian-vault__write_note`
- **filesystem** — Claude's `Write` tool with absolute path
Full decision tree: [`wiki/references/transport-fallback.md`](../../wiki/references/transport-fallback.md). Web fetches (`WebFetch`/`WebSearch`) are transport-agnostic.
---
## Mode awareness (v1.8+)
Before filing research output, consult the vault's methodology mode via `python3 scripts/wiki-mode.py route research "<topic>"`. The router returns the vault-relative path:
- **generic**: `wiki/concepts/<Topic>.md` (v1.7 default)
- **LYT**: `wiki/notes/<topic>.md` + create or update a topic MOC at `wiki/mocs/<topic>-moc.md`
- **PARA**: `wiki/resources/<topic>/<topic>.md` (topic-named subfolder under resources)
- **Zettelkasten**: `wiki/<ID>-<topic>.md` (timestamped ID prefix)
If `.vault-meta/mode.json` is absent, the router returns mode=generic paths.
When the research session produces multiple entity / concept pages alongside the main synthesis, route EACH via the appropriate router call (`route entity` / `route concept`), not just the synthesis page. Mode awareness applies to every new file the loop creates.
## Web egress hygiene (v1.8.2+)
Autoresearch calls `WebFetch` and `WebSearch` to pull arbitrary URLs. Before each fetch and before writing fetched content to the vault, apply these guards:
**1. URL validation.** Reject these schemes and targets:
- `file://`, `javascript:`, `data:` schemes — fetch only `http(s)://`
- RFC1918 private addresses (`10.x.x.x`, `172.16-31.x.x`, `192.168.x.x`) and `localhost`/`127.0.0.1` — these would target the user's internal network
- Hosts not surfaced by the prior `WebSearch` step (be conservative; do not follow redirects to domains that never appeared in search results)
The Claude Code `WebFetch` tool has built-in defenses against many of these. Apply them here as defense-in-depth.
**2. Content sanitization before writing fetched HTML into a wiki page.** Fetched content can contain prompt-style injections, fake wikilinks, or executable code fences. Before any `Write` to `wiki/sources/<source>.md`:
- Strip `<script>`, `<iframe>`, `<style>` tags and their contents
- Escape `[[` and `]]` in the source body so adversarial content cannot inject wikilinks into the vault's link graph (encode as `\[\[` or HTML-entity `&#91;&#91;`)
- Reject any `---` YAML-frontmatter delimiter inside fetched content — the source page's frontmatter is authored by the loop, not by the upstream source
- Truncate fetched bodies to ~50KB to avoid context blowout
**3. Per-loop cost expectation.** A full autoresearch run is up to **3 rounds × 5 sources × 3 angles ≈ 45 `WebFetch` calls**. WebFetch is metered through the Anthropic plan. The `max_pages: 15` cap in `references/program.md` limits FILING cost but does NOT cap FETCH count. Surface the budget expectation to the user before kicking off research on a high-cost topic.
**4. Failure mode.** If a fetch fails (timeout, 4xx/5xx, content too large, sanitization removed everything), log the URL + reason to `wiki/log.md` and continue the loop. Do NOT abort the whole run. Do NOT silently swallow — every skipped source is a fact the user needs in the synthesis page's "Open Questions" section.
The router (`python3 scripts/wiki-mode.py route`) already sanitizes the topic-derived FILENAME via `safe_name()`. This section adds the second layer: BODY-content hygiene for fetched pages.
---
## Concurrency (v1.7+)
The research loop is a high write-rate skill (often 10-30 page writes per topic). Every wiki page write MUST be preceded by `wiki-lock acquire <path>`:
```bash
bash scripts/wiki-lock.sh acquire wiki/sources/<slug>.md || sleep 2 && bash scripts/wiki-lock.sh acquire wiki/sources/<slug>.md
# … write via §Transport-selected method …
bash scripts/wiki-lock.sh release wiki/sources/<slug>.md
```
If autoresearch is invoked in parallel (e.g., two `/autoresearch` commands fired at once on overlapping topics), the locks ensure that the same source/concept/entity page is written by only one loop at a time. The losing acquire skips that page for the current pass and logs `wiki/log.md`; the page will be picked up in the next iteration of the winning loop's pass.
See `skills/wiki-ingest/SKILL.md` §Concurrency for the full lock semantics.
---
## Before Starting
Read `references/program.md` to load the research objectives and constraints. This file is user-configurable. It defines what sources to prefer, how to score confidence, and any domain-specific constraints.
---
## Topic Selection
Three paths to a topic:
### A. Explicit topic (always respected)
When the user says `/autoresearch [topic]` or "research X", use the given topic verbatim and skip the sections below.
### B. Boundary-first selection (agenda control, opt-in)
**This is agenda control, not pure memory.** DragonScale Memory.md Mechanism 4 labels this mechanism as such because it shapes which direction the research agent moves next. Users who want a strict memory-layer subset should omit this path entirely.
When `/autoresearch` is invoked WITHOUT a topic AND the vault has adopted DragonScale, default to surfacing the frontier of the vault as a set of candidate topics the user can accept, override, or decline.
Feature detection (shell):
```bash
if [ -x ./scripts/boundary-score.py ] && [ -d ./.vault-meta ] && command -v python3 >/dev/null 2>&1; then
BOUNDARY_MODE=1
else
BOUNDARY_MODE=0
fi
```
When `BOUNDARY_MODE=1`:
1. Run `./scripts/boundary-score.py --json --top 5`. Returns the top 5 frontier pages by `boundary_score = (out_degree - in_degree) * recency_weight`.
2. **Helper failure handling**: if the helper exits non-zero, emits invalid JSON, or returns an empty `results` array, set `BOUNDARY_MODE=0` and fall through to section C below. Do NOT prompt the user with an empty candidate list, and do NOT improvise a topic.
3. Present the candidate list to the user: "Your top frontier pages are: [list]. Research which one? (1-5, or type a topic to override, or say 'cancel' to be asked normally.)"
4. If the user picks 1-5, use the selected page's title as the topic.
5. If the user types free text, use that.
6. If the user cancels or does not choose, fall through to C.
The boundary score is a heuristic, not an objective measure of what SHOULD be researched. The user always has the option to type a free-text topic to override the surfaced candidates.
**Link-resolution semantics**: the boundary helper uses **filename-stem wikilink resolution only**. `[[Foo]]` is counted as an edge to `Foo.md` anywhere in the vault. Aliases declared via frontmatter `aliases:` are **not** parsed. Folder-qualified links (e.g. `[[notes/Foo]]`) are resolved by stem only. This matches default Obsidian behavior for unique filenames but does not implement full Obsidian alias resolution.
### C. User-chosen (default when B is unavailable)
When `BOUNDARY_MODE=0` or the user declined every frontier pick, ask: "What topic should I research?"
---
## Research Loop
```
Input: topic (from Topic Selection, above)
Round 1. Broad search
1. Decompose topic into 3-5 distinct search angles
2. For each angle: run 2-3 WebSearch queries
3. For top 2-3 results per angle: WebFetch the page
4. Extract from each: key claims, entities, concepts, open questions
Round 2. Gap fill
5. Identify what's missing or contradicted from Round 1
6. Run targeted searches for each gap (max 5 queries)
7. Fetch top results for each gap
Round 3. Synthesis check (optional, if gaps remain)
8. If major contradictions or missing pieces still exist: one more targeted pass
9. Otherwise: proceed to filing
Max rounds: 3 (as set in program.md). Stop when depth is reached or max rounds hit.
```
---
## Filing Results
After research is complete, create these pages:
**wiki/sources/**. One page per major reference found
- Use source frontmatter (type, source_type, author, date_published, url, confidence, key_claims)
- Body: summary of the source, what it contributes to the topic
**wiki/concepts/**. One page per significant concept extracted
- Only create a page if the concept is substantive enough to stand alone
- Check the index first: update existing concept pages rather than creating duplicates
**wiki/entities/**. One page per significant person, org, or product identified
- Check the index first: update existing entity pages
**wiki/questions/**. One synthesis page titled "Research: [Topic]"
- This is the master synthesis. Everything comes together here.
- Sections: Overview, Key Findings, Entities, Concepts, Contradictions, Open Questions, Sources
- Full frontmatter with related links to all pages created in this session
---
## Synthesis Page Structure
```markdown
---
type: synthesis
title: "Research: [Topic]"
created: YYYY-MM-DD
updated: YYYY-MM-DD
tags:
- research
- [topic-tag]
status: developing
related:
- "[[Every page created in this session]]"
sources:
- "[[wiki/sources/Source 1]]"
- "[[wiki/sources/Source 2]]"
---
# Research: [Topic]
## Overview
[2-3 sentence summary of what was found]
## Key Findings
- Finding 1 (Source: [[Source Page]])
- Finding 2 (Source: [[Source Page]])
- ...
## Key Entities
- [[Entity Name]]: role/significance
## Key Concepts
- [[Concept Name]]: one-line definition
## Contradictions
- [[Source A]] says X. [[Source B]] says Y. [Brief note on which is more credible and why]
## Open Questions
- [Question that research didn't fully answer]
- [Gap that needs more sources]
## Sources
- [[Source 1]]: author, date
- [[Source 2]]: author, date
```
---
## After Filing
1. Update `wiki/index.md`. Add all new pages to the right sections
2. Append to `wiki/log.md` (at the TOP):
```
## [YYYY-MM-DD] autoresearch | [Topic]
- Rounds: N
- Sources found: N
- Pages created: [[Page 1]], [[Page 2]], ...
- Synthesis: [[Research: Topic]]
- Key finding: [one sentence]
```
3. Update `wiki/hot.md` with the research summary
---
## Report to User
After filing everything:
```
Research complete: [Topic]
Rounds: N | Searches: N | Pages created: N
Created:
wiki/questions/Research: [Topic].md (synthesis)
wiki/sources/[Source 1].md
wiki/concepts/[Concept 1].md
wiki/entities/[Entity 1].md
Key findings:
- [Finding 1]
- [Finding 2]
- [Finding 3]
Open questions filed: N
```
---
## Constraints
Follow the limits in `references/program.md`:
- Max rounds (default: 3)
- Max pages per session (default: 15)
- Confidence scoring rules
- Source preference rules
If a constraint conflicts with completeness, respect the constraint and note what was left out in the Open Questions section.
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Read `references/program.md` to load constraints. Read the topic verbatim. Note what's already in the wiki. |
| 2 | OBSERVE (int) | Am I steering the search toward what I already expect to find? Confirmation bias kills research. |
| 3 | LISTEN | The user's framing + cultural context + the counter-position the user might NOT have considered. |
| 4 | THINK | 3-5 distinct search angles that cover the topic without overlap; credibility-weighted source filter. |
| 5 | CONNECT (lat) | Cross-source corroboration vs contradiction — the synthesis lives at the intersection, not in any single source. |
| 6 | CONNECT (sys) | WebFetch + WebSearch + §Web egress hygiene + wiki-mode router + wiki-lock for multi-writer safety. |
| 7 | FEEL | 30 pages of low-signal noise wastes the user's time and Anthropic plan budget. Quality over volume. |
| 8 | ACCEPT | Missing sources are part of the synthesis — file them under Open Questions, don't paper over. |
| 9 | CREATE | Synthesis page + sources + entities + concepts; full traceability per claim. |
| 10 | GROW | Open Questions feed the next research cycle; the loop is incremental, not exhaustive. |
+75
View File
@@ -0,0 +1,75 @@
# Research Program
This file configures the autoresearch loop. Edit it to match your domain and research style. The autoresearch skill reads it before every run.
---
## Search Objectives
Default objectives for every research session:
- Find authoritative sources (prefer: .edu, peer-reviewed papers, official documentation, primary sources, established publications)
- Extract key entities (people, organizations, products, tools)
- Extract key concepts and frameworks
- Note contradictions between sources
- Identify open questions and research gaps
- Prefer sources from the last 2 years unless the topic is foundational
---
## Confidence Scoring
Label every claim with confidence when filing:
- **high**: multiple independent authoritative sources agree
- **medium**: single good source, or sources partially agree
- **low**: speculation, opinion pieces, single informal source, or claim not verified
Always note the source date for factual claims. Mark claims from sources older than 3 years as potentially stale.
---
## Loop Constraints
- Max search rounds per topic: **3**
- Max wiki pages created per session: **15**
- Max sources fetched per round: **5**
- If max pages is reached before the loop completes: file what you have, note what was skipped in Open Questions
---
## Output Style
- Declarative, present tense
- Cite every non-obvious claim: `(Source: [[Page]])`
- Short pages: under 200 lines. Split if longer.
- No hedging language ("it seems", "perhaps", "might be")
- Flag uncertainty explicitly: `> [!gap] This claim needs verification.`
---
## Domain Notes
[Add domain-specific instructions here. Examples:]
For AI/tech research:
- Prefer: arXiv, official GitHub repos, official product documentation, Hacker News discussions with high karma
- Note: LLM benchmarks are often gamed: treat leaderboard claims as low confidence unless independently verified
For business/market research:
- Prefer: company filings, Crunchbase, Bloomberg, verified industry reports
- Flag: press releases as low confidence without independent verification
For medical/health research:
- Prefer: PubMed, Cochrane reviews, peer-reviewed clinical trials
- Always note: sample size, study type (RCT vs observational), and recency
---
## Exclusions
Do not cite as high-confidence sources:
- Reddit posts or forums (use as pointers to primary sources only)
- Social media posts
- Undated web pages
- Sources that don't cite their own claims
+299
View File
@@ -0,0 +1,299 @@
---
name: canvas
description: "Visual layer of the wiki. Add images, text cards, PDFs, and wiki pages to Obsidian canvas files with auto-positioning inside zones. Integrates with /banana for image capture. Triggers on: /canvas, canvas new, canvas add image, canvas add text, canvas add pdf, canvas add note, canvas zone, canvas list, canvas from banana, add to canvas, put this on the canvas, open canvas, create canvas."
allowed-tools: Read Write Edit Glob Grep
---
# canvas: Visual Reference Layer
The three knowledge capture layers:
- `/save` → text synthesis (wiki/questions/, wiki/concepts/)
- `/autoresearch` → structured knowledge (wiki/sources/, wiki/concepts/)
- `/canvas` → visual references (wiki/canvases/)
A canvas is a JSON file Obsidian renders as an infinite visual board. This skill reads and writes canvas JSON directly. Read `references/canvas-spec.md` for the full format reference before making any edits. This spec aligns with the [JSON Canvas open standard](https://jsoncanvas.org/).
**Substrate preference (v1.7+)**: This skill is a self-contained fallback. **Prefer `kepano/obsidian-skills`** as the authoritative substrate — its `json-canvas` skill is the canonical spec reference. If you see a `json-canvas` skill available without the `claude-obsidian:` namespace, that is kepano's version: use it for spec questions. Continue to use this `canvas` skill for the wiki-scoped *workflows* (positioning into wiki/canvases/, /banana integration, zone layout) — those are unique to claude-obsidian and live above kepano's primitive. Install kepano: `claude plugin marketplace add kepano/obsidian-skills`.
---
## Default Canvas
`wiki/canvases/main.canvas`
If it does not exist, create it:
```json
{
"nodes": [
{
"id": "title",
"type": "text",
"text": "# Visual Reference\n\nDrop images, PDFs, and notes here.",
"x": -400, "y": -300, "width": 400, "height": 120, "color": "6"
},
{
"id": "zone-default",
"type": "group",
"label": "General",
"x": -400, "y": -140, "width": 800, "height": 400, "color": "4"
}
],
"edges": []
}
```
---
## Operations
### open / status (`/canvas` with no args)
1. Check if `wiki/canvases/main.canvas` exists.
2. If yes: read it, count nodes by type, list all group node labels (zone names).
Report: "Canvas has N nodes: X images, Y text cards, Z wiki pages. Zones: [list]"
3. If no: create it with the starter structure above.
Report: "Created main.canvas with a General zone."
4. Tell user: "Open `wiki/canvases/main.canvas` in Obsidian to view."
---
### new (`/canvas new [name]`)
1. Slugify the name: lowercase, spaces → hyphens, strip special chars.
2. Create `wiki/canvases/[slug].canvas` with the starter structure, title updated to `# [Name]`.
3. Add entry to `wiki/overview.md` under a "## Canvases" subsection (append after the Current State section). Do not modify `wiki/index.md`. It uses a fixed section schema (Domains, Entities, Concepts, Sources, Questions, Comparisons).
4. Report: "Created wiki/canvases/[slug].canvas"
---
### add image (`/canvas add image [path or url]`)
**Resolve the image:**
- If URL (starts with `http`): download with `curl -sL [url] -o _attachments/images/canvas/[filename]`
Derive filename from URL path, or use `img-[timestamp].jpg` if unclear.
- If local path outside vault: `cp [path] _attachments/images/canvas/`
- If already vault-relative: use as-is.
Create `_attachments/images/canvas/` if it doesn't exist.
**Detect aspect ratio:**
Use `python3 -c "from PIL import Image; img=Image.open('[path]'); print(img.width, img.height)"` or `identify -format '%w %h' [path]`.
See `references/canvas-spec.md` for the full aspect ratio → canvas size table (7 ratios including 4:3, 3:4, ultra-wide). Do not use an inline table here. The spec is the single source of truth for sizing.
**Position using auto-layout** (see Auto-Positioning section below).
**Append node to canvas JSON and write.**
Report: "Added [filename] to [zone] zone at position ([x], [y])."
---
### add text (`/canvas add text [content]`)
Create a text node:
```json
{
"id": "text-[timestamp]",
"type": "text",
"text": "[content]",
"x": [auto], "y": [auto],
"width": 300, "height": 120,
"color": "4"
}
```
Position using auto-layout. Write and report.
---
### add pdf (`/canvas add pdf [path]`)
Same as add image. Obsidian renders PDFs natively as file nodes.
- Copy to `_attachments/pdfs/canvas/` if outside vault.
- Fixed size: width=400, height=520.
- Report page count if you can determine it.
---
### add note (`/canvas add note [wiki-page]`)
1. Search `wiki/` for a file matching the page name (case-insensitive, partial match ok).
2. Use the vault-relative path as the `file` field.
- Use `"type": "file"` (not `"type": "link"`): `.md` files use file nodes, not link nodes.
- `"type": "link"` takes a `url: "https://..."`: it is for web URLs only.
3. Create a file node: width=300, height=100.
4. Position using auto-layout.
```json
{
"id": "note-[timestamp]",
"type": "file",
"file": "wiki/concepts/LLM Wiki Pattern.md",
"x": [auto], "y": [auto],
"width": 300, "height": 100
}
```
---
### zone (`/canvas zone [name] [color]`)
1. Read canvas JSON.
2. Find max_y: `max(node.y + node.height for all nodes) + 60`. Use 280 if no nodes (leaves room above the starter title node).
3. Create a group node:
```json
{
"id": "zone-[slug]",
"type": "group",
"label": "[name]",
"x": -400,
"y": [max_y],
"width": 1000,
"height": 400,
"color": "[color or '3']"
}
```
Valid colors: `"1"`=red `"2"`=orange `"3"`=yellow `"4"`=green `"5"`=cyan `"6"`=purple
Write and report.
---
### list (`/canvas list`)
1. `glob wiki/canvases/*.canvas`
2. For each canvas: read JSON, count nodes by type.
3. Report:
```
wiki/canvases/main.canvas . 14 nodes (8 images, 3 text, 2 file, 1 group)
wiki/canvases/design-ideas.canvas. 42 nodes (30 images, 4 text, 8 groups)
```
---
### from banana (`/canvas from banana`) (if the banana-claude plugin is installed)
1. Check `wiki/canvases/.recent-images.txt` first (session log of newly written images).
2. If not found or empty: use `find` with correct precedence (parentheses required. Without them `-newer` only binds to the last `-name` clause):
```bash
python3 -c "import time,os; open('/tmp/ten-min-ago','w').close(); os.utime('/tmp/ten-min-ago',(time.time()-600,time.time()-600))"
find _attachments/images -newer /tmp/ten-min-ago \( -name "*.png" -o -name "*.jpg" \)
```
Note: `/banana` is an optional external skill not shipped in this plugin. If the user has it installed, the `.recent-images.txt` log will be populated. If not, the `find` command above is the fallback.
3. If still none: show the 5 most recently modified images.
4. Present list: "Found N recent images: [list]. Add to canvas? Which zone? (zone name / 'new [name]' / 'skip')"
5. On confirmation: add each using the add image logic.
---
## Auto-Positioning Algorithm
Read `references/canvas-spec.md` for the full coordinate system.
```python
def next_position(canvas_nodes, target_zone_label, new_w, new_h):
# Find zone group node
zone = next((n for n in canvas_nodes
if n.get('type') == 'group'
and n.get('label') == target_zone_label), None)
if zone is None:
# No zone: place below all content
max_y = max((n['y'] + n.get('height', 0) for n in canvas_nodes), default=-140)
return -400, max_y + 60
zx, zy = zone['x'], zone['y']
zw, zh = zone['width'], zone['height']
# Nodes inside this zone
inside = [n for n in canvas_nodes
if n.get('type') != 'group'
and zx <= n['x'] < zx + zw
and zy <= n['y'] < zy + zh]
if not inside:
return zx + 20, zy + 20
rightmost_x = max(n['x'] + n.get('width', 0) for n in inside)
next_x = rightmost_x + 40
if next_x + new_w > zx + zw:
# New row
max_row_y = max(n['y'] + n.get('height', 0) for n in inside)
return zx + 20, max_row_y + 20
# Same row: align to the top of all existing nodes in the zone
current_row_y = min(n['y'] for n in inside)
return next_x, current_row_y
```
---
## ID Generation
Read the canvas, collect all existing IDs. Never reuse one.
Safe ID pattern: `[type]-[content-slug]-[full-unix-timestamp]`
Use the full Unix timestamp (10 digits) to avoid collisions in batch operations.
Examples: `img-cover-1744032823`, `text-note-1744032845`, `zone-branding-1744032901`
If a collision is detected (ID already exists in the canvas), append `-2`, `-3`, etc.
---
## Session Log (optional hook)
If `wiki/canvases/.recent-images.txt` exists, append any new image path written to `_attachments/images/` during this session (one path per line, keep last 20).
`/canvas from banana` reads this file first, making it instant without filesystem search.
---
## Banana Integration (if the banana-claude plugin is installed)
After any `/banana` run in the same session, if the user says "add to canvas" or "put on canvas", treat it as `/canvas from banana`.
When `/banana` finishes generating images, suggest:
> "Add generated images to canvas? Run `/canvas from banana`"
---
## Summary
1. Read canvas-spec.md before editing any canvas JSON.
2. Always read the canvas file before writing. Parse existing nodes to avoid ID collisions and calculate auto-positions.
3. Create `_attachments/images/canvas/` for downloaded/copied images.
4. Update `wiki/index.md` when creating new canvases.
5. Report position and zone after every add operation.
## See Also
For standalone visual production (12 templates, 6 layout algorithms, AI generation,
presentations), see [claude-canvas](https://github.com/AgriciDaniel/claude-canvas).
This skill handles wiki-scoped visual boards. claude-canvas handles full-featured
canvas orchestration for any project.
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Which images, PDFs, notes belong on this canvas? Read each before adding. |
| 2 | OBSERVE (int) | Am I aestheticizing or actually communicating? Pretty canvases that don't inform are noise. |
| 3 | LISTEN | The user's mental model of how these items relate. The canvas should mirror that, not impose another. |
| 4 | THINK | Layout, group hierarchy, edge structure. Spatial reasoning matters; arbitrary positions confuse. |
| 5 | CONNECT (lat) | Edges between canvas nodes reveal hidden structure not visible in the linear wiki. |
| 6 | CONNECT (sys) | JSON Canvas 1.0 spec + Obsidian-native rendering + banana skill for AI image gen. |
| 7 | FEEL | A canvas should be readable at first glance, not a maze of arrows. |
| 8 | ACCEPT | Not every project needs a canvas. Admit when prose is enough. |
| 9 | CREATE | Write the `.canvas` JSON with stable IDs and sensible positions. |
| 10 | GROW | Which canvases get reopened? Which are abandoned? That signal informs canvas-worthiness over time. |
+292
View File
@@ -0,0 +1,292 @@
# Obsidian Canvas JSON Specification
Canvas files are JSON with two top-level keys: `nodes` (array) and `edges` (array).
Obsidian reads and writes them as UTF-8 JSON files with `.canvas` extension.
This reference aligns with the [JSON Canvas 1.0 open specification](https://jsoncanvas.org/spec/1.0/). All structures support arbitrary additional fields (`[key: string]: any`) for forward compatibility. Obsidian will preserve unknown fields when reading and writing canvas files.
**ID format**: The JSON Canvas 1.0 spec recommends 16-character lowercase hexadecimal IDs (e.g., `"a1b2c3d4e5f67890"`). Obsidian itself generates IDs in this format. The descriptive ID examples in this reference (`"text-title-4821"`, `"img-cover-7823"`) are an alternative naming convention that this plugin uses for human readability. Both are valid JSON Canvas. Use whichever fits your workflow.
---
## Coordinate System
```
x increases →
┌─────────────────────────────────
│ (-920, -2400) (0, -2400)
y │ (-920, 0) (0, 0) ← origin
↓ │
│ (-920, 540) (500, 540)
```
- **Origin** (0, 0) is the center of the canvas viewport.
- **x increases rightward.** Negative x = left of center.
- **y increases downward.** Negative y = above center.
- Node `x` and `y` are the **top-left corner** of the node, not the center.
- Obsidian pans to fit all nodes on first open.
---
## Node Types
### Text node
Renders markdown content as a styled card.
```json
{
"id": "text-title-4821",
"type": "text",
"text": "# Heading\n\nParagraph with **bold** and `code`.",
"x": -400,
"y": -300,
"width": 400,
"height": 120,
"color": "6"
}
```
- `text`: markdown string. Use `\n` for newlines.
- Minimum readable size: width ≥ 200, height ≥ 60.
- `color` is optional. Omit for default (no color).
---
### File node
Renders an image, PDF, markdown note, or other vault file inline.
```json
{
"id": "img-cover-7823",
"type": "file",
"file": "_attachments/images/example.png",
"x": -900,
"y": -100,
"width": 420,
"height": 236
}
```
- `file`: **vault-relative path** (not absolute, not `~/`).
- Supported: `.png` `.jpg` `.webp` `.gif` `.pdf` `.md` `.canvas`
- For `.md` files: renders as a preview card.
- For `.pdf` files: renders the first page as preview.
- No `color` field for file nodes: color is ignored.
---
### Group node (Zone)
A labeled rectangular region. Does not clip or contain nodes. It's a visual guide.
Nodes placed "inside" a group are just positioned within its bounding box.
```json
{
"id": "zone-branding-3391",
"type": "group",
"label": "Brand Identity",
"x": -920,
"y": -880,
"width": 1060,
"height": 290,
"color": "6",
"background": "_attachments/images/grid-bg.png",
"backgroundStyle": "cover"
}
```
- `label`: shown at the top of the group box.
- `color`: colors the group border and label.
- `background` *(optional)*: vault-relative path to a background image for the group.
- `backgroundStyle` *(optional)*: how the background is rendered.
- `"cover"`: fills the group, cropping if needed (default-ish behavior)
- `"ratio"`: preserves aspect ratio, fits inside the group
- `"repeat"`: tiles the image
- Groups do not affect auto-layout: they are purely visual containers.
---
### Link node
Renders a web URL as an embedded preview card.
```json
{
"id": "link-karpathy-2233",
"type": "link",
"url": "https://github.com/karpathy",
"x": 200,
"y": -300,
"width": 400,
"height": 120
}
```
- `url`: must be a valid `https://` URL.
- Obsidian fetches the Open Graph preview (title, description, thumbnail).
---
## Edges
Connections between nodes. Usually empty for mood boards.
```json
{
"id": "e-hub-cidx",
"fromNode": "hub",
"fromSide": "right",
"fromEnd": "none",
"toNode": "c-idx",
"toSide": "left",
"toEnd": "arrow",
"label": "concepts",
"color": "5"
}
```
**Required fields**: `id`, `fromNode`, `toNode`. Everything else is optional.
- `fromNode` / `toNode`: IDs of the source and target nodes.
- `fromSide` / `toSide` *(optional)*: `"top"` `"bottom"` `"left"` `"right"`. If omitted, Obsidian auto-calculates the best side based on relative node positions.
- `fromEnd` *(optional)*: end-cap on the source side. Defaults to `"none"`. Values: `"none"` | `"arrow"`.
- `toEnd` *(optional)*: end-cap on the target side. **Defaults to `"arrow"`**: note the asymmetric default vs `fromEnd`. Values: `"none"` | `"arrow"`.
- `label` *(optional)*: text shown on the edge.
- `color` *(optional)*: same color palette as nodes (`"1"``"6"` or hex).
Most edges represent directed relationships, so the asymmetric defaults (`fromEnd: "none"`, `toEnd: "arrow"`) produce a single arrow pointing from source to target without specifying anything explicitly.
---
## Color Reference
| Code | Color | Hex (approx) | Use case |
|------|-------|-------------|----------|
| `"1"` | Red / Tomato | #e03e3e | Warnings, archive |
| `"2"` | Orange | #d09035 | Active work |
| `"3"` | Yellow / Gold | #d0a023 | WIP, notes |
| `"4"` | Green / Teal | #448361 | Content, sources |
| `"5"` | Blue / Cyan | #3ea7d3 | Navigation, info |
| `"6"` | Purple / Violet | #9063d2 | Title, identity |
Omit `color` entirely for the default (no border color, transparent label).
---
## Image Sizing Guidelines
Calculate from actual image dimensions using PIL or `identify`:
```bash
python3 -c "from PIL import Image; img=Image.open('path.png'); print(img.width, img.height)"
# or
identify -format '%w %h' path.png
```
| Aspect ratio | Condition | Canvas width | Canvas height |
|-------------|-----------|-------------|--------------|
| 16:9 (wide) | ratio 1.62.0 | 420 | 236 |
| 2:1 (ultra wide) | ratio > 2.0 | 440 | 220 |
| 4:3 | ratio 1.21.6 | 380 | 285 |
| 1:1 (square) | ratio 0.91.1 | 280 | 280 |
| 3:4 | ratio 0.60.9 | 240 | 320 |
| 9:16 (portrait) | ratio < 0.6 | 200 | 356 |
| PDF | any | 400 | 520 |
| Unknown | fallback | 320 | 240 |
---
## Auto-Positioning Pseudocode
```
function place_node(canvas, zone_label, new_w, new_h):
zone = find group node where label == zone_label
padding = 20
if zone not found:
max_y = max(n.y + n.height for n in canvas.nodes) + 60
return (-400, max_y)
# Nodes visually inside zone
inside = [n for n in canvas.nodes
if n.type != 'group'
and zone.x <= n.x < zone.x + zone.width
and zone.y <= n.y < zone.y + zone.height]
if inside is empty:
return (zone.x + padding, zone.y + padding)
# Rightmost point in zone
rightmost = max(n.x + n.width for n in inside)
next_x = rightmost + 40
if next_x + new_w > zone.x + zone.width - padding:
# Overflow → new row
bottom_of_row = max(n.y + n.height for n in inside)
return (zone.x + padding, bottom_of_row + padding)
# Same row
row_y = min(n.y for n in inside) # align to top of existing row
return (next_x, row_y)
```
---
## Full Example: Two-Zone Canvas
```json
{
"nodes": [
{
"id": "title-0001",
"type": "text",
"text": "# Brand Reference\n\n**AI Marketing Hub** visual assets",
"x": -920, "y": -2440, "width": 560, "height": 180, "color": "6"
},
{
"id": "zone-logos",
"type": "group",
"label": "Logos & Icons",
"x": -920, "y": -2200, "width": 1800, "height": 320, "color": "6"
},
{
"id": "img-logo-pro",
"type": "file",
"file": "_attachments/images/example.png",
"x": -900, "y": -2180, "width": 420, "height": 236
},
{
"id": "img-icon-free",
"type": "file",
"file": "_attachments/images/example-icon.png",
"x": -440, "y": -2180, "width": 280, "height": 280
},
{
"id": "zone-covers",
"type": "group",
"label": "Skill Covers",
"x": -920, "y": -1820, "width": 1800, "height": 340, "color": "3"
},
{
"id": "img-seo",
"type": "file",
"file": "_attachments/images/example-cover.png",
"x": -900, "y": -1800, "width": 420, "height": 236
}
],
"edges": []
}
```
---
## Common Mistakes
- **Wrong path format**: use `_attachments/images/file.png` not `/home/user/...` or `~/...`
- **ID collision**: always read existing IDs before generating a new one
- **Negative y confusion**: `y: -2400` is ABOVE `y: -1000` (more negative = higher up)
- **Group does not clip**: placing a node "inside" a group is just positioning it within the group's bounding box: there is no parent-child relationship in the JSON
- **Missing height on text nodes**: Obsidian will render the text but may clip it if height is too small. Use height ≥ content-lines × 24.
+105
View File
@@ -0,0 +1,105 @@
---
name: defuddle
description: "Strip clutter from web pages before ingesting into the wiki. Removes ads, navigation, headers, footers, and boilerplate: leaving clean readable markdown that saves 40-60% tokens. Triggers on: defuddle, clean this page, strip this url, fetch and clean, clean web content before ingesting, strip ads, remove clutter, clean URL content, readable markdown from URL."
allowed-tools: Read Bash
---
# defuddle: Web Page Cleaner
Defuddle extracts the meaningful content from a web page and drops everything else: ads, cookie banners, nav bars, related articles, footers, social sharing buttons. What remains is the article body as clean markdown.
Use this before any URL ingestion. It is optional but strongly recommended. It cuts token usage by 40-60% on typical web articles and produces cleaner wiki pages.
**Substrate note (v1.7+)**: Unlike `obsidian-markdown` / `obsidian-bases` / `json-canvas` (where we defer to kepano/obsidian-skills as upstream), the `defuddle` skill is original to claude-obsidian — kepano's marketplace does not ship a defuddle skill. This is the canonical version. The underlying `defuddle-cli` is independent of either marketplace and lives at [github.com/kepano/defuddle](https://github.com/kepano/defuddle).
---
## Install
```bash
npm install -g defuddle-cli
```
Verify: `defuddle --version`
---
## Usage
### Clean a URL directly
```bash
defuddle https://example.com/article
```
Outputs clean markdown to stdout.
### Save to .raw/
```bash
defuddle https://example.com/article > .raw/articles/article-slug-$(date +%Y-%m-%d).md
```
### Add frontmatter header after saving
After running defuddle, prepend the source URL and fetch date:
```bash
SLUG="article-slug-$(date +%Y-%m-%d)"
{ echo "---"; echo "source_url: https://example.com/article"; echo "fetched: $(date +%Y-%m-%d)"; echo "---"; echo ""; defuddle https://example.com/article; } > .raw/articles/$SLUG.md
```
### Clean a local HTML file
```bash
defuddle page.html
```
---
## When to Use
**Use defuddle when:**
- Ingesting a news article, blog post, or documentation page from a URL
- The page has a lot of surrounding content (most web pages do)
- You want to stay within token budget on a long article
**Skip defuddle when:**
- The source is already a clean markdown or PDF file
- The page is a dashboard, app, or structured data (defuddle expects article-style content)
- defuddle is not installed and the article is short enough to process raw
---
## Fallback
If defuddle is not installed, check:
```bash
which defuddle 2>/dev/null || echo "not installed"
```
If not installed: use WebFetch directly. The content will be less clean but still workable.
---
## Integration with /wiki-ingest
The `/wiki-ingest` skill checks for defuddle automatically when a URL is passed. You do not need to run defuddle manually before ingesting a URL. The ingest skill will call it if available.
To manually clean a page and save before ingesting:
1. Run the save command above
2. Then: `ingest .raw/articles/[slug].md`
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Which URL? What's actually on the page? Don't assume the title matches the content. |
| 2 | OBSERVE (int) | Am I assuming the page has the content the user expects? Verify before extracting. |
| 3 | LISTEN | Did the user say "the article" (main content only) or "the link" (everything visible)? |
| 4 | THINK | Strip boilerplate, preserve structure, capture metadata. Quote URLs in shell to avoid injection. |
| 5 | CONNECT (lat) | How does this domain typically render? Some sites mangle defuddle's heuristics; track those. |
| 6 | CONNECT (sys) | Shells out to defuddle-cli (kepano); output lands in `.raw/` for wiki-ingest pickup. |
| 7 | FEEL | Clean markdown that reads like the original, not boilerplate residue. |
| 8 | ACCEPT | Some pages don't extract well. Flag and move on; don't force when the heuristic loses. |
| 9 | CREATE | Markdown to stdout, redirected to `.raw/articles/<slug>-<date>.md`. |
| 10 | GROW | Extraction failures suggest defuddle-cli upgrade or alternative extractor — track them as backlog. |
+312
View File
@@ -0,0 +1,312 @@
---
name: obsidian-bases
description: "Create and edit Obsidian Bases (.base files): Obsidian's native database layer for dynamic tables, card views, list views, filters, formulas, and summaries over vault notes. Triggers on: create a base, add a base file, obsidian bases, base view, filter notes, formula, database view, dynamic table, task tracker base, reading list base."
allowed-tools: Read Write
---
# obsidian-bases: Obsidian's Database Layer
Obsidian Bases (launched 2025) turns vault notes into queryable, dynamic views. Tables, cards, lists, maps. Defined in `.base` files. No plugin required; it is a core Obsidian feature.
**Substrate preference (v1.7+)**: This skill is a self-contained fallback. **Prefer `kepano/obsidian-skills`** as the authoritative substrate — its `obsidian-bases` skill is the canonical reference for Bases YAML, formulas, and view definitions. If you see an `obsidian-bases` skill available without the `claude-obsidian:` namespace, that is kepano's version: use it. The reference below is provided so the plugin remains functional when kepano's marketplace is not installed. Install: `claude plugin marketplace add kepano/obsidian-skills`. Official Bases docs: https://help.obsidian.md/bases/syntax
---
## File Format
`.base` files contain valid YAML. The root keys are `filters`, `formulas`, `properties`, `summaries`, and `views`.
```yaml
# Global filters: apply to ALL views
filters:
and:
- file.hasTag("wiki")
- 'status != "archived"'
# Computed properties
formulas:
age_days: '(now() - file.ctime).days.round(0)'
status_icon: 'if(status == "mature", "✅", "🔄")'
# Display name overrides for properties panel
properties:
status:
displayName: "Status"
formula.age_days:
displayName: "Age (days)"
# One or more views
views:
- type: table
name: "All Pages"
order:
- file.name
- type
- status
- updated
- formula.age_days
```
---
## Filters
Filters select which notes appear. Applied globally or per-view.
```yaml
# Single string filter
filters: 'status == "current"'
# AND: all must be true
filters:
and:
- 'status != "archived"'
- file.hasTag("wiki")
# OR: any can be true
filters:
or:
- file.hasTag("concept")
- file.hasTag("entity")
# NOT: exclude matches
filters:
not:
- file.inFolder("wiki/meta")
# Nested
filters:
and:
- file.inFolder("wiki/")
- or:
- 'type == "concept"'
- 'type == "entity"'
```
### Filter operators
`==` `!=` `>` `<` `>=` `<=`
### Useful filter functions
| Function | Example |
|----------|---------|
| `file.hasTag("x")` | Notes with tag `x` |
| `file.inFolder("path/")` | Notes in folder |
| `file.hasLink("Note")` | Notes linking to Note |
---
## Properties
Three types:
- **Note properties**: from frontmatter: `status`, `type`, `updated`
- **File properties**: metadata: `file.name`, `file.mtime`, `file.size`, `file.ctime`, `file.tags`, `file.folder`
- **Formula properties**: computed: `formula.age_days`
---
## Formulas
Defined in `formulas:`. Referenced as `formula.name` in `order:` and `properties:`.
```yaml
formulas:
# Days since created
age_days: '(now() - file.ctime).days.round(0)'
# Days until a date property
days_until: 'if(due_date, (date(due_date) - today()).days, "")'
# Conditional label
status_icon: 'if(status == "mature", "✅", if(status == "developing", "🔄", "🌱"))'
# Word count estimate
word_est: '(file.size / 5).round(0)'
```
**Key rule**: Subtracting two dates returns a `Duration`. Not a number. Always access `.days` first:
```yaml
# CORRECT
age: '(now() - file.ctime).days'
# WRONG: crashes
age: '(now() - file.ctime).round(0)'
```
**Always guard nullable properties with `if()`**:
```yaml
# CORRECT
days_left: 'if(due_date, (date(due_date) - today()).days, "")'
```
---
## View Types
### Table
```yaml
views:
- type: table
name: "Wiki Index"
limit: 100
order:
- file.name
- type
- status
- updated
groupBy:
property: type
direction: ASC
```
### Cards
```yaml
views:
- type: cards
name: "Gallery"
order:
- file.name
- tags
- status
```
### List
```yaml
views:
- type: list
name: "Quick List"
order:
- file.name
- status
```
---
## Wiki Vault Templates
### Wiki content dashboard (all non-meta pages)
```yaml
filters:
and:
- file.inFolder("wiki/")
- not:
- file.inFolder("wiki/meta")
formulas:
age: '(now() - file.ctime).days.round(0)'
properties:
formula.age:
displayName: "Age (days)"
views:
- type: table
name: "All Wiki Pages"
order:
- file.name
- type
- status
- updated
- formula.age
groupBy:
property: type
direction: ASC
```
### Entity index (people, orgs, repos)
```yaml
filters:
and:
- file.inFolder("wiki/entities/")
- 'file.ext == "md"'
views:
- type: table
name: "Entities"
order:
- file.name
- entity_type
- status
- updated
groupBy:
property: entity_type
direction: ASC
```
### Recent ingests
```yaml
filters:
and:
- file.inFolder("wiki/sources/")
views:
- type: table
name: "Sources"
order:
- file.name
- source_type
- created
- status
groupBy:
property: source_type
direction: ASC
```
---
## Embedding in Notes
```markdown
![[MyBase.base]]
![[MyBase.base#View Name]]
```
---
## Where to Save
Store `.base` files in `wiki/meta/` for vault dashboards:
- `wiki/meta/dashboard.base`: main content view
- `wiki/meta/entities.base`: entity tracker
- `wiki/meta/sources.base`: ingestion log
---
## YAML Quoting Rules
- Formulas with double quotes → wrap in single quotes: `'if(done, "Yes", "No")'`
- Strings with colons or special chars → wrap in double quotes: `"Status: Active"`
- Unquoted strings with `:` break YAML parsing
---
## What Not to Do
- Do not use `from:` or `where:`: those are Dataview syntax, not Obsidian Bases
- Do not use `sort:` at the root level: sorting is per-view via `order:` and `groupBy:`
- Do not put `.base` files outside the vault: they only render inside Obsidian
- Do not reference `formula.X` in `order:` without defining `X` in `formulas:`
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | The `.base` YAML the user is composing — read it carefully before suggesting changes. |
| 2 | OBSERVE (int) | Am I documenting yesterday's spec or today's? Bases evolves fast post-GA. |
| 3 | LISTEN | The user's specific Bases use-case (dashboard, filter chain, computed property). |
| 4 | THINK | Which filter operators, formula syntax, view types apply? Validate against the current spec. |
| 5 | CONNECT (lat) | How do Bases relate to Dataview queries? Properties? Canvas overlays? Map the deltas. |
| 6 | CONNECT (sys) | Obsidian Bases is post-1.10 GA; substrate-defer to kepano/obsidian-skills when present. |
| 7 | FEEL | Examples that actually parse and render. Pseudo-syntax wastes the user. |
| 8 | ACCEPT | Bases spec evolves; some features in this doc may have changed. Keep the version note current. |
| 9 | CREATE | Schema docs + worked examples that render in the user's actual Obsidian version. |
| 10 | GROW | As Bases features ship, refresh the reference. Track upstream releases. |
+250
View File
@@ -0,0 +1,250 @@
---
name: obsidian-markdown
description: "Write correct Obsidian Flavored Markdown: wikilinks, embeds, callouts, properties, tags, highlights, math, and canvas syntax. Reference this when creating or editing any wiki page. Triggers on: write obsidian note, obsidian syntax, wikilink, callout, embed, obsidian markdown, wikilink format, callout syntax, embed syntax, obsidian formatting, how to write obsidian markdown."
allowed-tools: Read Write Edit
---
# obsidian-markdown: Obsidian Flavored Markdown
Reference this skill when writing any wiki page. Obsidian extends standard Markdown with wikilinks, embeds, callouts, and properties. Getting syntax wrong causes broken links, invisible callouts, or malformed frontmatter.
**Substrate preference (v1.7+)**: This skill is a self-contained fallback. **Prefer `kepano/obsidian-skills`** (by Steph Ango, Obsidian CEO) as the authoritative substrate — its `obsidian-markdown` skill is the canonical Obsidian syntax reference for any Agent-Skills runtime. If you see an `obsidian-markdown` skill available without the `claude-obsidian:` namespace, that is kepano's version: use it. The reference below is provided so the plugin remains functional when kepano's marketplace is not installed. Install: `claude plugin marketplace add kepano/obsidian-skills`. Repo: [github.com/kepano/obsidian-skills](https://github.com/kepano/obsidian-skills).
---
## Wikilinks
Internal links use double brackets. The filename without extension.
| Syntax | What it does |
|---|---|
| `[[Note Name]]` | Basic link |
| `[[Note Name\|Display Text]]` | Aliased link (shows "Display Text") |
| `[[Note Name#Heading]]` | Link to a specific heading |
| `[[Note Name#^block-id]]` | Link to a specific block |
Rules:
- Case-sensitive on some systems. Match the exact filename.
- No path needed: Obsidian resolves by filename uniqueness.
- If two files have the same name, use `[[Folder/Note Name]]` to disambiguate.
---
## Embeds
Embeds use `!` before the wikilink. They display the content inline.
| Syntax | What it does |
|---|---|
| `![[Note Name]]` | Embed a full note |
| `![[Note Name#Heading]]` | Embed a section |
| `![[image.png]]` | Embed an image |
| `![[image.png\|300]]` | Embed image with width 300px |
| `![[document.pdf]]` | Embed a PDF (Obsidian renders natively) |
| `![[audio.mp3]]` | Embed audio |
---
## Callouts
Callouts are blockquotes with a type keyword. They render as styled alert boxes.
```markdown
> [!note]
> Default informational callout.
> [!note] Custom Title
> Callout with a custom title.
> [!note]- Collapsible (closed by default)
> Click to expand.
> [!note]+ Collapsible (open by default)
> Click to collapse.
```
### All callout types
| Type | Aliases | Use for |
|------|---------|---------|
| `note` |: | General notes |
| `abstract` | `summary`, `tldr` | Summaries |
| `info` |: | Information |
| `todo` |: | Action items |
| `tip` | `hint`, `important` | Tips and highlights |
| `success` | `check`, `done` | Positive outcomes |
| `question` | `help`, `faq` | Open questions |
| `warning` | `caution`, `attention` | Warnings |
| `failure` | `fail`, `missing` | Errors or failures |
| `danger` | `error` | Critical issues |
| `bug` |: | Known bugs |
| `example` |: | Examples |
| `quote` | `cite` | Quotations |
| `contradiction` |: | Conflicting information (wiki convention) |
---
## Properties (Frontmatter)
Obsidian renders YAML frontmatter as a Properties panel. Rules:
```yaml
---
type: concept # plain string
title: "Note Title" # quoted if it contains special chars
created: 2026-04-08 # date as YYYY-MM-DD (not ISO datetime)
updated: 2026-04-08
tags:
- tag-one # list items use - format
- tag-two
status: developing
related:
- "[[Other Note]]" # wikilinks must be quoted in YAML
sources:
- "[[source-page]]"
---
```
Rules:
- Flat YAML only. Never nest objects.
- Dates as `YYYY-MM-DD`, not `2026-04-08T00:00:00`.
- Lists as `- item`, not inline `[a, b, c]`.
- Wikilinks in YAML must be quoted: `"[[Page]]"`.
- `tags` field: Obsidian reads this as the tag list, searchable in vault.
---
## Tags
Two valid forms:
```markdown
#tag-name : inline tag anywhere in the body
#parent/child-tag : nested tag (shows hierarchy in tag pane)
```
In frontmatter:
```yaml
tags:
- research
- ai/obsidian
```
Do not use `#` inside frontmatter tag lists. Just the tag name.
---
## Text Formatting
Standard Markdown plus Obsidian extensions:
| Syntax | Result |
|---|---|
| `**bold**` | Bold |
| `*italic*` | Italic |
| `~~strikethrough~~` | Strikethrough |
| `==highlight==` | Highlighted text (yellow in Obsidian) |
| `` `inline code` `` | Inline code |
---
## Math
Obsidian uses MathJax/KaTeX:
Inline math:
```markdown
$E = mc^2$
```
Block math:
```markdown
$$
\int_0^\infty e^{-x} dx = 1
$$
```
---
## Code Blocks
Standard fenced code blocks. Obsidian highlights all common languages:
````markdown
```python
def hello():
return "world"
```
````
---
## Tables
Standard Markdown tables:
```markdown
| Column A | Column B | Column C |
|----------|----------|----------|
| Value | Value | Value |
| Value | Value | Value |
```
Obsidian renders tables natively. No plugin needed.
---
## Mermaid Diagrams
Obsidian renders Mermaid natively:
````markdown
```mermaid
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[End]
B -->|No| D[Loop]
D --> A
```
````
Supported: `graph`, `sequenceDiagram`, `gantt`, `classDiagram`, `pie`, `flowchart`.
---
## Footnotes
```markdown
This sentence has a footnote.[^1]
[^1]: The footnote text goes here.
```
---
## What NOT to Do
- Do not use `[link text](path/to/note.md)` for internal links: use `[[Note Name]]` instead.
- Do not use HTML inside callouts: stick to Markdown.
- Do not use `##` inside a callout body: headings don't render inside callouts.
- Do not write `tags: [a, b, c]` inline in frontmatter: Obsidian prefers the list format.
- Do not write ISO datetimes in frontmatter (`2026-04-08T00:00:00Z`): use `2026-04-08`.
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Which syntax does the user need? (Wikilinks? Callouts? Embeds? Math? Mermaid?) |
| 2 | OBSERVE (int) | Am I documenting Obsidian Flavored Markdown as I remember it or as it currently is? Check the spec. |
| 3 | LISTEN | The user's source-of-confusion — what specific syntax did they get wrong? |
| 4 | THINK | Minimal correct examples. "What NOT to do" is often as valuable as "what to do." |
| 5 | CONNECT (lat) | How does OFM differ from CommonMark and GFM? The deltas are where users get confused. |
| 6 | CONNECT (sys) | Substrate-defer to kepano/obsidian-skills when present — single source of truth, less drift. |
| 7 | FEEL | A cheat sheet that's scannable in 30 seconds, not a wall of text. |
| 8 | ACCEPT | Not every wikilink needs an alias; some syntax is genuinely optional. Don't over-prescribe. |
| 9 | CREATE | Syntax reference, current to Obsidian X.Y. Include the gotchas section. |
| 10 | GROW | As OFM evolves (newer Mermaid types, callout types, cssclasses, etc.), refresh. |
+187
View File
@@ -0,0 +1,187 @@
---
name: save
description: >
Save the current conversation, answer, or insight into the Obsidian wiki vault as a
structured note. Analyzes the chat, determines the right note type, creates frontmatter,
files it in the correct wiki folder, and updates index, log, and hot cache.
Triggers on: "save this", "save that answer", "/save", "file this",
"save to wiki", "save this session", "file this conversation", "keep this",
"save this analysis", "add this to the wiki".
allowed-tools: Read Write Edit Glob Grep
---
# save: File Conversations Into the Wiki
Good answers and insights shouldn't disappear into chat history. This skill takes what was just discussed and files it as a permanent wiki page.
The wiki compounds. Save often.
---
## Transport (v1.7+)
The session-note write itself follows the standard transport policy. Read `.vault-meta/transport.json` (auto-created by `bash scripts/detect-transport.sh`):
- **cli** — `obsidian-cli write "$VAULT" "$NOTE" < session.md`; see [`skills/wiki-cli/SKILL.md`](../wiki-cli/SKILL.md)
- **mcp-obsidian** / **mcpvault**`mcp__obsidian-vault__write_note`
- **filesystem** — Claude's `Write` tool with absolute path
Full decision tree: [`wiki/references/transport-fallback.md`](../../wiki/references/transport-fallback.md). Index/log/hot updates use the same transport.
---
## Mode awareness (v1.8+)
Before creating the session note, consult the vault's methodology mode via `python3 scripts/wiki-mode.py route session "<topic-summary>"`. The router returns the vault-relative path:
- **generic**: `wiki/sessions/<date>-<topic>.md` (v1.7 default)
- **LYT**: `wiki/notes/<date>-<topic>.md` + update the relevant session/journal MOC
- **PARA**: `wiki/projects/inbox/<date>-<topic>.md` (user reroutes to specific projects)
- **Zettelkasten**: `wiki/<ID>-session-<topic>.md` (timestamped ID becomes the filename prefix)
If `.vault-meta/mode.json` is absent, the router returns mode=generic paths. **Important global rule**: per global CLAUDE.md `/save` convention, sessions for cross-project work should still file to `~/Documents/Obsidian Vault/sessions/` rather than the project's wiki. The mode router applies when filing to the project's own wiki/, not when filing to the global personal vault.
## Concurrency (v1.7+)
Session-note writes MUST be preceded by `wiki-lock acquire`:
```bash
NOTE_PATH="wiki/questions/<slug>.md" # or wiki/concepts/, wiki/meta/, etc.
bash scripts/wiki-lock.sh acquire "$NOTE_PATH" || {
echo "skipped: $NOTE_PATH currently locked by another writer"; exit 0
}
# … write the note via §Transport-selected method …
bash scripts/wiki-lock.sh release "$NOTE_PATH"
```
For multi-file saves (e.g., session note + index update + log append), acquire each lock in sorted-path order to avoid deadlocks. Index/log/hot updates lock just like content pages.
See `skills/wiki-ingest/SKILL.md` §Concurrency for the full lock semantics.
---
## Note Type Decision
Determine the best type from the conversation content:
| Type | Folder | Use when |
|------|--------|---------|
| synthesis | wiki/questions/ | Multi-step analysis, comparison, or answer to a specific question |
| concept | wiki/concepts/ | Explaining or defining an idea, pattern, or framework |
| source | wiki/sources/ | Summary of external material discussed in the session |
| decision | wiki/meta/ | Architectural, project, or strategic decision that was made |
| session | wiki/meta/ | Full session summary: captures everything discussed |
If the user specifies a type, use that. If not, pick the best fit based on the content. When in doubt, use `synthesis`.
---
## Save Workflow
**Step 0: Decide the destination root.** Check in order:
1. **User explicit override.** If the user said "save to this project's wiki" / "save to the personal vault" / a specific path, respect it.
2. **Project CLAUDE.md or global `~/.claude/CLAUDE.md` `/save` rule.** If either declares a personal-vault destination (e.g., `~/Documents/Obsidian Vault/`), that is the destination ROOT. The Note Type table below describes paths relative to whichever root is active. Append the new note to `<root>/log/ingest-log.md` at the top, in the format that file already uses.
3. **Default.** The project's own `wiki/` folder.
The mode router (`python3 scripts/wiki-mode.py route session "<topic>"`) applies when filing into the project's own `wiki/`. When filing into a personal-vault root, use the canonical folders documented in that vault's CLAUDE.md (commonly `sessions/`, `concepts/`, `sources/`) — the mode router is NOT consulted for personal-vault writes by default. Filename sanitization (slug + safe_name) still applies regardless of root: strip path separators, NUL bytes, control chars, leading dots/hyphens.
**Then continue the workflow:**
1. **Scan** the current conversation. Identify the most valuable content to preserve.
2. **Ask** (if not already named): "What should I call this note?" Keep the name short and descriptive.
3. **Determine** note type using the table above.
4. **Extract** all relevant content from the conversation. Rewrite it in declarative present tense (not "the user asked" but the actual content itself).
5. **Create** the note in `<destination-root>/<chosen-folder>/<title>.md` (per Step 0). Full frontmatter. If a note with the same path already exists, ASK before overwriting.
6. **Collect links**: identify any wiki pages mentioned in the conversation. Add them to `related` in frontmatter.
7. **Update** `wiki/index.md`. Add the new entry at the top of the relevant section.
8. **Append** to `wiki/log.md`. New entry at the TOP:
```
## [YYYY-MM-DD] save | Note Title
- Type: [note type]
- Location: wiki/[folder]/Note Title.md
- From: conversation on [brief topic description]
```
9. **Update** `wiki/hot.md` to reflect the new addition.
10. **Confirm**: "Saved as [[Note Title]] in wiki/[folder]/."
---
## Frontmatter Template
```yaml
---
type: <synthesis|concept|source|decision|session>
title: "Note Title"
created: YYYY-MM-DD
updated: YYYY-MM-DD
tags:
- <relevant-tag>
status: developing
related:
- "[[Any Wiki Page Mentioned]]"
sources:
- "[[.raw/source-if-applicable.md]]"
---
```
For `question` type, add:
```yaml
question: "The original query as asked."
answer_quality: solid
```
For `decision` type, add:
```yaml
decision_date: YYYY-MM-DD
status: active
```
---
## Writing Style
- Declarative, present tense. Write the knowledge, not the conversation.
- Not: "The user asked about X and Claude explained..."
- Yes: "X works by doing Y. The key insight is Z."
- Include all relevant context. Future sessions should be able to read this page cold.
- Link every mentioned concept, entity, or wiki page with wikilinks.
- Cite sources where applicable: `(Source: [[Page]])`.
---
## What to Save vs. Skip
Save:
- Non-obvious insights or synthesis
- Decisions with rationale
- Analyses that took significant effort
- Comparisons that are likely to be referenced again
- Research findings
Skip:
- Mechanical Q&A (lookup questions with obvious answers)
- Setup steps already documented elsewhere
- Temporary debugging sessions with no lasting insight
- Anything already in the wiki
If it's already in the wiki, update the existing page instead of creating a duplicate.
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Read the full conversation. Identify the actual decisions and synthesis, not the verbatim transcript. |
| 2 | OBSERVE (int) | Am I in a save-everything mood? Some sessions don't have lasting insight; the Skip criteria exists for a reason. |
| 3 | LISTEN | Did the user specify destination or type? Their explicit override comes first; defaults come second. |
| 4 | THINK | Pick destination root (Step 0), then note type, then folder. Match path sanitization to destination convention. |
| 5 | CONNECT (lat) | Does this content already have a wiki page? Update vs create matters — duplicates pollute the index. |
| 6 | CONNECT (sys) | Index + log + hot cache + frontmatter relations all update together — atomicity matters. |
| 7 | FEEL | Filename future-me can read cold; frontmatter that supports search. Avoid noise that drowns the signal. |
| 8 | ACCEPT | Some sessions don't deserve saving. Honor the Skip criteria; don't archive everything. |
| 9 | CREATE | Write the note, append to log at top, update index, refresh hot cache. |
| 10 | GROW | Skipped saves are also signal — what threshold filtered them? Refine the type table over time. |
+214
View File
@@ -0,0 +1,214 @@
---
name: think
description: "Apply the 10-principle thinking loop (OBSERVE-OBSERVE-LISTEN-THINK-CONNECT-CONNECT-FEEL-ACCEPT-CREATE-GROW) to any non-trivial problem. Walks Claude through external observation, metacognition, active listening, first-principles analysis, lateral connection, system orchestration, intuition, intellectual humility, generative output, and iterative growth. Triggers on: think this through, 10-principle review, /think, OBSERVE LISTEN THINK, deep think, systematic thinking, structured reasoning, walk this through, audit my thinking, am I thinking about this right."
allowed-tools: Read, Grep, Glob, Bash
---
# think: The 10-principle thinking loop
A meditation, a discipline, and a checklist. Use this skill when a problem is non-trivial enough that disciplined thinking pays for itself: architectural decisions, post-mortems, ambiguous user requests, audits, multi-stakeholder tradeoffs, "should we ship?" moments, "what are we missing?" moments.
The 10 principles are not a recipe. They are stages of attention. You move through them in order on the first pass, then loop back to the earlier ones as new information emerges. The discipline is in NOT skipping the awkward ones (OBSERVE-internal, ACCEPT, GROW) just because they are uncomfortable.
This skill ships v1.9.0 of claude-obsidian. It is the meta-skill that informs how the other 14 skills think. Each of those skills also has a per-skill "How to think" appendix mapping these 10 stages to that skill's specific work.
---
## The 10 principles
### 1. OBSERVE (the external input)
Thinking begins with data collection. Look at the environment, the current landscape, the patterns and inefficiencies and opportunities — without immediately trying to solve them. Read the raw inputs.
In practice: read the code before changing it. Read every commit before claiming the branch is clean. Read every page in the vault before answering a question that should be sourced. Resist the urge to jump to a fix on the first symptom.
### 2. OBSERVE (the internal metacognition)
Now observe yourself. This is metacognition — thinking about how you are thinking. Are you operating on assumptions? Do you have a bias in this architecture? Are you anchored on a previous decision? Is there a finding count you are unconsciously targeting?
In practice: write a one-paragraph "bias log" before scoring something. Note ownership bias, ship-it bias, familiarity bias, anchoring. The bias does not go away by being noted — it gets contained.
### 3. LISTEN (active receptivity)
Observing is often visual or analytical. Listening requires shutting down the ego to absorb external feedback. Pay attention to user intent, community discussions, error messages, the subtle signals in the noise that tell you what people actually need rather than what you think they need.
In practice: read the SKILL.md description before assuming what a skill does. Read the user's exact phrasing before paraphrasing it back. Read the failure message before guessing the failure mode. The user's confusion is data.
### 4. THINK (critical processing)
The analytical engine. Once you have the inputs, break the problem down to first principles. Structure the logic, map the workflows, evaluate the constraints, synthesize the raw data into a coherent strategy.
In practice: this is the cut where the six-cut engineering kernel lives. Read-before-write. Name like the next reader is hostile. Smallest unit that works. Delete more than you add. Evidence over intuition. Failure is the spec. THINK is where rigor pays off, but it cannot start without 1-3.
### 5. CONNECT (associative / lateral thinking)
Great ideas rarely happen in a vacuum; they happen at intersections. Take two seemingly unrelated concepts and link them. SEO algorithms × agentic AI behavior. Retrieval architecture × LLM compaction. The "Aha!" moment is finding the hidden relationship between distinct variables.
In practice: when auditing a skill, ask "does this bug pattern exist in adjacent skills?" When designing an API, ask "what other interface is this isomorphic to?" Lateral thinking finds cross-cutting bugs the per-component view misses.
### 6. CONNECT (system orchestration)
The second CONNECT is about execution. Moving from an isolated idea to an integrated system. How do these individual thoughts, tools, or agents plug into one another to create a seamless, functioning whole? This is the principle of building the wiring.
In practice: when shipping a new skill, audit how it integrates with hooks, transport, locks, the router, the verifier agent. The skill that works in isolation but breaks the auto-commit hook is not a working skill.
### 7. FEEL (emotional intelligence + intuition)
Pure logic is brittle without empathy. Factor in the human element. Design with user experience in mind. Understand the emotional resonance of your messaging. Trust hard-earned intuition when the data is ambiguous.
In practice: an error message that says "ERR: exit code 4" fails FEEL even if it passes THINK. A skill description that lists 12 triggers but doesn't explain WHEN to use it fails FEEL. The user installing the plugin for the first time experiences your decisions in a way `make test` cannot measure.
### 8. ACCEPT (intellectual humility)
No plan survives first contact with reality. Embrace constraints. Acknowledge when a hypothesis fails. Recognize when the market wants something different than what you built. Let go of sunk cost.
In practice: tier findings honestly. If your skill is 78/100, do not write 95/100. If the verdict is YELLOW, do not call it GREEN to please anyone. ACCEPT is the firewall against sycophancy.
### 9. CREATE (generative output)
Analysis paralysis is the enemy of progress. At some point, stop strategizing and start producing. Write the code. Draft the content. Launch the system. Ship the audit report.
In practice: an audit that never gets written is worse than a B+ audit that ships. A v1.8.2 fix that sits in working tree forever is worse than the same fix committed and pushed. CREATE is the answer when the prior stages have given you enough.
### 10. GROW (the iterative loop)
Thinking is not a straight line; it is a feedback loop. Take what you built (CREATE), see how it performs in reality, and use those lessons to upgrade your skills and expand your capacity for the next cycle.
In practice: every audit must end with a GROW section. What worked? What to improve next cycle? What inputs feed v_next? GROW is what turns one good decision into a compounding habit.
---
## When to invoke
Invoke `/think` when:
- You are about to make a non-trivial architectural decision (designing a new skill, restructuring a module, choosing between approaches)
- You are auditing a system and need a methodology spine (per the v1.8.0 pre-push audit pattern)
- The user's request is ambiguous and you need to listen harder before responding
- You hit a surprising result and need OBSERVE-internal before adjusting
- You are about to call something done and need to verify ACCEPT (anti-sycophancy) before claiming the verdict
- A post-mortem after something went sideways
- Closing out a session and need a GROW step before /save
Do NOT invoke `/think` for:
- Single-line typo fixes (the discipline is overkill; just fix it)
- Trivial lookups (no decision is being made; just answer)
- Cases where you have already moved through the 10 stages implicitly (don't ceremonially re-do it)
The framework's value scales with problem novelty + irreversibility. For a one-line fix that's easily reverted, the loop is dead weight. For a release-blocking audit decision, skipping any stage loses calibration.
---
## How to use
```
/think <problem statement>
```
Walks through the 10 stages in order. For each, answer the prompt questions below. Stage outputs feed into stage 9 (CREATE), which produces a recommendation or artifact.
Stages 1, 4, 9 are usually short. Stages 2, 7, 8, 10 are where most people skip. Watch yourself there.
---
## Stage-by-stage prompts
For each stage, answer these questions before moving to the next:
### 1. OBSERVE (external)
- What are the raw inputs? (Code? Docs? User intent? Logs?)
- What have I read in full vs. skimmed vs. assumed?
- What is the environment / state right now? (Working tree, recent commits, test status, deployment state)
- What surprises me, before I start interpreting?
### 2. OBSERVE (internal)
- What am I biased toward here? (Ownership, ship-it, novelty, anchoring, familiarity)
- What outcome am I unconsciously hoping for? Why?
- If a fresh-context reviewer joined right now, what would they question that I am taking for granted?
- Is my confidence calibrated to the evidence I actually have?
### 3. LISTEN
- What did the user actually ask for? (Quote verbatim.)
- What signals are in the noise? (Word choice, what they did NOT say, prior corrections in this thread or in memory)
- Are there community / domain signals I should consult? (Github issues, error messages, neighbor patterns)
- Whose voice is missing from this decision?
### 4. THINK
- What are the first principles in play? (Constraints, invariants, blast radius)
- Apply the six-cut engineering kernel: read-before-write, hostile naming, smallest unit, delete-more-than-you-add, evidence-over-intuition, failure-is-the-spec
- What are the alternatives I have NOT considered?
- What is the cheapest experiment that would prove me wrong?
### 5. CONNECT (lateral)
- Where else does this pattern show up?
- What seemingly unrelated domain solved a structurally similar problem?
- If the same bug exists in this code, does it exist in three neighbors?
- What metaphor unlocks the user's intuition for this?
### 6. CONNECT (system)
- How does this plug into the existing wiring? (Hooks, transport, locks, router, agents)
- Does anything need to be updated downstream / upstream / across?
- What new failure modes does integration create that the isolated component doesn't have?
- Is the documentation about integration up to date?
### 7. FEEL
- How does this LAND for the user? (UX, error messages, naming, onboarding friction)
- What emotional state is the user in when they hit this code path? (Frustrated? Exploring? Time-pressured?)
- Does my intuition say "something is off" even when the data says "we're good"?
- Does my intuition say "we're good" even when I cannot articulate why?
### 8. ACCEPT
- What is the honest tier of this finding / verdict? (No inflation.)
- What constraint am I being asked to soften that I should not?
- What sunk cost am I protecting that I should release?
- If this were someone else's work, would I be more critical?
### 9. CREATE
- What is the smallest artifact that ships the decision?
- What is the cleanest path from here to "done"?
- Are the inputs sufficient, or do I need to loop back to an earlier stage?
- Ship it.
### 10. GROW
- What worked well in this cycle?
- What would I do differently next time?
- What inputs feed v_next?
- Where should this lesson be stored so future-me does not have to re-derive it? (Wiki page? Memory? CLAUDE.md? Audit doc?)
---
## Anti-patterns
The loop fails when:
- **Skipping OBSERVE-internal.** Going straight from external observation to THINK without auditing your own biases produces confident wrong answers. The bias does not announce itself.
- **Skipping ACCEPT.** Padding a score, hedging a verdict, calling YELLOW "GREEN with disclosure". The framework's anti-sycophancy contract dies the moment ACCEPT becomes optional.
- **Skipping GROW.** Producing the artifact, shipping it, and moving on without feedback. Next cycle starts at the same first-principles baseline; nothing compounds.
- **Analysis paralysis at THINK.** Looping inside stage 4 forever, never reaching CREATE. The framework is a sequence, not a stopping rule. ACCEPT what you have, CREATE the artifact, GROW from the response.
- **Ceremony.** Writing all 10 stages for a one-line fix. The framework's cost should scale with problem stakes. Trivial problems are answered, not audited.
---
## Composition with other skills
The 10-principle framework composes with the rest of the plugin:
- **`/best-practices`** (six-cut engineering kernel): The THINK stage's analytical engine. The 10-principle loop wraps the six-cut; the six-cut is the inside of stage 4.
- **`/save`**: After GROW, save the insights worth not re-deriving. The session note IS the GROW artifact.
- **`/wiki-lint`**: Periodic audits of the wiki are themselves a GROW step at the system level.
- **`agents/verifier.md`**: An OBSERVE-internal substitute for solo work — fresh-context reviewer that catches biases the chair missed.
- **`/autoresearch`**: A LISTEN amplifier — surfaces external signals the chair would not have found alone.
Every other skill in this plugin has a "How to think" appendix mapping its specific work to these 10 stages. Read those appendices for skill-specific applications.
---
## Reference
- This skill is the canonical source for the 10-principle framework in this plugin.
- Pre-push audit example using the framework as audit methodology: [`docs/audits/v1.8.0-pre-push-audit-2026-05-18.md`](../../docs/audits/v1.8.0-pre-push-audit-2026-05-18.md)
- The framework's enforcement layer is `/best-practices` (loaded separately).
- The skill does not modify files or execute mutations. It loads structure and discipline; what you do with that is the next decision.
The 10 principles are a meditation. Without the stance, the framework becomes ceremony. With the stance, every cycle compounds.
+178
View File
@@ -0,0 +1,178 @@
---
name: wiki-cli
description: "Default vault-mutation transport for claude-obsidian v1.7+. Wraps the Obsidian CLI (Obsidian 1.12+) as the preferred way to read, write, search, and modify vault notes from Claude — no MCP server, no REST API plugin, no TLS workarounds. Falls back to direct filesystem Read/Write/Edit when the CLI is unavailable. Triggers on: wiki-cli, obsidian cli, obsidian read, obsidian write, obsidian search, daily note, obsidian create, obsidian append, vault transport, which transport, transport detection, obsidian command line."
allowed-tools: Read Bash
---
# wiki-cli: Default Transport Layer
claude-obsidian v1.7+ standardizes on the **Obsidian CLI** (shipped with Obsidian 1.12) as the preferred transport for all vault mutations on desktop. This skill is the recipe reference for using it.
**Substrate preference (v1.7+)**: This skill is a self-contained fallback. **Prefer `kepano/obsidian-skills`** (by Steph Ango, Obsidian CEO) as the authoritative substrate — its `obsidian-cli` skill is the canonical CLI reference for any Agent-Skills runtime. If you see an `obsidian-cli` skill available without the `claude-obsidian:` namespace, that is kepano's version: use it. The recipes below are provided so claude-obsidian remains functional when kepano's marketplace is not installed. Install kepano: `claude plugin marketplace add kepano/obsidian-skills`.
---
## Why CLI over MCP
| Concern | MCP (Options A/B) | Obsidian CLI |
|---|---|---|
| Install | Local REST API plugin + MCP server config | Built into Obsidian 1.12+ |
| Auth | API key + TLS bypass (`NODE_TLS_REJECT_UNAUTHORIZED=0`) | None — direct subprocess |
| Latency | HTTP round-trip per call | In-process binary |
| Failure mode | Plugin disabled → silent breakage | Binary missing → loud `command -v` failure |
| Reentrancy | Self-MCP-calls inside Claude session can deadlock | Pure subprocess, safe |
| Mobile / headless | Limited | Limited (CLI is desktop-only too) |
CLI loses to MCP on exactly one axis: it only works on machines where Obsidian itself is installed. For headless servers and mobile, fall through to the next transport in the chain.
---
## Detection
At session start (or vault setup), run:
```bash
bash scripts/detect-transport.sh
```
This writes `.vault-meta/transport.json` with the schema:
```json
{
"preferred": "cli",
"fallback_chain": ["cli", "filesystem"],
"available": {
"cli": {"present": true, "binary": "obsidian-cli", "version_string": "..."},
"filesystem": {"present": true},
"mcp_obsidian": {"present": null, "detection": "deferred"},
"mcpvault": {"present": null, "detection": "deferred"}
}
}
```
**Read this file before any non-trivial vault mutation.** Skills that need to read or write should consult `preferred` and pick the corresponding transport. The decision tree lives at `wiki/references/transport-fallback.md`.
Refresh detection with `--force` after installing/removing the Obsidian CLI:
```bash
bash scripts/detect-transport.sh --force
```
---
## Recipes (CLI-first; fallback noted inline)
Each recipe shows the CLI form first. If the CLI is unavailable per the detection snapshot, fall through to the noted fallback. Variable substitution: `$VAULT` is the absolute vault root; `$NOTE` is a vault-relative path like `wiki/concepts/Foo.md`.
### Read a note
```bash
# CLI
obsidian-cli read "$VAULT" "$NOTE"
# Fallback: Claude's Read tool with absolute path
# Read $VAULT/$NOTE
```
### Create or overwrite a note
```bash
# CLI
obsidian-cli write "$VAULT" "$NOTE" < /path/to/content.md
# Fallback: Claude's Write tool with absolute path
# Write $VAULT/$NOTE with the desired content string
```
### Append to a note
```bash
# CLI
echo "additional content" | obsidian-cli append "$VAULT" "$NOTE"
# Fallback: Read $VAULT/$NOTE, append manually, Write back
```
### Search note content (CLI uses Obsidian's own search ranking)
```bash
# CLI
obsidian-cli search "$VAULT" "<query>"
# Fallback: ripgrep
rg --type=md "<query>" "$VAULT/wiki/"
```
### Today's daily note (if Daily Notes plugin is enabled)
```bash
# CLI
obsidian-cli daily:today "$VAULT"
obsidian-cli daily:append "$VAULT" "captured at $(date)"
# Fallback: compute path manually
NOTE="$VAULT/wiki/daily/$(date +%Y-%m-%d).md"
```
### Patch a frontmatter property
```bash
# CLI
obsidian-cli property:set "$VAULT" "$NOTE" status "evergreen"
# Fallback: read frontmatter, parse, mutate, rewrite (use mcp__obsidian-vault__update_frontmatter if MCP is configured)
```
### List backlinks for a page
```bash
# CLI
obsidian-cli backlinks "$VAULT" "$NOTE"
# Fallback: ripgrep for wikilink references
rg --type=md "\[\[$(basename "$NOTE" .md)" "$VAULT/wiki/"
```
### Open a Bases (.base) file's resolved view
```bash
# CLI
obsidian-cli bases "$VAULT" "$NOTE"
# (returns the resolved row list; supplements obsidian-bases skill which handles the .base file's YAML)
# Fallback: read the .base file directly; no resolved-view available without Obsidian itself
```
### Tags + bookmarks
```bash
obsidian-cli tags "$VAULT"
obsidian-cli bookmarks "$VAULT"
```
---
## When CLI is NOT the right choice
- **Mobile (iOS Share extension)**: filesystem write into `.raw/` is the only path; CLI is desktop-only.
- **CI / headless ingest jobs**: filesystem with manual frontmatter parsing.
- **Cross-vault operations**: CLI binds to one vault root per invocation; for federation, fall back to filesystem walks.
- **Live edits while Obsidian is mid-save**: rare race; CLI handles it correctly but in pathological cases the v1.7 `wiki-lock.sh` advisory locks (see [skills/wiki-fold/](../wiki-fold/SKILL.md) and `agents/wiki-ingest.md`) should be acquired first.
---
## Cross-reference
- Decision tree: [`wiki/references/transport-fallback.md`](../../wiki/references/transport-fallback.md)
- Legacy MCP options (A/B/C/D): [`skills/wiki/references/mcp-setup.md`](../wiki/references/mcp-setup.md)
- Concurrency policy (v1.7+): [`skills/wiki-ingest/SKILL.md`](../wiki-ingest/SKILL.md) §Concurrency
- Detection script: [`scripts/detect-transport.sh`](../../scripts/detect-transport.sh)
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Detect which Obsidian CLI binaries are installed; check if Obsidian app is running. Read `.vault-meta/transport.json` if it exists. |
| 2 | OBSERVE (int) | Don't be biased toward filesystem fallback when CLI is actually available — verify auto-detection caught what's installed. |
| 3 | LISTEN | If `manual_override: true` is set in transport.json, the user has spoken — preserve their `preferred` and `fallback_chain`. |
| 4 | THINK | Compute the right fallback chain for this environment. CLI > MCP > filesystem; freshness check before recomputing. |
| 5 | CONNECT (lat) | How does this transport choice affect every other skill's write? Six downstream skills depend on this snapshot. |
| 6 | CONNECT (sys) | Schema stability of transport.json matters more than feature richness — consumers parse the JSON via simple shell idioms. |
| 7 | FEEL | Error message when no transport works should tell the user EXACTLY what to do (install CLI, configure MCP, etc.). |
| 8 | ACCEPT | Filesystem fallback is fine. Admit when CLI doesn't exist; don't fabricate a binary that isn't there. |
| 9 | CREATE | Write transport.json atomically (temp + rename). Round-trip `manual_override` every cycle. |
| 10 | GROW | As MCP support matures, auto-detection should cover the deferred tiers. Track that as v1.7.x scope. |
+232
View File
@@ -0,0 +1,232 @@
---
name: wiki-fold
description: "Rollup of wiki log entries into meta-pages. Reads the last 2^k entries from wiki/log.md, writes a structurally-idempotent fold page to wiki/folds/ that links back to children. Extractive summarization (no invention). Dry-run by default, stdout-only; commit mode writes and accepts that the PostToolUse hook auto-commits. Triggers on: fold the log, run a fold, run wiki-fold, log rollup, roll up log entries."
---
# wiki-fold: Extractive Log Rollup
Implements a bounded subset of Mechanism 1 from [[DragonScale Memory]]: flat fold over raw `wiki/log.md` entries. Fold-of-folds (hierarchical level-stacking) is **out of scope for this skill**; see "Scope boundary" below.
A fold is **additive**: child log entries and their referenced pages are never modified, moved, or deleted. A fold is **extractive**: every outcome and theme in the output must be traceable to a specific child log entry. No invented facts, no synthesis beyond what the child entries support.
---
## Scope boundary (explicit)
This skill does **not** implement:
- Fold-of-folds / hierarchical level stacking (DragonScale spec calls for it; deferred to a future skill).
- Automatic triggering (folds are always human-invoked in Phase 1).
- Semantic-tiling dedup (Mechanism 3; separate skill).
It **does** implement:
- Flat fold over raw log.md entries at a chosen batch exponent `k`.
- Structural idempotency via a deterministic fold ID.
- Extractive summarization with count-checking.
When referring to level in frontmatter, use `batch_exponent: k` (not `level: k`), because this skill does not produce hierarchical levels.
---
## Modes
| Mode | Writes? | Invocation |
|---|---|---|
| **dry-run (default)** | **No Write tool calls.** Emit fold content via Bash `cat`/`heredoc` to stdout only. | `fold the log, dry-run k=3` |
| **commit** | Uses Write/Edit tools. Each Write fires the repo PostToolUse hook which auto-commits wiki changes. Accept this. Compose full content first, then sequence writes. | `fold the log, commit k=3` (only after a clean dry-run) |
**Why stdout-only in dry-run**: the repo's `hooks/hooks.json` PostToolUse hook fires on any `Write|Edit` and runs `git add wiki/ .raw/`. Writing to `/tmp` does not stage /tmp, but it still triggers the hook, which will commit *any pending wiki changes* under a generic message. Dry-run must leave zero residue. Bash stdout does not fire the hook.
---
## Concurrency (v1.7+)
The fold-page write in commit mode MUST be preceded by `wiki-lock acquire`:
```bash
FOLD_PATH="wiki/folds/${FOLD_ID}.md"
bash scripts/wiki-lock.sh acquire "$FOLD_PATH" || {
echo "FAIL: another writer holds $FOLD_PATH; aborting fold."; exit 75
}
# … write the fold via Write/Edit (which fires the PostToolUse hook) …
bash scripts/wiki-lock.sh release "$FOLD_PATH"
```
Fold pages are deterministically named (`fold-k{K}-from-{DATE}-to-{DATE}-n{COUNT}.md`), so two parallel folds with the same parameters target the same path. Without the lock, they could overwrite each other's outputs. The duplicate-detection check inside this skill (already documented below) handles the "fold already exists" case at the SKILL level; the lock handles the in-flight-write race at the OS level.
Dry-run mode does not acquire a lock (no writes happen).
See `skills/wiki-ingest/SKILL.md` §Concurrency for the full lock semantics.
---
## Deterministic fold ID
Every fold has an ID derived from its inputs:
```
fold-k{K}-from-{EARLIEST-DATE}-to-{LATEST-DATE}-n{COUNT}
```
Example: `fold-k3-from-2026-04-10-to-2026-04-23-n8`.
The filename in commit mode is `wiki/folds/{FOLD-ID}.md`. No date-of-creation in the filename. No timestamp in the title.
**Duplicate detection (required)**: before emitting any output, check if `wiki/folds/{FOLD-ID}.md` already exists. If so, report "Fold already exists at wiki/folds/{FOLD-ID}.md. Use --force to overwrite, or pick a different range." and stop. This is the no-op idempotency guarantee; byte-identical content is NOT guaranteed (LLM prose varies) but the filename and scope are.
---
## Parameters
- `k` (default 4): batch exponent. Batch size = `2^k`. Typical values: k=3 (8), k=4 (16), k=5 (32).
- `range` (optional): explicit entry range `entries 1-16`. Overrides k.
- `--force`: overwrite an existing fold with the same ID. Default no.
- `--commit`: write to wiki/. Without it, dry-run stdout-only.
If fewer than `2^k` log entries exist, report the shortfall and stop. Do not silently fold a partial batch.
---
## Procedure
### 1. Parse log entries
```
grep -n "^## \[" wiki/log.md | head -{2^k}
```
Record for each entry: line number, date, operation, title, and the following bullet lines until the next `## [` or end-of-section.
### 2. Extract child page identifiers
From each entry's bullet list, extract:
- `Location: wiki/path/to/page.md` (the primary page)
- `[[Wikilinks]]` inline
- `Pages created:` and `Pages updated:` lists
Build a structured children list:
```yaml
children:
- date: "2026-04-23"
op: "save"
title: "DragonScale Memory v0.2 — post-adversarial-review"
page: "[[DragonScale Memory]]"
- ...
```
One record per log entry. Do not dedupe by page: if two entries both point to `[[DragonScale Memory]]`, both records appear, distinguishable by date and title.
### 3. Read referenced pages (bounded)
Read only the pages that are not already captured fully in the log entry's bullets. Budget: 0-10 page reads. Hard ceiling: 15. If an entry's referenced page is missing, record `page_missing: true` and proceed.
### 4. Extractive summarization with count checks
Write the fold body per `references/fold-template.md`. **Rules**:
- **Extractive only.** Every outcome bullet and theme bullet must cite a specific child entry (e.g., `(from 2026-04-14 session)`) or a quoted line from that entry. Do not introduce events, counts, or interpretations not present in a child entry.
- **Log entry is the primary source.** If the log entry's bullets and the referenced meta-page disagree on a fact (e.g., a count), prefer the log-entry bullets and flag the mismatch as "source mismatch: log says X, meta says Y."
- **Count checks.** If you write "N concept pages" or "M repos updated," grep the source entries for the number and verify. Numeric mismatches are dry-run blockers.
- **No merging across entries without naming them.** A theme that spans multiple entries must name each contributing entry inline.
- **Uncertainty is a feature.** If an entry is ambiguous, say "ambiguous in source: [[Entry]]" rather than picking one interpretation.
### 5. Self-check before emitting
Before printing output, verify:
- Every child in `children:` frontmatter appears exactly once in the Child Entries table.
- Every entry in the table appears in the `children:` frontmatter.
- Every numeric claim in Key Outcomes is grep-verifiable against a child entry.
- The fold ID is deterministic and the file does not already exist (or `--force` is set).
If any check fails, abort and report the specific failure.
### 6. Emit
**Dry-run**: use Bash `cat <<'EOF' ... EOF` to stdout. Do not use Write. Print the fold ID and a one-line summary of what the commit step would do.
**Commit** (only after user says "commit the fold"):
1. `Write` the fold page to `wiki/folds/{FOLD-ID}.md`. (PostToolUse hook will auto-commit this.)
2. `Edit` `wiki/index.md` to add the fold link under a `## Folds` section (create section if missing). (Hook auto-commits.)
3. `Edit` `wiki/log.md` to prepend one entry:
```
## [YYYY-MM-DD] fold | batch-exponent-k{K} rollup of N entries
- Location: wiki/folds/{FOLD-ID}.md
- Range: {EARLIEST-DATE} to {LATEST-DATE}
- Children: N log entries
```
(Hook auto-commits.)
Three auto-commits result. The user sees three separate `wiki: auto-commit` entries in git log. This is expected; do not attempt to suppress the hook.
---
## Output schema
See `references/fold-template.md` for the canonical frontmatter and body layout.
---
## Invariants
1. **Structural idempotency**: same range + same k → same fold ID → duplicate detection prevents double-writes. LLM prose may vary across runs; the *location and scope* are fixed.
2. **Additive**: children are never modified.
3. **Bounded reads**: 0-15 child-page reads per fold.
4. **Extractive**: zero invented facts. Count checks enforced.
5. **No chaining**: wiki-fold does not invoke wiki-lint, wiki-ingest, autoresearch, or save.
---
## What NOT to do
- Do not use Write/Edit during dry-run. Bash stdout only.
- Do not include the current date in the fold filename or title. Use the child entry range.
- Do not silently dedupe children by page title. One record per log entry.
- Do not write "emergent themes" that span entries without naming which entries contribute.
- Do not claim byte-identical idempotency. Structural idempotency is the actual guarantee.
- Do not suppress or bypass the PostToolUse auto-commit hook.
- Do not update `wiki/hot.md`. Ownership stays with save/ingest skills.
---
## Reversal
Committed fold reversal (three commits, land in this order):
1. Remove the log.md fold entry.
2. Remove the index.md entry.
3. Delete the fold page file.
Or: `git revert` the three auto-commits. Child pages are untouched in either path.
---
## Example dry-run sequence
User: "fold the log, dry-run k=3"
1. Parse `wiki/log.md` top 8 entries.
2. Build structured children list (8 records).
3. Read 0-10 referenced pages as needed.
4. Produce fold ID: `fold-k3-from-2026-04-10-to-2026-04-23-n8`.
5. Check `wiki/folds/fold-k3-from-2026-04-10-to-2026-04-23-n8.md` does not exist.
6. Write fold body following the template.
7. Run self-check (frontmatter/table consistency, count verification).
8. Emit via `cat <<'EOF' ... EOF` to stdout.
9. Report: "Dry-run complete. Fold ID: {FOLD-ID}. To commit: 'commit the fold'."
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Read the last 2^k log entries FULLY. Skimming defeats extractive summarization. |
| 2 | OBSERVE (int) | Am I tempted to synthesize beyond what the child entries support? Extractive-only is the binding rule. |
| 3 | LISTEN | Which themes emerge naturally from the child entries? Don't impose themes from outside the children. |
| 4 | THINK | Extractive only. Every outcome must be traceable to a specific child entry. Count check at the end. |
| 5 | CONNECT (lat) | Cross-entry patterns ARE the value-add. The single-entry view misses these. |
| 6 | CONNECT (sys) | DragonScale Mechanism 1 + wiki-lock + address allocator. Folds are part of the memory architecture. |
| 7 | FEEL | A good fold lets future-me skim a year of work in 5 minutes. Aim for that compression. |
| 8 | ACCEPT | Dry-run first. Commit only when the self-check passes. Honor the bounded-scope constraint (no fold-of-folds yet). |
| 9 | CREATE | Fold page at `wiki/folds/<fold-id>.md` linking to all child entries. |
| 10 | GROW | Fold-of-folds (hierarchical level-stacking) is v_next scope — note as you encounter it, don't sneak it in. |
@@ -0,0 +1,133 @@
# Fold Page Template
Canonical output format for `wiki-fold`. Every fold page uses this layout exactly.
---
## Frontmatter
```yaml
---
type: fold
title: "Fold k{K} — {EARLIEST-DATE} to {LATEST-DATE} — n{COUNT}"
fold_id: "fold-k{K}-from-{EARLIEST-DATE}-to-{LATEST-DATE}-n{COUNT}"
batch_exponent: {K}
entry_count: {COUNT}
entry_range:
from: "{EARLIEST-CHILD-DATE}"
to: "{LATEST-CHILD-DATE}"
created: "{YYYY-MM-DD}"
updated: "{YYYY-MM-DD}"
tags:
- meta
- fold
- "fold/k{K}"
status: mature
children:
- date: "{YYYY-MM-DD}"
op: "{save|ingest|fold|session|setup|decision}"
title: "{log entry title verbatim}"
page: "[[{canonical page wikilink}]]"
page_missing: false
# ... one record per log entry. No dedupe by page.
related:
- "[[DragonScale Memory]]"
- "[[log]]"
- "[[index]]"
---
```
All fields are required. Missing any field is a dry-run failure. `title` does not contain the current date. `fold_id` is deterministic and matches the filename.
---
## Body Sections (in order, all required)
### 1. Scope (one paragraph)
```markdown
Level-{K} fold of {COUNT} log entries spanning {FROM} to {TO}. Dominant themes: {THEME-1}, {THEME-2}, {THEME-3}.
```
### 2. Child Entries
One row per log entry. Row count must equal `entry_count` in frontmatter and the length of `children:`.
```markdown
## Child Entries
| Date | Op | Title | Page | Summary (extractive) |
|---|---|---|---|---|
| 2026-04-23 | save | DragonScale Memory v0.2 — post-adversarial-review | [[DragonScale Memory]] | Adversarial-review rewrite; 7/7 critiques accepted after one surgical fix. |
| 2026-04-15 | save | Claude SEO v1.9.0 Slides and GitHub Release | [[2026-04-15-slides-and-release-session]] | 15-slide HTML deck, v1.9.0 tagged, GitHub release with PDF asset. |
<!-- one row per log entry; no dedupe by page -->
```
The Summary column is extractive: one sentence paraphrased from the log entry's bullets. If the source is ambiguous, write "ambiguous in source" rather than guessing.
### 3. Key Outcomes (3-7 bullets, extractive)
Every bullet must cite the specific child entry (by date) it draws from. Every numeric value must be grep-verifiable against that child entry. Count-check before emitting.
```markdown
## Key Outcomes
- {CONCRETE CHANGE 1, quoting or paraphrasing a child entry} (from 2026-04-14 session entry)
- {CONCRETE CHANGE 2, with numeric grep-verified against source} (from 2026-04-10 session entry)
<!-- max 7 bullets. Each bullet names a concrete artifact or decision AND cites its source entry. -->
```
### 4. Cross-entry Themes (0-4 bullets, must name contributing entries)
Themes are optional. If a theme cannot be supported by naming at least two child entries that contribute to it, do not write it.
```markdown
## Cross-entry Themes
- {THEME: describes a pattern supported by multiple entries} (supported by: 2026-04-14, 2026-04-15, 2026-04-23 entries)
```
Do not invent a theme to justify the fold. If no cross-entry patterns are present, write "No cross-entry themes identified; entries are independent within this range."
### 5. Contradictions or Corrections
```markdown
## Contradictions or Corrections
- None detected.
```
Or, if present:
```markdown
## Contradictions or Corrections
- [[Earlier Entry]] claimed X; [[Later Entry]] corrected to Y. Resolution: {STATUS}.
```
### 6. Links
The `Child Pages` section is **deduped by page**: one wikilink per unique target page, even if multiple log entries point at it. This is the graph-connection section, different from frontmatter `children:` which is **per log entry** (no dedupe).
```markdown
## Child Pages
- [[{UNIQUE-PAGE-1}]]
- [[{UNIQUE-PAGE-2}]]
<!-- dedupe by page; see frontmatter `children:` for per-entry records -->
## Related
- [[DragonScale Memory]] - fold-operator spec
- [[log]] - source entries
- [[index]] - vault catalog
```
---
## Notes
- No hot-cache update: that is the save/ingest skill's responsibility.
- No edits to child pages. Folds are strictly read-only with respect to children.
- If a child entry's referenced pages are missing, note "source missing" in the Summary column rather than fabricating content.
- The body is terse. A fold is a rollup, not a retelling. Target 200-400 lines total for a k=4 fold.
+361
View File
@@ -0,0 +1,361 @@
---
name: wiki-ingest
description: "Ingest sources into the Obsidian wiki vault. Reads a source, extracts entities and concepts, creates or updates wiki pages, cross-references, and logs the operation. Supports files, URLs, and batch mode. Triggers on: ingest, process this source, add this to the wiki, read and file this, batch ingest, ingest all of these, ingest this url."
---
# wiki-ingest: Source Ingestion
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.
---
## Transport (v1.7+)
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:
- **cli** — `obsidian-cli write "$VAULT" "$NOTE" < content.md` (or `append`, `property:set`); see [`skills/wiki-cli/SKILL.md`](../wiki-cli/SKILL.md)
- **mcp-obsidian** / **mcpvault**`mcp__obsidian-vault__write_note` and friends; see [`skills/wiki/references/mcp-setup.md`](../wiki/references/mcp-setup.md)
- **filesystem** — Claude's `Write`/`Edit` tools with absolute vault-rooted paths (final floor; always works)
Full decision tree: [`wiki/references/transport-fallback.md`](../../wiki/references/transport-fallback.md).
---
## Mode awareness (v1.8+)
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.
```bash
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:
- **LYT**: after filing the atomic note, update the relevant MOC (`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`.
- **Zettelkasten**: filename already includes the timestamp ID. Populate the `id:` frontmatter field to match.
- **PARA**: new ingests land in `wiki/resources/incoming/` by default. Do NOT auto-guess the topic; leave in incoming/ for user review.
## Concurrency (v1.7+)
**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>`.
```bash
# 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:
- **Per-file granularity.** Locks key on `sha1(<vault-relative-path>)`; concurrent writes to DIFFERENT pages run in parallel.
- **Age-based staleness.** Default `STALE_AFTER_SEC=60`. A crashed holder unblocks in ≤60 seconds without manual intervention. See `scripts/wiki-lock.sh` header for the full semantics.
- **Cross-process release.** Release is `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).
- **The PostToolUse hook now defers `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`.
---
## Delta Tracking
Before ingesting any file, check `.raw/.manifest.json` to avoid re-processing unchanged sources.
```bash
# Check if manifest exists
[ -f .raw/.manifest.json ] && echo "exists" || echo "no manifest yet"
```
**Manifest format** (create if missing):
```json
{
"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:**
1. Compute a hash: `md5sum [file] | cut -d' ' -f1` (or `sha256sum` on Linux).
2. Check if the path exists in `.manifest.json` with the same hash.
3. If hash matches, skip. Report: "Already ingested (unchanged). Use `force` to re-ingest."
4. If missing or hash differs, proceed with ingest.
**After ingesting a file:**
1. Record `{hash, ingested_at, pages_created, pages_updated}` in `.manifest.json`.
2. Write the updated manifest back.
Skip delta checking if the user says "force ingest" or "re-ingest".
---
## URL Ingestion
Trigger: user passes a URL starting with `https://`.
Steps:
1. **Fetch** the page using WebFetch.
2. **Clean** (optional): if `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.
3. **Derive slug** from the URL path (last segment, lowercased, spaces→hyphens, strip query strings).
4. **Save** to `.raw/articles/[slug]-[YYYY-MM-DD].md` with a frontmatter header:
```markdown
---
source_url: [url]
fetched: [YYYY-MM-DD]
---
```
5. Proceed with **Single Source Ingest** starting at step 2 (file is now in `.raw/`).
---
## Image / Vision Ingestion
Trigger: user passes an image file path (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.svg`, `.avif`).
Steps:
1. **Read** the image file using the Read tool. Claude can process images natively.
2. **Describe** the image contents: extract all text (OCR), identify key concepts, entities, diagrams, and data visible in the image.
3. **Save** the description to `.raw/images/[slug]-[YYYY-MM-DD].md`:
```markdown
---
source_type: image
original_file: [original path]
fetched: YYYY-MM-DD
---
# Image: [slug]
[Full description of image contents, transcribed text, entities visible, etc.]
```
4. Copy the image to `_attachments/images/[slug].[ext]` if it's not already in the vault.
5. Proceed with **Single Source Ingest** on the saved description file.
Use cases: whiteboard photos, screenshots, diagrams, infographics, document scans.
---
## Single Source Ingest
Trigger: user drops a file into `.raw/` or pastes content.
Steps:
1. **Read** the source completely. Do not skim.
2. **Discuss** key takeaways with the user. Ask: "What should I emphasize? How granular?" Skip this if the user says "just ingest it."
3. **Create** source summary in `wiki/sources/`. Use the source frontmatter schema from `references/frontmatter.md`. Assign an address per the **Address Assignment** section below.
4. **Create or update** entity pages for every person, org, product, and repo mentioned. One page per entity. Assign addresses to new entity pages.
5. **Create or update** concept pages for significant ideas and frameworks. Assign addresses to new concept pages.
6. **Update** relevant domain page(s) and their `_index.md` sub-indexes.
7. **Update** `wiki/overview.md` if the big picture changed.
8. **Update** `wiki/index.md`. Add entries for all new pages.
9. **Update** `wiki/hot.md` with this ingest's context.
10. **Append** to `wiki/log.md` (new entries at the TOP):
```markdown
## [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.
```
11. **Check for contradictions.** If new info conflicts with existing pages, add `> [!contradiction]` callouts on both pages.
---
## Batch Ingest
Trigger: user drops multiple files or says "ingest all of these."
Steps:
1. List all files to process. Confirm with user before starting.
2. Process each source following the single ingest flow. Defer cross-referencing between sources until step 3.
3. After all sources: do a cross-reference pass. Look for connections between the newly ingested sources.
4. Update index, hot cache, and log once at the end (not per-source).
5. Report: "Processed N sources. Created X pages, updated Y pages. Here are the key connections I found."
Batch ingest is less interactive. For 30+ sources, expect significant processing time. Check in with the user after every 10 sources.
---
## Context Window Discipline
Token budget matters. Follow these rules during ingest:
- Read `wiki/hot.md` first. If it contains the relevant context, don't re-read full pages.
- Read `wiki/index.md` to find existing pages before creating new ones.
- Read only 3-5 existing pages per ingest. If you need 10+, you are reading too broadly.
- Use PATCH for surgical edits. Never re-read an entire file just to update one field.
- Keep wiki pages short. 100-300 lines max. If a page grows beyond 300 lines, split it.
- Use search (`/search/simple/`) to find specific content without reading full pages.
---
## Contradictions
> [!note] Custom callout dependency
> The `[!contradiction]` callout type used below is a **custom callout** defined in `.obsidian/snippets/vault-colors.css` (auto-installed by `/wiki` scaffold). 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:
```markdown
> [!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:
```markdown
> [!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.
---
## What Not to Do
- **Source files under `.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.
- Do not create duplicate pages. Always check the index and search before creating.
- Do not skip the log entry. Every ingest must be recorded.
- Do not skip the hot cache update. It is what keeps future sessions fast.
---
## Address Assignment (DragonScale Mechanism 2 MVP)
**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)**:
```bash
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:
```yaml
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.
### Required tool: `scripts/allocate-address.sh`
Address 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.
```bash
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).
### Helper modes
- `./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.
### Assignment procedure (per new page)
1. Before writing a new non-meta page, call `./scripts/allocate-address.sh` and capture the output.
2. Include `address: c-XXXXXX` in the page's frontmatter.
3. Record the path-to-address mapping in `.raw/.manifest.json` under a new top-level key `address_map` (see schema below).
### `address_map` in `.raw/.manifest.json`
```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.
### Exclusions (do NOT assign an address to)
- Meta files: `_index.md`, `index.md`, `log.md`, `hot.md`, `overview.md`, `dashboard.md`, `dashboard.base`, `Wiki Map.md`, `getting-started.md`.
- Fold pages under `wiki/folds/` (they use their own deterministic `fold_id`).
- Pre-rollout legacy pages (`created:` < 2026-04-23). Legacy pages get `l-NNNNNN` addresses only via a deliberate backfill operation.
### Idempotency rules
- If a page being (re)written already has an `address:` field in its current content, REUSE it. Do not allocate a new one.
- If a source is re-ingested and `address_map` has a mapping for the target path, reuse that mapping.
- If the source has been ingested before AND the target page has no address AND the page `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).
### Concurrency policy
- **Single-writer only** in Phase 2. Do not run parallel ingests from multiple Claude sessions or sub-agents that assign addresses. The `flock` in the helper prevents counter corruption but does not serialize page writes themselves.
- Sub-agents (codex, general-purpose) that are dispatched for research or review MUST NOT call the allocator. They are read-only in this respect.
- Multi-writer support is a deferred feature.
### Batch ingest
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).
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../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. |
+394
View File
@@ -0,0 +1,394 @@
---
name: wiki-lint
description: >
Health check the Obsidian wiki vault. Finds orphan pages, dead wikilinks, stale claims,
missing cross-references, frontmatter gaps, and empty sections. Creates or updates
Dataview dashboards. Generates canvas maps. Triggers on: "lint", "health check",
"clean up wiki", "check the wiki", "wiki maintenance", "find orphans", "wiki audit".
---
# wiki-lint: Wiki Health Check
Run lint after every 10-15 ingests, or weekly. Ask before auto-fixing anything. Output a lint report to `wiki/meta/lint-report-YYYY-MM-DD.md`.
---
## Transport (v1.7+)
Lint primarily reads, then writes a single report file. Both follow the standard transport policy. Read `.vault-meta/transport.json` (auto-created by `bash scripts/detect-transport.sh`):
- **cli** — `obsidian-cli read "$VAULT" "$NOTE"` for individual reads; `obsidian-cli backlinks "$VAULT" "$NOTE"` natively handles backlink graph (avoids re-rolling it via Grep); see [`skills/wiki-cli/SKILL.md`](../wiki-cli/SKILL.md)
- **mcp-obsidian** / **mcpvault**`mcp__obsidian-vault__read_multiple_notes`, `list_all_tags`
- **filesystem** — Claude's `Read`/`Glob`/`Grep` (final floor; current v1.6 behavior)
Full decision tree: [`wiki/references/transport-fallback.md`](../../wiki/references/transport-fallback.md). DragonScale Mechanism 3 tiling lint is a separate code path (Python script) and bypasses transport selection.
---
## Lint Checks
Work through these in order:
1. **Orphan pages**. Wiki pages with no inbound wikilinks. They exist but nothing points to them.
2. **Dead links**. Wikilinks that reference a page that does not exist.
3. **Stale claims**. Assertions on older pages that newer sources have contradicted or updated.
4. **Missing pages**. Concepts or entities mentioned in multiple pages but lacking their own page.
5. **Missing cross-references**. Entities mentioned in a page but not linked.
6. **Frontmatter gaps**. Pages missing required fields (type, status, created, updated, tags).
7. **Empty sections**. Headings with no content underneath.
8. **Stale index entries**. Items in `wiki/index.md` pointing to renamed or deleted pages.
9. **Address validity** (DragonScale Mechanism 2). For every page that has an `address:` frontmatter field, validate the format. See the **Address Validation** section below.
10. **Semantic tiling** (DragonScale Mechanism 3, opt-in). Flag candidate duplicate pages (across all scanned types, not just concepts) via embedding cosine similarity. See the **Semantic Tiling** section below.
---
## Lint Report Format
Create at `wiki/meta/lint-report-YYYY-MM-DD.md`:
```markdown
---
type: meta
title: "Lint Report YYYY-MM-DD"
created: YYYY-MM-DD
updated: YYYY-MM-DD
tags: [meta, lint]
status: developing
---
# Lint Report: YYYY-MM-DD
## Summary
- Pages scanned: N
- Issues found: N
- Auto-fixed: N
- Needs review: N
## Orphan Pages
- [[Page Name]]: no inbound links. Suggest: link from [[Related Page]] or delete.
## Dead Links
- [[Missing Page]]: referenced in [[Source Page]] but does not exist. Suggest: create stub or remove link.
## Missing Pages
- "concept name": mentioned in [[Page A]], [[Page B]], [[Page C]]. Suggest: create a concept page.
## Frontmatter Gaps
- [[Page Name]]: missing fields: status, tags
## Stale Claims
- [[Page Name]]: claim "X" may conflict with newer source [[Newer Source]].
## Cross-Reference Gaps
- [[Entity Name]] mentioned in [[Page A]] without a wikilink.
```
---
## Naming Conventions
Enforce these during lint:
| Element | Convention | Example |
|---------|-----------|---------|
| Filenames | Title Case with spaces | `Machine Learning.md` |
| Folders | lowercase with dashes | `wiki/data-models/` |
| Tags | lowercase, hierarchical | `#domain/architecture` |
| Wikilinks | match filename exactly | `[[Machine Learning]]` |
Filenames must be unique across the vault. Wikilinks work without paths only if filenames are unique.
---
## Writing Style Check
During lint, flag pages that violate the style guide:
- Not declarative present tense ("X basically does Y" instead of "X does Y")
- Missing source citations where claims are made
- Uncertainty not flagged with `> [!gap]`
- Contradictions not flagged with `> [!contradiction]`
---
## Dataview Dashboard
Create or update `wiki/meta/dashboard.md` with these queries:
````markdown
---
type: meta
title: "Dashboard"
updated: YYYY-MM-DD
---
# Wiki Dashboard
## Recent Activity
```dataview
TABLE type, status, updated FROM "wiki" SORT updated DESC LIMIT 15
```
## Seed Pages (Need Development)
```dataview
LIST FROM "wiki" WHERE status = "seed" SORT updated ASC
```
## Entities Missing Sources
```dataview
LIST FROM "wiki/entities" WHERE !sources OR length(sources) = 0
```
## Open Questions
```dataview
LIST FROM "wiki/questions" WHERE answer_quality = "draft" SORT created DESC
```
````
---
## Canvas Map
Create or update `wiki/meta/overview.canvas` for a visual domain map:
```json
{
"nodes": [
{
"id": "1",
"type": "file",
"file": "wiki/overview.md",
"x": 0, "y": 0,
"width": 300, "height": 140,
"color": "1"
}
],
"edges": []
}
```
Add one node per domain page. Connect domains that have significant cross-references. Colors map to the CSS scheme: 1=blue, 2=purple, 3=yellow, 4=orange, 5=green, 6=red.
---
## Address Validation (DragonScale Mechanism 2 MVP)
**Opt-in feature.** Address Validation runs only if the vault is using DragonScale, detected by:
```bash
if [ -x ./scripts/allocate-address.sh ] && [ -f ./.vault-meta/address-counter.txt ]; then
DRAGONSCALE_ADDRESSES=1
else
DRAGONSCALE_ADDRESSES=0
fi
```
When `DRAGONSCALE_ADDRESSES=0`, skip this entire section. Missing `address:` fields are not flagged, not even informationally. Pages that happen to have an `address:` field are passed through unvalidated (treat as user-managed metadata).
When `DRAGONSCALE_ADDRESSES=1`, proceed with the rollout baseline and checks below.
Rollout baseline: **2026-04-23** (Phase 2 ship date in vaults that adopted DragonScale on that day). Vaults that adopted DragonScale later should override this baseline by setting the earliest `created:` date of any addressed page as their personal rollout date. Record the chosen baseline at the top of `.vault-meta/legacy-pages.txt` as a commented line: `# rollout: YYYY-MM-DD`.
### Classification rule (applied per page)
Before validating anything, classify the page:
| Classification | Criteria |
|---|---|
| **Meta / fold / excluded** | File is in `wiki/folds/` OR filename in `{_index.md, index.md, log.md, hot.md, overview.md, dashboard.md, dashboard.base, Wiki Map.md, getting-started.md}`. Address not required. |
| **Post-rollout (must have address)** | `type` is not meta/fold AND frontmatter `created:` date is >= 2026-04-23 AND file path is NOT in the legacy baseline manifest. |
| **Legacy (backfill-eligible)** | `type` is not meta/fold AND frontmatter `created:` date is < 2026-04-23 OR file path IS in the legacy baseline manifest. Address not required until backfill. |
**Legacy baseline manifest**: optional file at `.vault-meta/legacy-pages.txt`, one relative path per line. Pages listed there are treated as legacy regardless of `created:` date. Use this to grandfather pages whose `created:` metadata is wrong or missing.
### Validation checks (run in order)
1. **Format check**: any page with `address:` set must match one of:
- `^c-[0-9]{6}$` — post-rollout creation address.
- `^l-[0-9]{6}$` — legacy-backfill address.
- Pages under `wiki/folds/` use `fold_id`, not `address`; do not apply the `c-`/`l-` regex there.
2. **Uniqueness check**: no two pages share the same address value. Report both paths.
3. **Counter consistency**: `./scripts/allocate-address.sh --peek` returns the next counter value. Every observed `c-NNNNNN` must satisfy `NNNNNN < peek_value`. Violation = counter drift.
4. **Post-rollout enforcement**: every page classified as "post-rollout (must have address)" that LACKS the `address:` field is a lint **error**, not informational. This prevents the silent-regression path where a new page skips address assignment.
5. **Legacy identification**: every page classified as "legacy" that LACKS an address is informational. The lint report lists them under "Pending backfill" with total count.
6. **Address-map consistency** (`.raw/.manifest.json`): for every page path in `address_map`, the page must exist and its frontmatter `address` must match the mapping. Mismatches are errors (either a rename dropped the map update, or a manual edit diverged).
### Lint posture summary
- Pages that HAVE an address with bad format: **error**.
- Pages that HAVE colliding addresses: **error**.
- Pages classified **post-rollout** WITHOUT an address: **error**.
- Pages classified **legacy** WITHOUT an address: **informational** (expected).
- Meta and fold pages without `address`: **ignored** (not applicable).
- Counter drift (observed counter >= peek): **error**.
- Address-map mismatch: **error**.
Lint only observes. Do NOT auto-assign missing addresses during lint. Assignment is `wiki-ingest`'s responsibility only.
### Output section in the lint report
```markdown
## Address Validation
- Counter state: `$(./scripts/allocate-address.sh --peek)`
- Highest c- address observed: c-XXXXXX
- Post-rollout pages checked: N (X passing, Y errors)
- Legacy pages pending backfill: M
### Errors
- [[Page Name]]: invalid address format `{value}`. Expected `c-NNNNNN` or `l-NNNNNN`.
- [[Page A]] and [[Page B]] share address `c-000042`.
- [[Post-Rollout Page]]: missing address. Page created 2026-04-25 (post-rollout); address required. Run wiki-ingest or manually run `./scripts/allocate-address.sh` and add to frontmatter.
- [[Page Name]] has address `c-000100` but counter peek is `50`. Counter drift; run `./scripts/allocate-address.sh --rebuild`.
- `.raw/.manifest.json` maps `wiki/foo.md` -> `c-000010` but page frontmatter has `c-000012`. Resolve mismatch.
### Pending backfill (informational)
- M legacy pages without addresses. See `.vault-meta/legacy-pages.txt` for the canonical legacy set, or filter by `created:` < 2026-04-23.
```
---
## Semantic Tiling (DragonScale Mechanism 3 MVP, opt-in)
**Opt-in feature.** Semantic tiling flags candidate duplicate *pages* (not just concept pages — see Scope below) using embedding cosine similarity. Local ollama only by default; remote endpoints require an explicit override flag.
### Detection and delegation
```bash
if [ -x ./scripts/tiling-check.py ] && command -v python3 >/dev/null 2>&1; then
./scripts/tiling-check.py --peek > /tmp/tiling-peek.json 2>/dev/null
PEEK_EXIT=$?
case $PEEK_EXIT in
0) TILING_READY=1 ;; # ready
2) TILING_READY=0 ; echo "tiling ERROR: usage error (exit 2); inspect /tmp/tiling-peek.json" ;;
3) TILING_READY=0 ; echo "tiling ERROR: cache corrupt (exit 3); inspect .vault-meta/tiling-cache.json" ;;
4) TILING_READY=0 ; echo "tiling ERROR: vault exceeds scale hard-fail (exit 4); batching required" ;;
10) TILING_READY=0 ; echo "tiling skipped: ollama not reachable (exit 10)" ;;
11) TILING_READY=0 ; echo "tiling skipped: run 'ollama pull nomic-embed-text' to enable (exit 11)" ;;
*) TILING_READY=0 ; echo "tiling ERROR: unexpected exit code $PEEK_EXIT from tiling-check.py --peek" ;;
esac
else
TILING_READY=0
echo "tiling skipped: scripts/tiling-check.py or python3 not available"
fi
```
Inspect `/tmp/tiling-peek.json` (structured diagnostics: script path, python interpreter, ollama URL, cache state, thresholds state) whenever the status is ambiguous. Never collapse unknown exits into "unknown status" silently.
When `TILING_READY=1`:
```bash
./scripts/tiling-check.py --report wiki/meta/tiling-report-YYYY-MM-DD.md
REPORT_EXIT=$?
case $REPORT_EXIT in
0) echo "tiling report written" ;;
2) echo "tiling ERROR: usage error during --report" ;;
3) echo "tiling ERROR: cache corrupt during --report" ;;
4) echo "tiling ERROR: scale hard-fail during --report" ;;
10) echo "tiling ERROR: ollama became unreachable between --peek and --report" ;;
11) echo "tiling ERROR: model became unavailable between --peek and --report" ;;
*) echo "tiling ERROR: unexpected exit code $REPORT_EXIT from tiling-check.py --report" ;;
esac
```
### Scope (what the helper scans)
- Includes: every `.md` under `wiki/` **except** the exclusion set below. The scope is "candidate tileable pages," not just `type: concept`.
- Excludes (path): anything under `wiki/folds/` or `wiki/meta/`.
- Excludes (filename): `_index.md`, `index.md`, `log.md`, `hot.md`, `overview.md`, `dashboard.md`, `Wiki Map.md`, `getting-started.md`.
- Excludes (frontmatter): `type: meta` or `type: fold`.
- Excludes (security): symlinks. Any page file that is a symlink, or whose resolved path escapes the vault root, is skipped.
If you place a real concept under `wiki/meta/` it will be excluded by path regardless of content. Keep concepts in their canonical folders.
### How the helper works
- Computes one embedding per included page via the ollama `nomic-embed-text` model by default.
- Caches embeddings at `.vault-meta/tiling-cache.json`, keyed on `sha256(model + body)` so model drift auto-invalidates. Frontmatter is not part of the hash or the embedding input — pure frontmatter edits (tag changes, status bumps) do not trigger recomputation.
- Orphans are GC'd: when a cached page path no longer exists on disk, its entry is dropped on save.
- Concurrent-safe: exclusive flock on `.vault-meta/.tiling.lock` around cache I/O; per-PID temp file for atomic writes.
### Security posture
- Defaults to `http://127.0.0.1:11434`. `OLLAMA_URL` env override is accepted **only** with `--allow-remote-ollama` because page bodies are POSTed as embedding input.
- Symlinks and vault-root escapes are rejected.
### Default bands (conservative seeds, NOT calibrated)
| Band | Similarity | Report section |
|---|---|---|
| Error | `>= 0.90` | **Errors** — strong near-duplicate, likely the same concept |
| Review | `0.80 - 0.90` | **Review** — possible tile overlap; human judgement needed |
| Pass | `< 0.80` | not emitted |
**These values are conservative seeds, not literature-backed interpolation.** Published reference points: Sentence Transformers `community_detection` defaults to 0.75; Quora-duplicate calibrations land around 0.7715-0.8352 depending on objective. The 0.80 review floor is already stricter than at least one cited Quora optimum, so expect **false negatives** against those baselines. Reduce the review floor during calibration if you want more sensitivity.
### Calibration procedure (manual, one-time per vault)
1. Run the helper with defaults. Capture the **Review** band pairs.
2. Temporarily lower `bands.review` to `0.70` in `.vault-meta/tiling-thresholds.json` to surface a wider sample. Aim for >=50 pairs spanning 0.70-0.95.
3. Label each pair: `duplicate`, `similar`, `distinct`.
4. Pick bands such that: (a) the `error` band contains >= 95% true duplicates; (b) the `review` band captures `similar` pairs without swamping the report with `distinct` ones.
5. Edit `.vault-meta/tiling-thresholds.json`: set new `bands.error` and `bands.review`, set `calibrated: true`, set `calibration_pairs_labeled` to the label count.
6. Re-run lint. Report footer now says `calibrated: true`.
### Scale
- Cold-cache cost is O(N) POSTs to ollama. Warm-cache cost is O(N^2) cosines in pure Python.
- Helper prints a warning at > 500 pages and hard-fails (exit 4) at > 5000. Revisit the implementation (batching, vectorized cosine, or external tooling) before exceeding either limit.
### Lint report embed
```markdown
## Semantic Tiling
See [[tiling-report-YYYY-MM-DD]] for the full pair listing.
- Errors (>=0.90): N pairs
- Review (0.80-0.90): M pairs
- Calibrated: true|false
```
### Invariants
- Read-only. `tiling-check.py` never modifies wiki pages.
- No auto-merge. Duplicates are listed, never resolved.
- Cache is incremental and model-scoped. Unchanged pages are not re-embedded.
- Exit codes: `0` ok, `2` usage error, `3` cache corrupt, `4` scale hard-fail, `10` ollama unreachable, `11` model missing. Surface all of them; do not collapse into a single "unknown" bucket.
---
## Before Auto-Fixing
Always show the lint report first. Ask: "Should I fix these automatically, or do you want to review each one?"
Safe to auto-fix:
- Adding missing frontmatter fields with placeholder values
- Creating stub pages for missing entities
- Adding wikilinks for unlinked mentions
Needs review before fixing:
- Deleting orphan pages (they might be intentionally isolated)
- Resolving contradictions (requires human judgment)
- Merging duplicate pages
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Scan every page, every wikilink, every frontmatter block. No skipping for size or apparent obviousness. |
| 2 | OBSERVE (int) | Am I biased toward "looks fine"? Pretend you're a hostile reader looking for what's actually wrong. |
| 3 | LISTEN | Did the user mention specific concerns this session? Prioritize those over generic checks. |
| 4 | THINK | Which checks matter? Tier findings by severity (BLOCKER / HIGH / MEDIUM / LOW), not by ease of fix. |
| 5 | CONNECT (lat) | Orphan + dead-link + frontmatter-gap patterns often co-occur. Cluster findings to expose root causes. |
| 6 | CONNECT (sys) | Tiling-check + Dataview dashboards + canvas overview — multiple lint surfaces to integrate. |
| 7 | FEEL | A lint report should empower, not shame. Actionable items beat exhaustive catalog. |
| 8 | ACCEPT | Some lint findings are deliberate (orphan-by-design, intentional stub). Flag, don't force. |
| 9 | CREATE | Lint report at `wiki/meta/lint-report-YYYY-MM-DD.md` with tiered findings. |
| 10 | GROW | Recurring lint findings → process improvement targets, not just one-time fixes. |
+214
View File
@@ -0,0 +1,214 @@
---
name: wiki-mode
description: "Methodology modes for the Compound Vault. Lets the vault declare an organizational style (LYT / PARA / Zettelkasten / Generic) that wiki-ingest, save, and autoresearch consult before filing new pages. Reads `.vault-meta/mode.json`; defaults to `generic` (v1.6/v1.7 behavior) when absent. Per the May 2026 compass artifact, methodology support was priority gap 5 — no other Claude+Obsidian competitor ships it as a first-class skill. Triggers on: set vault mode, switch to PARA, use LYT, what's my vault mode, zettelkasten setup, wiki mode, methodology mode, change mode, configure mode."
allowed-tools: Read, Write, Bash
---
# wiki-mode: Methodology Modes for the Compound Vault
The v1.6 + v1.7 vault structure was opinion-free — `wiki/sources/`, `wiki/entities/`, `wiki/concepts/`, and so on. That works for power-users with their own organizational instincts. It does NOT serve the large segment of Obsidian users who want a named methodology to follow.
**v1.8 ships `wiki-mode` to close that gap.** A vault declares a mode (LYT, PARA, Zettelkasten, or Generic) in `.vault-meta/mode.json`; the other skills consult it before deciding where to file new pages. Mode = `generic` is the default and preserves v1.6/v1.7 behavior exactly.
**Per May 2026 compass artifact**: This was priority gap 5 of the 5 identified. Ideaverse Pro 2.0 ($200 paid vault) ships LYT as an opinionated structure; no Claude+Obsidian competitor ships PARA / Zettelkasten / mode-aware routing as a first-class skill. v1.8 takes us from TIE → LEAD on the audit §9 methodology-support axis (5 of 7 axes #1).
---
## The four modes
### LYT (Linking Your Thinking — Nick Milo)
**Philosophy:** notes link, folders don't. The organizational primitive is the **MOC** (Map of Content) — a hub note that links into a cluster of atomic notes. You never browse folders; you navigate by following links.
**Filing convention:**
- `wiki/mocs/<topic>-moc.md` — the MOC for a topic cluster
- `wiki/notes/<atomic-note>.md` — flat list of atomic notes, named by their idea, all linked from at least one MOC
**When to use:** mid-to-large knowledge bases (>100 notes), users who think in terms of conceptual clusters, knowledge graphs.
### PARA (Tiago Forte)
**Philosophy:** organize by **actionability**, not topic. Active work in Projects, ongoing responsibilities in Areas, reference material in Resources, completed/inactive in Archives.
**Filing convention:**
- `wiki/projects/<project-name>/<note>.md` — active projects with a deadline/outcome
- `wiki/areas/<area-name>/<note>.md` — ongoing responsibilities (no deadline)
- `wiki/resources/<topic>/<note>.md` — reference material, organized by topic
- `wiki/archives/<year>/<note>.md` — completed projects, sunsetted areas
**When to use:** workflow-heavy users, knowledge workers managing many projects, GTD-adjacent practitioners.
### Zettelkasten (Niklas Luhmann's slip-box)
**Philosophy:** atomic notes, unique IDs, dense bidirectional linking. No folders. Every note answers exactly one idea. Notes find each other by ID references.
**Filing convention:**
- `wiki/<YYYYMMDDHHMMSSffffff>-<slug>.md` — flat, timestamped IDs (20 digits = date + microseconds, collision-resistant)
- Every note has `id:`, `parent_id:` (optional), `child_ids:` (optional) in frontmatter
- No subdirectories; the wiki/ root is the whole vault
**When to use:** academics, researchers, long-term thinkers building permanent knowledge artifacts. Highest discipline; smallest filing surface.
### Generic (default — v1.7 behavior)
**Filing convention:** preserves the v1.6/v1.7 default — `wiki/sources/`, `wiki/entities/`, `wiki/concepts/`, `wiki/<domain>/`. No opinion imposed.
**When to use:** when you don't want to commit to a methodology, or you're migrating from v1.7 and want zero behavior change.
---
## How to set the mode
```bash
bash bin/setup-mode.sh
```
Interactive prompt: pick one of the 4 modes. Writes `.vault-meta/mode.json`. Optionally seeds template folders (LYT `mocs/`, PARA `projects/areas/resources/archives/`).
To check the current mode programmatically:
```bash
cat .vault-meta/mode.json | python3 -c 'import json,sys; print(json.load(sys.stdin)["mode"])'
```
To switch modes later: re-run `setup-mode.sh`. Existing files are NOT auto-migrated; the new mode only affects newly-filed pages from that point. Migration is a manual operation (see [migration section](#migration-between-modes) below).
---
## Mode config schema (`.vault-meta/mode.json`)
```json
{
"schema_version": 1,
"mode": "lyt|para|zettelkasten|generic",
"configured_at": "ISO-8601 timestamp",
"config": {
"lyt": {
"moc_folder": "wiki/mocs/",
"notes_folder": "wiki/notes/"
},
"para": {
"projects_folder": "wiki/projects/",
"areas_folder": "wiki/areas/",
"resources_folder": "wiki/resources/",
"archives_folder": "wiki/archives/"
},
"zettelkasten": {
"id_format": "YYYYMMDDHHMMSSffffff",
"no_folders": true,
"root_folder": "wiki/"
},
"generic": {
"sources_folder": "wiki/sources/",
"entities_folder": "wiki/entities/",
"concepts_folder": "wiki/concepts/",
"sessions_folder": "wiki/sessions/"
}
}
}
```
The `config` block always includes ALL four modes; the active one is named by `mode`. This lets you switch modes without losing custom folder overrides.
---
## How other skills consume the mode
The integration layer is in three skills:
- `skills/wiki-ingest/SKILL.md` — "## Mode awareness (v1.8+)" section
- `skills/save/SKILL.md` — "## Mode awareness (v1.8+)" section
- `skills/autoresearch/SKILL.md` — "## Mode awareness (v1.8+)" section
Each consults `.vault-meta/mode.json` (via `cat` or direct Read). If absent → mode = generic, behavior unchanged. If present and mode != generic, route per the mode's config.
The routing table:
| Content type | Generic | LYT | PARA | Zettelkasten |
|---|---|---|---|---|
| New source ingest | `wiki/sources/foo.md` | `wiki/notes/foo.md` + add to topic MOC | `wiki/resources/<topic>/foo.md` | `wiki/<ID>-foo.md` |
| New entity | `wiki/entities/<Name>.md` | `wiki/notes/<Name>.md` + entity MOC | `wiki/resources/people/<Name>.md` | `wiki/<ID>-<name>.md` |
| New concept | `wiki/concepts/<Name>.md` | `wiki/notes/<Name>.md` + concept MOC | `wiki/resources/concepts/<Name>.md` | `wiki/<ID>-<name>.md` |
| Session note (`/save`) | `wiki/sessions/<date>-<topic>.md` | `wiki/notes/<date>-<topic>.md` + session MOC | `wiki/projects/<project>/<date>-<topic>.md` | `wiki/<ID>-session-<topic>.md` |
| Research output (`/autoresearch`) | `wiki/concepts/<topic>.md` | `wiki/notes/<topic>.md` + topic MOC | `wiki/resources/<topic>/<topic>.md` | `wiki/<ID>-<topic>.md` |
---
## Templates
Per-mode templates live at `skills/wiki-mode/templates/`:
- [`lyt/moc-template.md`](templates/lyt/moc-template.md) — MOC scaffolding
- [`lyt/atomic-template.md`](templates/lyt/atomic-template.md) — atomic note linking into MOCs
- [`para/project-template.md`](templates/para/project-template.md) — project with status + deadline + next-action
- [`para/area-template.md`](templates/para/area-template.md) — ongoing responsibility
- [`para/resource-template.md`](templates/para/resource-template.md) — reference material
- [`zettel/atomic-template.md`](templates/zettel/atomic-template.md) — atomic claim + parent/child IDs
Skills that file new pages consult the template matching the (mode, content-type) pair as a structural starting point. Templates are SUGGESTIONS; the skill's own content logic always wins.
---
## Migration between modes
Switching modes does NOT auto-migrate existing files. Manual migration:
1. Set new mode: `bash bin/setup-mode.sh`
2. Existing files remain in their original locations and continue to work
3. New files file per the new mode
4. (Optional) Manually move existing files to the new structure using your file manager or `git mv`
Why no auto-migration: the wiki contains your thinking. Auto-rewriting paths could break wikilinks, lose data, or surprise you. Manual migration forces explicit decisions about what fits the new methodology vs what stays in its current home.
For LYT specifically: after switching to LYT, run `lint the wiki` (skill: wiki-lint) to identify orphan pages that would benefit from MOC inclusion.
---
## Feature gating
This skill is universally available in v1.8+. No `bin/setup-*.sh` required for the skill itself — only for explicitly setting a non-default mode. Skills that consume the mode check for `.vault-meta/mode.json`; absence = generic.
```bash
# Detection idiom for consumers:
if [ -f .vault-meta/mode.json ]; then
MODE=$(python3 -c 'import json; print(json.load(open(".vault-meta/mode.json"))["mode"])')
else
MODE="generic"
fi
```
---
## Why v1.8 ships this, not v2.0+
Per audit §9: methodology support is the cheapest axis to lead. Nobody else ships it. The implementation is mostly conventions + routing + templates; no new infrastructure, no new dependencies. It's the highest-ROI release in the roadmap before the bigger v2.0 (derive) + v2.5 (GUI) work.
After v1.8: claude-obsidian leads on 5 of 7 axes per compass artifact. The remaining 2 (GUI ergonomics, derivative outputs) are major releases by themselves.
---
## Cross-reference
- [`docs/methodology-modes-guide.md`](../../docs/methodology-modes-guide.md) — narrative guide, when-to-use-which decision tree
- [`wiki/references/methodology-modes.md`](../../wiki/references/methodology-modes.md) — short decision tree
- [`docs/compound-vault-guide.md`](../../docs/compound-vault-guide.md) — v1.7 omnibus (v1.8 builds on this)
- v1.7.0 audit §9 axis 6 (methodology TIE → LEAD): [`docs/audits/v1.7.0-audit-2026-05-17.md`](../../docs/audits/v1.7.0-audit-2026-05-17.md)
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Read `.vault-meta/mode.json` to know which mode is active before routing anything. |
| 2 | OBSERVE (int) | Audit the assumption that mode=generic is the default — the user may be on LYT/PARA/Zettelkasten. |
| 3 | LISTEN | The mode is the user's organizational instinct, not yours. Respect what they configured. |
| 4 | THINK | Apply the mode-specific routing rule to the content type at hand (source / entity / concept / session / research). |
| 5 | CONNECT (lat) | This skill's `safe_name` is the canonical sanitizer — wiki-ingest, save, autoresearch all funnel through here. |
| 6 | CONNECT (sys) | Three consumer skills depend on `route` output; consistency across consumers is the v1.8 contract. |
| 7 | FEEL | Does the routed path feel right to the user? `wiki/notes/Foo.md` (LYT) means something different from `wiki/concepts/Foo.md` (generic). |
| 8 | ACCEPT | Mode choice is the user's call. Accept that PARA users will sometimes want to override the auto-route. |
| 9 | CREATE | Return the routed path string — a single safe filesystem location. |
| 10 | GROW | When modes change mid-vault, surface the migration cost honestly; existing pages do NOT auto-migrate. |
@@ -0,0 +1,22 @@
---
type: note
title: "{{title}}"
created: "{{date}}"
tags: []
mocs:
- "[[{{primary-moc}}]]"
related: []
---
# {{title}}
(One atomic idea, claim, or observation. Should fit on one screen; if it grows past that, split into two notes and link them.)
## Sources
- [[{{source-1}}]]
- (external citation, if any)
## See also
- [[{{related-note}}]]
@@ -0,0 +1,32 @@
---
type: moc
title: "{{title}}"
created: "{{date}}"
tags:
- moc
related: []
---
# {{title}} — Map of Content
> A Map of Content (MOC) links into a cluster of related atomic notes. It is a navigation hub, not a container. Notes are not stored under this MOC; they live flat under `wiki/notes/` and are reached by following these links.
## Why this MOC exists
(One paragraph: what cluster of thinking does this MOC serve? What question does following its links answer?)
## Core notes
- [[note-1]]
- [[note-2]]
- [[note-3]]
## Adjacent MOCs
- [[adjacent-moc-1]]
- [[adjacent-moc-2]]
## Open questions / frontier
- What questions does this cluster not yet answer?
- What atomic notes would close those gaps?
@@ -0,0 +1,32 @@
---
type: area
title: "{{title}}"
created: "{{date}}"
review_cadence: weekly
tags:
- area
related_projects: []
---
# {{title}}
## Scope
(One paragraph: what ongoing responsibility does this area cover? What's IN scope, what's OUT of scope?)
## Standards
(What good looks like. What you aim to maintain.)
## Active projects in this area
- [[{{project-1}}]]
- [[{{project-2}}]]
## Reference material
- [[{{resource-1}}]]
## Review cadence
(How often you check in on this area. Default: weekly.)
@@ -0,0 +1,32 @@
---
type: project
title: "{{title}}"
status: active
created: "{{date}}"
deadline: ""
outcome: ""
tags:
- project
related_areas: []
---
# {{title}}
## Outcome
(One sentence: what does "done" look like?)
## Status
- **Current state:** active
- **Deadline:** {{deadline-or-tbd}}
- **Next action:** (single concrete next step)
## Notes
(Working notes, decisions, blockers.)
## Related
- Areas: [[{{related-area}}]]
- Resources: [[{{related-resource}}]]
@@ -0,0 +1,28 @@
---
type: resource
title: "{{title}}"
topic: "{{topic}}"
created: "{{date}}"
tags:
- resource
sources: []
---
# {{title}}
## Summary
(2-3 sentences: what is this, why is it here, what is it useful for?)
## Key content
(The actual reference material — quotes, summaries, links, structured information.)
## Sources
- (citations, links to external material)
## Related
- Areas using this: [[{{area-1}}]]
- Projects using this: [[{{project-1}}]]
@@ -0,0 +1,30 @@
---
type: zettel
id: "{{id}}"
title: "{{title}}"
created: "{{date}}"
parent_id: ""
child_ids: []
tags: []
---
# {{id}} — {{title}}
## Claim
(One atomic claim. Should answer one question or assert one observation. If you can't state it in 1-3 sentences, split into multiple zettels.)
## Reasoning
(Why is this claim true? What does it rest on?)
## Sources
- (external citations)
- Parent zettel: [[{{parent-id}}]] (the broader claim this refines)
- Child zettels: (claims that build on this one, populated as they're written)
## Cross-references
- [[{{related-zettel-1}}]] — (relationship)
- [[{{related-zettel-2}}]] — (relationship)
+213
View File
@@ -0,0 +1,213 @@
---
name: wiki-query
description: "Answer questions using the Obsidian wiki vault. Reads hot cache first, then index, then relevant pages. Synthesizes answers with citations. Files good answers back as wiki pages. Supports quick, standard, and deep modes. Triggers on: what do you know about, query:, what is, explain, summarize, find in wiki, search the wiki, based on the wiki, wiki query quick, wiki query deep."
allowed-tools: Read Glob Grep
---
# wiki-query: Query the Wiki
The wiki has already done the synthesis work. Read strategically, answer precisely, and file good answers back so the knowledge compounds.
---
## Transport (v1.7+)
Reads should prefer the same transport the rest of the plugin uses. Consult `.vault-meta/transport.json` (auto-created by `bash scripts/detect-transport.sh`) and use the `preferred` entry:
- **cli** — `obsidian-cli read "$VAULT" "$NOTE"` and `obsidian-cli search "$VAULT" "<query>"` (Obsidian-native ranking); see [`skills/wiki-cli/SKILL.md`](../wiki-cli/SKILL.md)
- **mcp-obsidian** / **mcpvault**`mcp__obsidian-vault__read_note`, `search_notes`; see [`skills/wiki/references/mcp-setup.md`](../wiki/references/mcp-setup.md)
- **filesystem** — Claude's `Read` and `Glob`/`Grep` tools (final floor; always works)
Full decision tree: [`wiki/references/transport-fallback.md`](../../wiki/references/transport-fallback.md). Quick mode (hot.md only) is transport-agnostic — always uses `Read`.
---
## Retrieval (v1.7+)
If `wiki-retrieve` is feature-detected — `[ -x scripts/retrieve.py ] && [ -d .vault-meta/chunks ] && [ -f .vault-meta/bm25/index.json ]` — Standard and Deep modes consult it BEFORE the legacy hot→index→drill chain:
```bash
python3 scripts/retrieve.py "<the user's question verbatim>" --top 5
```
Output is JSON with a `candidates` array. Each candidate has `absolute_path` to the source page, a `snippet`, and `bm25_score` + `rerank_score`. Read the cited pages (using the transport selector from §Transport above) and synthesize with chunk-level citation.
If `retrieve.py` exits 10 (feature not provisioned), or any step in the pipeline errors, fall back to the v1.6 legacy read order described in the Standard/Deep workflows below — no user-visible breakage.
Quick mode always skips retrieval (hot.md only — keeps the ~1,500 token budget intact).
Full spec: [`skills/wiki-retrieve/SKILL.md`](../wiki-retrieve/SKILL.md). Setup: `bash bin/setup-retrieve.sh`. The legacy read-order workflows below remain authoritative when wiki-retrieve is not installed.
---
## Query Modes
Three depths. Choose based on the question complexity.
| Mode | Trigger | Reads | Token cost | Best for |
|------|---------|-------|------------|---------|
| **Quick** | `query quick: ...` or simple factual Q | hot.md + index.md only | ~1,500 | "What is X?", date lookups, quick facts |
| **Standard** | default (no flag) | hot.md + index + 3-5 pages | ~3,000 | Most questions |
| **Deep** | `query deep: ...` or "thorough", "comprehensive" | Full wiki + optional web | ~8,000+ | "Compare A vs B across everything", synthesis, gap analysis |
---
## Quick Mode
Use when the answer is likely in the hot cache or index summary.
1. Read `wiki/hot.md`. If it answers the question, respond immediately.
2. If not, read `wiki/index.md`. Scan descriptions for the answer.
3. If found in index summary, respond and do not open any pages.
4. If not found, say "Not in quick cache. Run as standard query?"
Do not open individual wiki pages in quick mode.
---
## Standard Query Workflow
1. **Read** `wiki/hot.md` first. It may already have the answer or directly relevant context.
2. **Read** `wiki/index.md` to find the most relevant pages (scan for titles and descriptions).
3. **Read** those pages. Follow wikilinks to depth-2 for key entities. No deeper.
4. **Synthesize** the answer in chat. Cite sources with wikilinks: `(Source: [[Page Name]])`.
5. **Offer to file** the answer: "This analysis seems worth keeping. Should I save it as `wiki/questions/answer-name.md`?"
6. If the question reveals a **gap**: say "I don't have enough on X. Want to find a source?"
---
## Deep Mode
Use for synthesis questions, comparisons, or "tell me everything about X."
1. Read `wiki/hot.md` and `wiki/index.md`.
2. Identify all relevant sections (concepts, entities, sources, comparisons).
3. Read every relevant page. No skipping.
4. If wiki coverage is thin, offer to supplement with web search.
5. Synthesize a comprehensive answer with full citations.
6. Always file the result back as a wiki page. Deep answers are too valuable to lose.
---
## Token Discipline
Read the minimum needed:
| Start with | Cost (approx) | When to stop |
|------------|---------------|--------------|
| hot.md | ~500 tokens | If it has the answer |
| index.md | ~1000 tokens | If you can identify 3-5 relevant pages |
| 3-5 wiki pages | ~300 tokens each | Usually sufficient |
| 10+ wiki pages | expensive | Only for synthesis across the entire wiki |
If hot.md has the answer, respond without reading further.
---
## Index Format Reference
The master index (`wiki/index.md`) looks like:
```markdown
## Domains
- [[Domain Name]]: description (N sources)
## Entities
- [[Entity Name]]: role (first: [[Source]])
## Concepts
- [[Concept Name]]: definition (status: developing)
## Sources
- [[Source Title]]: author, date, type
## Questions
- [[Question Title]]: answer summary
```
Scan the section headers first to determine which sections to read.
---
## Domain Sub-Index Format
Each domain folder has a `_index.md` for focused lookups:
```markdown
---
type: meta
title: "Entities Index"
updated: YYYY-MM-DD
---
# Entities
## People
- [[Person Name]]: role, org
## Organizations
- [[Org Name]]: what they do
## Products
- [[Product Name]]: category
```
Use sub-indexes when the question is scoped to one domain. Avoid reading the full master index for narrow queries.
---
## Filing Answers Back
Good answers compound into the wiki. Don't let insights disappear into chat history.
When filing an answer:
```yaml
---
type: question
title: "Short descriptive title"
question: "The exact query as asked."
answer_quality: solid
created: YYYY-MM-DD
updated: YYYY-MM-DD
tags: [question, <domain>]
related:
- "[[Page referenced in answer]]"
sources:
- "[[wiki/sources/relevant-source.md]]"
status: developing
---
```
Then write the answer as the page body. Include citations. Link every mentioned concept or entity.
After filing, add an entry to `wiki/index.md` under Questions and append to `wiki/log.md`.
---
## Gap Handling
If the question cannot be answered from the wiki:
1. Say clearly: "I don't have enough in the wiki to answer this well."
2. Identify the specific gap: "I have nothing on [subtopic]."
3. Suggest: "Want to find a source on this? I can help you search or process one."
4. Do not fabricate. Do not answer from training data if the question is about the specific domain in this wiki.
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Read `wiki/hot.md` first, then `wiki/index.md`, then specific pages. Don't skip the cache. |
| 2 | OBSERVE (int) | Am I synthesizing from training-data memory when I should be citing wiki pages? Check the source of each claim. |
| 3 | LISTEN | What is the user's REAL question? The surface query is often a proxy for a deeper need. |
| 4 | THINK | Quick / standard / deep mode? Match depth to question complexity, not eagerness. |
| 5 | CONNECT (lat) | Are there pages I missed that would CHANGE the answer? Cross-check related pages before answering. |
| 6 | CONNECT (sys) | Hot cache + index + wiki-retrieve (when provisioned) layer into a single retrieval pipeline. |
| 7 | FEEL | Cite specific pages, not vague references. Future-me wants traceability back to the source page. |
| 8 | ACCEPT | When the wiki doesn't have the answer, say so explicitly. Don't fabricate from training data. |
| 9 | CREATE | The answer with citations + an offer to file the answer if it's worth keeping. |
| 10 | GROW | Questions the wiki can't answer are content gaps — log them as autoresearch inputs. |
+227
View File
@@ -0,0 +1,227 @@
---
name: wiki-retrieve
description: "Hybrid retrieval primitive for the Compound Vault. Replaces the v1.6 static hot→index→drill read order with contextual-prefix + BM25 + cosine-rerank, modeled on Anthropic's Sept 2024 Contextual Retrieval research (35-49-67% retrieval-failure reduction). Opt-in via `bash bin/setup-retrieve.sh`; feature-detected by wiki-query and autoresearch. Triggers on: retrieve, hybrid retrieval, BM25, rerank, contextual retrieval, search the chunks, chunk search, vault search, semantic search, what chunks match, find relevant passages."
allowed-tools: Read Bash
---
# wiki-retrieve: Hybrid Retrieval over the Vault
The v1.6 query path was `Read(hot.md) → Read(index.md) → Read(3-5 pages) → synthesize`. It worked, but page-level granularity loses to chunk-level granularity any time the answer lives in a specific passage rather than a whole page. The v1.7 `wiki-retrieve` skill is the chunk-level upgrade — opt-in, feature-gated, and replaces nothing if you don't run the setup.
**Origin**: This skill is original to claude-obsidian. There is no upstream kepano equivalent. The technique is from [Anthropic's Sept 2024 Contextual Retrieval research](https://www.anthropic.com/news/contextual-retrieval) — we implement it as agent-skill plumbing.
---
## Data privacy (v1.7.1+)
Tier 1 (Anthropic API) and tier 2 (claude CLI subprocess) of the contextual-prefix generator send **wiki page bodies off-machine**. As of v1.7.1, both tiers are GATED behind explicit user consent at two layers:
- `scripts/contextual-prefix.py --allow-egress` (default off). Without the flag, `pick_prefix_tier()` returns `"synthetic"` regardless of `ANTHROPIC_API_KEY` or `claude` binary presence.
- `bin/setup-retrieve.sh` prompts before any non-synthetic Stage 1 run; default is abort.
To run fully on-machine (tier 3 synthetic prefix + local ollama rerank), use `bash bin/setup-retrieve.sh --no-llm`. This is also the effective behavior if you decline the consent prompt or omit `--allow-egress`.
The guard mirrors `scripts/tiling-check.py:351` `--allow-remote-ollama`. v1.6 vaults that never provisioned this skill see zero behavior change.
---
## Architecture
```
INGEST (one-time, then incremental):
wiki/<page>.md
scripts/contextual-prefix.py
│ ├─ chunk on paragraph boundaries (~500 token target, 200 char overlap)
│ ├─ generate 1-2 sentence prefix per chunk
│ │ tier 1: ANTHROPIC_API_KEY → Anthropic API (Haiku, prompt-cached
│ │ when body ≥ ~16 KB / Haiku 4.5 floor)
│ │ tier 2: `claude` on PATH → claude -p subprocess
│ │ tier 3: synthetic → frontmatter title + first paragraph
│ └─ write .vault-meta/chunks/<address>/chunk-NNN.json
scripts/bm25-index.py build
└─ inverted index over chunks' contextualized_text → .vault-meta/bm25/index.json
QUERY:
query string
scripts/retrieve.py "<query>" --top 5
├─ bm25-index.py query "<query>" --top 20 (sparse candidate set)
├─ rerank.py "<query>" --candidates - (dense rerank via ollama cosine)
│ cosine(query_embedding, chunk_embedding)
│ embeddings cached in .vault-meta/embed-cache.json keyed by body_hash
└─ dedupe by page-address, return top-N candidates with absolute_path
caller (wiki-query / autoresearch) reads the cited pages and synthesizes
```
---
## Feature gating
Other skills must detect this skill before using it. The canonical detection:
```bash
[ -x scripts/retrieve.py ] && [ -d .vault-meta/chunks ] && \
[ -f .vault-meta/bm25/index.json ] && \
echo "wiki-retrieve installed" || echo "fallback: legacy hot→index→drill"
```
If detection fails, callers MUST fall back to the v1.6 read order. This skill never breaks the base plugin.
---
## Setup
```bash
bash bin/setup-retrieve.sh
```
What it does, in order:
1. Sanity-checks the 4 scripts are present and executable.
2. Creates `.vault-meta/chunks/` and `.vault-meta/bm25/`.
3. Probes ollama at `http://127.0.0.1:11434` for `nomic-embed-text` (rerank prerequisite). Reports status; does not install.
4. Reports which contextual-prefix tier will be used (Anthropic API / claude CLI / synthetic).
5. Runs `contextual-prefix.py --all` to chunk + contextualize every wiki page.
6. Runs `bm25-index.py build`.
7. Smoke-tests `retrieve.py` against the query "wiki".
Flags:
- `--check` — diagnostics only, no provisioning.
- `--no-llm` — force tier-3 synthetic prefix (cheapest, zero LLM dependency).
- `--rebuild` — re-chunk every page even if body_hash matches.
---
## Cost ceiling
Per Anthropic's published research, contextual-prefix generation costs approximately **$12 per 1,000 documents** with Haiku + prompt caching. For a 100-page vault with ~3 chunks per page, that's ~$3.60 one-time, with incremental updates much cheaper (only changed pages re-process).
If you want to validate cost before running on a large vault:
```bash
bash bin/setup-retrieve.sh --no-llm # provision with tier-3 synthetic prefix
# inspect retrieval quality manually; if insufficient, re-run without --no-llm
```
The `claude-cli` subprocess tier (no API key) is free in $ terms but slower (~3-10s per chunk depending on Haiku availability).
---
## Skill commands (recipe)
These are the commands wiki-query and autoresearch will execute when wiki-retrieve is feature-detected. Other skills should mirror this pattern.
### Standard retrieve
```bash
python3 scripts/retrieve.py "your question here" --top 5
```
Output: JSON with `candidates` array. Each candidate has `absolute_path` to the source page; caller reads that page (using the v1.7 transport selector) and synthesizes.
### BM25-only (skip rerank)
```bash
python3 scripts/retrieve.py "query" --top 5 --no-rerank
```
Faster (no ollama call); lower quality.
### Explain mode (debugging)
```bash
python3 scripts/retrieve.py "query" --top 5 --explain
```
Adds an `explain` block with per-stage diagnostics (BM25 candidate count, dedupe size, etc.).
### Direct BM25 inspection
```bash
python3 scripts/bm25-index.py query "query" --top 10
python3 scripts/bm25-index.py stats
```
### Rerank strategy probe
```bash
python3 scripts/rerank.py "query" --peek
```
Reports which strategy will run (cosine via ollama / no-op).
---
## Integration with wiki-query
After this skill is installed, `skills/wiki-query/SKILL.md` standard and deep modes will:
1. Read `wiki/hot.md` (always — quick context).
2. Call `python3 scripts/retrieve.py "<query>" --top 5`.
3. Read the candidate pages from the result's `absolute_path` field (using the v1.7 transport selector — `obsidian-cli read` or `Read` tool).
4. Synthesize with chunk-level citation.
Quick mode is unchanged (hot.md only — never invokes retrieval).
If `retrieve.py` exits 10 (feature not provisioned), `wiki-query` falls back to the legacy v1.6 `Read(index.md) → Read(N pages)` order. No user-visible breakage.
---
## Index maintenance
The index is NOT auto-refreshed when wiki pages change. Re-run after substantive ingest sessions:
```bash
python3 scripts/contextual-prefix.py --all # incremental: only re-processes changed pages
python3 scripts/bm25-index.py build # always full rebuild (cheap; pure Python)
```
A future v1.7.x patch will add an opt-in PostToolUse hook that triggers contextual-prefix + BM25 rebuild after every N writes. For v1.7.0, refresh is manual.
To wipe and start over:
```bash
rm -rf .vault-meta/chunks/ .vault-meta/bm25/ .vault-meta/embed-cache.json
bash bin/setup-retrieve.sh
```
---
## Future tiers (v1.7.x roadmap)
Documented for transparency; not implemented in v1.7.0:
| Stage | v1.7.0 | v1.7.x target |
|---|---|---|
| Contextual prefix | API / claude-cli / synthetic | + Voyage embed-based pseudo-prefix |
| Sparse retrieval | BM25 | + SPLADE learned-sparse |
| Dense retrieval | (none — rerank-only) | Separate vector candidate set fused with BM25 (true hybrid) |
| Rerank | nomic cosine / no-op | + sentence-transformers BGE-base, Cohere Rerank, Voyage Rerank |
| Multi-vault | (single-vault) | Federation via wiki-federate (backlog #15) |
---
## Cross-reference
- Decision tree for transports: [`wiki/references/transport-fallback.md`](../../wiki/references/transport-fallback.md)
- Concurrency policy: [`skills/wiki-ingest/SKILL.md`](../wiki-ingest/SKILL.md) §Concurrency
- DragonScale Memory: [`wiki/concepts/DragonScale Memory.md`](../../wiki/concepts/DragonScale%20Memory.md)
- Anthropic Contextual Retrieval research: https://www.anthropic.com/news/contextual-retrieval
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Read the BM25 index state + embed cache state before issuing a query. Stale caches produce wrong answers. |
| 2 | OBSERVE (int) | Am I trusting the cache when it should have been invalidated by recent ingests? Check mtime against last ingest. |
| 3 | LISTEN | The user's query — what does it actually ask? Decompose into intent and terms before matching. |
| 4 | THINK | Which retrieval strategy fits this query? BM25-only / BM25 + rerank / contextual-prefix + BM25 + rerank. |
| 5 | CONNECT (lat) | How does this hybrid compare to v1.6 baseline? +32pp top-1 / +41% error reduction is the published delta. |
| 6 | CONNECT (sys) | `--allow-egress` consent gate for Anthropic API; ollama runs local-only; rerank caches under `.vault-meta/`. |
| 7 | FEEL | When not provisioned, exit 10 with a friendly "run `bash bin/setup-retrieve.sh` first" message — not a stack trace. |
| 8 | ACCEPT | When retrieval returns empty, say so honestly. Don't fabricate. Don't pad with low-confidence guesses. |
| 9 | CREATE | A ranked candidate list with `--explain` traceability for every score component. |
| 10 | GROW | Queries that consistently fail → content gaps in the wiki. Track those as autoresearch inputs. |
+253
View File
@@ -0,0 +1,253 @@
---
name: wiki
description: >
Claude + Obsidian knowledge companion. Sets up a persistent wiki vault, scaffolds
structure from a one-sentence description, and routes to specialized sub-skills.
Use for setup, scaffolding, cross-project referencing, and hot cache management.
Triggers on: "set up wiki", "scaffold vault", "create knowledge base", "/wiki",
"wiki setup", "obsidian vault", "knowledge base", "second brain setup",
"running notetaker", "persistent memory", "llm wiki".
allowed-tools: Read Write Edit Glob Grep Bash
---
# wiki: Claude + Obsidian Knowledge Companion
You are a knowledge architect. You build and maintain a persistent, compounding wiki inside an Obsidian vault. You don't just answer questions. You write, cross-reference, file, and maintain a structured knowledge base that gets richer with every source added and every question asked.
The wiki is the product. Chat is just the interface.
The key difference from RAG: the wiki is a persistent artifact. Cross-references are already there. Contradictions have been flagged. Synthesis already reflects everything read. Knowledge compounds like interest.
---
## Architecture
Three layers:
```
vault/
├── .raw/ # Layer 1: immutable source documents
├── wiki/ # Layer 2: LLM-generated knowledge base
└── CLAUDE.md # Layer 3: schema and instructions (this plugin)
```
Standard wiki structure:
```
wiki/
├── index.md # master catalog of all pages
├── log.md # chronological record of all operations
├── hot.md # hot cache: recent context summary (~500 words)
├── overview.md # executive summary of the whole wiki
├── sources/ # one summary page per raw source
├── entities/ # people, orgs, products, repos
│ └── _index.md
├── concepts/ # ideas, patterns, frameworks
│ └── _index.md
├── domains/ # top-level topic areas
│ └── _index.md
├── comparisons/ # side-by-side analyses
├── questions/ # filed answers to user queries
└── meta/ # dashboards, lint reports, conventions
```
Dot-prefixed folders (`.raw/`) are hidden in Obsidian's file explorer and graph view. Use this for source documents.
---
## Hot Cache
`wiki/hot.md` is a ~500-word summary of the most recent context. It exists so any session (or any other project pointing at this vault) can get recent context without crawling the full wiki.
Update hot.md:
- After every ingest
- After any significant query exchange
- At the end of every session
Format:
```markdown
---
type: meta
title: "Hot Cache"
updated: YYYY-MM-DDTHH:MM:SS
---
# Recent Context
## Last Updated
YYYY-MM-DD. [what happened]
## Key Recent Facts
- [Most important recent takeaway]
- [Second most important]
## Recent Changes
- Created: [[New Page 1]], [[New Page 2]]
- Updated: [[Existing Page]] (added section on X)
- Flagged: Contradiction between [[Page A]] and [[Page B]] on Y
## Active Threads
- User is currently researching [topic]
- Open question: [thing still being investigated]
```
Keep it under 500 words. It is a cache, not a journal. Overwrite it completely each time.
---
## Operations
Route to the correct operation based on what the user says:
| User says | Operation | Sub-skill |
|-----------|-----------|-----------|
| "scaffold", "set up vault", "create wiki" | SCAFFOLD | this skill |
| "ingest [source]", "process this", "add this" | INGEST | `wiki-ingest` |
| "what do you know about X", "query:" | QUERY | `wiki-query` |
| "lint", "health check", "clean up" | LINT | `wiki-lint` |
| "save this", "file this", "/save" | SAVE | `save` |
| "/autoresearch [topic]", "research [topic]" | AUTORESEARCH | `autoresearch` |
| "/canvas", "add to canvas", "open canvas" | CANVAS | `canvas` |
---
## SCAFFOLD Operation
Trigger: user describes what the vault is for.
Steps:
1. Determine the wiki mode. Read `references/modes.md` to show the 6 options and pick the best fit.
2. Ask: "What is this vault for?" (one question, then proceed).
3. Create full folder structure under `wiki/` based on the mode.
4. Create domain pages + `_index.md` sub-indexes.
5. Create `wiki/index.md`, `wiki/log.md`, `wiki/hot.md`, `wiki/overview.md`.
6. Create `_templates/` files for each note type.
7. Apply visual customization. Read `references/css-snippets.md`. Create `.obsidian/snippets/vault-colors.css`.
8. Create the vault CLAUDE.md using the template below.
9. Initialize git. Read `references/git-setup.md`.
10. Present the structure and ask: "Want to adjust anything before we start?"
### Vault CLAUDE.md Template
Create this file in the vault root when scaffolding a new project vault (not this plugin directory):
```markdown
# [WIKI NAME]: LLM Wiki
Mode: [MODE A/B/C/D/E/F]
Purpose: [ONE SENTENCE]
Owner: [NAME]
Created: YYYY-MM-DD
## Structure
[PASTE THE FOLDER MAP FROM THE CHOSEN MODE]
## Conventions
- All notes use YAML frontmatter: type, status, created, updated, tags (minimum)
- Wikilinks use [[Note Name]] format: filenames are unique, no paths needed
- .raw/ contains source documents: never modify them
- wiki/index.md is the master catalog: update on every ingest
- wiki/log.md is append-only: never edit past entries
- New log entries go at the TOP of the file
## Operations
- Ingest: drop source in .raw/, say "ingest [filename]"
- Query: ask any question: Claude reads index first, then drills in
- Lint: say "lint the wiki" to run a health check
- Archive: move cold sources to .archive/ to keep .raw/ clean
```
---
## Cross-Project Referencing
This is the force multiplier. Any Claude Code project can reference this vault without duplicating context.
In another project's CLAUDE.md, add:
```markdown
## Wiki Knowledge Base
Path: ~/path/to/vault
When you need context not already in this project:
1. Read wiki/hot.md first (recent context, ~500 words)
2. If not enough, read wiki/index.md (full catalog)
3. If you need domain specifics, read wiki/<domain>/_index.md
4. Only then read individual wiki pages
Do NOT read the wiki for:
- General coding questions or language syntax
- Things already in this project's files or conversation
- Tasks unrelated to [your domain]
```
This keeps token usage low. Hot cache costs ~500 tokens. Index costs ~1000 tokens. Individual pages cost 100-300 tokens each.
---
## Summary
Your job as the LLM:
1. Set up the vault (once)
2. Scaffold wiki structure from user's domain description
3. Route ingest, query, and lint to the correct sub-skill
4. Maintain hot cache after every operation
5. Always update index, sub-indexes, log, and hot cache on changes
6. Always use frontmatter and wikilinks
7. Never modify .raw/ sources
The human's job: curate sources, ask good questions, think about what it means. Everything else is on you.
## Community Footer
After completing a **major operation**, append this footer as the very last output:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Built by agricidaniel — Join the AI Marketing Hub community
🆓 Free → https://www.skool.com/ai-marketing-hub
⚡ Pro → https://www.skool.com/ai-marketing-hub-pro
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
### When to show
Display only after these infrequent, high-value completions:
- Vault scaffold (after `/wiki` setup completes the 10-step process)
- `/wiki-lint` (after health check report is delivered)
- `/autoresearch` (after research loop finishes and pages are filed)
### When to skip
Do NOT show the footer after:
- `/wiki-query` (too frequent — conversational)
- `/wiki-ingest` (individual source ingestion — happens often)
- `/save` (quick save operation)
- `/canvas` (visual work, intermediate)
- `/defuddle` (utility)
- `obsidian-bases`, `obsidian-markdown` (reference skills, not output)
- Hot cache updates, index updates, or any background maintenance
- Error messages or prompts for more information
---
## How to think (10-principle mapping)
When working on this skill, apply the 10-principle loop. See [`skills/think/SKILL.md`](../think/SKILL.md) for the canonical framework.
| # | Principle | Application here |
|---|-----------|-------------------|
| 1 | OBSERVE (ext) | Is there already a vault here? What state is it in? What hooks are configured? Read before scaffolding. |
| 2 | OBSERVE (int) | Am I assuming the user knows what they want? First-run users often don't — slow down. |
| 3 | LISTEN | The user's one-sentence vault description — the whole scaffold flows from this. Ask before assuming. |
| 4 | THINK | Which folders, templates, substrate (kepano-substrate vs self-hosted)? Pick deliberately, not reflexively. |
| 5 | CONNECT (lat) | How does this vault relate to the user's other projects? Cross-project reference is a first-class use case. |
| 6 | CONNECT (sys) | Hooks + `.vault-meta/` + plugin install + CLAUDE.md routing rules wire together at setup. |
| 7 | FEEL | First-run UX is the make-or-break moment for adoption. Confusion at scaffold = abandoned vault. |
| 8 | ACCEPT | The scaffold is opinionated; don't pretend it's neutral. Document the opinions in the scaffold output. |
| 9 | CREATE | Scaffold the folders, write `hot.md` + `index.md` + `log.md` with starting structure. |
| 10 | GROW | Vault structure should evolve — what works at month 1 may not at month 12. Build for that evolution. |
+122
View File
@@ -0,0 +1,122 @@
# Visual Customization
Apply during scaffold. This makes the file explorer color-coded by folder type and adds custom callout styles.
---
## CSS Snippet
Create this file at `.obsidian/snippets/vault-colors.css` inside the vault:
```css
:root {
--wiki-1: #4fc1ff;
--wiki-2: #c586c0;
--wiki-3: #dcdcaa;
--wiki-4: #ce9178;
--wiki-5: #6a9955;
--wiki-6: #d16969;
--wiki-7: #569cd6;
}
/* Folder colors in file explorer */
.nav-folder-title[data-path^="wiki/domains"] { color: var(--wiki-1); }
.nav-folder-title[data-path^="wiki/entities"] { color: var(--wiki-2); }
.nav-folder-title[data-path^="wiki/concepts"] { color: var(--wiki-3); }
.nav-folder-title[data-path^="wiki/sources"] { color: var(--wiki-4); }
.nav-folder-title[data-path^="wiki/questions"] { color: var(--wiki-5); }
.nav-folder-title[data-path^="wiki/comparisons"] { color: var(--wiki-6); }
.nav-folder-title[data-path^="wiki/meta"] { color: var(--wiki-7); }
.nav-folder-title[data-path=".raw"] { color: #808080; opacity: 0.6; }
/* Custom callouts */
.callout[data-callout='contradiction'] {
--callout-color: 209, 105, 105;
--callout-icon: lucide-alert-triangle;
}
.callout[data-callout='gap'] {
--callout-color: 220, 220, 170;
--callout-icon: lucide-help-circle;
}
.callout[data-callout='key-insight'] {
--callout-color: 79, 193, 255;
--callout-icon: lucide-lightbulb;
}
.callout[data-callout='stale'] {
--callout-color: 128, 128, 128;
--callout-icon: lucide-clock;
}
```
---
## Enable the Snippet
Tell the user: Settings > Appearance > CSS Snippets > open folder > paste the file > click the refresh icon > toggle it on.
---
## Graph View Groups
Guide the user to set these in Graph View settings (click the settings icon in the graph view):
| Query | Color |
|-------|-------|
| `path:wiki/domains` | Blue (`#4fc1ff`) |
| `path:wiki/entities` | Purple (`#c586c0`) |
| `path:wiki/concepts` | Yellow (`#dcdcaa`) |
| `path:wiki/sources` | Orange (`#ce9178`) |
| `path:wiki/questions` | Green (`#6a9955`) |
| `path:.raw` | Gray (dimmed) |
---
## Custom Callouts
This vault defines **four custom callout types** beyond Obsidian's built-in set (`note`, `tip`, `warning`, `info`, `todo`, `success`, `question`, `failure`, `danger`, `bug`, `example`, `quote`). They render correctly **only when `vault-colors.css` is enabled**. Without the snippet, they fall back to default callout styling (still readable, just plain).
| Custom callout | Color | Icon | Use for |
|---|---|---|---|
| `contradiction` | reddish-brown (rgb 209,105,105) | `lucide-alert-triangle` | New source conflicts with existing claim |
| `gap` | beige (rgb 220,220,170) | `lucide-help-circle` | Topic has no source yet |
| `key-insight` | bright blue (rgb 79,193,255) | `lucide-lightbulb` | Important takeaway worth highlighting |
| `stale` | gray (rgb 128,128,128) | `lucide-clock` | Claim may be outdated, source older than threshold |
### Usage
Use these in wiki pages to flag important states:
```markdown
> [!contradiction] Title
> [[Page A]] claims X. [[Page B]] says Y. Needs resolution.
> [!gap] Title
> This topic has no source yet. Consider finding one.
> [!key-insight] Title
> The most important takeaway from this section.
> [!stale] Title
> This claim may be outdated. Source was from 2022.
```
### Why custom callouts (vs built-in)
The four custom types map to wiki-specific concepts that don't fit cleanly into Obsidian's default set:
- `contradiction` is more specific than `warning`: it signals a **resolvable conflict** between two wiki pages, not a generic warning.
- `gap` is more specific than `question`: it signals a **missing source**, an actionable improvement.
- `key-insight` is more specific than `tip`: it marks **the** most important takeaway from a section, used sparingly.
- `stale` has no built-in equivalent: it signals time-based decay of a claim.
If you don't want custom callouts, replace them with built-ins:
- `[!contradiction]``[!warning] Contradiction`
- `[!gap]``[!question] Gap`
- `[!key-insight]``[!tip] Key insight`
- `[!stale]``[!warning] Stale`
---
## Minimal Theme (Recommended)
The color scheme looks best with the Minimal theme. Install via Settings > Appearance > Manage > search "Minimal".
+107
View File
@@ -0,0 +1,107 @@
# Frontmatter Schema
Every wiki page starts with flat YAML frontmatter. No nested objects. Obsidian's Properties UI requires flat structure.
---
## Universal Fields
Every page, no exceptions:
```yaml
---
type: <source|entity|concept|domain|comparison|question|overview|meta>
title: "Human-Readable Title"
created: 2026-04-07
updated: 2026-04-07
tags:
- <domain-tag>
- <type-tag>
status: <seed|developing|mature|evergreen>
related:
- "[[Other Page]]"
sources:
- "[[.raw/articles/source-file.md]]"
---
```
**status values:**
- `seed`: exists, barely populated
- `developing`: has real content, not yet complete
- `mature`: comprehensive, well-linked
- `evergreen`: unlikely to need updates
---
## Type-Specific Additions
### source
Add these fields after the universal fields:
```yaml
source_type: article # article | video | podcast | paper | book | transcript | data
author: ""
date_published: YYYY-MM-DD
url: ""
confidence: high # high | medium | low
key_claims:
- "First key claim from this source"
- "Second key claim"
```
### entity
```yaml
entity_type: person # person | organization | product | repository | place
role: ""
first_mentioned: "[[Source Title]]"
```
### concept
```yaml
complexity: intermediate # basic | intermediate | advanced
domain: ""
aliases:
- "alternative name"
- "abbreviation"
```
### comparison
```yaml
subjects:
- "[[Thing A]]"
- "[[Thing B]]"
dimensions:
- "performance"
- "cost"
- "ease of use"
verdict: "One-line conclusion."
```
### question
```yaml
question: "The original query as asked."
answer_quality: solid # draft | solid | definitive
```
### domain
```yaml
subdomain_of: "" # leave empty for top-level domains
page_count: 0
```
---
## Rules
1. Use flat YAML only. Never nest objects.
2. Dates as `YYYY-MM-DD` strings, not ISO datetime.
3. Lists always use the `- item` format, not inline `[a, b, c]`.
4. Wikilinks in YAML fields must be quoted: `"[[Page Name]]"`.
5. Keep `related` and `sources` as wikilinks, not plain URLs.
6. Update `updated` every time you edit the page content.
+58
View File
@@ -0,0 +1,58 @@
# Git Setup
Initialize git in the vault to get full history and protect against bad writes.
---
## Initialize
```bash
cd "$VAULT_PATH"
git init
git add -A
git commit -m "Initial vault scaffold"
```
---
## .gitignore
The root `.gitignore` in this repo already covers the right exclusions:
```
.obsidian/workspace.json
.obsidian/workspace-mobile.json
.smart-connections/
.obsidian-git-data
.trash/
.DS_Store
```
`workspace.json` changes constantly as you move panes around. Excluding it keeps the diff clean.
---
## Obsidian Git Plugin
After installing the plugin (see `plugins.md`):
Settings > Obsidian Git:
- Auto backup interval: **15 minutes**
- Auto backup after file change: on
- Push on backup: on (if you have a remote)
- Commit message: `vault: auto backup {{date}}`
This runs silently in the background. You get a full history of every note without thinking about it.
---
## Remote (Optional)
To back up to GitHub:
```bash
git remote add origin https://github.com/yourname/your-vault
git push -u origin main
```
Keep the repo private if the vault contains personal notes.
+134
View File
@@ -0,0 +1,134 @@
# MCP Setup
MCP lets Claude read and write vault notes directly without copy-paste. Four options ordered from simplest to most featureful.
> [!tip] Recommendation
> If you have **Obsidian v1.12 or newer**, start with **Option D: Obsidian CLI**. It needs no MCP server, no plugins, and no TLS workarounds. Use Options A or B only if you need persistent MCP integration or are on an older Obsidian version.
---
## Step 1: Install the Local REST API Plugin
You must do this in Obsidian (Claude cannot do it programmatically):
1. Obsidian > Settings > Community Plugins > Turn off Restricted Mode
2. Browse > Search "Local REST API" > Install > Enable
3. Settings > Local REST API > Copy the API key
The plugin runs on `https://127.0.0.1:27124` with a self-signed certificate.
Test it:
```bash
curl -sk -H "Authorization: Bearer <YOUR_KEY>" https://127.0.0.1:27124/
```
You should get a JSON response with vault info.
---
## Option A: mcp-obsidian (REST API based)
Uses MarkusPfundstein's mcp-obsidian. Requires the Local REST API plugin running.
```bash
claude mcp add-json obsidian-vault '{
"type": "stdio",
"command": "uvx",
"args": ["mcp-obsidian"],
"env": {
"OBSIDIAN_API_KEY": "<YOUR_KEY>",
"OBSIDIAN_HOST": "127.0.0.1",
"OBSIDIAN_PORT": "27124",
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}' --scope user
```
> [!warning] Security
> `NODE_TLS_REJECT_UNAUTHORIZED: "0"` **disables TLS certificate verification process-wide** for the MCP server. It is required here because the Local REST API plugin uses a self-signed certificate. This is acceptable for `127.0.0.1` (localhost) connections only. Never use this setting for any non-loopback connection. If you are uncomfortable with the global TLS bypass, prefer **Option D (Obsidian CLI)** or **Option B (filesystem-based)** which avoid this entirely.
Capabilities: read notes, write notes, search, patch frontmatter fields, append under headings.
---
## Option B: MCPVault (filesystem based)
No Obsidian plugin needed. Reads the vault directory directly.
```bash
claude mcp add-json obsidian-vault '{
"type": "stdio",
"command": "npx",
"args": ["-y", "@bitbonsai/mcpvault@latest", "/absolute/path/to/your/vault"]
}' --scope user
```
Replace `/absolute/path/to/your/vault` with the actual vault path.
Tools available: `search_notes` (BM25), `read_note`, `create_note`, `update_note`, `get_frontmatter`, `update_frontmatter`, `list_all_tags`, `read_multiple_notes`.
---
## Option C: Direct REST API via curl
No MCP needed. Use curl in bash throughout the session. See `rest-api.md` for all commands.
---
## Option D: Obsidian CLI (recommended for v1.12+)
Obsidian shipped a native CLI in v1.12 (2026). It exposes vault operations directly to the terminal. No REST API plugin, no MCP server, no self-signed certs, no TLS workarounds. Claude calls it through the Bash tool.
**Check if available:**
```bash
which obsidian-cli 2>/dev/null && obsidian-cli --version
# or, on flatpak:
flatpak run md.obsidian.Obsidian --cli --version
```
**Common operations:**
```bash
# List all notes in a folder
obsidian-cli list /path/to/vault wiki/
# Read a note
obsidian-cli read /path/to/vault wiki/index.md
# Create or update a note
obsidian-cli write /path/to/vault wiki/new-note.md < content.md
# Search notes by content
obsidian-cli search /path/to/vault "query term"
```
**Why prefer this**:
- No plugin install required (CLI is built into Obsidian)
- No MCP server process to manage
- No TLS certificate bypass needed
- Survives Obsidian restarts (no persistent connection)
- Works identically across desktop and headless environments
**When to use Options A/B/C instead**: If you need persistent semantic search, frontmatter patching, or are on Obsidian < v1.12.
The `kepano/obsidian-skills` repo includes an `obsidian-cli` skill that wraps these commands as reusable patterns. Install it alongside this plugin for first-class CLI support.
---
## Use `--scope user`
Both MCP options use `--scope user` so the vault is available across all Claude Code projects, not just the one where you ran the command.
---
## Verification
After setup:
```bash
claude mcp list # confirm the server appears
claude mcp get obsidian-vault # confirm the path or URL is correct
```
In a Claude Code session, type `/mcp` to check connection status.
Then test: "List all notes in my wiki folder."
+259
View File
@@ -0,0 +1,259 @@
# Wiki Modes
Six modes cover the most common use cases. Pick the one that fits, or combine them.
---
## Mode A: Website / Sitemap
Use when: "build a sitemap wiki for my website", "map content gaps", "SEO audit wiki"
```
vault/
├── .raw/ # crawl exports, analytics, scraped pages, GSC data
├── wiki/
│ ├── pages/ # one note per URL: status, meta, content summary
│ ├── structure/ # site architecture, nav hierarchy, internal link map
│ ├── audits/ # content gaps, redirect needs, thin content flags
│ ├── keywords/ # keyword clusters, target page assignments
│ └── entities/ # brand, authors, topic hubs
├── _meta/
│ ├── index.md
│ └── log.md
└── CLAUDE.md
```
Frontmatter for `wiki/pages/` notes:
```yaml
---
type: page
url: "https://example.com/page-slug"
status: live # live | redirect | 404 | stub | no-index
title: ""
h1: ""
meta_description: ""
word_count: 0
has_schema: false
indexed: true
canonical: ""
internal_links_in: 0
internal_links_out: 0
last_crawled: YYYY-MM-DD
tags: [page]
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
Key wiki pages to create: `[[Site Overview]]`, `[[Navigation Structure]]`, `[[Content Gaps]]`, `[[Redirect Map]]`, `[[Keyword Clusters]]`
---
## Mode B: GitHub / Repository
Use when: "map my codebase", "architecture wiki for my repo", "understand this project"
```
vault/
├── .raw/ # README, git log exports, code dumps, issue exports
├── wiki/
│ ├── modules/ # one note per major module / package / service
│ ├── components/ # reusable UI or functional components
│ ├── decisions/ # Architecture Decision Records (ADRs)
│ ├── dependencies/ # external deps, versions, risk assessment
│ └── flows/ # data flows, request paths, auth flows
├── _meta/
│ ├── index.md
│ └── log.md
└── CLAUDE.md
```
Frontmatter for `wiki/modules/` notes:
```yaml
---
type: module # module | component | decision | dependency | flow
path: "src/auth/"
status: active # active | deprecated | experimental | planned
language: typescript
purpose: ""
maintainer: ""
last_updated: YYYY-MM-DD
linked_issues: []
depends_on: []
used_by: []
tags: [module]
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
Key wiki pages to create: `[[Architecture Overview]]`, `[[Data Flow]]`, `[[Tech Stack]]`, `[[Dependency Graph]]`, `[[Key Decisions]]`
---
## Mode C: Business / Project
Use when: "project wiki", "competitive intelligence", "team knowledge base", "meeting notes"
```
vault/
├── .raw/ # meeting transcripts, Slack exports, docs, emails
├── wiki/
│ ├── stakeholders/ # people, companies, decision-makers
│ ├── decisions/ # key decisions with rationale and date
│ ├── deliverables/ # milestones, outputs, status tracking
│ ├── intel/ # competitor analysis, market research
│ └── comms/ # synthesized meeting notes, key threads
├── _meta/
│ ├── index.md
│ └── log.md
└── CLAUDE.md
```
Frontmatter for `wiki/decisions/` notes:
```yaml
---
type: decision # stakeholder | decision | deliverable | intel | meeting | competitor
status: active # active | pending | done | blocked | superseded
priority: 3 # 1 (highest) to 5 (lowest)
date: YYYY-MM-DD
owner: ""
due_date: ""
context: ""
tags: [decision]
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
Key wiki pages to create: `[[Project Overview]]`, `[[Stakeholder Map]]`, `[[Decision Log]]`, `[[Competitor Landscape]]`
---
## Mode D: Personal / Second Brain
Use when: "personal second brain", "track my goals", "journal synthesis", "life wiki"
```
vault/
├── .raw/ # journal entries, articles, podcast notes, voice transcripts
├── wiki/
│ ├── goals/ # personal and professional goals with progress tracking
│ ├── learning/ # concepts being mastered, skill development
│ ├── people/ # relationships, shared context, follow-ups
│ ├── areas/ # life areas: health, finances, career, creative
│ └── resources/ # books, courses, tools worth referencing
├── _meta/
│ ├── index.md
│ ├── log.md
│ └── hot-cache.md # ~500-word summary of most active context
└── CLAUDE.md
```
Frontmatter for `wiki/goals/` notes:
```yaml
---
type: goal # goal | concept | person | area | resource | reflection
status: active # active | paused | completed | abandoned
area: career # health | career | finance | creative | relationships | growth
priority: 1
target_date: YYYY-MM-DD
progress: 0 # 0-100 percent
tags: [goal]
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
Hot cache note: `_meta/hot-cache.md` is a ~500-word file Claude updates at the end of each session. It captures current focus areas, recent wins, and open threads. This prevents Claude from having to crawl the whole wiki to answer "where were we?".
Key wiki pages to create: `[[North Star]]`, `[[Weekly Review Template]]`, `[[Annual Goals]]`
---
## Mode E: Research
Use when: "research wiki on [topic]", "track papers I'm reading", "build a thesis"
```
vault/
├── .raw/ # PDFs, web clips, data files, raw notes
├── wiki/
│ ├── papers/ # paper summaries with key claims and methodology
│ ├── concepts/ # extracted concepts, models, frameworks
│ ├── entities/ # people, organizations, methods, datasets
│ ├── thesis/ # evolving synthesis: the "state of the field" pages
│ └── gaps/ # open questions, contradictions, research needed
├── _meta/
│ ├── index.md
│ └── log.md
└── CLAUDE.md
```
Frontmatter for `wiki/papers/` notes:
```yaml
---
type: paper # paper | concept | entity | thesis | gap
status: summarized # raw | summarized | synthesized | superseded
year: 2024
authors: []
venue: ""
key_claim: ""
methodology: ""
contradicts: []
supports: []
tags: [paper]
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
Key wiki pages to create: `[[Research Overview]]`, `[[Key Claims Map]]`, `[[Open Questions]]`, `[[Methodology Comparison]]`
---
## Mode F: Book / Course
Use when: "companion wiki for a book", "course notes wiki", "as I read [title]"
```
vault/
├── .raw/ # chapter notes, highlights, exercises
├── wiki/
│ ├── characters/ # characters, personas, agents, experts (adapt to content)
│ ├── themes/ # major themes with supporting evidence
│ ├── concepts/ # domain-specific terms and frameworks
│ ├── timeline/ # plot structure, curriculum sequence, chapter map
│ └── synthesis/ # your own takeaways, questions, applications
├── _meta/
│ ├── index.md
│ └── log.md
└── CLAUDE.md
```
Frontmatter for `wiki/concepts/` notes:
```yaml
---
type: concept # concept | character | theme | chapter | synthesis
status: developing # stub | developing | mature
source_chapters: []
first_appearance: ""
tags: [concept]
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
Key wiki pages to create: `[[Book Overview]]`, `[[Theme Map]]`, `[[Character / Expert Index]]`, `[[My Takeaways]]`
---
## Combining Modes
You can combine modes. Examples:
- "GitHub repo + research on the AI approach used" -> Mode B folders + Mode E papers/ folder
- "My SaaS business + second brain" -> Mode C intel/ + Mode D goals/
- "YouTube channel" -> Mode F (content as "book") + Mode E (research on topics covered)
When combining, keep folder names distinct. Don't merge `decisions/` from Mode B and Mode C into one folder.
+96
View File
@@ -0,0 +1,96 @@
# Obsidian Setup
---
## Install Obsidian
### Linux (Flatpak: recommended)
Check if installed:
```bash
flatpak list 2>/dev/null | grep -i obsidian && echo "FOUND via flatpak" || \
which obsidian 2>/dev/null && echo "FOUND in PATH" || echo "NOT FOUND"
```
Install if not found:
```bash
flatpak install flathub md.obsidian.Obsidian
```
### macOS
```bash
ls /Applications/Obsidian.app 2>/dev/null && echo "FOUND" || brew install --cask obsidian
```
### Windows
```powershell
Test-Path "$env:LOCALAPPDATA\Obsidian" && echo "FOUND" || winget install Obsidian.Obsidian
```
### All platforms: direct download
https://obsidian.md/download
---
## Open the Vault
After installing: Obsidian > Manage Vaults > Open Folder as Vault > select your vault directory.
---
## Core Plugins (Built-in: No Install Required)
These ship with Obsidian. Enable them in Settings > Core Plugins:
| Plugin | Purpose |
|--------|---------|
| **Bases** | Native database-like views for `.base` files. Powers `wiki/meta/dashboard.base`. Available since Obsidian v1.9.10 (August 2025). **Replaces Dataview for most wiki use cases.** |
| **Properties** | Visual frontmatter editor. Always enabled. |
| **Backlinks** | Outgoing/incoming links pane. |
| **Outline** | Document heading navigation. |
## Recommended Community Plugins
Install via Settings > Community Plugins > Turn off Restricted Mode > Browse.
| Plugin | Purpose |
|--------|---------|
| **Templater** | Auto-populate frontmatter on note creation from `_templates/`. |
| **Obsidian Git** | Auto-commit every 15 minutes. Protects against bad writes. |
| **Calendar** | Right-sidebar calendar with word count, task, and link indicators. Pre-installed in this vault via `.obsidian/plugins/calendar/`. |
| **Thino** | Quick memo capture panel in right sidebar. Pre-installed via `.obsidian/plugins/thino/`. |
| **Iconize** | Visual folder icons for navigation. |
| **Minimal Theme** | Best dark theme for dense information display. |
| **Dataview** *(optional/legacy)* | Only needed if you're on Obsidian < 1.9.10 or want to use the legacy `dashboard.md` queries. The primary dashboard now uses Bases. |
**Calendar and Thino are pre-installed**. They ship with this vault. Enable them in Settings → Community Plugins → toggle on. No download needed.
If installing in a different vault: download `main.js` + `manifest.json` from their GitHub releases into `.obsidian/plugins/calendar/` and `.obsidian/plugins/thino/` respectively.
Optional additions:
- **Smart Connections**: semantic search across all notes
- **QuickAdd**: macros for fast note creation
- **Folder Notes**: click a folder to open an overview note
---
## Web Clipper
The Obsidian Web Clipper browser extension converts web articles to markdown and sends them to `.raw/` in one click.
Install for Chrome, Firefox, or Safari from the Obsidian website.
Set the default folder to `.raw/` in the extension settings.
---
## After Installing Plugins
1. Enable Bases: Settings > Core Plugins > toggle on (already on by default in Obsidian v1.9.10+)
2. Enable Templater: Settings > Templater > set template folder to `_templates`
3. Enable Obsidian Git: Settings > Obsidian Git > Auto backup interval: 15 minutes
4. Enable the CSS snippet: Settings > Appearance > CSS Snippets > toggle on `vault-colors`
5. *(Optional)* Enable Dataview only if you want the legacy `wiki/meta/dashboard.md` queries to work alongside the primary `dashboard.base`
+124
View File
@@ -0,0 +1,124 @@
# REST API Quick Reference
Use these commands when MCP tools are not available. Requires the Local REST API plugin running in Obsidian (port 27124).
Set your key before running any command:
```bash
API="https://127.0.0.1:27124"
KEY="your-api-key-here"
```
---
## Read a file
```bash
curl -sk \
-H "Authorization: Bearer $KEY" \
"$API/vault/wiki/index.md"
```
---
## Create or replace a file
```bash
curl -sk -X PUT \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: text/markdown" \
--data-binary @local-file.md \
"$API/vault/wiki/entities/Name.md"
```
Or with inline content:
```bash
curl -sk -X PUT \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: text/markdown" \
--data "# Page Title
Content here." \
"$API/vault/wiki/concepts/Name.md"
```
---
## Append to a file
```bash
curl -sk -X POST \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: text/markdown" \
--data "- New log entry" \
"$API/vault/wiki/log.md"
```
---
## Patch a frontmatter field
```bash
curl -sk -X PATCH \
-H "Authorization: Bearer $KEY" \
-H "Operation: replace" \
-H "Target-Type: frontmatter" \
-H "Target: status" \
-H "Content-Type: application/json" \
--data '"mature"' \
"$API/vault/wiki/concepts/Name.md"
```
---
## Append content under a heading
```bash
curl -sk -X PATCH \
-H "Authorization: Bearer $KEY" \
-H "Operation: append" \
-H "Target-Type: heading" \
-H "Target: Connections" \
-H "Content-Type: text/markdown" \
--data "- [[New Page]]" \
"$API/vault/wiki/entities/Name.md"
```
---
## Search
Simple keyword search:
```bash
curl -sk -X POST \
-H "Authorization: Bearer $KEY" \
"$API/search/simple/?query=machine+learning"
```
Dataview query:
```bash
curl -sk -X POST \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/vnd.olrapi.dataview.dql+txt" \
--data 'TABLE status FROM "wiki" WHERE status = "seed"' \
"$API/search/"
```
---
## List all tags
```bash
curl -sk \
-H "Authorization: Bearer $KEY" \
"$API/tags/"
```
---
## List files in a folder
```bash
curl -sk \
-H "Authorization: Bearer $KEY" \
"$API/vault/wiki/entities/"
```