@zosmaai/pi-llm-wiki 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,15 +49,16 @@ The result is a wiki that **compounds** as you capture sources, ask questions, a
49
49
 
50
50
  | Capability | Description |
51
51
  |------------|-------------|
52
+ | 🏠 **Personal fallback** | Always-on `~/.llm-wiki/` vault — knowledge compounds across projects even when no project wiki exists |
52
53
  | 🔗 **Immutable source capture** | URLs, local files (PDF/md/txt/html/XML/JSON), or pasted text → structured source packets |
53
54
  | 🧠 **Automated ingestion** | `wiki_ingest` batch-processes sources into concept, entity, synthesis & analysis pages |
54
55
  | 🔍 **Full-text search** | Generated registry with keyword lookup across all pages and sources |
55
56
  | 🩺 **Mechanical linting** | Orphans, broken links, duplicate aliases, coverage gaps, stale captures |
56
57
  | 📊 **Dashboard** | `wiki_status` — counts, source states, recent activity |
57
58
  | 🤖 **Auto-update watch** | `wiki_watch` — schedule periodic discovery + ingest |
58
- | 🧠 **Auto-recall** | Wiki searched automatically before every turnrelevant pages injected into context |
59
+ | 🧠 **Layered recall** | Searches both personal (`~/.llm-wiki/`) and project (`.llm-wiki/`) vaults personal knowledge follows you everywhere |
59
60
  | 📝 **Auto-bootstrap** | Extension suggests creating a wiki when none exists in the current directory |
60
- | 💾 **Auto-capture** | `wiki_retro` — save atomic insights from completed tasks with one call |
61
+ | 💾 **Lightweight capture** | `wiki_retro` — save atomic insights as a single markdown file; full 4-layer pipeline also available via `wiki_capture_source` |
61
62
  | 🌐 **MCP Server** | Use with Claude Code, Cursor, Windsurf via stdio MCP transport |
62
63
  | 📝 **Obsidian-friendly** | Folder-qualified wikilinks, stable source-ID citations, compatible vault |
63
64
  | 🛡️ **Guardrails** | Blocks direct edits to raw sources and generated metadata |
@@ -72,7 +73,7 @@ The result is a wiki that **compounds** as you capture sources, ask questions, a
72
73
  |------|-------------|
73
74
  | `wiki_bootstrap` | Initialize a new wiki vault with config, templates, schema, and metadata |
74
75
  | `wiki_capture_source` | Capture a URL, local file, or pasted text into an immutable source packet |
75
- | `wiki_recall` | 🔄 **Auto-called at turn start**search wiki for task-relevant pages |
76
+ | `wiki_recall` | Search wiki for task-relevant pagessearches both personal (`~/.llm-wiki/`) and project (`.llm-wiki/`) vaults, deduplicated |
76
77
  | `wiki_retro` | Save atomic insights from completed tasks into the wiki |
77
78
  | `wiki_ingest` | Process uningested source packets into wiki pages (batch) |
78
79
  | `wiki_ensure_page` | Resolve or safely create entity / concept / synthesis / analysis pages |
@@ -99,6 +100,28 @@ The result is a wiki that **compounds** as you capture sources, ask questions, a
99
100
 
100
101
  ---
101
102
 
103
+ ## Layered Vault Architecture
104
+
105
+ Knowledge follows you everywhere. pi-llm-wiki uses a layered vault system:
106
+
107
+ | Layer | Location | Purpose |
108
+ |-------|----------|---------|
109
+ | 🏠 **Personal** | `~/.llm-wiki/` | Always active. Zero setup. Knowledge compounds across all your sessions — regardless of which project you're in. |
110
+ | 📁 **Project** | `{project}/.llm-wiki/` | Explicit opt-in. Dedicated wiki per project, sharing personal knowledge when relevant. |
111
+ | 🏢 **Company** (future) | git-tracked | Shared wiki across a team. `wiki_publish` promotes personal/project pages to the company wiki. |
112
+
113
+ **How it works:**
114
+
115
+ 1. `resolveVaultRoot()` checks: cwd → walk up for `.llm-wiki/` → `~/.llm-wiki/`
116
+ 2. `wiki_recall` (layered) searches **both** personal and project vaults, merging results with vault labels
117
+ 3. Personal results are shown first in recall output, tagged as "📓 personal"
118
+ 4. `wiki_retro` writes to whichever vault is active (project takes priority)
119
+ 5. Set `WIKI_HOME` env var to override the personal wiki location
120
+
121
+ This means: you can have a project wiki for team documentation **and** a personal wiki for your own notes, and recall searches both simultaneously.
122
+
123
+ ---
124
+
102
125
  ## Quick Start (Detailed)
