@zosmaai/pi-llm-wiki 0.10.7 → 0.10.9
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/README.md
CHANGED
|
@@ -447,6 +447,20 @@ Thanks to everyone who has contributed! This list is regenerated automatically b
|
|
|
447
447
|
<sub><b>Daniel Naab</b></sub>
|
|
448
448
|
</a>
|
|
449
449
|
</td>
|
|
450
|
+
<td align="center">
|
|
451
|
+
<a href="https://github.com/deestax">
|
|
452
|
+
<img src="https://avatars.githubusercontent.com/u/152369481?v=4" width="64;" alt="deestax"/>
|
|
453
|
+
<br />
|
|
454
|
+
<sub><b>Superdao</b></sub>
|
|
455
|
+
</a>
|
|
456
|
+
</td>
|
|
457
|
+
<td align="center">
|
|
458
|
+
<a href="https://github.com/xcsf">
|
|
459
|
+
<img src="https://avatars.githubusercontent.com/u/43439835?v=4" width="64;" alt="xcsf"/>
|
|
460
|
+
<br />
|
|
461
|
+
<sub><b>xcsf</b></sub>
|
|
462
|
+
</a>
|
|
463
|
+
</td>
|
|
450
464
|
<td align="center">
|
|
451
465
|
<a href="https://github.com/mystery4f">
|
|
452
466
|
<img src="https://avatars.githubusercontent.com/u/40482524?v=4" width="64;" alt="mystery4f"/>
|
|
@@ -12,26 +12,162 @@ import { isProtectedPath, resolveVaultPaths } from "./utils.js";
|
|
|
12
12
|
|
|
13
13
|
let pendingRebuild = false;
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const APPLY_PATCH_PATH_NOISE =
|
|
16
|
+
/^\*{0,3}\s*(?:(?:update|add|delete|move)[^A-Za-z0-9]*(?:file|to)?[^A-Za-z0-9]*:)?\s*\*{0,3}\s*/i;
|
|
17
|
+
const PATCH_INPUT_KEYS: Record<string, true> = { input: true, _input: true, patch: true };
|
|
18
|
+
const DESTINATION_KEYS: Record<string, true> = {
|
|
19
|
+
rename: true,
|
|
20
|
+
move: true,
|
|
21
|
+
dest: true,
|
|
22
|
+
destination: true,
|
|
23
|
+
newPath: true,
|
|
24
|
+
};
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
interface MutationScan {
|
|
27
|
+
paths: string[];
|
|
28
|
+
complete: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeMutationPath(target: string): string | undefined {
|
|
32
|
+
const trimmed = target.trim();
|
|
33
|
+
if (!trimmed) return undefined;
|
|
34
|
+
const first = trimmed[0];
|
|
35
|
+
const last = trimmed[trimmed.length - 1];
|
|
36
|
+
const quoted = first === '"' || first === "'";
|
|
37
|
+
if (quoted !== (last === '"' || last === "'") || (quoted && first !== last)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const unquoted = quoted ? trimmed.slice(1, -1) : trimmed;
|
|
41
|
+
return unquoted.replace(APPLY_PATCH_PATH_NOISE, "") || undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parsePatchHeader(line: string): string | undefined {
|
|
45
|
+
const trimmed = line.replace(/\r$/, "").trimEnd();
|
|
46
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return undefined;
|
|
47
|
+
|
|
48
|
+
const body = trimmed.slice(1, -1).trim();
|
|
49
|
+
const tag = /#[0-9A-Fa-f]{4}\s*$/.exec(body);
|
|
50
|
+
const rawTarget = tag ? body.slice(0, tag.index) : body.replace(/\s+$/, "");
|
|
51
|
+
if (!rawTarget || rawTarget.includes("#")) return undefined;
|
|
52
|
+
|
|
53
|
+
return normalizeMutationPath(rawTarget);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseMoveDestination(line: string): string | undefined {
|
|
57
|
+
const rawDestination = line.trim().slice(2).trim();
|
|
58
|
+
if (!rawDestination) return undefined;
|
|
59
|
+
const quote = rawDestination[0];
|
|
60
|
+
if (quote !== '"' && quote !== "'") return normalizeMutationPath(rawDestination);
|
|
61
|
+
|
|
62
|
+
let cursor = 1;
|
|
63
|
+
while (cursor < rawDestination.length) {
|
|
64
|
+
if (rawDestination[cursor] === "\\" && cursor + 1 < rawDestination.length) {
|
|
65
|
+
cursor += 2;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (rawDestination[cursor] === quote) {
|
|
69
|
+
return cursor === rawDestination.length - 1
|
|
70
|
+
? normalizeMutationPath(rawDestination)
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
cursor++;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function scanPatchString(input: string): MutationScan {
|
|
79
|
+
const paths: string[] = [];
|
|
80
|
+
let sawHeader = false;
|
|
81
|
+
let sectionHasMove = false;
|
|
82
|
+
let complete = true;
|
|
83
|
+
const stripped = input.startsWith("\uFEFF") ? input.slice(1) : input;
|
|
84
|
+
|
|
85
|
+
for (const line of stripped.split("\n")) {
|
|
86
|
+
const trimmed = line.replace(/\r$/, "").trim();
|
|
87
|
+
if (trimmed.startsWith("[")) {
|
|
88
|
+
sawHeader = true;
|
|
89
|
+
sectionHasMove = false;
|
|
90
|
+
const path = parsePatchHeader(line);
|
|
91
|
+
if (path) paths.push(path);
|
|
92
|
+
else complete = false;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!/^MV(?:\s|$)/.test(trimmed)) continue;
|
|
96
|
+
const destination = parseMoveDestination(trimmed);
|
|
97
|
+
if (!sawHeader || sectionHasMove || !destination) complete = false;
|
|
98
|
+
else {
|
|
99
|
+
paths.push(destination);
|
|
100
|
+
sectionHasMove = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { paths, complete: sawHeader && complete };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function mergeMutationScans(target: MutationScan, source: MutationScan): void {
|
|
108
|
+
target.paths.push(...source.paths);
|
|
109
|
+
target.complete &&= source.complete;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addMutationPath(scan: MutationScan, target: string): void {
|
|
113
|
+
const path = normalizeMutationPath(target);
|
|
114
|
+
if (path) scan.paths.push(path);
|
|
115
|
+
else scan.complete = false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function collectMutationPaths(
|
|
119
|
+
input: unknown,
|
|
120
|
+
seen: WeakSet<object>,
|
|
121
|
+
stringsArePatches = false,
|
|
122
|
+
): MutationScan {
|
|
18
123
|
if (typeof input === "string") {
|
|
19
|
-
return
|
|
124
|
+
return stringsArePatches ? scanPatchString(input) : { paths: [], complete: true };
|
|
125
|
+
}
|
|
126
|
+
if (!input || typeof input !== "object" || seen.has(input)) {
|
|
127
|
+
return { paths: [], complete: true };
|
|
20
128
|
}
|
|
21
|
-
if (!input || typeof input !== "object" || seen.has(input)) return [];
|
|
22
129
|
|
|
23
130
|
seen.add(input);
|
|
24
|
-
|
|
131
|
+
const scan: MutationScan = { paths: [], complete: true };
|
|
132
|
+
if (Array.isArray(input)) {
|
|
133
|
+
for (const value of input) {
|
|
134
|
+
mergeMutationScans(scan, collectMutationPaths(value, seen, stringsArePatches));
|
|
135
|
+
}
|
|
136
|
+
return scan;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const record = input as Record<string, unknown>;
|
|
140
|
+
if (typeof record.path === "string" && record.path.length > 0) {
|
|
141
|
+
addMutationPath(scan, record.path);
|
|
142
|
+
}
|
|
143
|
+
const eventPaths = Array.isArray(record.paths) ? record.paths : [record.paths];
|
|
144
|
+
for (const path of eventPaths) {
|
|
145
|
+
if (typeof path === "string" && path.length > 0) addMutationPath(scan, path);
|
|
146
|
+
}
|
|
25
147
|
|
|
26
|
-
const
|
|
27
|
-
|
|
148
|
+
for (const [key, value] of Object.entries(record)) {
|
|
149
|
+
if (key === "path" || key === "paths") continue;
|
|
150
|
+
if (DESTINATION_KEYS[key] === true) {
|
|
151
|
+
if (typeof value === "string" && value.length > 0) addMutationPath(scan, value);
|
|
152
|
+
else scan.complete = false;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const childStringsArePatches = stringsArePatches || PATCH_INPUT_KEYS[key] === true;
|
|
156
|
+
if (typeof value !== "string" && (!value || typeof value !== "object")) continue;
|
|
157
|
+
mergeMutationScans(scan, collectMutationPaths(value, seen, childStringsArePatches));
|
|
158
|
+
}
|
|
159
|
+
return scan;
|
|
160
|
+
}
|
|
28
161
|
|
|
29
|
-
|
|
162
|
+
function inspectMutationPaths(input: unknown): MutationScan {
|
|
163
|
+
const stringsArePatches = typeof input === "string" || Array.isArray(input);
|
|
164
|
+
const scan = collectMutationPaths(input, new WeakSet(), stringsArePatches);
|
|
165
|
+
return { paths: [...new Set(scan.paths)], complete: scan.complete };
|
|
30
166
|
}
|
|
31
167
|
|
|
32
168
|
/** Return every file path targeted by a write or patch-shaped edit input. */
|
|
33
169
|
export function extractMutationPaths(input: unknown): string[] {
|
|
34
|
-
return
|
|
170
|
+
return inspectMutationPaths(input).paths;
|
|
35
171
|
}
|
|
36
172
|
|
|
37
173
|
/** True when a write or patch-shaped edit targets a page in the wiki directory. */
|
|
@@ -59,8 +195,9 @@ export function installGuardrails(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
59
195
|
}
|
|
60
196
|
|
|
61
197
|
if (isToolCallEventType("edit", event)) {
|
|
62
|
-
const
|
|
63
|
-
|
|
198
|
+
const mutation = inspectMutationPaths(event.input);
|
|
199
|
+
const targetPaths = mutation.paths;
|
|
200
|
+
if (!mutation.complete || targetPaths.length === 0) {
|
|
64
201
|
return { block: true, reason: "Cannot determine the files targeted by this edit." };
|
|
65
202
|
}
|
|
66
203
|
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
getVaultPaths,
|
|
27
27
|
readJson,
|
|
28
28
|
resolveVaultPaths,
|
|
29
|
+
slugify,
|
|
29
30
|
writeJson,
|
|
30
31
|
} from "./utils.js";
|
|
31
32
|
|
|
@@ -544,12 +545,7 @@ export function registerWikiEnsurePage(pi: ExtensionAPI, runtime?: Runtime): voi
|
|
|
544
545
|
| "requirement"
|
|
545
546
|
| "skill"
|
|
546
547
|
| "case";
|
|
547
|
-
const slug = params.title
|
|
548
|
-
.toLowerCase()
|
|
549
|
-
.replace(/[^a-z0-9\s-]/g, "")
|
|
550
|
-
.trim()
|
|
551
|
-
.replace(/\s+/g, "-")
|
|
552
|
-
.slice(0, 80);
|
|
548
|
+
const slug = slugify(params.title);
|
|
553
549
|
|
|
554
550
|
const folderMap: Record<string, string> = {
|
|
555
551
|
entity: "entities",
|
|
@@ -370,12 +370,14 @@ export function extractWikilinks(content: string): string[] {
|
|
|
370
370
|
|
|
371
371
|
/** Slugify a title. */
|
|
372
372
|
export function slugify(title: string): string {
|
|
373
|
-
return
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
373
|
+
return (
|
|
374
|
+
title
|
|
375
|
+
.toLocaleLowerCase()
|
|
376
|
+
.replace(/[^\p{L}\p{N}\s-]/gu, "")
|
|
377
|
+
.trim()
|
|
378
|
+
.replace(/\s+/g, "-")
|
|
379
|
+
.slice(0, 80) || "untitled"
|
|
380
|
+
);
|
|
379
381
|
}
|
|
380
382
|
|
|
381
383
|
/** Format date as YYYY-MM-DD. */
|
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.9",
|
|
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",
|