Skip to content

Commit 0234628

Browse files
committed
feat(skill): wiki-import — reconstruct vault pages from graph.json export
Pairs with wiki-export: takes a graph.json snapshot and rebuilds page stubs in the target vault with correct frontmatter, wikilinks, and typed relationships. Supports skip/overwrite/merge conflict modes and updates all vault metadata files.
1 parent 46e699b commit 0234628

2 files changed

Lines changed: 215 additions & 0 deletions

File tree

.skills/wiki-import/SKILL.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
---
2+
name: wiki-import
3+
description: >
4+
Import a wiki knowledge graph from a graph.json export file into the current vault.
5+
Use this skill when the user says "import wiki", "import from export", "load graph.json",
6+
"import vault", "/wiki-import", or wants to transfer pages from one vault to another
7+
using the output of wiki-export.
8+
---
9+
10+
# Wiki Import — Reconstruct Pages from graph.json
11+
12+
You are importing a vault's knowledge graph from a `graph.json` export (produced by `wiki-export`) into the current vault. This reconstructs page stubs with correct frontmatter, wikilinks, and typed relationships, then updates all vault metadata.
13+
14+
## Before You Start
15+
16+
1. **Resolve config** — follow the Config Resolution Protocol in `llm-wiki/SKILL.md` (walk up CWD for `.env``~/.obsidian-wiki/config` → prompt setup). This gives `OBSIDIAN_VAULT_PATH`.
17+
2. Read `$OBSIDIAN_VAULT_PATH/AGENTS.md` if it exists — apply any owner-specific conventions.
18+
19+
## Step 1: Locate and Validate Source
20+
21+
**Find the import source:**
22+
- If the user provided a path argument, use it directly.
23+
- Otherwise auto-detect `./wiki-export/graph.json` in the current directory.
24+
- If neither exists, ask the user for the path.
25+
26+
**Validate the file:**
27+
- Must be valid JSON
28+
- Must have top-level keys: `nodes` (array), `links` (array), `graph` (object)
29+
- Must have at least 1 node
30+
31+
If validation fails, report what's wrong and stop.
32+
33+
**Show a preview before importing:**
34+
```
35+
Import preview
36+
Source: <path> (exported at <graph.exported_at>)
37+
Nodes: N total (concepts: A, entities: B, skills: C, references: D, ...)
38+
Links: M edges (X typed, Y untyped)
39+
Target: $OBSIDIAN_VAULT_PATH
40+
```
41+
42+
## Step 2: Determine Conflict Resolution Mode
43+
44+
Read the user's phrasing to determine mode. Default is `skip`.
45+
46+
| Mode | Trigger phrases | Behaviour |
47+
|---|---|---|
48+
| `skip` | (default, no special phrasing) | Leave existing pages untouched |
49+
| `overwrite` | "overwrite", "replace existing", "force import" | Replace existing pages with reconstructed stubs |
50+
| `merge` | "merge", "update existing", "fill in missing" | Preserve existing body; update frontmatter tags/summary/relationships and add missing wikilinks to Related section |
51+
52+
## Step 3: Build Internal Maps
53+
54+
Before writing anything, build two maps from the `links` array:
55+
56+
**Adjacency map** — for each node id, collect all neighbour ids (edges in either direction):
57+
```
58+
adjacency["concepts/transformers"] = ["entities/vaswani", "concepts/lstm", ...]
59+
```
60+
61+
**Typed edge map** — for each node id, collect outgoing typed edges only (`typed: true`):
62+
```
63+
typed_edges["concepts/transformers"] = [
64+
{target: "concepts/lstm", relation: "contradicts"},
65+
...
66+
]
67+
```
68+
69+
## Step 4: Reconstruct Pages
70+
71+
Record counts: `created = 0`, `skipped = 0`, `merged = 0`.
72+
73+
For each node in `nodes`:
74+
75+
1. Compute `page_path = $VAULT/<node.id>.md`
76+
2. Ensure the parent directory exists (e.g. `$VAULT/concepts/`)
77+
3. Check if the file already exists:
78+
- **skip mode + exists** → increment `skipped`, continue to next node
79+
- **overwrite mode + exists** → proceed (will overwrite)
80+
- **merge mode + exists** → read existing file, apply merge logic (see below), increment `merged`
81+
- **doesn't exist** → proceed to create, increment `created`
82+
83+
### Page template (new or overwrite)
84+
85+
```markdown
86+
---
87+
title: <node.label>
88+
category: <node.category>
89+
tags: <node.tags as YAML list>
90+
sources:
91+
- "imported from <graph.json path>"
92+
<if node.summary exists>
93+
summary: "<node.summary>"
94+
</if>
95+
<if typed_edges[node.id] is non-empty>
96+
relationships:
97+
<for each {target, relation} in typed_edges[node.id]>
98+
- target: "[[<target>]]"
99+
type: <relation>
100+
</for>
101+
</if>
102+
lifecycle: draft
103+
lifecycle_changed: <today YYYY-MM-DD>
104+
base_confidence: 0.5
105+
tier: supporting
106+
created: <ISO timestamp>
107+
updated: <ISO timestamp>
108+
---
109+
110+
# <node.label>
111+
112+
<node.summary paragraph if available, else omit>
113+
114+
## Related
115+
116+
<for each neighbour in adjacency[node.id], sorted alphabetically>
117+
<if edge is typed>
118+
- [[<neighbour>]] — <relation>
119+
<else>
120+
- [[<neighbour>]]
121+
</if>
122+
</for>
123+
```
124+
125+
If `adjacency[node.id]` is empty, omit the `## Related` section entirely.
126+
127+
### Merge logic (merge mode, existing page)
128+
129+
1. Read the existing page's frontmatter.
130+
2. **Tags**: union of existing tags and `node.tags` (deduplicated, keep existing order, append new ones).
131+
3. **Summary**: if the existing page has no `summary` field and `node.summary` exists, add it.
132+
4. **Relationships**: union of existing `relationships:` entries and `typed_edges[node.id]` — skip entries where the same `(target, type)` pair already exists.
133+
5. **Updated**: set `updated` to the current ISO timestamp.
134+
6. **Body**: scan for a `## Related` section. If it exists, append any missing wikilinks from `adjacency[node.id]` that aren't already linked anywhere in the body. If no `## Related` section exists, append one with the missing links.
135+
7. Leave the rest of the body untouched.
136+
137+
## Step 5: Update Vault Metadata
138+
139+
### `.manifest.json`
140+
141+
Add a new entry keyed by the canonical path of the graph.json file:
142+
143+
```json
144+
"<absolute path to graph.json>": {
145+
"ingested_at": "<ISO timestamp>",
146+
"source_type": "wiki-export",
147+
"pages_created": ["list/of/created/pages.md"],
148+
"pages_updated": ["list/of/merged/pages.md"]
149+
}
150+
```
151+
152+
Also increment:
153+
- `stats.total_sources_ingested` by 1
154+
- `stats.total_pages` by the count of pages actually created (not skipped/merged)
155+
156+
If `.manifest.json` doesn't exist, create it with the standard structure:
157+
```json
158+
{
159+
"stats": {
160+
"total_sources_ingested": 1,
161+
"total_pages": <created count>
162+
},
163+
"<graph.json path>": { ... }
164+
}
165+
```
166+
167+
### `index.md`
168+
169+
For each **created** or **merged** page:
170+
- Add or update the entry under its category section using the format:
171+
`- [[<id>]] — <summary or title> ( #tag1 #tag2)`
172+
(Note: space before `(``description ( #tag)` not `description(#tag)`)
173+
174+
Keep categories sorted alphabetically. Create the category section if it doesn't exist.
175+
176+
### `log.md`
177+
178+
Append one line:
179+
```
180+
- [<ISO timestamp>] IMPORT source="<graph.json path>" pages_created=<N> pages_skipped=<K> pages_merged=<M>
181+
```
182+
183+
### `hot.md`
184+
185+
Rewrite the **Recent Activity** section to include this import as the latest entry:
186+
```
187+
- [<timestamp>] IMPORT from <graph.json path> — created X, merged Z pages
188+
```
189+
Update the `updated:` frontmatter timestamp. Leave other hot.md sections (Active Threads, Key Takeaways) intact unless they reference pages that were just created — in which case add brief mentions.
190+
191+
## Step 6: Print Summary
192+
193+
```
194+
Wiki import complete → $OBSIDIAN_VAULT_PATH
195+
Source: <graph.json path>
196+
exported at <graph.exported_at>, <N> nodes, <M> links
197+
Created: <X> pages
198+
Skipped: <Y> pages (already exist — use "merge" or "overwrite" to update)
199+
Merged: <Z> pages
200+
```
201+
202+
If all pages were skipped (Y = N), add a hint:
203+
```
204+
Hint: all pages already exist. Re-run with "merge" to update existing pages,
205+
or "overwrite" to replace them with stubs from the export.
206+
```
207+
208+
## Notes
209+
210+
- **Stub quality**: Imported pages are stubs — they have structure and wikilinks but no full body content. They're starting points for future ingestion, not finished pages.
211+
- **Re-running is safe**: In `skip` mode, re-running the same import is a no-op. Use `merge` for incremental updates from a re-exported graph.
212+
- **Directory creation**: Always create missing category directories before writing pages.
213+
- **Broken wikilinks**: Since pages are being created together from the same export, most links will resolve. Any node referenced in `links` but absent from `nodes` (broken in the original export) will still appear as a wikilink — it just won't have a corresponding page file, which is valid.
214+
- **Filtered exports**: If the source `graph.json` was produced with visibility filtering (noted in `graph.metadata`), imported pages will only reflect the filtered set. Note this in the summary if `graph.graph` contains a `filtered` key.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Skills live in `.skills/<name>/SKILL.md`. Match the user's intent to the right s
6767
| "fix my tags" / "normalize tags" / "tag audit" | `tag-taxonomy` |
6868
| "update wiki" / "sync to wiki" / "save this to my wiki" | `wiki-update` |
6969
| "export wiki" / "export graph" / "graphml" / "neo4j" | `wiki-export` |
70+
| "import wiki" / "import from export" / "load graph.json" / "import vault" / "/wiki-import" | `wiki-import` |
7071
| "color my graph" / "color code obsidian" / "color by tag/category/visibility" | `graph-colorize` |
7172
| "save this" / "/wiki-capture" / "capture this" / "file this conversation" | `wiki-capture` |
7273
| "/wiki-quick-chat-capture" / "quick capture" / "capture this finding" / "save this gotcha" / "drop to raw" | `wiki-quick-chat-capture` |

0 commit comments

Comments
 (0)