103
126
 
104
127
  ### 1) Create a new wiki
@@ -309,7 +332,13 @@ The bundled `llm-wiki` skill teaches the model to:
309
332
 
310
333
  ## Architecture
311
334
 
312
- Four layers with clear ownership:
335
+ ### Vault Layers
336
+
337
+ See the [Layered Vault Architecture](#layered-vault-architecture) section above for the personal/project/company layering.
338
+
339
+ ### Four-Layer Page Model
340
+
341
+ Each wiki vault has four layers with clear ownership:
313
342
 
314
343
  ```
315
344
  .llm-wiki/raw/sources/SRC-*/ # Immutable source packets (extension-owned)
@@ -1,6 +1,37 @@
1
1
  # Architecture
2
2
 
3
- ## Four Layers
3
+ ## Layered Vault Architecture
4
+
5
+ pi-llm-wiki supports multiple vault layers that are searched together:
6
+
7
+ | Layer | Location | Resolution | Searched by recall |
8
+ |-------|----------|------------|-------------------|
9
+ | **Personal** | `~/.llm-wiki/` | Fallback when no project wiki found | ✅ Always |
10
+ | **Project** | `{project}/.llm-wiki/` | Walk up from cwd | ✅ When present |
11
+
12
+ ### Resolution Order
13
+
14
+ 1. Check current directory for `.llm-wiki/` → use as project wiki
15
+ 2. Walk up parent directories looking for `.llm-wiki/` → use as project wiki
16
+ 3. Check `WIKI_HOME` env var → use as personal wiki
17
+ 4. Fall back to `~/.llm-wiki/` → create if doesn't exist
18
+
19
+ This means a project wiki is always preferred when you're inside a project that has one, but your personal wiki is always available as the fallback.
20
+
21
+ ### Dual-Vault Recall
22
+
23
+ `wiki_recall` uses `searchWikiLayered()` which:
24
+ 1. Searches the **project vault** (if one exists in cwd)
25
+ 2. Searches the **personal vault** (`~/.llm-wiki/` or `WIKI_HOME`)
26
+ 3. Deduplicates results by page ID (project takes priority on duplicates)
27
+ 4. Tags personal results with "📓 personal" label
28
+ 5. Merges results: personal first, then project
29
+
30
+ Results are injected into the context with vault source tags so the model can distinguish between personal and project knowledge.
31
+
32
+ ---
33
+
34
+ ## Four-Layer Page Model (within each vault)
4
35
 
5
36
  ```
6
37
  WIKI_ROOT/
package/docs/commands.md CHANGED
@@ -22,7 +22,7 @@ The extension registers 12 tools the LLM can call directly:
22
22
  | --------------------- | ------------------------------------------- |
23
23
  | `wiki_bootstrap` | Initialize a new vault |
24
24
  | `wiki_capture_source` | Capture URL/file/text into immutable packet |
25
- | `wiki_recall` | 🔄 Auto-called at turn start search wiki |
25
+ | `wiki_recall` | Search personal + project wikis for task-relevant pages (layered) |
26
26
  | `wiki_retro` | Save atomic insights from completed tasks |
27
27
  | `wiki_ingest` | Get batch of uningested sources |
28
28
  | `wiki_ensure_page` | Create canonical page from template |
@@ -6,6 +6,8 @@ Wiki configuration lives in `.llm-wiki/config.json`.
6
6
 
7
7
  ### Personal
8
8
 
9
+ The personal vault lives at `~/.llm-wiki/` (or `$WIKI_HOME`) and is always available as a fallback when no project wiki exists. It accumulates knowledge across all your sessions.
10
+
9
11
  - Extra folders: `wiki/journal/`, `wiki/goals/`
10
12
  - Track: learning, books, health, reflections
11
13
 
@@ -25,9 +27,19 @@ Wiki configuration lives in `.llm-wiki/config.json`.
25
27
 
26
28
  ## Environment Variables
27
29
 
28
- | Variable | Default | Description |
29
- | ----------------------------- | ------- | ----------------------------------------------- |
30
- | `WIKI_MARKITDOWN_TIMEOUT_MS` | 180000 | Timeout (ms) for MarkItDown PDF/text extraction |
30
+ | Variable | Default | Description |
31
+ | ----------------------------- | ----------- | ----------------------------------------------- |
32
+ | `WIKI_HOME` | `~/.llm-wiki` | Override the personal wiki vault location |
33
+ | `WIKI_MARKITDOWN_TIMEOUT_MS` | 180000 | Timeout (ms) for MarkItDown PDF/text extraction |
34
+
35
+ ## Vault Resolution
36
+
37
+ The vault root is resolved in this priority order:
38
+
39
+ 1. **Project vault**: walk up from current directory looking for `.llm-wiki/`
40
+ 2. **Personal vault**: fall back to `$WIKI_HOME` or `~/.llm-wiki/`
41
+
42
+ This means when you're in a project with its own `.llm-wiki/`, that project wiki is active. When you're outside any project wiki, your personal `~/.llm-wiki/` takes over automatically.
31
43
 
32
44
  ## Page Frontmatter
33
45
 
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, join } from "node:path";
3
3
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
4
  import { installGuardrails } from "./lib/guardrails.js";
