@zosmaai/pi-llm-wiki 0.10.6 → 0.10.7
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.
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolve, sep } from "node:path";
|
|
1
2
|
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
|
|
2
3
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
4
|
import { scheduleReindex } from "./indexing.js";
|
|
@@ -11,6 +12,39 @@ import { isProtectedPath, resolveVaultPaths } from "./utils.js";
|
|
|
11
12
|
|
|
12
13
|
let pendingRebuild = false;
|
|
13
14
|
|
|
15
|
+
const PATCH_HEADER = /^\[([^#\r\n]+)#[0-9A-F]{4}\]$/gm;
|
|
16
|
+
|
|
17
|
+
function collectMutationPaths(input: unknown, seen: WeakSet<object>): string[] {
|
|
18
|
+
if (typeof input === "string") {
|
|
19
|
+
return Array.from(input.matchAll(PATCH_HEADER), ([, target]) => target);
|
|
20
|
+
}
|
|
21
|
+
if (!input || typeof input !== "object" || seen.has(input)) return [];
|
|
22
|
+
|
|
23
|
+
seen.add(input);
|
|
24
|
+
if (Array.isArray(input)) return input.flatMap((value) => collectMutationPaths(value, seen));
|
|
25
|
+
|
|
26
|
+
const { path, ...nested } = input as Record<string, unknown>;
|
|
27
|
+
if (typeof path === "string" && path.length > 0) return [path];
|
|
28
|
+
|
|
29
|
+
return Object.values(nested).flatMap((value) => collectMutationPaths(value, seen));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Return every file path targeted by a write or patch-shaped edit input. */
|
|
33
|
+
export function extractMutationPaths(input: unknown): string[] {
|
|
34
|
+
return [...new Set(collectMutationPaths(input, new WeakSet()))];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** True when a write or patch-shaped edit targets a page in the wiki directory. */
|
|
38
|
+
export function hasWikiMutation(input: unknown, wikiPath: string): boolean {
|
|
39
|
+
const resolvedWikiPath = resolve(wikiPath);
|
|
40
|
+
return extractMutationPaths(input).some((path) => {
|
|
41
|
+
const resolvedPath = resolve(path);
|
|
42
|
+
return (
|
|
43
|
+
resolvedPath === resolvedWikiPath || resolvedPath.startsWith(`${resolvedWikiPath}${sep}`)
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
14
48
|
/** Install guardrails on the extension API. */
|
|
15
49
|
export function installGuardrails(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
16
50
|
// Block direct edits to raw/ and meta/
|
|
@@ -25,11 +59,17 @@ export function installGuardrails(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
25
59
|
}
|
|
26
60
|
|
|
27
61
|
if (isToolCallEventType("edit", event)) {
|
|
28
|
-
const
|
|
62
|
+
const targetPaths = extractMutationPaths(event.input);
|
|
63
|
+
if (targetPaths.length === 0) {
|
|
64
|
+
return { block: true, reason: "Cannot determine the files targeted by this edit." };
|
|
65
|
+
}
|
|
66
|
+
|
|
29
67
|
const paths = resolveVaultPaths(process.cwd());
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
for (const path of targetPaths) {
|
|
69
|
+
const check = isProtectedPath(path, paths);
|
|
70
|
+
if (check.protected) {
|
|
71
|
+
return { block: true, reason: check.reason };
|
|
72
|
+
}
|
|
33
73
|
}
|
|
34
74
|
}
|
|
35
75
|
});
|
|
@@ -37,10 +77,8 @@ export function installGuardrails(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
37
77
|
// Track wiki edits for auto-rebuild
|
|
38
78
|
pi.on("tool_result", async (event) => {
|
|
39
79
|
if (event.toolName === "write" || event.toolName === "edit") {
|
|
40
|
-
const path = event.input.path as string;
|
|
41
80
|
const paths = resolveVaultPaths(process.cwd());
|
|
42
|
-
|
|
43
|
-
if (path?.startsWith(wikiPath)) {
|
|
81
|
+
if (hasWikiMutation(event.input, paths.wiki)) {
|
|
44
82
|
pendingRebuild = true;
|
|
45
83
|
}
|
|
46
84
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
3
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { launchEmbedPages, reindexEmbeddings, resolveEmbedder } from "./embeddings.js";
|
|
@@ -390,7 +390,11 @@ export function registerWikiIngest(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
390
390
|
const manifestPath = join(paths.rawSources, id, "manifest.json");
|
|
391
391
|
const extracted = existsSync(extractedPath) ? readFileSync(extractedPath, "utf-8") : "";
|
|
392
392
|
const manifest = readJson<Record<string, unknown>>(manifestPath, {});
|
|
393
|
-
|
|
393
|
+
// Vault-relative path used in tool messages so the read tool can open
|
|
394
|
+
// the file from the vault root (fix #101: agent previously got
|
|
395
|
+
// "raw/sources/..." and failed on new-layout vaults).
|
|
396
|
+
const relRaw = relative(paths.root, paths.rawSources);
|
|
397
|
+
return { id, extracted, manifest, relRaw };
|
|
394
398
|
});
|
|
395
399
|
|
|
396
400
|
// ── Background synthesis (issue #65) ──────────────────
|
|
@@ -474,7 +478,7 @@ export function registerWikiIngest(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
474
478
|
[
|
|
475
479
|
`- **${s.id}**: ${s.manifest.title || s.id}`,
|
|
476
480
|
` - Extracted: ${s.extracted.length} chars`,
|
|
477
|
-
` - Read:
|
|
481
|
+
` - Read: \`${s.relRaw}/${s.id}/extracted.md\``,
|
|
478
482
|
].join("\n"),
|
|
479
483
|
),
|
|
480
484
|
"",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zosmaai/pi-llm-wiki",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.7",
|
|
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",
|
package/prompts/wiki-ingest.md
CHANGED
|
@@ -19,7 +19,7 @@ $ARGUMENTS
|
|
|
19
19
|
2. If the tool reports "All sources ingested", inform the user and stop.
|
|
20
20
|
3. **If the tool reports it is ingesting in the background**, the synthesis sub-agent is handling those sources on the configured task model. Do NOT synthesize them yourself — just report which sources were dispatched and stop. (You'll be notified as each completes.)
|
|
21
21
|
4. **Otherwise** (the tool returned extracted content — background unavailable or `background=false`), for each source in the returned batch:
|
|
22
|
-
a. Read the extracted text from
|
|
22
|
+
a. Read the extracted text from `.llm-wiki/raw/sources/<SOURCE_ID>/extracted.md`
|
|
23
23
|
b. Update the skeleton source page in `wiki/sources/` with a proper summary, key entities, and concepts
|
|
24
24
|
c. Use `wiki_ensure_page(type=entity, title=<name>)` for each new entity (people, orgs, tools, products)
|
|
25
25
|
d. Use `wiki_ensure_page(type=concept, title=<name>)` for each new concept (ideas, patterns, frameworks)
|