@zosmaai/pi-llm-wiki 0.10.7 → 0.11.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 +4 -0
- package/README.de.md +35 -4
- package/README.es.md +260 -170
- package/README.fr.md +35 -4
- package/README.hi.md +35 -4
- package/README.ja.md +35 -4
- package/README.ko.md +35 -4
- package/README.md +38 -3
- package/README.pt.md +35 -4
- package/README.ru.md +35 -4
- package/README.zh.md +260 -170
- package/assets/demo.gif +0 -0
- package/dist/extensions/llm-wiki/lib/bootstrap.js +71 -0
- package/dist/extensions/llm-wiki/lib/embeddings.js +401 -0
- package/dist/extensions/llm-wiki/lib/guardrails.js +232 -0
- package/dist/extensions/llm-wiki/lib/indexing.js +78 -0
- package/dist/extensions/llm-wiki/lib/ingest-worker.js +310 -0
- package/dist/extensions/llm-wiki/lib/inject.js +65 -0
- package/dist/extensions/llm-wiki/lib/knowledge-document.js +442 -0
- package/dist/extensions/llm-wiki/lib/knowledge-links.js +206 -0
- package/dist/extensions/llm-wiki/lib/legacy-repair.js +443 -0
- package/dist/extensions/llm-wiki/lib/metadata.js +499 -0
- package/dist/extensions/llm-wiki/lib/model-command.js +86 -0
- package/dist/extensions/llm-wiki/lib/observation.js +283 -0
- package/dist/extensions/llm-wiki/lib/recall.js +875 -0
- package/dist/extensions/llm-wiki/lib/retro.js +158 -0
- package/dist/extensions/llm-wiki/lib/runtime.js +191 -0
- package/dist/extensions/llm-wiki/lib/source-extractors.js +426 -0
- package/dist/extensions/llm-wiki/lib/source-packet.js +229 -0
- package/dist/extensions/llm-wiki/lib/subagent.js +41 -0
- package/dist/extensions/llm-wiki/lib/task-config.js +172 -0
- package/dist/extensions/llm-wiki/lib/tools.js +1192 -0
- package/dist/extensions/llm-wiki/lib/trajectories-command.js +51 -0
- package/dist/extensions/llm-wiki/lib/trajectory.js +467 -0
- package/dist/extensions/llm-wiki/lib/utils.js +347 -0
- package/dist/extensions/llm-wiki/lib/vault-format.js +247 -0
- package/dist/extensions/llm-wiki/lib/visible-status.js +31 -0
- package/dist/extensions/llm-wiki/lib/wiki-service.js +128 -0
- package/dist/mcp/exec.js +121 -0
- package/dist/mcp/index.js +229 -0
- package/dist/mcp/operations.js +130 -0
- package/dist/package.json +1 -0
- package/docs/superpowers/plans/2026-08-02-okf-foundation.md +1579 -0
- package/docs/superpowers/plans/2026-08-03-okf-foundation-remediation.md +3005 -0
- package/docs/superpowers/plans/2026-08-06-okf-foundation-release-remediation.md +1174 -0
- package/docs/superpowers/specs/2026-08-02-okf-foundation-design.md +578 -0
- package/docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md +538 -0
- package/extensions/llm-wiki/index.ts +22 -36
- package/extensions/llm-wiki/lib/bootstrap.ts +84 -0
- package/extensions/llm-wiki/lib/embeddings.ts +9 -3
- package/extensions/llm-wiki/lib/guardrails.ts +174 -29
- package/extensions/llm-wiki/lib/indexing.ts +2 -1
- package/extensions/llm-wiki/lib/ingest-worker.ts +170 -29
- package/extensions/llm-wiki/lib/knowledge-document.ts +661 -0
- package/extensions/llm-wiki/lib/knowledge-links.ts +282 -0
- package/extensions/llm-wiki/lib/legacy-repair.ts +572 -0
- package/extensions/llm-wiki/lib/metadata.ts +531 -116
- package/extensions/llm-wiki/lib/observation.ts +37 -43
- package/extensions/llm-wiki/lib/recall.ts +61 -33
- package/extensions/llm-wiki/lib/retro.ts +65 -41
- package/extensions/llm-wiki/lib/source-extractors.ts +12 -17
- package/extensions/llm-wiki/lib/source-packet.ts +44 -31
- package/extensions/llm-wiki/lib/tools.ts +406 -348
- package/extensions/llm-wiki/lib/trajectory.ts +15 -1
- package/extensions/llm-wiki/lib/utils.ts +121 -130
- package/extensions/llm-wiki/lib/vault-format.ts +363 -0
- package/extensions/llm-wiki/lib/wiki-service.ts +183 -0
- package/mcp/exec.ts +122 -0
- package/mcp/index.ts +60 -250
- package/mcp/operations.ts +176 -0
- package/package.json +8 -2
- package/scripts/migrate-llm-wiki.js +801 -0
- package/skills/llm-wiki/SKILL.md +8 -6
package/mcp/index.ts
CHANGED
|
@@ -4,84 +4,34 @@
|
|
|
4
4
|
* LLM Wiki MCP Server
|
|
5
5
|
*
|
|
6
6
|
* Exposes wiki tools over the Model Context Protocol (MCP).
|
|
7
|
-
* Run: node mcp/index.js
|
|
8
|
-
* Or via package.json: pi install npm:@zosmaai/pi-llm-wiki && node mcp/index.js
|
|
7
|
+
* Run: node dist/mcp/index.js
|
|
9
8
|
*
|
|
10
9
|
* Environment:
|
|
11
10
|
* WIKI_ROOT — path to wiki vault (default: auto-detect from cwd)
|
|
12
|
-
* WIKI_MARKITDOWN_TIMEOUT_MS — PDF extraction timeout (default: 180000)
|
|
13
11
|
*/
|
|
14
12
|
|
|
15
|
-
import { existsSync
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
16
14
|
import { join } from "node:path";
|
|
17
15
|
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
|
|
18
16
|
import * as z from "zod/v4";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return "none";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function resolveVaultRoot(cwd: string): string | null {
|
|
41
|
-
// Check cwd first
|
|
42
|
-
if (detectFormat(cwd) !== "none") return cwd;
|
|
43
|
-
|
|
44
|
-
// Walk up
|
|
45
|
-
const parts = cwd.split("/");
|
|
46
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
47
|
-
const dir = parts.slice(0, i + 1).join("/") || "/";
|
|
48
|
-
if (detectFormat(dir) !== "none") return dir;
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function getPaths(): VaultPaths {
|
|
54
|
-
const detectedRoot = resolveVaultRoot(process.cwd());
|
|
55
|
-
const root = process.env.WIKI_ROOT || detectedRoot || process.cwd();
|
|
56
|
-
const format = process.env.WIKI_ROOT
|
|
57
|
-
? detectFormat(root) // Use format detection even with explicit WIKI_ROOT
|
|
58
|
-
: detectedRoot
|
|
59
|
-
? detectFormat(root)
|
|
60
|
-
: "none";
|
|
61
|
-
|
|
62
|
-
if (format === "legacy") {
|
|
63
|
-
return {
|
|
64
|
-
root,
|
|
65
|
-
raw: join(root, "raw"),
|
|
66
|
-
rawSources: join(root, "raw", "sources"),
|
|
67
|
-
wiki: join(root, "wiki"),
|
|
68
|
-
meta: join(root, "meta"),
|
|
69
|
-
dotWiki: join(root, ".wiki"),
|
|
70
|
-
outputs: join(root, "outputs"),
|
|
71
|
-
discoveries: join(root, ".discoveries"),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
root,
|
|
77
|
-
raw: join(root, ".llm-wiki", "raw"),
|
|
78
|
-
rawSources: join(root, ".llm-wiki", "raw", "sources"),
|
|
79
|
-
wiki: join(root, ".llm-wiki", "wiki"),
|
|
80
|
-
meta: join(root, ".llm-wiki", "meta"),
|
|
81
|
-
dotWiki: join(root, ".llm-wiki"),
|
|
82
|
-
outputs: join(root, ".llm-wiki", "outputs"),
|
|
83
|
-
discoveries: join(root, ".llm-wiki", ".discoveries"),
|
|
84
|
-
};
|
|
17
|
+
import { resolveVaultPaths } from "../extensions/llm-wiki/lib/utils.js";
|
|
18
|
+
import { createExecApi } from "./exec.js";
|
|
19
|
+
import {
|
|
20
|
+
captureSourceOperation,
|
|
21
|
+
recallOperation,
|
|
22
|
+
retroOperation,
|
|
23
|
+
searchOperation,
|
|
24
|
+
statusOperation,
|
|
25
|
+
} from "./operations.js";
|
|
26
|
+
|
|
27
|
+
const execApi = createExecApi();
|
|
28
|
+
|
|
29
|
+
// ─── Vault Detection ────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/** Resolve vault paths, same as Pi extension. */
|
|
32
|
+
function getPaths(): ReturnType<typeof resolveVaultPaths> {
|
|
33
|
+
const root = process.env.WIKI_ROOT || process.cwd();
|
|
34
|
+
return resolveVaultPaths(root);
|
|
85
35
|
}
|
|
86
36
|
|
|
87
37
|
function hasVault(): boolean {
|
|
@@ -89,17 +39,6 @@ function hasVault(): boolean {
|
|
|
89
39
|
return existsSync(join(paths.dotWiki, "config.json"));
|
|
90
40
|
}
|
|
91
41
|
|
|
92
|
-
// ─── Helpers ────────────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
function readJson<T>(path: string, defaultVal: T): T {
|
|
95
|
-
try {
|
|
96
|
-
if (!existsSync(path)) return defaultVal;
|
|
97
|
-
return JSON.parse(readFileSync(path, "utf-8")) as T;
|
|
98
|
-
} catch {
|
|
99
|
-
return defaultVal;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
42
|
// ─── MCP Server ─────────────────────────────────────────
|
|
104
43
|
|
|
105
44
|
const server = new McpServer({
|
|
@@ -133,72 +72,16 @@ server.registerTool(
|
|
|
133
72
|
}
|
|
134
73
|
|
|
135
74
|
const paths = getPaths();
|
|
136
|
-
const
|
|
137
|
-
pages: Record<string, { type: string; title: string; [key: string]: unknown }>;
|
|
138
|
-
}>(join(paths.meta, "registry.json"), { pages: {} });
|
|
139
|
-
|
|
140
|
-
const terms = query
|
|
141
|
-
.toLowerCase()
|
|
142
|
-
.split(/\s+/)
|
|
143
|
-
.filter((t) => t.length > 2)
|
|
144
|
-
.slice(0, 10);
|
|
145
|
-
|
|
146
|
-
if (terms.length === 0) {
|
|
147
|
-
return {
|
|
148
|
-
content: [{ type: "text" as const, text: "Query too short." }],
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
type Scored = { id: string; score: number };
|
|
153
|
-
const scored: Scored[] = [];
|
|
154
|
-
|
|
155
|
-
for (const [id, entry] of Object.entries(registry.pages)) {
|
|
156
|
-
let score = 0;
|
|
157
|
-
const title = String(entry.title || "").toLowerCase();
|
|
158
|
-
const type = String(entry.type || "").toLowerCase();
|
|
159
|
-
|
|
160
|
-
for (const term of terms) {
|
|
161
|
-
if (id.toLowerCase().includes(term)) score += 3;
|
|
162
|
-
if (title.includes(term)) score += 4;
|
|
163
|
-
if (type.includes(term)) score += 1;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const tags = String(entry.tags || entry.category || entry.domain || "").toLowerCase();
|
|
167
|
-
for (const term of terms) {
|
|
168
|
-
if (tags.includes(term)) score += 2;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (score > 0) scored.push({ id, score });
|
|
172
|
-
}
|
|
75
|
+
const result = await recallOperation(paths, query, Math.min(max_results ?? 5, 10));
|
|
173
76
|
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const results = top.map(({ id }) => {
|
|
178
|
-
const entry = registry.pages[id];
|
|
179
|
-
let preview = "";
|
|
180
|
-
const pagePath = join(paths.wiki, `${id}.md`);
|
|
181
|
-
if (existsSync(pagePath)) {
|
|
182
|
-
const content = readFileSync(pagePath, "utf-8");
|
|
183
|
-
preview = content
|
|
184
|
-
.replace(/^---[\s\S]*?---\n/, "")
|
|
185
|
-
.trim()
|
|
186
|
-
.slice(0, 200)
|
|
187
|
-
.replace(/\n/g, " ");
|
|
188
|
-
}
|
|
189
|
-
return {
|
|
190
|
-
id,
|
|
191
|
-
title: String(entry?.title || id),
|
|
192
|
-
type: String(entry?.type || "page"),
|
|
193
|
-
preview,
|
|
194
|
-
};
|
|
195
|
-
});
|
|
77
|
+
const detailLines = result.diagnostics.map((d) => `⚠️ ${d.code}: ${d.message}`);
|
|
78
|
+
const detailText = detailLines.length > 0 ? `\n\n${detailLines.join("\n")}` : "";
|
|
196
79
|
|
|
197
80
|
return {
|
|
198
81
|
content: [
|
|
199
82
|
{
|
|
200
83
|
type: "text" as const,
|
|
201
|
-
text: JSON.stringify(results, null, 2),
|
|
84
|
+
text: JSON.stringify(result.results, null, 2) + detailText,
|
|
202
85
|
},
|
|
203
86
|
],
|
|
204
87
|
};
|
|
@@ -233,32 +116,19 @@ server.registerTool(
|
|
|
233
116
|
}
|
|
234
117
|
|
|
235
118
|
const paths = getPaths();
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const matchesQuery =
|
|
244
|
-
id.toLowerCase().includes(q) ||
|
|
245
|
-
String(entry.title).toLowerCase().includes(q) ||
|
|
246
|
-
String(entry.type).toLowerCase().includes(q);
|
|
247
|
-
const matchesType = !type || String(entry.type).toLowerCase() === type.toLowerCase();
|
|
248
|
-
return matchesQuery && matchesType;
|
|
249
|
-
})
|
|
250
|
-
.map(([id, entry]) => ({
|
|
251
|
-
id,
|
|
252
|
-
title: entry.title,
|
|
253
|
-
type: entry.type,
|
|
254
|
-
}));
|
|
119
|
+
const result = await searchOperation(paths, query, type);
|
|
120
|
+
|
|
121
|
+
if (result.matches.length === 0) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: "text" as const, text: `No pages found for "${query}"` }],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
255
126
|
|
|
256
127
|
return {
|
|
257
128
|
content: [
|
|
258
129
|
{
|
|
259
130
|
type: "text" as const,
|
|
260
|
-
text:
|
|
261
|
-
matches.length > 0 ? JSON.stringify(matches, null, 2) : `No pages found for "${query}"`,
|
|
131
|
+
text: JSON.stringify(result.matches, null, 2),
|
|
262
132
|
},
|
|
263
133
|
],
|
|
264
134
|
};
|
|
@@ -287,38 +157,13 @@ server.registerTool(
|
|
|
287
157
|
}
|
|
288
158
|
|
|
289
159
|
const paths = getPaths();
|
|
290
|
-
const
|
|
291
|
-
version: string;
|
|
292
|
-
last_updated: string;
|
|
293
|
-
pages: Record<string, { type: string; title: string; [key: string]: unknown }>;
|
|
294
|
-
}>(join(paths.meta, "registry.json"), {
|
|
295
|
-
version: "1.0",
|
|
296
|
-
last_updated: "",
|
|
297
|
-
pages: {},
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const config = readJson<Record<string, unknown>>(join(paths.dotWiki, "config.json"), {});
|
|
301
|
-
|
|
302
|
-
const byType: Record<string, number> = {};
|
|
303
|
-
for (const entry of Object.values(registry.pages)) {
|
|
304
|
-
byType[entry.type] = (byType[entry.type] || 0) + 1;
|
|
305
|
-
}
|
|
160
|
+
const status = await statusOperation(paths);
|
|
306
161
|
|
|
307
162
|
return {
|
|
308
163
|
content: [
|
|
309
164
|
{
|
|
310
165
|
type: "text" as const,
|
|
311
|
-
text: JSON.stringify(
|
|
312
|
-
{
|
|
313
|
-
topic: config.topic || "Unknown",
|
|
314
|
-
mode: config.mode || "personal",
|
|
315
|
-
totalPages: Object.keys(registry.pages).length,
|
|
316
|
-
byType,
|
|
317
|
-
lastUpdated: registry.last_updated || "Never",
|
|
318
|
-
},
|
|
319
|
-
null,
|
|
320
|
-
2,
|
|
321
|
-
),
|
|
166
|
+
text: JSON.stringify(status, null, 2),
|
|
322
167
|
},
|
|
323
168
|
],
|
|
324
169
|
};
|
|
@@ -331,15 +176,11 @@ server.registerTool(
|
|
|
331
176
|
"wiki_retro",
|
|
332
177
|
{
|
|
333
178
|
description:
|
|
334
|
-
"Save an atomic insight from a completed task into the wiki. Creates a source
|
|
179
|
+
"Save an atomic insight from a completed task into the wiki. Creates a source page.",
|
|
335
180
|
inputSchema: z.object({
|
|
336
181
|
slug: z.string().describe("Unique kebab-case identifier (e.g. 'jwt-revocation-pattern')"),
|
|
337
182
|
title: z.string().describe("Short descriptive title (60 chars max)"),
|
|
338
|
-
body: z
|
|
339
|
-
.string()
|
|
340
|
-
.describe(
|
|
341
|
-
"Markdown body explaining what was learned. Include [[wikilinks]] to related pages.",
|
|
342
|
-
),
|
|
183
|
+
body: z.string().describe("Markdown body explaining what was learned."),
|
|
343
184
|
category: z
|
|
344
185
|
.string()
|
|
345
186
|
.optional()
|
|
@@ -359,24 +200,26 @@ server.registerTool(
|
|
|
359
200
|
};
|
|
360
201
|
}
|
|
361
202
|
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
paths: Record<string, string>,
|
|
365
|
-
slug: string,
|
|
366
|
-
title: string,
|
|
367
|
-
body: string,
|
|
368
|
-
category?: string,
|
|
369
|
-
) => { sourceId: string; packetPath: string; sourcePagePath: string };
|
|
370
|
-
};
|
|
203
|
+
const paths = getPaths();
|
|
204
|
+
const result = await retroOperation(paths, slug, title, body, category);
|
|
371
205
|
|
|
372
|
-
|
|
373
|
-
|
|
206
|
+
if (!result.ok) {
|
|
207
|
+
return {
|
|
208
|
+
content: [
|
|
209
|
+
{
|
|
210
|
+
type: "text" as const,
|
|
211
|
+
text: `Vault error: ${result.diagnostics[0].message}`,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
374
217
|
|
|
375
218
|
return {
|
|
376
219
|
content: [
|
|
377
220
|
{
|
|
378
221
|
type: "text" as const,
|
|
379
|
-
text: `Insight saved: ${result.
|
|
222
|
+
text: `Insight saved: ${result.slug} — ${title}`,
|
|
380
223
|
},
|
|
381
224
|
],
|
|
382
225
|
};
|
|
@@ -396,7 +239,7 @@ server.registerTool(
|
|
|
396
239
|
title: z.string().optional().describe("Title for the captured source"),
|
|
397
240
|
}),
|
|
398
241
|
},
|
|
399
|
-
async ({ text, url
|
|
242
|
+
async ({ text, url, file_path, title }) => {
|
|
400
243
|
if (!hasVault()) {
|
|
401
244
|
return {
|
|
402
245
|
content: [
|
|
@@ -409,52 +252,19 @@ server.registerTool(
|
|
|
409
252
|
};
|
|
410
253
|
}
|
|
411
254
|
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
url: string,
|
|
421
|
-
signal?: AbortSignal,
|
|
422
|
-
) => Promise<{ sourceId: string }>;
|
|
423
|
-
};
|
|
424
|
-
result = await captureUrl(
|
|
425
|
-
{ exec: async () => ({ stdout: "", stderr: "", code: 0 }) } as never,
|
|
426
|
-
vaultPaths,
|
|
427
|
-
urlParam,
|
|
428
|
-
);
|
|
429
|
-
} else if (file_path) {
|
|
430
|
-
const { captureFile } = (await import("../extensions/llm-wiki/lib/source-packet.js")) as {
|
|
431
|
-
captureFile: (
|
|
432
|
-
pi: never,
|
|
433
|
-
paths: Record<string, string>,
|
|
434
|
-
filePath: string,
|
|
435
|
-
signal?: AbortSignal,
|
|
436
|
-
) => Promise<{ sourceId: string }>;
|
|
437
|
-
};
|
|
438
|
-
result = await captureFile(
|
|
439
|
-
{ exec: async () => ({ stdout: "", stderr: "", code: 0 }) } as never,
|
|
440
|
-
vaultPaths,
|
|
441
|
-
file_path,
|
|
442
|
-
);
|
|
443
|
-
} else if (text) {
|
|
444
|
-
const { captureText } = (await import("../extensions/llm-wiki/lib/source-packet.js")) as {
|
|
445
|
-
captureText: (
|
|
446
|
-
paths: Record<string, string>,
|
|
447
|
-
text: string,
|
|
448
|
-
title?: string,
|
|
449
|
-
) => { sourceId: string };
|
|
450
|
-
};
|
|
451
|
-
result = captureText(vaultPaths, text, title);
|
|
452
|
-
} else {
|
|
255
|
+
const paths = getPaths();
|
|
256
|
+
const result = await captureSourceOperation(
|
|
257
|
+
paths,
|
|
258
|
+
{ text, url, filePath: file_path, title },
|
|
259
|
+
execApi,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (!result.ok) {
|
|
453
263
|
return {
|
|
454
264
|
content: [
|
|
455
265
|
{
|
|
456
266
|
type: "text" as const,
|
|
457
|
-
text:
|
|
267
|
+
text: result.diagnostics[0].message,
|
|
458
268
|
},
|
|
459
269
|
],
|
|
460
270
|
isError: true,
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP operation adapters over shared wiki services.
|
|
3
|
+
*
|
|
4
|
+
* Each operation is a thin, testable wrapper around the same services
|
|
5
|
+
* used by Pi tools. No operation parses YAML, scans files, scores
|
|
6
|
+
* registry entries, or builds page strings itself.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
10
|
+
import { type ProjectionResult, rebuildMetadata } from "../extensions/llm-wiki/lib/metadata.js";
|
|
11
|
+
import { searchWiki } from "../extensions/llm-wiki/lib/recall.js";
|
|
12
|
+
import { saveInsight } from "../extensions/llm-wiki/lib/retro.js";
|
|
13
|
+
import { captureFile, captureText, captureUrl } from "../extensions/llm-wiki/lib/source-packet.js";
|
|
14
|
+
import type { VaultPaths } from "../extensions/llm-wiki/lib/utils.js";
|
|
15
|
+
import {
|
|
16
|
+
VaultWriteError,
|
|
17
|
+
inspectVaultFormat,
|
|
18
|
+
inspectWritableVault,
|
|
19
|
+
} from "../extensions/llm-wiki/lib/vault-format.js";
|
|
20
|
+
import { getWikiStatus, searchRegistry } from "../extensions/llm-wiki/lib/wiki-service.js";
|
|
21
|
+
|
|
22
|
+
function projectionOutcome(
|
|
23
|
+
projection: ProjectionResult,
|
|
24
|
+
): { ok: true } | { ok: false; diagnostics: Array<{ code: string; message: string }> } {
|
|
25
|
+
return projection.ok
|
|
26
|
+
? { ok: true }
|
|
27
|
+
: {
|
|
28
|
+
ok: false,
|
|
29
|
+
diagnostics: projection.diagnostics.map(({ code, message }) => ({ code, message })),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Shared recall operation: calls searchWiki and appends vault diagnostics. */
|
|
34
|
+
export async function recallOperation(
|
|
35
|
+
paths: VaultPaths,
|
|
36
|
+
query: string,
|
|
37
|
+
maxResults = 5,
|
|
38
|
+
): Promise<{
|
|
39
|
+
results: Array<{ id: string; title: string; type: string; preview?: string }>;
|
|
40
|
+
diagnostics: Array<{ code: string; message: string }>;
|
|
41
|
+
}> {
|
|
42
|
+
const results = searchWiki(paths, query, maxResults);
|
|
43
|
+
const vaultState = inspectVaultFormat(paths);
|
|
44
|
+
return {
|
|
45
|
+
results,
|
|
46
|
+
diagnostics: vaultState.diagnostics.map((d) => ({ code: d.code, message: d.message })),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Shared search operation: delegates directly to wiki-service. */
|
|
51
|
+
export async function searchOperation(
|
|
52
|
+
paths: VaultPaths,
|
|
53
|
+
query: string,
|
|
54
|
+
type?: string,
|
|
55
|
+
): Promise<{
|
|
56
|
+
matches: Array<{ id: string; title: string; type: string }>;
|
|
57
|
+
diagnostics: Array<{ code: string; message: string }>;
|
|
58
|
+
}> {
|
|
59
|
+
const result = searchRegistry(paths, query, type);
|
|
60
|
+
return {
|
|
61
|
+
matches: result.matches,
|
|
62
|
+
diagnostics: result.diagnostics.map((d) => ({ code: d.code, message: d.message })),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Shared status operation: delegates directly to wiki-service. */
|
|
67
|
+
export async function statusOperation(paths: VaultPaths): Promise<{
|
|
68
|
+
knowledgeFormat: string;
|
|
69
|
+
totalPages: number;
|
|
70
|
+
byType: Record<string, number>;
|
|
71
|
+
blockingDiagnostics: Array<{ code: string; message: string }>;
|
|
72
|
+
lastUpdated: string;
|
|
73
|
+
}> {
|
|
74
|
+
const status = getWikiStatus(paths);
|
|
75
|
+
return {
|
|
76
|
+
knowledgeFormat: status.knowledgeFormat,
|
|
77
|
+
totalPages: status.totalPages,
|
|
78
|
+
byType: status.byType,
|
|
79
|
+
blockingDiagnostics: status.blockingDiagnostics.map((d) => ({
|
|
80
|
+
code: d.code,
|
|
81
|
+
message: d.message,
|
|
82
|
+
})),
|
|
83
|
+
lastUpdated: status.lastUpdated,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Shared retro operation: validates vault then delegates to saveInsight. */
|
|
88
|
+
export async function retroOperation(
|
|
89
|
+
paths: VaultPaths,
|
|
90
|
+
slug: string,
|
|
91
|
+
title: string,
|
|
92
|
+
body: string,
|
|
93
|
+
category?: string,
|
|
94
|
+
): Promise<
|
|
95
|
+
| { ok: true; slug: string; sourcePagePath: string }
|
|
96
|
+
| { ok: false; diagnostics: Array<{ code: string; message: string }> }
|
|
97
|
+
> {
|
|
98
|
+
const vaultCheck = inspectWritableVault(paths);
|
|
99
|
+
if (!vaultCheck.ok) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
diagnostics: vaultCheck.diagnostics.map((d) => ({ code: d.code, message: d.message })),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const result = saveInsight(paths, slug, title, body, category, { rebuild: false });
|
|
107
|
+
const projection = projectionOutcome(rebuildMetadata(paths));
|
|
108
|
+
if (!projection.ok) return projection;
|
|
109
|
+
return { ok: true, slug: result.slug, sourcePagePath: result.sourcePagePath };
|
|
110
|
+
} catch (error: unknown) {
|
|
111
|
+
if (error instanceof VaultWriteError) {
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
diagnostics: error.diagnostics.map((d) => ({ code: d.code, message: d.message })),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if ((error as Error).message.startsWith("Invalid insight slug:")) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
diagnostics: [{ code: "invalid_insight_slug", message: (error as Error).message }],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Shared capture operation: validates vault then delegates to capture functions. */
|
|
128
|
+
export async function captureSourceOperation(
|
|
129
|
+
paths: VaultPaths,
|
|
130
|
+
input: { text?: string; url?: string; filePath?: string; title?: string },
|
|
131
|
+
execApi: Pick<ExtensionAPI, "exec">,
|
|
132
|
+
): Promise<
|
|
133
|
+
| { ok: true; sourceId: string }
|
|
134
|
+
| { ok: false; diagnostics: Array<{ code: string; message: string }> }
|
|
135
|
+
> {
|
|
136
|
+
const vaultCheck = inspectWritableVault(paths);
|
|
137
|
+
if (!vaultCheck.ok) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
diagnostics: vaultCheck.diagnostics.map((d) => ({ code: d.code, message: d.message })),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
let sourceId: string;
|
|
146
|
+
if (input.url) {
|
|
147
|
+
sourceId = (await captureUrl(execApi, paths, input.url)).sourceId;
|
|
148
|
+
} else if (input.filePath) {
|
|
149
|
+
sourceId = (await captureFile(execApi, paths, input.filePath)).sourceId;
|
|
150
|
+
} else if (input.text) {
|
|
151
|
+
sourceId = captureText(paths, input.text, input.title).sourceId;
|
|
152
|
+
} else {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
diagnostics: [
|
|
156
|
+
{
|
|
157
|
+
code: "event_missing_kind" as const,
|
|
158
|
+
message: "Provide one of: text, url, or filePath",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const projection = projectionOutcome(rebuildMetadata(paths));
|
|
165
|
+
if (!projection.ok) return projection;
|
|
166
|
+
return { ok: true, sourceId };
|
|
167
|
+
} catch (error: unknown) {
|
|
168
|
+
if (error instanceof VaultWriteError) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
diagnostics: error.diagnostics.map((d) => ({ code: d.code, message: d.message })),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zosmaai/pi-llm-wiki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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",
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
"extensions",
|
|
38
38
|
"skills",
|
|
39
39
|
"prompts",
|
|
40
|
+
"scripts/migrate-llm-wiki.js",
|
|
40
41
|
"mcp",
|
|
42
|
+
"dist",
|
|
41
43
|
"docs",
|
|
42
44
|
"assets",
|
|
43
45
|
"README.md",
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
],
|
|
60
62
|
"image": "https://raw.githubusercontent.com/zosmaai/pi-llm-wiki/main/assets/screenshot.png",
|
|
61
63
|
"mcpservers": {
|
|
62
|
-
"llm-wiki": "node ./mcp/index.
|
|
64
|
+
"llm-wiki": "node ./dist/mcp/index.js"
|
|
63
65
|
}
|
|
64
66
|
},
|
|
65
67
|
"peerDependencies": {
|
|
@@ -72,7 +74,9 @@
|
|
|
72
74
|
"dependencies": {
|
|
73
75
|
"@cfworker/json-schema": "^4.1.1",
|
|
74
76
|
"@modelcontextprotocol/server": "^2.0.0-alpha.2",
|
|
77
|
+
"mdast-util-from-markdown": "^2.0.3",
|
|
75
78
|
"node-html-markdown": "^2.0.0",
|
|
79
|
+
"yaml": "^2.9.0",
|
|
76
80
|
"zod": "^4.0"
|
|
77
81
|
},
|
|
78
82
|
"devDependencies": {
|
|
@@ -81,6 +85,7 @@
|
|
|
81
85
|
"@mariozechner/pi-ai": "^0.70.2",
|
|
82
86
|
"@mariozechner/pi-coding-agent": "^0.70.2",
|
|
83
87
|
"@mermaid-js/mermaid-cli": "^11.12.0",
|
|
88
|
+
"@types/mdast": "^4.0.4",
|
|
84
89
|
"@types/node": "^22",
|
|
85
90
|
"@vitest/coverage-v8": "^3.2.4",
|
|
86
91
|
"typebox": "^1.1.34",
|
|
@@ -91,6 +96,7 @@
|
|
|
91
96
|
"test": "vitest run",
|
|
92
97
|
"test:watch": "vitest",
|
|
93
98
|
"test:coverage": "vitest run --coverage",
|
|
99
|
+
"build:mcp": "node scripts/build-mcp.js",
|
|
94
100
|
"typecheck": "tsc --noEmit",
|
|
95
101
|
"lint": "biome check .",
|
|
96
102
|
"lint:fix": "biome check --apply .",
|