@zosmaai/pi-llm-wiki 0.4.0 → 0.6.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.6.0] - 2026-05-11
6
+
7
+ ### Added
8
+ - **Phase 1 — Auto-recall** (PR #19 by @arjun-zosma): New `wiki_recall` tool for explicit searches. Extension now auto-searches wiki before every user turn via `before_agent_start` hook. Matching pages injected as "Relevant Wiki Knowledge" into system prompt. 8 new tests.
9
+ - **Phase 2 — Auto-capture** (PR #20 by @arjun-zosma): New `wiki_retro` tool for saving atomic insights from completed tasks. Creates source packets with manifest, extracted text, and source page. 4 new tests.
10
+ - **Phase 3 — MCP Server** (PR #21 by @arjun-zosma): Standalone MCP server using `@modelcontextprotocol/server` (v2 SDK) with stdio transport. Exposes 5 tools: wiki_recall, wiki_search, wiki_status, wiki_retro, wiki_capture_source. Cross-platform reach to Claude Code, Cursor, Windsurf.
11
+ - **12 extension tools** (up from 10): wiki_recall (auto at turn start) and wiki_retro (manual at task end)
12
+ - **SKILL.md**: Auto-Recall section, wiki_recall + wiki_retro tool docs, "Task → Capture → Retro" workflow
13
+
14
+ ### Changed
15
+ - Extension registers 12 tools instead of 10
16
+ - Status bar now shows "12 tools, auto-recall active"
17
+
18
+ ## [0.5.0] - 2026-05-11
19
+
20
+ ### Added
21
+ - **Overhauled README**: npm downloads badge, slash commands table, guardrails section, skill behavior, vault layout, source packet format, integration flow, linking style guide
22
+ - **Better npm discoverability**: 19 keywords (was 10), expanded description with search terms, `files` field to slim package
23
+ - **GitHub topics**: pi, llm-wiki, knowledge-base, wiki, markdown, obsidian, karpathy, second-brain, pkm, memory
24
+
25
+ ## [0.4.0] - 2026-05-11
26
+
27
+ - JSON file support (PR #15 by jfraser)
28
+ - Extractor strategy pattern refactor
29
+
30
+ ## [0.3.0] - 2026-05-07
31
+
32
+ - Release
33
+
34
+ ## [0.2.2] - 2026-05-03
35
+
36
+ - Fix: CodeQL alerts for safe tag stripping and entity decoding
37
+ - Fix: README contributors via contrib.rocks
38
+ - Added: Features section and env var documentation
39
+
40
+ ## [0.2.1] - 2026-04-29
41
+
42
+ - Minor fixes
43
+
5
44
  ## [0.2.0] - 2026-04-28
6
45
 
7
46
  ### Added
package/README.md CHANGED
@@ -2,53 +2,264 @@
2
2
 
3
3
  [![CI](https://github.com/zosmaai/pi-llm-wiki/actions/workflows/ci.yml/badge.svg)](https://github.com/zosmaai/pi-llm-wiki/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/@zosmaai/pi-llm-wiki)](https://www.npmjs.com/package/@zosmaai/pi-llm-wiki)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@zosmaai/pi-llm-wiki)](https://www.npmjs.com/package/@zosmaai/pi-llm-wiki)
5
6
  [![Coverage](https://codecov.io/gh/zosmaai/pi-llm-wiki/branch/main/graph/badge.svg)](https://codecov.io/gh/zosmaai/pi-llm-wiki)
6
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
8
  [![CodeQL](https://github.com/zosmaai/pi-llm-wiki/actions/workflows/codeql.yml/badge.svg)](https://github.com/zosmaai/pi-llm-wiki/actions/workflows/codeql.yml)
8
9
 
9
- Self-maintaining, Obsidian-compatible knowledge base for [pi](https://pi.dev). Following Andrej Karpathy's LLM Wiki pattern.
10
+ **Self-maintaining, Obsidian-compatible knowledge base for [pi](https://pi.dev).**
11
+ Follows Andrej Karpathy's [LLM Wiki pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f).
10
12
 
11
- ## Install
13
+ Turn raw sources (URLs, PDFs, markdown, JSON, XML) into a durable, interlinked, LLM-maintained wiki that compounds over time.
14
+
15
+ ---
16
+
17
+ ## Quick Start
12
18
 
13
19
  ```bash
14
20
  pi install npm:@zosmaai/pi-llm-wiki
15
21
  ```
16
22
 
17
- ## Quick Start
18
-
19
23
  ```
20
24
  /wiki-init "AI Engineering"
25
+ /wiki-ingest
26
+ /wiki-query What are the key patterns?
21
27
  ```
22
28
 
23
- Drop sources into `raw/`, then:
29
+ ---
30
+
31
+ ## Why This Package?
32
+
33
+ Most file-based LLM workflows behave like one-shot RAG: the model searches raw documents every time you ask a question. Synthesis is ephemeral.
34
+
35
+ **pi-llm-wiki** creates a middle layer:
36
+
37
+ - **Raw source packets** preserve source-of-truth inputs
38
+ - **Source pages** summarize what each source says
39
+ - **Canonical wiki pages** track what the wiki currently believes
40
+ - **Generated metadata** keeps everything searchable and navigable
24
41
 
42
+ The result is a wiki that **compounds** as you capture sources, ask questions, and file durable analyses.
43
+
44
+ ---
45
+
46
+ ## Features
47
+
48
+ | Capability | Description |
49
+ |------------|-------------|
50
+ | 🔗 **Immutable source capture** | URLs, local files (PDF/md/txt/html/XML/JSON), or pasted text → structured source packets |
51
+ | 🧠 **Automated ingestion** | `wiki_ingest` batch-processes sources into concept, entity, synthesis & analysis pages |
52
+ | 🔍 **Full-text search** | Generated registry with keyword lookup across all pages and sources |
53
+ | 🩺 **Mechanical linting** | Orphans, broken links, duplicate aliases, coverage gaps, stale captures |
54
+ | 📊 **Dashboard** | `wiki_status` — counts, source states, recent activity |
55
+ | 🤖 **Auto-update watch** | `wiki_watch` — schedule periodic discovery + ingest |
56
+ | 📝 **Obsidian-friendly** | Folder-qualified wikilinks, stable source-ID citations, compatible vault |
57
+ | 🛡️ **Guardrails** | Blocks direct edits to raw sources and generated metadata |
58
+ | 🔧 **Configurable PDF extraction** | MarkItDown timeout via `WIKI_MARKITDOWN_TIMEOUT_MS` env var |
59
+ | 🧪 **38+ tests, CI, CodeQL** | TypeScript, Vitest, Biome, Codecov |
60
+
61
+ ---
62
+
63
+ ## Tools
64
+
65
+ | Tool | Description |
66
+ |------|-------------|
67
+ | `wiki_bootstrap` | Initialize a new wiki vault with config, templates, schema, and metadata |
68
+ | `wiki_capture_source` | Capture a URL, local file, or pasted text into an immutable source packet |
69
+ | `wiki_ingest` | Process uningested source packets into wiki pages (batch) |
70
+ | `wiki_ensure_page` | Resolve or safely create entity / concept / synthesis / analysis pages |
71
+ | `wiki_search` | Search the generated wiki registry |
72
+ | `wiki_lint` | Deterministic health checks (orphans, gaps, contradictions, auto-fix) |
73
+ | `wiki_status` | Show counts, source states, and recent activity |
74
+ | `wiki_rebuild_meta` | Force a full metadata rebuild (registry, backlinks, index, log) |
75
+ | `wiki_log_event` | Append a structured event to the wiki activity log |
76
+ | `wiki_watch` | Schedule automatic wiki updates (daily / weekly / hourly) |
77
+
78
+ ### Slash Commands
79
+
80
+ | Command | Description |
81
+ |---------|-------------|
82
+ | `/wiki-status` | Show a concise operational summary |
83
+ | `/wiki-lint [mode]` | Run mechanical lint (`all`, `links`, `orphans`, `frontmatter`, `duplicates`, `coverage`, `staleness`) |
84
+ | `/wiki-rebuild` | Force a full metadata rebuild |
85
+
86
+ ---
87
+
88
+ ## Quick Start (Detailed)
89
+
90
+ ### 1) Create a new wiki
91
+
92
+ ```bash
93
+ mkdir my-wiki
94
+ cd my-wiki
95
+ pi
25
96
  ```
26
- /wiki-ingest
27
- /wiki-query What are the key patterns?
97
+
98
+ Ask pi:
99
+
100
+ ```
101
+ Initialize an llm wiki here for AI research.
28
102
  ```
29
103
 
30
- ## What It Does
104
+ This calls `wiki_bootstrap` and creates:
31
105
 
32
- | Tool | Purpose |
33
- | --------------------- | --------------------------------------------- |
34
- | `wiki_bootstrap` | Initialize a new wiki vault |
35
- | `wiki_capture_source` | Capture URL/file/text into immutable packet |
36
- | `wiki_ingest` | Process sources into wiki pages |
37
- | `wiki_ensure_page` | Create entity/concept/synthesis/analysis page |
38
- | `wiki_search` | Search the wiki registry |
39
- | `wiki_lint` | Health check (orphans, gaps, contradictions) |
40
- | `wiki_status` | Stats dashboard |
41
- | `wiki_rebuild_meta` | Force metadata rebuild |
42
- | `wiki_log_event` | Record custom event |
43
- | `wiki_watch` | Schedule auto-updates |
106
+ ```
107
+ raw/
108
+ wiki/
109
+ meta/
110
+ .wiki/
111
+ WIKI_SCHEMA.md
112
+ ```
44
113
 
45
- ## Features
114
+ ### 2) Capture a source
46
115
 
47
- - **Configurable PDF extraction** — MarkItDown timeout adjustable via `WIKI_MARKITDOWN_TIMEOUT_MS` env var
48
- - **Smart content detection** PDF bytes sniffed even from non-`.pdf` URLs, never written as markdown
49
- - **Original artifacts preserved** — URL captures save the fetched payload under `original/source.*`
50
- - **Clickable source links** — Captured URLs render as clickable Markdown links in source pages
51
- - **Reliable prompt forwarding** — Slash commands properly forward user arguments to the model
116
+ ```
117
+ Capture this article into the wiki: https://example.com/some-article
118
+ ```
119
+
120
+ ```
121
+ Capture this PDF into the wiki: ./papers/context-windows.pdf
122
+ ```
123
+
124
+ ```
125
+ Capture these notes into the wiki: ...pasted text...
126
+ ```
127
+
128
+ ### 3) Integrate the source
129
+
130
+ 1. Capture the source
131
+ 2. Read `wiki/sources/SRC-*.md`
132
+ 3. Update that source page
133
+ 4. Search for impacted canonical pages with `wiki_search`
134
+ 5. Create missing pages with `wiki_ensure_page`
135
+ 6. Update concept / entity / synthesis pages with citations
136
+ 7. Mark the integration with `wiki_log_event kind=integrate`
137
+
138
+ ### 4) Query the wiki
139
+
140
+ ```
141
+ Based on the wiki, what are the main tradeoffs between long-context models and RAG?
142
+ ```
143
+
144
+ By default, query mode is **read-only**. To file a durable answer:
145
+
146
+ ```
147
+ Answer the question and file the result as an analysis page.
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Vault Layout
153
+
154
+ ```
155
+ my-wiki/
156
+ ├─ raw/
157
+ │ └─ sources/
158
+ │ └─ SRC-2026-05-11-001/
159
+ │ ├─ manifest.json
160
+ │ ├─ original/ # Original artifact
161
+ │ ├─ extracted.md # Normalized text
162
+ │ └─ attachments/
163
+ ├─ wiki/
164
+ │ ├─ sources/ # Source pages (what each source says)
165
+ │ ├─ concepts/ # Concepts and recurring ideas
166
+ │ ├─ entities/ # People, orgs, products, papers, systems
167
+ │ ├─ syntheses/ # Cross-source theses and tensions
168
+ │ └─ analyses/ # Durable filed answers from queries
169
+ ├─ meta/
170
+ │ ├─ registry.json # Auto-generated search index
171
+ │ ├─ backlinks.json
172
+ │ ├─ index.md
173
+ │ ├─ events.jsonl # Append-only event log
174
+ │ ├─ log.md
175
+ │ └─ lint-report.md
176
+ ├─ .wiki/
177
+ │ ├─ config.json
178
+ │ └─ templates/
179
+ └─ WIKI_SCHEMA.md
180
+ ```
181
+
182
+ ### Ownership Model
183
+
184
+ | Path | Owner | Rule |
185
+ |------|-------|------|
186
+ | `raw/**` | Extension tools | Immutable after capture |
187
+ | `wiki/**` | Model + user | Editable knowledge pages |
188
+ | `meta/registry.json` | Extension | Generated |
189
+ | `meta/backlinks.json` | Extension | Generated |
190
+ | `meta/index.md` | Extension | Generated |
191
+ | `meta/events.jsonl` | Extension / tool | Append-only |
192
+ | `meta/log.md` | Extension | Generated from events |
193
+ | `meta/lint-report.md` | Extension | Generated |
194
+ | `WIKI_SCHEMA.md` | Human + explicit request | Operating manual |
195
+
196
+ ---
197
+
198
+ ## Linking & Citation Style
199
+
200
+ ### Internal Navigation
201
+
202
+ ```markdown
203
+ [[concepts/retrieval-augmented-generation]]
204
+ [[entities/openai|OpenAI]]
205
+ [[syntheses/long-context-vs-rag]]
206
+ ```
207
+
208
+ ### Factual Citations
209
+
210
+ ```markdown
211
+ [[sources/SRC-2026-04-04-001|SRC-2026-04-04-001]]
212
+ ```
213
+
214
+ Stable source-page IDs keep provenance stable even if titles change.
215
+
216
+ ---
217
+
218
+ ## Guardrails
219
+
220
+ The extension **blocks** direct tool-call edits to:
221
+
222
+ - `raw/**` — immutable source artifacts
223
+ - `meta/registry.json`
224
+ - `meta/backlinks.json`
225
+ - `meta/events.jsonl`
226
+ - `meta/index.md`
227
+ - `meta/log.md`
228
+ - `meta/lint-report.md`
229
+
230
+ If the model directly edits `wiki/**` using Pi's built-in `write` or `edit` tools, the extension **automatically rebuilds** generated metadata at the end of the agent turn.
231
+
232
+ ---
233
+
234
+ ## Source Packet Format
235
+
236
+ Each captured source is stored as a structured packet:
237
+
238
+ ```
239
+ raw/sources/SRC-YYYY-MM-DD-NNN/
240
+ ├─ manifest.json # Capture metadata (title, URL, format, timestamp)
241
+ ├─ original/ # Original artifact (preserved as-is)
242
+ ├─ extracted.md # Normalized text (PDF→md, XML→md, JSON→md, etc.)
243
+ └─ attachments/ # Future attachment downloads
244
+ ```
245
+
246
+ This preserves both the **original artifact** and a **normalized extracted view** for reading.
247
+
248
+ ---
249
+
250
+ ## Skill Behavior
251
+
252
+ The bundled `llm-wiki` skill teaches the model to:
253
+
254
+ - ❌ Never edit raw sources directly
255
+ - ❌ Never edit generated metadata files
256
+ - ✅ Capture first, integrate second
257
+ - ✅ Search before creating new canonical pages
258
+ - ✅ Cite facts using source-page IDs
259
+ - ✅ Keep query mode read-only by default
260
+ - ✅ Use "Tensions / caveats" and "Open questions" when evidence is mixed
261
+
262
+ ---
52
263
 
53
264
  ## Architecture
54
265
 
@@ -61,19 +272,27 @@ meta/ # Auto-generated registry, backlinks, index, log
61
272
  .wiki/ # Config and templates
62
273
  ```
63
274
 
64
- Read [docs/architecture.md](docs/architecture.md) for details.
275
+ Read [docs/architecture.md](docs/architecture.md) for the full design document.
276
+
277
+ ---
65
278
 
66
279
  ## Documentation
67
280
 
68
- - [Architecture](docs/architecture.md) How the four layers work
69
- - [Commands](docs/commands.md) — All slash commands and tools
70
- - [Obsidian Integration](docs/obsidian.md) Vault setup and recommended plugins
71
- - [Configuration](docs/configuration.md) Wiki modes, topics, settings
72
- - [API](docs/api.md) Extension tool reference
281
+ | Document | What it covers |
282
+ |----------|---------------|
283
+ | [Architecture](docs/architecture.md) | How the four layers work, ownership model |
284
+ | [Commands](docs/commands.md) | All slash commands and tool reference |
285
+ | [Obsidian Integration](docs/obsidian.md) | Vault setup and recommended plugins |
286
+ | [Configuration](docs/configuration.md) | Wiki modes, topics, environment variables |
287
+ | [API](docs/api.md) | Extension tool parameter reference |
288
+
289
+ ---
73
290
 
74
291
  ## Contributing
75
292
 
76
- See [CONTRIBUTING.md](CONTRIBUTING.md).
293
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, test patterns, and PR workflow.
294
+
295
+ ---
77
296
 
78
297
  ## Star History
79
298
 
@@ -88,7 +307,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
88
307
  ---
89
308
 
90
309
  <div align="center">
91
- <sub>Built with ❤️ by <a href="https://github.com/zosmaai">zosmaai</a></sub>
310
+ <sub>Built with ❤️ by <a href="https://github.com/zosmaai">zosmaai</a> · </sub>
311
+ <a href="https://pi.dev">pi.dev</a> · <a href="https://github.com/zosmaai/pi-llm-wiki/issues">Issues</a>
92
312
  </div>
93
313
 
94
314
  ## License
@@ -1,5 +1,9 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
4
  import { installGuardrails } from "./lib/guardrails.js";
5
+ import { formatRecallContext, registerWikiRecall, searchWiki } from "./lib/recall.js";
6
+ import { registerWikiRetro } from "./lib/retro.js";
3
7
  import {
4
8
  registerWikiBootstrap,
5
9
  registerWikiCaptureSource,
@@ -12,25 +16,22 @@ import {
12
16
  registerWikiStatus,
13
17
  registerWikiWatch,
14
18
  } from "./lib/tools.js";
19
+ import { getVaultPaths, resolveVaultRoot } from "./lib/utils.js";
15
20
 
16
21
  /**
17
22
  * @zosmaai/pi-llm-wiki — LLM Wiki extension for Pi
18
23
  *
19
- * Registers 10 custom tools and installs guardrails:
20
- * - wiki_bootstrap Initialize a new vault
21
- * - wiki_capture_source Capture URL/file/text into source packet
22
- * - wiki_ingest Get batch of sources needing synthesis
23
- * - wiki_ensure_page Create canonical page from template
24
- * - wiki_search Search generated registry
25
- * - wiki_lint Health check with auto-fix
26
- * - wiki_status Instant stats from registry
27
- * - wiki_rebuild_meta Force metadata rebuild
28
- * - wiki_log_event Append event and regenerate log
29
- * - wiki_watch Schedule auto-updates
24
+ * Registers 11 custom tools and installs guardrails:
25
+ * All 10 original tools + wiki_recall (auto-recall at session start)
30
26
  *
31
27
  * Guardrails:
32
28
  * - Blocks direct edits to raw/** and meta/**
33
29
  * - Auto-rebuilds metadata after wiki/** edits
30
+ *
31
+ * Auto-recall:
32
+ * - before_agent_start hook searches wiki for pages relevant to user prompt
33
+ * - Injects matching knowledge as system context
34
+ * - wiki_recall tool available for explicit deep searches
34
35
  */
35
36
 
36
37
  export default function (pi: ExtensionAPI) {
@@ -44,10 +45,36 @@ export default function (pi: ExtensionAPI) {
44
45
  registerWikiRebuildMeta(pi);
45
46
  registerWikiLogEvent(pi);
46
47
  registerWikiWatch(pi);
48
+ registerWikiRecall(pi);
49
+ registerWikiRetro(pi);
47
50
 
48
51
  installGuardrails(pi);
49
52
 
50
53
  pi.on("session_start", async (_event, ctx) => {
51
- ctx.ui.setStatus("llm-wiki", "🧠 LLM Wiki (10 tools, guardrails active)");
54
+ ctx.ui.setStatus("llm-wiki", "🧠 LLM Wiki (11 tools, auto-recall active)");
55
+ });
56
+
57
+ // ─── Auto-recall hook ──────────────────────────────
58
+ // Before each agent turn, search the wiki for pages relevant
59
+ // to the user's prompt and inject them as system context.
60
+ pi.on("before_agent_start", async (event, _ctx) => {
61
+ const root = resolveVaultRoot(process.cwd());
62
+ if (!existsSync(join(root, ".wiki", "config.json"))) {
63
+ return; // No wiki vault — nothing to recall
64
+ }
65
+
66
+ const paths = getVaultPaths(root);
67
+ const prompt = event.prompt || "";
68
+ if (!prompt.trim()) return;
69
+
70
+ const results = searchWiki(paths, prompt);
71
+ if (results.length === 0) return;
72
+
73
+ const context = formatRecallContext(results);
74
+ if (!context) return;
75
+
76
+ return {
77
+ systemPrompt: `${event.systemPrompt}\n\n${context}`,
78
+ };
52
79
  });
53
80
  }
@@ -0,0 +1,208 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
+ import { Type } from "typebox";
5
+ import type { Registry } from "./metadata.js";
6
+ import { type VaultPaths, getVaultPaths, readJson, readText, resolveVaultRoot } from "./utils.js";
7
+
8
+ // ─── Public API ────────────────────────────────────────
9
+
10
+ export interface RecallResult {
11
+ /** Page identifier (folder-qualified, e.g. "concepts/rag") */
12
+ id: string;
13
+ /** Page title */
14
+ title: string;
15
+ /** Page type: source, entity, concept, synthesis, analysis */
16
+ type: string;
17
+ /** First N chars of page content for context */
18
+ preview: string;
19
+ /** Relative path from wiki root */
20
+ path: string;
21
+ }
22
+
23
+ /**
24
+ * Search the wiki registry for pages matching a query.
25
+ * Returns up to `maxResults` matches, each with a content preview.
26
+ */
27
+ export function searchWiki(paths: VaultPaths, query: string, maxResults = 5): RecallResult[] {
28
+ const registry = readJson<Registry>(join(paths.meta, "registry.json"), {
29
+ version: "1.0",
30
+ last_updated: "",
31
+ pages: {},
32
+ });
33
+
34
+ const q = query.toLowerCase();
35
+ const terms = q
36
+ .split(/\s+/)
37
+ .filter((t) => t.length > 2)
38
+ .slice(0, 10);
39
+
40
+ if (terms.length === 0) return [];
41
+
42
+ type Scored = { id: string; entry: Registry["pages"][string]; score: number };
43
+ const scored: Scored[] = [];
44
+
45
+ for (const [id, entry] of Object.entries(registry.pages)) {
46
+ let score = 0;
47
+ const title = String(entry.title || "").toLowerCase();
48
+ const type = String(entry.type || "").toLowerCase();
49
+
50
+ for (const term of terms) {
51
+ if (id.toLowerCase().includes(term)) score += 3;
52
+ if (title.includes(term)) score += 4;
53
+ if (type.includes(term)) score += 1;
54
+ }
55
+
56
+ // Boost if query terms appear in tags/categories
57
+ const tags = String(entry.tags || entry.category || entry.domain || "");
58
+ for (const term of terms) {
59
+ if (tags.toLowerCase().includes(term)) score += 2;
60
+ }
61
+
62
+ if (score > 0) {
63
+ scored.push({ id, entry, score });
64
+ }
65
+ }
66
+
67
+ scored.sort((a, b) => b.score - a.score);
68
+ const top = scored.slice(0, maxResults);
69
+
70
+ return top.map(({ id, entry }) => {
71
+ // Try to read page content for preview
72
+ let preview = "";
73
+ const pagePath = join(paths.wiki, `${id}.md`);
74
+ if (existsSync(pagePath)) {
75
+ const content = readFileSync(pagePath, "utf-8");
76
+ // Strip frontmatter
77
+ const body = content.replace(/^---[\s\S]*?---\n/, "").trim();
78
+ preview = body.slice(0, 200).replace(/\n/g, " ");
79
+ }
80
+
81
+ return {
82
+ id,
83
+ title: String(entry.title || id),
84
+ type: String(entry.type || "page"),
85
+ preview,
86
+ path: pagePath,
87
+ };
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Format recall results as a compact system-prompt section.
93
+ */
94
+ export function formatRecallContext(results: RecallResult[]): string {
95
+ if (results.length === 0) return "";
96
+
97
+ const lines: string[] = [
98
+ "## Relevant Wiki Knowledge",
99
+ "",
100
+ `_${results.length} page(s) matched your query — reviewed automatically by LLM Wiki._`,
101
+ "",
102
+ ];
103
+
104
+ for (const r of results) {
105
+ lines.push(`- **[[${r.id}]]** — *${r.type}* — ${r.title}`);
106
+ if (r.preview) {
107
+ // Truncate preview to one line
108
+ const preview = r.preview.length > 120 ? `${r.preview.slice(0, 120)}…` : r.preview;
109
+ lines.push(` ${preview}`);
110
+ }
111
+ lines.push("");
112
+ }
113
+
114
+ lines.push(
115
+ "Use `read` to view full pages. Add new findings via wiki_ensure_page or wiki_retro.",
116
+ "",
117
+ );
118
+
119
+ return lines.join("\n");
120
+ }
121
+
122
+ // ─── Tool Registration ──────────────────────────────────
123
+
124
+ /**
125
+ * Register the `wiki_recall` tool.
126
+ * The model can call this explicitly to search the wiki.
127
+ * It is also called automatically via before_agent_start hook.
128
+ */
129
+ export function registerWikiRecall(pi: ExtensionAPI): void {
130
+ pi.registerTool({
131
+ name: "wiki_recall",
132
+ label: "Wiki Recall",
133
+ description:
134
+ "Search the wiki for pages relevant to a query. " +
135
+ "Returns matching page IDs, titles, types, and content previews. " +
136
+ "Called automatically at session start — use explicitly to dig deeper.",
137
+ promptSnippet: "Recall wiki knowledge relevant to the current task",
138
+ promptGuidelines: [
139
+ "Use wiki_recall at the START of every task to find relevant wiki knowledge.",
140
+ "The extension auto-calls wiki_recall — but calling it explicitly with specific terms gets better results.",
141
+ ],
142
+ parameters: Type.Object({
143
+ query: Type.String({
144
+ description: "Search query — use the user's full request or key terms",
145
+ }),
146
+ max_results: Type.Optional(
147
+ Type.Number({ description: "Max results (default: 5, max: 10)", default: 5 }),
148
+ ),
149
+ }),
150
+ async execute(_toolCallId, params) {
151
+ const root = resolveVaultRoot(process.cwd());
152
+ const paths = getVaultPaths(root);
153
+
154
+ if (!existsSync(join(root, ".wiki", "config.json"))) {
155
+ return {
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: "No wiki vault found at this location. Initialize one with wiki_bootstrap first.",
160
+ },
161
+ ],
162
+ details: { error: "no_vault" } as Record<string, unknown>,
163
+ isError: true,
164
+ };
165
+ }
166
+
167
+ const maxResults = Math.min(params.max_results ?? 5, 10);
168
+ const results = searchWiki(paths, params.query, maxResults);
169
+
170
+ if (results.length === 0) {
171
+ return {
172
+ content: [
173
+ {
174
+ type: "text",
175
+ text: `No wiki pages found matching "${params.query}". Use wiki_search for broader results.`,
176
+ },
177
+ ],
178
+ details: { query: params.query, matches: [] } as Record<string, unknown>,
179
+ };
180
+ }
181
+
182
+ return {
183
+ content: [
184
+ {
185
+ type: "text",
186
+ text: [
187
+ `🧠 **${results.length} wiki page(s) relevant** to "${params.query}":`,
188
+ "",
189
+ ...results.map(
190
+ (r) =>
191
+ `- [[${r.id}]] — *${r.type}* — ${r.title}${
192
+ r.preview ? `\n > ${r.preview.slice(0, 150)}` : ""
193
+ }`,
194
+ ),
195
+ "",
196
+ "Use `read` on any page for full content.",
197
+ "Use `wiki_retro` to save new insights from this task.",
198
+ ].join("\n"),
199
+ },
200
+ ],
201
+ details: { query: params.query, matches: results.map((r) => r.id) } as Record<
202
+ string,
203
+ unknown
204
+ >,
205
+ };
206
+ },
207
+ });
208
+ }