@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.de.md +35 -4
  3. package/README.es.md +260 -170
  4. package/README.fr.md +35 -4
  5. package/README.hi.md +35 -4
  6. package/README.ja.md +35 -4
  7. package/README.ko.md +35 -4
  8. package/README.md +38 -3
  9. package/README.pt.md +35 -4
  10. package/README.ru.md +35 -4
  11. package/README.zh.md +260 -170
  12. package/assets/demo.gif +0 -0
  13. package/dist/extensions/llm-wiki/lib/bootstrap.js +71 -0
  14. package/dist/extensions/llm-wiki/lib/embeddings.js +401 -0
  15. package/dist/extensions/llm-wiki/lib/guardrails.js +232 -0
  16. package/dist/extensions/llm-wiki/lib/indexing.js +78 -0
  17. package/dist/extensions/llm-wiki/lib/ingest-worker.js +310 -0
  18. package/dist/extensions/llm-wiki/lib/inject.js +65 -0
  19. package/dist/extensions/llm-wiki/lib/knowledge-document.js +442 -0
  20. package/dist/extensions/llm-wiki/lib/knowledge-links.js +206 -0
  21. package/dist/extensions/llm-wiki/lib/legacy-repair.js +443 -0
  22. package/dist/extensions/llm-wiki/lib/metadata.js +499 -0
  23. package/dist/extensions/llm-wiki/lib/model-command.js +86 -0
  24. package/dist/extensions/llm-wiki/lib/observation.js +283 -0
  25. package/dist/extensions/llm-wiki/lib/recall.js +875 -0
  26. package/dist/extensions/llm-wiki/lib/retro.js +158 -0
  27. package/dist/extensions/llm-wiki/lib/runtime.js +191 -0
  28. package/dist/extensions/llm-wiki/lib/source-extractors.js +426 -0
  29. package/dist/extensions/llm-wiki/lib/source-packet.js +229 -0
  30. package/dist/extensions/llm-wiki/lib/subagent.js +41 -0
  31. package/dist/extensions/llm-wiki/lib/task-config.js +172 -0
  32. package/dist/extensions/llm-wiki/lib/tools.js +1192 -0
  33. package/dist/extensions/llm-wiki/lib/trajectories-command.js +51 -0
  34. package/dist/extensions/llm-wiki/lib/trajectory.js +467 -0
  35. package/dist/extensions/llm-wiki/lib/utils.js +347 -0
  36. package/dist/extensions/llm-wiki/lib/vault-format.js +247 -0
  37. package/dist/extensions/llm-wiki/lib/visible-status.js +31 -0
  38. package/dist/extensions/llm-wiki/lib/wiki-service.js +128 -0
  39. package/dist/mcp/exec.js +121 -0
  40. package/dist/mcp/index.js +229 -0
  41. package/dist/mcp/operations.js +130 -0
  42. package/dist/package.json +1 -0
  43. package/docs/superpowers/plans/2026-08-02-okf-foundation.md +1579 -0
  44. package/docs/superpowers/plans/2026-08-03-okf-foundation-remediation.md +3005 -0
  45. package/docs/superpowers/plans/2026-08-06-okf-foundation-release-remediation.md +1174 -0
  46. package/docs/superpowers/specs/2026-08-02-okf-foundation-design.md +578 -0
  47. package/docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md +538 -0
  48. package/extensions/llm-wiki/index.ts +22 -36
  49. package/extensions/llm-wiki/lib/bootstrap.ts +84 -0
  50. package/extensions/llm-wiki/lib/embeddings.ts +9 -3
  51. package/extensions/llm-wiki/lib/guardrails.ts +174 -29
  52. package/extensions/llm-wiki/lib/indexing.ts +2 -1
  53. package/extensions/llm-wiki/lib/ingest-worker.ts +170 -29
  54. package/extensions/llm-wiki/lib/knowledge-document.ts +661 -0
  55. package/extensions/llm-wiki/lib/knowledge-links.ts +282 -0
  56. package/extensions/llm-wiki/lib/legacy-repair.ts +572 -0
  57. package/extensions/llm-wiki/lib/metadata.ts +531 -116
  58. package/extensions/llm-wiki/lib/observation.ts +37 -43
  59. package/extensions/llm-wiki/lib/recall.ts +61 -33
  60. package/extensions/llm-wiki/lib/retro.ts +65 -41
  61. package/extensions/llm-wiki/lib/source-extractors.ts +12 -17
  62. package/extensions/llm-wiki/lib/source-packet.ts +44 -31
  63. package/extensions/llm-wiki/lib/tools.ts +406 -348
  64. package/extensions/llm-wiki/lib/trajectory.ts +15 -1
  65. package/extensions/llm-wiki/lib/utils.ts +121 -130
  66. package/extensions/llm-wiki/lib/vault-format.ts +363 -0
  67. package/extensions/llm-wiki/lib/wiki-service.ts +183 -0
  68. package/mcp/exec.ts +122 -0
  69. package/mcp/index.ts +60 -250
  70. package/mcp/operations.ts +176 -0
  71. package/package.json +8 -2
  72. package/scripts/migrate-llm-wiki.js +801 -0
  73. 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, readFileSync } from "node:fs";
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
- // ─── Wiki Vault Detection ──────────────────────────────
21
-
22
- interface VaultPaths {
23
- root: string;
24
- raw: string;
25
- rawSources: string;
26
- wiki: string;
27
- meta: string;
28
- dotWiki: string;
29
- outputs: string;
30
- discoveries: string;
31
- }
32
-
33
- /** Detect vault format at a directory. */
34
- function detectFormat(dir: string): "new" | "legacy" | "none" {
35
- if (existsSync(join(dir, ".llm-wiki", "config.json"))) return "new";
36
- if (existsSync(join(dir, ".wiki", "config.json"))) return "legacy";
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 registry = readJson<{
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
- scored.sort((a, b) => b.score - a.score);
175
- const top = scored.slice(0, Math.min(max_results ?? 5, 10));
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 registry = readJson<{
237
- pages: Record<string, { type: string; title: string; [key: string]: unknown }>;
238
- }>(join(paths.meta, "registry.json"), { pages: {} });
239
-
240
- const q = query.toLowerCase();
241
- const matches = Object.entries(registry.pages)
242
- .filter(([id, entry]) => {
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 registry = readJson<{
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 packet and source page.",
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 { saveInsight } = (await import("../extensions/llm-wiki/lib/retro.js")) as {
363
- saveInsight: (
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
- const vaultPaths = getPaths();
373
- const result = saveInsight(vaultPaths, slug, title, body, category);
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.sourceId} — ${title}`,
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: urlParam, file_path, title }) => {
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 vaultPaths = getPaths();
413
- let result: { sourceId: string };
414
-
415
- if (urlParam) {
416
- const { captureUrl } = (await import("../extensions/llm-wiki/lib/source-packet.js")) as {
417
- captureUrl: (
418
- pi: never,
419
- paths: Record<string, string>,
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: "Provide one of: text, url, or file_path",
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.10.7",
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.ts"
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 .",