@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 +39 -0
- package/README.md +254 -34
- package/extensions/llm-wiki/index.ts +39 -12
- package/extensions/llm-wiki/lib/recall.ts +208 -0
- package/extensions/llm-wiki/lib/retro.ts +203 -0
- package/mcp/index.ts +463 -0
- package/package.json +33 -4
- package/skills/llm-wiki/SKILL.md +45 -10
- package/.coderabbit.yaml +0 -43
- package/.github/codeql/codeql-config.yml +0 -12
- package/.github/workflows/ci.yml +0 -45
- package/.github/workflows/codeql.yml +0 -39
- package/.github/workflows/release.yml +0 -86
- package/AGENTS.md +0 -57
- package/CONTRIBUTING.md +0 -43
- package/biome.json +0 -30
- package/scripts/release.js +0 -72
- package/test/llm-wiki.test.ts +0 -724
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -16
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
|
[](https://github.com/zosmaai/pi-llm-wiki/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@zosmaai/pi-llm-wiki)
|
|
5
|
+
[](https://www.npmjs.com/package/@zosmaai/pi-llm-wiki)
|
|
5
6
|
[](https://codecov.io/gh/zosmaai/pi-llm-wiki)
|
|
6
7
|
[](LICENSE)
|
|
7
8
|
[](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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
97
|
+
|
|
98
|
+
Ask pi:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Initialize an llm wiki here for AI research.
|
|
28
102
|
```
|
|
29
103
|
|
|
30
|
-
|
|
104
|
+
This calls `wiki_bootstrap` and creates:
|
|
31
105
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
114
|
+
### 2) Capture a source
|
|
46
115
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
275
|
+
Read [docs/architecture.md](docs/architecture.md) for the full design document.
|
|
276
|
+
|
|
277
|
+
---
|
|
65
278
|
|
|
66
279
|
## Documentation
|
|
67
280
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
|
20
|
-
* -
|
|
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 (
|
|
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
|
+
}
|