5
- import { formatRecallContext, registerWikiRecall, searchWiki } from "./lib/recall.js";
5
+ import { formatRecallContext, registerWikiRecall, searchWikiLayered } from "./lib/recall.js";
6
6
  import { registerWikiRetro } from "./lib/retro.js";
7
7
  import {
8
8
  registerWikiBootstrap,
@@ -27,17 +27,19 @@ import {
27
27
  /**
28
28
  * @zosmaai/pi-llm-wiki — LLM Wiki extension for Pi
29
29
  *
30
- * Registers 11 custom tools and installs guardrails:
31
- * All 10 original tools + wiki_recall (auto-recall at session start)
30
+ * Registers 12 custom tools and installs guardrails:
31
+ * - wiki_recall (layered: personal + project vaults)
32
+ * - wiki_retro (lightweight: single markdown file)
33
+ * - wiki_capture_source (full 4-layer pipeline)
32
34
  *
33
35
  * Guardrails:
34
36
  * - Blocks direct edits to raw/** and meta/**
35
37
  * - Auto-rebuilds metadata after wiki/** edits
36
38
  *
37
- * Auto-recall:
38
- * - before_agent_start hook searches wiki for pages relevant to user prompt
39
- * - Injects matching knowledge as system context
40
- * - wiki_recall tool available for explicit deep searches
39
+ * Layered recall:
40
+ * - before_agent_start hook searches personal + project vaults
41
+ * - Injects matching knowledge as system context with vault labels
42
+ * - wiki_recall tool available for explicit task-specific searches
41
43
  */
42
44
 
43
45
  export default function (pi: ExtensionAPI) {
@@ -95,14 +97,14 @@ export default function (pi: ExtensionAPI) {
95
97
  return;
96
98
  }
97
99
 
98
- ctx.ui.setStatus("llm-wiki", "🧠 LLM Wiki (12 tools, auto-recall active)");
100
+ ctx.ui.setStatus("llm-wiki", "🧠 LLM Wiki (12 tools, layered recall active)");
99
101
  });
100
102
 
101
- // ─── Auto-recall + topic inference hook ─────────────
103
+ // ─── Layered recall + topic inference hook ──────────
102
104
  // Before each agent turn:
103
105
  // 1. If wiki was just auto-created, inject a directive to infer topic/mode
104
106
  // from the user's first prompt and update config via wiki_bootstrap.
105
- // 2. Search wiki for relevant pages and inject as system context.
107
+ // 2. Search both personal + project vaults for relevant pages.
106
108
  pi.on("before_agent_start", async (event, _ctx) => {
107
109
  const paths = resolveVaultPaths(process.cwd());
108
110
  if (!existsSync(join(paths.dotWiki, "config.json"))) {
@@ -145,9 +147,9 @@ ${projectHints}
145
147
  Then call wiki_bootstrap with the inferred topic and mode to finalize the setup. This is a one-time step.`;
146
148
  }
147
149
 
148
- // Auto-recall: search wiki for relevant pages
150
+ // Layered recall: search personal + project vaults for relevant pages
149
151
  if (prompt.trim()) {
150
- const results = searchWiki(paths, prompt);
152
+ const results = searchWikiLayered(paths, prompt);
151
153
  if (results.length > 0) {
152
154
  const recallContext = formatRecallContext(results);
153
155
  if (recallContext) {
@@ -156,6 +158,11 @@ Then call wiki_bootstrap with the inferred topic and mode to finalize the setup.
156
158
  }
157
159
  }
158
160
 
161
+ // Always inject a visible wiki status footer, even when empty
162
+ // This ensures the model knows the wiki is active and can use it
163
+ injectedContext +=
164
+ "\n\n<wiki_status>LLM Wiki active — use wiki_recall for deeper search, wiki_retro to save new knowledge.</wiki_status>";
165
+
159
166
  if (injectedContext === event.systemPrompt) return;
160
167
  return { systemPrompt: injectedContext };
161
168
  });
@@ -3,7 +3,13 @@ import { join } from "node:path";
3
3
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
4
  import { Type } from "typebox";
5
5
  import type { Registry } from "./metadata.js";
6
- import { type VaultPaths, readJson, resolveVaultPaths } from "./utils.js";
6
+ import {
7
+ type VaultPaths,
8
+ getPersonalWikiPaths,
9
+ isPersonalVault,
10
+ readJson,
11
+ resolveVaultPaths,
12
+ } from "./utils.js";
7
13
 
8
14
  // ─── Public API ────────────────────────────────────────
9
15
 
@@ -18,10 +24,12 @@ export interface RecallResult {
18
24
  preview: string;
19
25
  /** Relative path from wiki root */
20
26
  path: string;
27
+ /** Vault source label for dual-vault results */
28
+ vaultLabel?: string;
21
29
  }
22
30
 
23
31
  /**
24
- * Search the wiki registry for pages matching a query.
32
+ * Search a single vault's registry for pages matching a query.
25
33
  * Returns up to `maxResults` matches, each with a content preview.
26
34
  */
27
35
  export function searchWiki(paths: VaultPaths, query: string, maxResults = 5): RecallResult[] {
@@ -88,21 +96,68 @@ export function searchWiki(paths: VaultPaths, query: string, maxResults = 5): Re
88
96
  });
89
97
  }
90
98
 
99
+ /**
100
+ * Format recall results as a compact system-prompt section.
101
+ */
102
+ /**
103
+ * Search both project/primary vault and personal vault, merging results.
104
+ * Personal results are appended after primary results, deduplicated by page ID.
105
+ */
106
+ export function searchWikiLayered(
107
+ primaryPaths: VaultPaths,
108
+ query: string,
109
+ maxResults = 5,
110
+ ): RecallResult[] {
111
+ // Search primary vault
112
+ const primaryResults = searchWiki(primaryPaths, query, maxResults);
113
+
114
+ // If primary is already the personal vault, no layered search needed
115
+ if (isPersonalVault(primaryPaths)) return primaryResults;
116
+
117
+ // Search personal vault as secondary layer
118
+ const personalPaths = getPersonalWikiPaths();
119
+ if (!existsSync(join(personalPaths.dotWiki, "config.json"))) return primaryResults;
120
+
121
+ const personalResults = searchWiki(personalPaths, query, maxResults);
122
+
123
+ // Merge: personal results first (they're the user's accumulated knowledge),
124
+ // then primary results (project-specific). Deduplicate by page ID.
125
+ const seen = new Set<string>();
126
+ const merged: RecallResult[] = [];
127
+
128
+ for (const r of [...personalResults, ...primaryResults]) {
129
+ if (seen.has(r.id)) continue;
130
+ seen.add(r.id);
131
+ // If it's from personal vault, tag it
132
+ if (personalResults.includes(r)) {
133
+ merged.push({ ...r, vaultLabel: "📓 personal" });
134
+ } else {
135
+ merged.push(r);
136
+ }
137
+ }
138
+
139
+ return merged.slice(0, maxResults);
140
+ }
141
+
91
142
  /**
92
143
  * Format recall results as a compact system-prompt section.
93
144
  */
94
145
  export function formatRecallContext(results: RecallResult[]): string {
95
146
  if (results.length === 0) return "";
96
147
 
148
+ const hasLayered = results.some((r) => r.vaultLabel);
149
+ const label = hasLayered ? " (personal + project)" : "";
150
+
97
151
  const lines: string[] = [
98
152
  "## Relevant Wiki Knowledge",
99
153
  "",
100
- `_${results.length} page(s) matched your query — reviewed automatically by LLM Wiki._`,
154
+ `_${results.length} page(s) matched your query${label}._`,
101
155
  "",
102
156
  ];
103
157
 
104
158
  for (const r of results) {
105
- lines.push(`- **[[${r.id}]]** *${r.type}* ${r.title}`);
159
+ const vaultTag = r.vaultLabel ? ` ${r.vaultLabel}` : "";
160
+ lines.push(`- **[[${r.id}]]** — *${r.type}* — ${r.title}${vaultTag}`);
106
161
  if (r.preview) {
107
162
  // Truncate preview to one line
108
163
  const preview = r.preview.length > 120 ? `${r.preview.slice(0, 120)}…` : r.preview;
@@ -164,30 +219,34 @@ export function registerWikiRecall(pi: ExtensionAPI): void {
164
219
  }
165
220
 
166
221
  const maxResults = Math.min(params.max_results ?? 5, 10);
167
- const results = searchWiki(paths, params.query, maxResults);
222
+ // Use layered search: personal vault + project vault
223
+ const results = searchWikiLayered(paths, params.query, maxResults);
168
224
 
169
225
  if (results.length === 0) {
170
226
  return {
171
227
  content: [
172
228
  {
173
229
  type: "text",
174
- text: `No wiki pages found matching "${params.query}". Use wiki_search for broader results.`,
230
+ text: `No wiki pages found matching "${params.query}". The wiki is empty — use wiki_retro to start building knowledge.`,
175
231
  },
176
232
  ],
177
233
  details: { query: params.query, matches: [] } as Record<string, unknown>,
178
234
  };
179
235
  }
180
236
 
237
+ const hasPersonal = results.some((r) => r.vaultLabel);
238
+ const layerTag = hasPersonal ? " (personal + project)" : "";
239
+
181
240
  return {
182
241
  content: [
183
242
  {
184
243
  type: "text",
185
244
  text: [
186
- `🧠 **${results.length} wiki page(s) relevant** to "${params.query}":`,
245
+ `🧠 **${results.length} wiki page(s) relevant** to "${params.query}"${layerTag}:`,
187
246
  "",
188
247
  ...results.map(
189
248
  (r) =>
190
- `- [[${r.id}]] — *${r.type}* — ${r.title}${
249
+ `- ${r.vaultLabel || "📁"} [[${r.id}]] — *${r.type}* — ${r.title}${
191
250
  r.preview ? `\n > ${r.preview.slice(0, 150)}` : ""
192
251
  }`,
193
252
  ),
@@ -3,19 +3,24 @@ import { join } from "node:path";
3
3
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
4
  import { Type } from "typebox";
5
5
  import { appendEvent, rebuildMetadataLight } from "./metadata.js";
6
- import { type VaultPaths, fmtDate, nextSourceId, resolveVaultPaths } from "./utils.js";
6
+ import { type VaultPaths, fmtDate, resolveVaultPaths } from "./utils.js";
7
7
 
8
8
  // ─── Public API ────────────────────────────────────────
9
9
 
10
10
  export interface RetroResult {
11
- sourceId: string;
12
- packetPath: string;
11
+ slug: string;
13
12
  sourcePagePath: string;
14
13
  }
15
14
 
16
15
  /**
17
- * Save an atomic insight into the wiki as a source packet + source page.
18
- * Returns the source ID, packet path, and source page path.
16
+ * Save an atomic insight into the wiki as a single markdown file.
17
+ *
18
+ * Unlike wiki_capture_source (which creates a full source packet with
19
+ * manifest.json, extracted.md, and attachments), this is a lightweight
20
+ * path for quick knowledge capture — one file, one call.
21
+ *
22
+ * The 4-layer pipeline (raw → source pages → canonical pages → metadata)
23
+ * is still available via wiki_capture_source → wiki_ingest for deep research.
19
24
  */
20
25
  export function saveInsight(
21
26
  paths: VaultPaths,
@@ -24,92 +29,54 @@ export function saveInsight(
24
29
  body: string,
25
30
  category?: string,
26
31
  ): RetroResult {
27
- const sourceId = nextSourceId(paths);
28
- const packetPath = join(paths.rawSources, sourceId);
29
- mkdirSync(packetPath, { recursive: true });
30
- mkdirSync(join(packetPath, "attachments"), { recursive: true });
31
-
32
32
  const today = fmtDate();
33
33
 
34
- // Write manifest
35
- const manifest = {
36
- id: sourceId,
37
- title,
38
- slug,
39
- category: category || "uncategorized",
40
- captured: today,
41
- format: "insight",
42
- packet_version: "1.0",
43
- };
44
- writeFileSync(
45
- join(packetPath, "manifest.json"),
46
- `${JSON.stringify(manifest, null, 2)}\n`,
47
- "utf-8",
48
- );
49
-
50
- // Write extracted text (the insight body in markdown)
51
- const extracted = [
52
- `# ${title}`,
53
- "",
54
- body,
55
- "",
56
- "---",
57
- `*Captured: ${today}*`,
58
- category ? `*Category: ${category}*` : "",
59
- ]
60
- .filter(Boolean)
61
- .join("\n");
62
- writeFileSync(join(packetPath, "extracted.md"), extracted, "utf-8");
63
-
64
- // Create source page
34
+ // Write a single markdown file to wiki/sources/{slug}.md
65
35
  const sourcePageDir = join(paths.wiki, "sources");
66
36
  mkdirSync(sourcePageDir, { recursive: true });
67
- const sourcePagePath = join(sourcePageDir, `${sourceId}.md`);
37
+ const sourcePagePath = join(sourcePageDir, `${slug}.md`);
68
38
 
69
- const tagLine = category ? `category: ${category}` : "";
70
- const sourcePageContent = [
39
+ const pageContent = [
71
40
  "---",
72
41
  "type: source",
73
42
  `title: "${title}"`,
74
- `source_id: ${sourceId}`,
43
+ `slug: ${slug}`,
75
44
  "status: insight",
76
45
  `created: ${today}`,
77
46
  `updated: ${today}`,
78
- tagLine,
47
+ category ? `category: ${category}` : "",
79
48
  "---",
80
49
  "",
81
50
  `# ${title}`,
82
51
  "",
83
52
  body,
84
53
  "",
85
- "## Source",
54
+ category ? `*Category: ${category}*` : "",
86
55
  "",
87
- `- **Packet:** \`${packetPath}\``,
88
- `- **Captured:** ${today}`,
89
- category ? `- **Category:** ${category}` : "",
56
+ "---",
57
+ `*Captured: ${today}*`,
90
58
  "",
91
59
  "## Related",
92
60
  "",
93
- "_(Add [[wikilinks]] to related pages)_",
61
+ "_Add links to related pages._",
94
62
  "",
95
63
  ]
96
64
  .filter((l) => l !== "")
97
65
  .join("\n");
98
- writeFileSync(sourcePagePath, sourcePageContent, "utf-8");
66
+ writeFileSync(sourcePagePath, pageContent, "utf-8");
99
67
 
100
68
  // Log event
101
69
  appendEvent(paths, {
102
70
  kind: "retro",
103
- source_id: sourceId,
104
- title,
105
71
  slug,
72
+ title,
106
73
  category: category || "uncategorized",
107
74
  });
108
75
 
109
- // Rebuild metadata
76
+ // Rebuild metadata so the insight is immediately searchable
110
77
  rebuildMetadataLight(paths);
111
78
 
112
- return { sourceId, packetPath, sourcePagePath };
79
+ return { slug, sourcePagePath };
113
80
  }
114
81
 
115
82
  // ─── Tool Registration ──────────────────────────────────
@@ -176,8 +143,6 @@ export function registerWikiRetro(pi: ExtensionAPI): void {
176
143
  text: [
177
144
  `🧠 **Insight saved**: ${params.title}`,
178
145
  "",
179
- `- Source: \`${result.sourceId}\``,
180
- `- Packet: \`${result.packetPath}\``,
181
146
  `- Page: \`${result.sourcePagePath}\``,
182
147
  "",
183
148
  "This insight will be auto-surfaced by wiki_recall in future sessions.",
@@ -185,7 +150,6 @@ export function registerWikiRetro(pi: ExtensionAPI): void {
185
150
  },
186
151
  ],
187
152
  details: {
188
- sourceId: result.sourceId,
189
153
  slug: params.slug,
190
154
  title: params.title,
191
155
  category: params.category || null,
@@ -1,4 +1,5 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
2
3
  import { dirname, join, resolve } from "node:path";
3
4
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
5
 
@@ -32,7 +33,35 @@ export function detectVaultFormat(dir: string): VaultFormat {
32
33
  return "none";
33
34
  }
34
35
 
35
- /** Resolve vault root from cwd or find nearest wiki root. */
36
+ /** Get the personal wiki root directory (~/.llm-wiki/). */
37
+ export function getPersonalWikiRoot(): string {
38
+ const envWiki = process.env.WIKI_HOME;
39
+ if (envWiki) return envWiki;
40
+ return join(homedir(), ".llm-wiki");
41
+ }
42
+
43
+ /** Get VaultPaths for the personal wiki. */
44
+ export function getPersonalWikiPaths(): VaultPaths {
45
+ return getVaultPaths(getPersonalWikiRoot());
46
+ }
47
+
48
+ /**
49
+ * Check if a vault is the personal wiki location.
50
+ * Used in layered recall to avoid double-counting.
51
+ */
52
+ export function isPersonalVault(paths: VaultPaths): boolean {
53
+ return paths.root === getPersonalWikiRoot();
54
+ }
55
+
56
+ /**
57
+ * Resolve vault root from cwd with personal fallback.
58
+ *
59
+ * Priority:
60
+ * 1. cwd has .llm-wiki/ → project wiki (explicit)
61
+ * 2. Walk up from cwd → parent project wiki
62
+ * 3. ~/.llm-wiki/ exists → personal wiki
63
+ * 4. Fallback: ~/.llm-wiki/ (create personal wiki)
64
+ */
36
65
  export function resolveVaultRoot(cwd: string): string {
37
66
  // Check for any vault format at cwd
38
67
  if (detectVaultFormat(cwd) !== "none") return cwd;
@@ -44,8 +73,12 @@ export function resolveVaultRoot(cwd: string): string {
44
73
  if (detectVaultFormat(dir) !== "none") return dir;
45
74
  }
46
75
 
47
- // Fallback: cwd itself
48
- return cwd;
76
+ // Check personal wiki at ~/.llm-wiki/
77
+ const personalRoot = getPersonalWikiRoot();
78
+ if (detectVaultFormat(personalRoot) !== "none") return personalRoot;
79
+
80
+ // Fallback: personal wiki
81
+ return personalRoot;
49
82
  }
50
83
 
51
84
  /** Get all vault paths for the new (.llm-wiki) layout. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zosmaai/pi-llm-wiki",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Self-maintaining LLM Wiki for Pi — Karpathy-pattern knowledge base with immutable source capture, automated ingestion, search, linting, and Obsidian-compatible vault. auto-updating personal & company wiki.",
5
5
  "keywords": [
6
6
  "pi",
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Save an atomic insight from the current task into the wiki. Creates a source packet and source page for future auto-recall.
2
+ description: Save an atomic insight from the current task into the wiki. Creates a single markdown file that layered recall surfaces in future sessions.
3
3
  argument-hint: "<title> [--category <category>]"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
@@ -9,7 +9,7 @@ topLevelCli: true
9
9
 
10
10
  Save an atomic insight from a completed task into the wiki.
11
11
 
12
- Captures what you learned as an immutable source packet + wiki source page so that `wiki_recall` automatically surfaces it in future sessions.
12
+ Captures what you learned as a single markdown file so that layered recall surfaces it in future sessions.
13
13
 
14
14
  ## User Arguments
15
15
 
@@ -25,7 +25,7 @@ Read the LLM Wiki skill at `.pi/skills/llm-wiki/SKILL.md` first to understand th
25
25
  - `title`: short descriptive phrase, ≤60 chars, noun phrase not a sentence
26
26
  - `body`: markdown explanation with `[[wikilinks]]` to related wiki pages
27
27
  - `category`: optional (frontend, architecture, devops, bugfix, design, etc.)
28
- 3. Confirm the insight was saved and will be auto-surfaced in future sessions
28
+ 3. Confirm the insight was saved and will be surfaced by layered recall in future sessions
29
29
  4. If the insight relates to existing wiki pages, update those pages with cross-references
30
30
 
31
31
  **Rules:**
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: llm-wiki
3
3
  description: Build and maintain a persistent, interlinked Obsidian-compatible markdown wiki using Karpathy's LLM Wiki pattern. Extension-backed with auto-generated metadata, guardrails, and 12 custom tools.
4
- whenToUse: At the START of every task, the extension auto-recalls relevant wiki pages — no manual action needed. After completing any meaningful task, call wiki_retro to save insights for future sessions. Invoke proactively do not wait for the user to ask.
4
+ whenToUse: Call wiki_recall at task start to find relevant wiki pages. Call wiki_retro at task end to save new insights. The extension injects a brief status line, but explicit wiki_recall calls with task-specific terms get better results.
5
5
  ---
6
6
 
7
7
  # LLM Wiki for Pi
@@ -56,25 +56,24 @@ WIKI_ROOT/
56
56
  | Find orphans | Shell `grep` scans | Instant from `backlinks.json` |
57
57
  | Block raw edits | Skill says "don't" | Extension **enforces** immutability |
58
58
  | Create source page | 8 tool calls | `wiki_capture_source` + LLM synthesis |
59
- | **Recall wiki knowledge** | Never happens | **Auto-search before every turn** |
59
+ | **Recall wiki knowledge** | Never happens | **Layered search before every turn (personal + project)** |
60
60
  | **Save task insights** | Manual capture | `wiki_retro` — one tool call |
61
61
 
62
- ## 🔄 Proactive Wiki Usage
62
+ ## 🔄 Wiki Usage
63
63
 
64
- ### At Task Start — Auto-Recall (Automatic)
64
+ ### At Start — Call wiki_recall
65
65
 
66
- **The extension automatically searches the wiki before every user turn.**
66
+ **Call `wiki_recall` at the START of every task** to find relevant wiki pages:
67
67
 
68
- When you send a prompt, the extension:
69
- 1. Extracts key terms from your request
70
- 2. Searches the wiki registry for matching pages
71
- 3. Injects matching page titles + summaries into context
72
- 4. You see this as "Relevant Wiki Knowledge" in your system prompt
68
+ ```
69
+ wiki_recall(query="key terms from the user's request", max_results=5)
70
+ ```
73
71
 
74
- **This means the wiki works as an automatic second brain.**
75
- You don't need to remember to search — relevant knowledge is surfaced automatically.
72
+ This searches both your **personal wiki** (`~/.llm-wiki/`) and the **project wiki** (`.llm-wiki/` in the current directory), merging results.
76
73
 
77
- ### At Task End Save Insights (Proactive)
74
+ The extension also briefly searches automatically, but explicit calls with task-specific terms get better results.
75
+
76
+ ### At End — Save Insights with wiki_retro
78
77
 
79
78
  After completing any meaningful task, call `wiki_retro` to save key insights:
80
79
  - Non-obvious bug fixes or workarounds
@@ -84,29 +83,26 @@ After completing any meaningful task, call `wiki_retro` to save key insights:
84
83
 
85
84
  **Do not wait for the user to ask.** Save insights proactively — one atomic insight per call.
86
85
 
87
- ```wiki_retro(slug="kebab-case-slug", title="Brief descriptive title", body="Insight in your own words with [[wikilinks]]")
86
+ ```
87
+ wiki_retro(slug="kebab-case-slug", title="Brief descriptive title", body="Insight in your own words with [[wikilinks]]")
88
88
  ```
89
89
 
90
- ### Manual Recall for Deeper Searches
90
+ ### Deeper Searches
91
91
 
92
- If the auto-recall doesn't find enough context, call `wiki_recall` explicitly:
92
+ For thorough research, also use `wiki_search` to browse the full registry:
93
93
 
94
94
  ```
95
- wiki_recall(query="specific terms...", max_results=10)
95
+ wiki_search(query="broad topic")
96
96
  ```
97
97
 
98
- This gives you more control over the search terms and returns content previews.
98
+ ### Auto-Bootstrap (One-Time)
99
99
 
100
- ### Auto-Bootstrap (New)
101
-
102
- **The extension now creates the wiki vault automatically on startup — no user prompt needed.**
103
-
104
- When you start in a directory without a wiki, the extension silently creates `.llm-wiki/` with placeholder config. On your first turn, it injects a directive asking you to:
105
- 1. Analyze the user's prompt and project context
106
- 2. Infer a topic (e.g. "React app", "startup finances")
107
- 3. Call `wiki_bootstrap(topic="...", mode="personal|company")` to finalize setup
100
+ The extension creates the wiki vault automatically on startup. On the first turn, it injects a directive asking you to infer topic and mode, then call:
101
+ ```
102
+ wiki_bootstrap(topic="...", mode="personal|company")
103
+ ```
108
104
 
109
- This is a one-time step — after bootstrap, normal auto-recall takes over.
105
+ This is a one-time step.
110
106
 
111
107
  ## Available Tools
112
108
 
@@ -114,7 +110,7 @@ Use these directly — they handle scaffolding, bookkeeping, recall, and capture
114
110
 
115
111
  - `wiki_bootstrap` — Initialize a new vault
116
112
  - `wiki_capture_source` — Capture URL/file/text into immutable packet + skeleton page
117
- - `wiki_recall` — **Auto-called at turn start.** Search wiki for task-relevant pages
113
+ - `wiki_recall` — Search both personal + project wikis for task-relevant pages
118
114
  - `wiki_retro` — Save an atomic insight from a completed task into the wiki
119
115
  - `wiki_ingest` — Get batch of uningested sources with extracted text
120
116
  - `wiki_ensure_page` — Create entity/concept/synthesis/analysis page from template
@@ -139,19 +135,20 @@ Use these directly — they handle scaffolding, bookkeeping, recall, and capture
139
135
 
140
136
  ### Query → Answer → File
141
137
 
142
- 1. **Auto-recall**: Extension surfaces relevant wiki pages automatically
143
- 2. Read those pages
144
- 3. Synthesize answer with `[[wikilink]]` citations
145
- 4. If novel: create analysis page via `wiki_ensure_page(type="analysis")`
146
- 5. Extension auto-updates metadata
138
+ 1. **Layered recall**: Extension searches personal + project vaults, injects matching pages with vault labels
139
+ 2. For better results: call `wiki_recall` explicitly with task-specific terms
140
+ 3. Read those pages
141
+ 4. Synthesize answer with `[[wikilink]]` citations
142
+ 5. If novel: create analysis page via `wiki_ensure_page(type="analysis")`
143
+ 6. Extension auto-updates metadata
147
144
 
148
145
  ### Task → Capture → Retro
149
146
 
150
147
  1. Complete a meaningful task
151
148
  2. Call `wiki_retro` to save key insights
152
- 3. The insight is captured as a source packet
149
+ 3. The insight is saved as a single markdown file
153
150
  4. Extension auto-updates metadata
154
- 5. Next time, auto-recall surfaces your saved insight
151
+ 5. Next time, layered recall surfaces your saved insight
155
152
 
156
153
  ## Page Conventions
157
154