@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
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
4
3
|
import { Type } from "typebox";
|
|
5
4
|
import { scheduleReindex } from "./indexing.js";
|
|
5
|
+
import { createKnowledgeDocument, writeKnowledgeDocumentFile } from "./knowledge-document.js";
|
|
6
6
|
import { appendEvent, rebuildMetadataLight } from "./metadata.js";
|
|
7
7
|
import type { Runtime } from "./runtime.js";
|
|
8
8
|
import { type VaultPaths, fmtDate, resolveVaultPaths } from "./utils.js";
|
|
9
|
+
import { assertWritableVault, inspectWritableVault } from "./vault-format.js";
|
|
9
10
|
|
|
10
11
|
// ─── Types ─────────────────────────────────────────────
|
|
11
12
|
|
|
@@ -51,6 +52,7 @@ export function saveObservation(
|
|
|
51
52
|
input: ObservationInput,
|
|
52
53
|
opts?: { rebuild?: boolean },
|
|
53
54
|
): ObservationResult {
|
|
55
|
+
assertWritableVault(paths);
|
|
54
56
|
const today = fmtDate();
|
|
55
57
|
const timestamp = new Date().toISOString();
|
|
56
58
|
|
|
@@ -62,50 +64,38 @@ export function saveObservation(
|
|
|
62
64
|
.slice(0, 60);
|
|
63
65
|
const slug = `obs-${today}-${slugBase}`;
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
const sourcePageDir = join(paths.wiki, "sources");
|
|
67
|
-
mkdirSync(sourcePageDir, { recursive: true });
|
|
68
|
-
const pagePath = join(sourcePageDir, `${slug}.md`);
|
|
67
|
+
const pagePath = join(paths.wiki, "sources", `${slug}.md`);
|
|
69
68
|
|
|
70
69
|
const relevanceEmoji = RELEVANCE_EMOJIS[input.relevance] ?? "📝";
|
|
71
70
|
const tags = input.tags ?? "";
|
|
72
71
|
const sourceContext = input.source_context ?? "";
|
|
73
72
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
tags ? `\n*Tags: ${tags}*` : "",
|
|
101
|
-
"",
|
|
102
|
-
"---",
|
|
103
|
-
`*Observed: ${timestamp}*`,
|
|
104
|
-
"",
|
|
105
|
-
]
|
|
106
|
-
.filter((l) => l !== "")
|
|
107
|
-
.join("\n");
|
|
108
|
-
writeFileSync(pagePath, pageContent, "utf-8");
|
|
73
|
+
const body = `# ${relevanceEmoji} Observation: ${input.title}
|
|
74
|
+
|
|
75
|
+
${input.content}
|
|
76
|
+
|
|
77
|
+
*Relevance: ${input.relevance}*${sourceContext ? `\n*Context: ${sourceContext}*` : ""}${tags ? `\n*Tags: ${tags}*` : ""}
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
*Observed: ${timestamp}*`;
|
|
81
|
+
|
|
82
|
+
const doc = createKnowledgeDocument(
|
|
83
|
+
`sources/${slug}.md`,
|
|
84
|
+
{
|
|
85
|
+
type: "source",
|
|
86
|
+
title: `Observation: ${input.title}`,
|
|
87
|
+
slug,
|
|
88
|
+
status: "observation",
|
|
89
|
+
created: today,
|
|
90
|
+
updated: today,
|
|
91
|
+
relevance: input.relevance,
|
|
92
|
+
observed_at: timestamp,
|
|
93
|
+
...(tags ? { tags: tags.split(/\s+/).filter(Boolean) } : {}),
|
|
94
|
+
...(sourceContext ? { source_context: sourceContext } : {}),
|
|
95
|
+
},
|
|
96
|
+
body,
|
|
97
|
+
);
|
|
98
|
+
writeKnowledgeDocumentFile(pagePath, doc);
|
|
109
99
|
|
|
110
100
|
// Log event
|
|
111
101
|
appendEvent(paths, {
|
|
@@ -213,15 +203,19 @@ export function registerWikiObserve(
|
|
|
213
203
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
214
204
|
const paths = resolveVaultPaths(ctx.cwd ?? process.cwd());
|
|
215
205
|
|
|
216
|
-
|
|
206
|
+
const vaultCheck = inspectWritableVault(paths);
|
|
207
|
+
if (!vaultCheck.ok) {
|
|
217
208
|
return {
|
|
218
209
|
content: [
|
|
219
210
|
{
|
|
220
211
|
type: "text",
|
|
221
|
-
text:
|
|
212
|
+
text: `Wiki vault error: ${vaultCheck.diagnostics[0].message}`,
|
|
222
213
|
},
|
|
223
214
|
],
|
|
224
|
-
details: {
|
|
215
|
+
details: {
|
|
216
|
+
error: vaultCheck.diagnostics[0].code,
|
|
217
|
+
diagnostics: vaultCheck.diagnostics,
|
|
218
|
+
} as Record<string, unknown>,
|
|
225
219
|
isError: true,
|
|
226
220
|
};
|
|
227
221
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
readEmbeddingStore,
|
|
11
11
|
resolveEmbedder,
|
|
12
12
|
} from "./embeddings.js";
|
|
13
|
+
import { parseKnowledgeDocument } from "./knowledge-document.js";
|
|
13
14
|
import type { Registry } from "./metadata.js";
|
|
14
15
|
import type { Runtime } from "./runtime.js";
|
|
15
16
|
import type { TaskConfig } from "./task-config.js";
|
|
@@ -17,10 +18,10 @@ import {
|
|
|
17
18
|
type VaultPaths,
|
|
18
19
|
getPersonalWikiPaths,
|
|
19
20
|
isPersonalVault,
|
|
20
|
-
parseFrontmatter,
|
|
21
21
|
readJson,
|
|
22
22
|
resolveVaultPaths,
|
|
23
23
|
} from "./utils.js";
|
|
24
|
+
import { inspectVaultFormat } from "./vault-format.js";
|
|
24
25
|
|
|
25
26
|
// ─── Public API ────────────────────────────────────────
|
|
26
27
|
|
|
@@ -296,8 +297,19 @@ function chunkPage(body: string): PageChunk[] {
|
|
|
296
297
|
return chunks;
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
function parsePage(
|
|
301
|
+
path: string,
|
|
302
|
+
id: string,
|
|
303
|
+
): { frontmatter: Record<string, unknown>; body: string } | undefined {
|
|
304
|
+
if (!existsSync(path)) return undefined;
|
|
305
|
+
const content = readFileSync(path, "utf-8");
|
|
306
|
+
const result = parseKnowledgeDocument(content, `${id}.md`);
|
|
307
|
+
if (!result.ok) return undefined;
|
|
308
|
+
return { frontmatter: result.document.frontmatter, body: result.document.body };
|
|
309
|
+
}
|
|
310
|
+
|
|
299
311
|
function pagePreview(content: string): string {
|
|
300
|
-
const
|
|
312
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
301
313
|
return body.trim().slice(0, 200).replace(/\n/g, " ");
|
|
302
314
|
}
|
|
303
315
|
|
|
@@ -330,7 +342,7 @@ function extractExpansionTerms(
|
|
|
330
342
|
const originalNorm = normalizeText(originalQuery);
|
|
331
343
|
const termFreq = new Map<string, number>();
|
|
332
344
|
|
|
333
|
-
for (const { pagePath, entry } of topResults) {
|
|
345
|
+
for (const { id, pagePath, entry } of topResults) {
|
|
334
346
|
// Collect text from registry metadata + file content
|
|
335
347
|
const metaText = normalizeText(
|
|
336
348
|
[entry.title, entry.aliases, entry.tags, entry.summary, entry.description]
|
|
@@ -345,12 +357,13 @@ function extractExpansionTerms(
|
|
|
345
357
|
|
|
346
358
|
// Also extract from file body
|
|
347
359
|
if (existsSync(pagePath)) {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
360
|
+
const parsed = parsePage(pagePath, id);
|
|
361
|
+
if (parsed) {
|
|
362
|
+
const bodyNorm = normalizeText(parsed.body);
|
|
363
|
+
for (const w of bodyNorm.split(/\s+/)) {
|
|
364
|
+
if (w.length >= 4 && !originalNorm.includes(w) && !STOPWORDS.has(w)) {
|
|
365
|
+
termFreq.set(w, (termFreq.get(w) || 0) + 1);
|
|
366
|
+
}
|
|
354
367
|
}
|
|
355
368
|
}
|
|
356
369
|
}
|
|
@@ -394,8 +407,11 @@ export function searchWiki(
|
|
|
394
407
|
|
|
395
408
|
for (const [id, entry] of Object.entries(registry.pages)) {
|
|
396
409
|
const pagePath = join(paths.wiki, `${id}.md`);
|
|
397
|
-
const
|
|
398
|
-
const
|
|
410
|
+
const pageExists = existsSync(pagePath);
|
|
411
|
+
const parsed = parsePage(pagePath, id);
|
|
412
|
+
if (pageExists && !parsed) continue;
|
|
413
|
+
const frontmatter = parsed?.frontmatter ?? {};
|
|
414
|
+
const body = parsed?.body ?? "";
|
|
399
415
|
|
|
400
416
|
let score = 0;
|
|
401
417
|
|
|
@@ -405,8 +421,7 @@ export function searchWiki(
|
|
|
405
421
|
score += scoreField(frontmatter.title, terms, 5);
|
|
406
422
|
score += scoreField(entry.type, terms, 1);
|
|
407
423
|
|
|
408
|
-
// Recall-oriented metadata.
|
|
409
|
-
// legacy comma/bracket strings still flatten into searchable text.
|
|
424
|
+
// Recall-oriented metadata.
|
|
410
425
|
score += scoreField(entry.aliases, terms, 6);
|
|
411
426
|
score += scoreField(frontmatter.aliases, terms, 6);
|
|
412
427
|
score += scoreField(entry.recall_triggers, terms, 7);
|
|
@@ -489,8 +504,8 @@ export function searchWiki(
|
|
|
489
504
|
// Apply expansion scoring to the top 25 results (cheap re-read)
|
|
490
505
|
const expansionCandidates = scored.slice(0, Math.min(25, scored.length));
|
|
491
506
|
for (const item of expansionCandidates) {
|
|
492
|
-
const
|
|
493
|
-
const
|
|
507
|
+
const parsed = parsePage(item.pagePath, item.id);
|
|
508
|
+
const body = parsed?.body ?? "";
|
|
494
509
|
let expChunkScore = 0;
|
|
495
510
|
if (body.trim()) {
|
|
496
511
|
const chunks = chunkPage(body);
|
|
@@ -960,6 +975,10 @@ export function registerWikiRecall(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
960
975
|
}
|
|
961
976
|
|
|
962
977
|
const maxResults = Math.min(params.max_results ?? 5, 10);
|
|
978
|
+
const vaultDiagnostics = inspectVaultFormat(paths).diagnostics;
|
|
979
|
+
const diagnosticText = vaultDiagnostics.length
|
|
980
|
+
? `\n\nDiagnostics: ${vaultDiagnostics.map((diagnostic) => diagnostic.code).join(", ")}`
|
|
981
|
+
: "";
|
|
963
982
|
// Use layered hybrid search: personal vault + project vault, blending
|
|
964
983
|
// lexical scoring with precomputed semantic embeddings when available.
|
|
965
984
|
// No embeddings / no embedder => pure lexical, no network call.
|
|
@@ -973,10 +992,14 @@ export function registerWikiRecall(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
973
992
|
content: [
|
|
974
993
|
{
|
|
975
994
|
type: "text",
|
|
976
|
-
text: `No wiki pages found matching "${params.query}". The wiki is empty — use wiki_retro to start building knowledge
|
|
995
|
+
text: `No wiki pages found matching "${params.query}". The wiki is empty — use wiki_retro to start building knowledge.${diagnosticText}`,
|
|
977
996
|
},
|
|
978
997
|
],
|
|
979
|
-
details: {
|
|
998
|
+
details: {
|
|
999
|
+
query: params.query,
|
|
1000
|
+
matches: [],
|
|
1001
|
+
diagnostics: vaultDiagnostics,
|
|
1002
|
+
} as Record<string, unknown>,
|
|
980
1003
|
};
|
|
981
1004
|
}
|
|
982
1005
|
|
|
@@ -997,19 +1020,22 @@ export function registerWikiRecall(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
997
1020
|
return `${i + 1}. [[${r.id}]] — ${r.title} (${r.type}, score ${r.score.toFixed(1)})${vault}\n Path: ${r.path}${tail}`;
|
|
998
1021
|
})
|
|
999
1022
|
.join("\n");
|
|
1000
|
-
const text =
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1023
|
+
const text =
|
|
1024
|
+
[
|
|
1025
|
+
`Found ${results.length} wiki page(s) matching "${params.query}"${layerTag} (two-stage recall — ranked links, expand on demand):`,
|
|
1026
|
+
"",
|
|
1027
|
+
linkLines,
|
|
1028
|
+
"",
|
|
1029
|
+
"Call `read` on the path(s) you need to pull full content.",
|
|
1030
|
+
].join("\n") + diagnosticText;
|
|
1007
1031
|
return {
|
|
1008
1032
|
content: [{ type: "text", text }],
|
|
1009
|
-
details: {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1033
|
+
details: {
|
|
1034
|
+
query: params.query,
|
|
1035
|
+
mode: "links",
|
|
1036
|
+
matches: results,
|
|
1037
|
+
diagnostics: vaultDiagnostics,
|
|
1038
|
+
} as Record<string, unknown>,
|
|
1013
1039
|
};
|
|
1014
1040
|
}
|
|
1015
1041
|
|
|
@@ -1022,13 +1048,15 @@ export function registerWikiRecall(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
1022
1048
|
const vault = r.vaultLabel ? ` ${r.vaultLabel}` : "";
|
|
1023
1049
|
return `## [[${r.id}]] — ${r.title}${vault}\nType: ${r.type}\nPath: ${r.path}\n\n${r.preview}`;
|
|
1024
1050
|
})
|
|
1025
|
-
.join("\n\n---\n\n")}`,
|
|
1051
|
+
.join("\n\n---\n\n")}${diagnosticText}`,
|
|
1026
1052
|
},
|
|
1027
1053
|
],
|
|
1028
|
-
details: {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1054
|
+
details: {
|
|
1055
|
+
query: params.query,
|
|
1056
|
+
mode: "preview",
|
|
1057
|
+
matches: results,
|
|
1058
|
+
diagnostics: vaultDiagnostics,
|
|
1059
|
+
} as Record<string, unknown>,
|
|
1032
1060
|
};
|
|
1033
1061
|
},
|
|
1034
1062
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
3
2
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
3
|
import { Type } from "typebox";
|
|
5
4
|
import { scheduleReindex } from "./indexing.js";
|
|
5
|
+
import { createKnowledgeDocument, writeKnowledgeDocumentFile } from "./knowledge-document.js";
|
|
6
6
|
import { appendEvent, rebuildMetadataLight } from "./metadata.js";
|
|
7
7
|
import type { Runtime } from "./runtime.js";
|
|
8
8
|
import { type VaultPaths, fmtDate, resolveVaultPaths } from "./utils.js";
|
|
9
|
+
import { assertWritableVault, inspectWritableVault } from "./vault-format.js";
|
|
9
10
|
|
|
10
11
|
// ─── Public API ────────────────────────────────────────
|
|
11
12
|
|
|
@@ -24,6 +25,16 @@ export interface RetroResult {
|
|
|
24
25
|
* The 4-layer pipeline (raw → source pages → canonical pages → metadata)
|
|
25
26
|
* is still available via wiki_capture_source → wiki_ingest for deep research.
|
|
26
27
|
*/
|
|
28
|
+
function insightPath(paths: VaultPaths, slug: string): string {
|
|
29
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) || slug === "index" || slug === "log") {
|
|
30
|
+
throw new Error(`Invalid insight slug: ${slug}`);
|
|
31
|
+
}
|
|
32
|
+
const directory = resolve(paths.wiki, "sources");
|
|
33
|
+
const target = resolve(directory, `${slug}.md`);
|
|
34
|
+
if (dirname(target) !== directory) throw new Error(`Invalid insight slug: ${slug}`);
|
|
35
|
+
return target;
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
export function saveInsight(
|
|
28
39
|
paths: VaultPaths,
|
|
29
40
|
slug: string,
|
|
@@ -32,41 +43,38 @@ export function saveInsight(
|
|
|
32
43
|
category?: string,
|
|
33
44
|
opts?: { rebuild?: boolean },
|
|
34
45
|
): RetroResult {
|
|
46
|
+
assertWritableVault(paths);
|
|
35
47
|
const today = fmtDate();
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
]
|
|
67
|
-
.filter((l) => l !== "")
|
|
68
|
-
.join("\n");
|
|
69
|
-
writeFileSync(sourcePagePath, pageContent, "utf-8");
|
|
49
|
+
const sourcePagePath = insightPath(paths, slug);
|
|
50
|
+
|
|
51
|
+
const pageBody = `# ${title}
|
|
52
|
+
|
|
53
|
+
${body}
|
|
54
|
+
|
|
55
|
+
${category ? `*Category: ${category}*` : ""}
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
*Captured: ${today}*
|
|
59
|
+
|
|
60
|
+
## Related
|
|
61
|
+
|
|
62
|
+
_Add links to related pages._`;
|
|
63
|
+
|
|
64
|
+
const doc = createKnowledgeDocument(
|
|
65
|
+
`sources/${slug}.md`,
|
|
66
|
+
{
|
|
67
|
+
type: "source",
|
|
68
|
+
title,
|
|
69
|
+
slug,
|
|
70
|
+
status: "insight",
|
|
71
|
+
created: today,
|
|
72
|
+
updated: today,
|
|
73
|
+
...(category ? { category } : {}),
|
|
74
|
+
},
|
|
75
|
+
pageBody,
|
|
76
|
+
);
|
|
77
|
+
writeKnowledgeDocumentFile(sourcePagePath, doc);
|
|
70
78
|
|
|
71
79
|
// Log event
|
|
72
80
|
appendEvent(paths, {
|
|
@@ -125,22 +133,38 @@ export function registerWikiRetro(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
125
133
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
126
134
|
const paths = resolveVaultPaths(ctx.cwd ?? process.cwd());
|
|
127
135
|
|
|
128
|
-
|
|
136
|
+
const vaultCheck = inspectWritableVault(paths);
|
|
137
|
+
if (!vaultCheck.ok) {
|
|
129
138
|
return {
|
|
130
139
|
content: [
|
|
131
140
|
{
|
|
132
141
|
type: "text",
|
|
133
|
-
text:
|
|
142
|
+
text: `Wiki vault error: ${vaultCheck.diagnostics[0].message}`,
|
|
134
143
|
},
|
|
135
144
|
],
|
|
136
|
-
details: {
|
|
145
|
+
details: {
|
|
146
|
+
error: vaultCheck.diagnostics[0].code,
|
|
147
|
+
diagnostics: vaultCheck.diagnostics,
|
|
148
|
+
} as Record<string, unknown>,
|
|
137
149
|
isError: true,
|
|
138
150
|
};
|
|
139
151
|
}
|
|
140
152
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
let result: RetroResult;
|
|
154
|
+
try {
|
|
155
|
+
result = saveInsight(paths, params.slug, params.title, params.body, params.category, {
|
|
156
|
+
rebuild: !runtime,
|
|
157
|
+
});
|
|
158
|
+
} catch (error: unknown) {
|
|
159
|
+
if ((error as Error).message.startsWith("Invalid insight slug:")) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: (error as Error).message }],
|
|
162
|
+
details: { error: "invalid_insight_slug" } as Record<string, unknown>,
|
|
163
|
+
isError: true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
144
168
|
if (runtime) {
|
|
145
169
|
scheduleReindex(runtime, { hasUI: ctx.hasUI, ui: ctx.ui }, paths);
|
|
146
170
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { open } from "node:fs/promises";
|
|
2
2
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
3
|
import { NodeHtmlMarkdown } from "node-html-markdown";
|
|
4
|
-
import { exec } from "./utils.js";
|
|
4
|
+
import { type ExecApi, exec } from "./utils.js";
|
|
5
5
|
|
|
6
6
|
export type ExtractionStatus = "success" | "failed" | "unsupported";
|
|
7
7
|
|
|
@@ -23,7 +23,7 @@ export interface FileExtractor {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
interface FileExtractArgs {
|
|
26
|
-
pi:
|
|
26
|
+
pi: ExecApi;
|
|
27
27
|
filePath: string;
|
|
28
28
|
content: string;
|
|
29
29
|
signal?: AbortSignal;
|
|
@@ -35,7 +35,7 @@ interface UrlExtractor {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
interface UrlExtractArgs {
|
|
38
|
-
pi:
|
|
38
|
+
pi: ExecApi;
|
|
39
39
|
url: string;
|
|
40
40
|
signal?: AbortSignal;
|
|
41
41
|
}
|
|
@@ -161,7 +161,7 @@ export function fileExtractorFor(filePath: string): FileExtractor {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
export function extractUrlContent(
|
|
164
|
-
pi:
|
|
164
|
+
pi: ExecApi,
|
|
165
165
|
url: string,
|
|
166
166
|
signal?: AbortSignal,
|
|
167
167
|
): Promise<ExtractedContent> {
|
|
@@ -197,7 +197,7 @@ function hasAnyExtension(extensions: string[]): (path: string) => boolean {
|
|
|
197
197
|
return (path) => extensions.some((extension) => hasExtension(extension)(path));
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
async function extractPdf(pi:
|
|
200
|
+
async function extractPdf(pi: ExecApi, source: string, signal?: AbortSignal): Promise<string> {
|
|
201
201
|
const extracted = await extractWithMarkItDown(pi, source, signal);
|
|
202
202
|
return extracted || pdfExtractionFailureMessage(source);
|
|
203
203
|
}
|
|
@@ -206,17 +206,13 @@ export function docxExtractionFailureMessage(source: string): string {
|
|
|
206
206
|
return `_DOCX content could not be converted to markdown from ${source}. Ensure uvx and markitdown are installed._\n`;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
async function extractDocx(
|
|
210
|
-
pi: ExtensionAPI,
|
|
211
|
-
source: string,
|
|
212
|
-
signal?: AbortSignal,
|
|
213
|
-
): Promise<string> {
|
|
209
|
+
async function extractDocx(pi: ExecApi, source: string, signal?: AbortSignal): Promise<string> {
|
|
214
210
|
const extracted = await extractWithMarkItDown(pi, source, signal);
|
|
215
211
|
return extracted || docxExtractionFailureMessage(source);
|
|
216
212
|
}
|
|
217
213
|
|
|
218
214
|
async function extractPdfUrl(
|
|
219
|
-
pi:
|
|
215
|
+
pi: ExecApi,
|
|
220
216
|
url: string,
|
|
221
217
|
signal?: AbortSignal,
|
|
222
218
|
): Promise<ExtractedContent> {
|
|
@@ -232,7 +228,7 @@ async function extractPdfUrl(
|
|
|
232
228
|
}
|
|
233
229
|
|
|
234
230
|
async function extractTextUrl(
|
|
235
|
-
pi:
|
|
231
|
+
pi: ExecApi,
|
|
236
232
|
url: string,
|
|
237
233
|
signal?: AbortSignal,
|
|
238
234
|
): Promise<ExtractedContent> {
|
|
@@ -266,13 +262,12 @@ async function extractTextUrl(
|
|
|
266
262
|
}
|
|
267
263
|
|
|
268
264
|
async function extractWithMarkItDown(
|
|
269
|
-
pi:
|
|
265
|
+
pi: ExecApi,
|
|
270
266
|
source: string,
|
|
271
267
|
signal?: AbortSignal,
|
|
272
268
|
): Promise<string> {
|
|
273
|
-
if (!(await hasMarkItDown(pi, signal))) return "";
|
|
274
|
-
|
|
275
269
|
try {
|
|
270
|
+
if (!(await hasMarkItDown(pi, signal))) return "";
|
|
276
271
|
const mdResult = await exec(
|
|
277
272
|
pi,
|
|
278
273
|
"sh",
|
|
@@ -285,7 +280,7 @@ async function extractWithMarkItDown(
|
|
|
285
280
|
}
|
|
286
281
|
}
|
|
287
282
|
|
|
288
|
-
async function hasMarkItDown(pi:
|
|
283
|
+
async function hasMarkItDown(pi: ExecApi, signal?: AbortSignal): Promise<boolean> {
|
|
289
284
|
const markitdown = await exec(
|
|
290
285
|
pi,
|
|
291
286
|
"sh",
|
|
@@ -295,7 +290,7 @@ async function hasMarkItDown(pi: ExtensionAPI, signal?: AbortSignal): Promise<bo
|
|
|
295
290
|
return markitdown.stdout.trim() === "yes";
|
|
296
291
|
}
|
|
297
292
|
|
|
298
|
-
async function fetchTextUrl(pi:
|
|
293
|
+
async function fetchTextUrl(pi: ExecApi, url: string, signal?: AbortSignal): Promise<string> {
|
|
299
294
|
try {
|
|
300
295
|
const curlResult = await exec(
|
|
301
296
|
pi,
|