|
| 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. |
0 commit comments