@zosmaai/pi-llm-wiki 0.11.0 → 0.11.2
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 +5 -0
- package/README.de.md +19 -1
- package/README.es.md +19 -1
- package/README.fr.md +19 -1
- package/README.hi.md +19 -1
- package/README.ja.md +19 -1
- package/README.ko.md +19 -1
- package/README.md +38 -3
- package/README.pt.md +19 -1
- package/README.ru.md +19 -1
- package/README.zh.md +19 -1
- package/dist/extensions/llm-wiki/lib/bootstrap.js +4 -1
- package/dist/extensions/llm-wiki/lib/ingest-worker.js +129 -29
- package/dist/extensions/llm-wiki/lib/metadata.js +37 -31
- package/dist/extensions/llm-wiki/lib/model-command.js +0 -1
- package/dist/extensions/llm-wiki/lib/runtime.js +0 -4
- package/dist/extensions/llm-wiki/lib/source-packet.js +1 -1
- package/dist/extensions/llm-wiki/lib/task-config.js +29 -0
- package/dist/extensions/llm-wiki/lib/tools.js +7 -0
- package/dist/extensions/llm-wiki/lib/utils.js +6 -0
- package/dist/mcp/index.js +2 -1
- package/docs/api.md +5 -2
- package/docs/architecture.md +5 -2
- package/docs/configuration.md +25 -0
- package/docs/superpowers/plans/2026-08-06-authoritative-event-history-phase-1-foundation-hardening.md +937 -0
- package/docs/superpowers/plans/2026-08-07-synthesis-language.md +98 -0
- package/docs/superpowers/specs/2026-08-02-okf-foundation-design.md +17 -2
- package/docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md +6 -2
- package/docs/superpowers/specs/2026-08-07-synthesis-language-design.md +94 -0
- package/extensions/llm-wiki/lib/bootstrap.ts +4 -1
- package/extensions/llm-wiki/lib/ingest-worker.ts +161 -26
- package/extensions/llm-wiki/lib/knowledge-document.ts +2 -0
- package/extensions/llm-wiki/lib/metadata.ts +38 -31
- package/extensions/llm-wiki/lib/model-command.ts +0 -1
- package/extensions/llm-wiki/lib/runtime.ts +0 -3
- package/extensions/llm-wiki/lib/source-packet.ts +1 -1
- package/extensions/llm-wiki/lib/task-config.ts +36 -0
- package/extensions/llm-wiki/lib/tools.ts +9 -0
- package/extensions/llm-wiki/lib/utils.ts +7 -0
- package/mcp/index.ts +2 -1
- package/package.json +2 -2
- package/skills/llm-wiki/SKILL.md +4 -2
|
@@ -89,14 +89,16 @@ export function rebuildMetadata(paths: VaultPaths): ProjectionResult {
|
|
|
89
89
|
const knownIds = new Set(documents.map((d) => d.id));
|
|
90
90
|
const backlinks = buildBacklinks(documents, knownIds, allDiagnostics);
|
|
91
91
|
|
|
92
|
-
const
|
|
93
|
-
|
|
92
|
+
const eventSource = readEventSource(join(paths.meta, "events.jsonl"));
|
|
93
|
+
const eventLogResult = eventSource.available ? buildOkfLog(eventSource.content) : undefined;
|
|
94
|
+
if (eventLogResult) allDiagnostics.push(...eventLogResult.diagnostics);
|
|
95
|
+
else if (!eventSource.available) allDiagnostics.push(eventSource.diagnostic);
|
|
94
96
|
|
|
95
97
|
// Step 5: Build meta/index.md
|
|
96
98
|
const metaIndex = buildIndexMarkdown(registry);
|
|
97
99
|
|
|
98
100
|
// Step 6: Build meta/log.md (existing rich format)
|
|
99
|
-
const metaLog = buildLogMarkdown(
|
|
101
|
+
const metaLog = eventSource.available ? buildLogMarkdown(eventSource.content) : undefined;
|
|
100
102
|
|
|
101
103
|
// Step 7: Build OKF projections if in okf-0.2 mode
|
|
102
104
|
const okfIndexes: Map<string, string> | null =
|
|
@@ -104,8 +106,8 @@ export function rebuildMetadata(paths: VaultPaths): ProjectionResult {
|
|
|
104
106
|
? buildDirectoryIndexes(documents, readJson(join(paths.dotWiki, "config.json"), {}))
|
|
105
107
|
: null;
|
|
106
108
|
|
|
107
|
-
const okfLog: string |
|
|
108
|
-
vaultState.knowledgeFormat === "okf-0.2" ? eventLogResult
|
|
109
|
+
const okfLog: string | undefined =
|
|
110
|
+
vaultState.knowledgeFormat === "okf-0.2" ? eventLogResult?.markdown : undefined;
|
|
109
111
|
|
|
110
112
|
// Step 8: Atomic write all projections
|
|
111
113
|
mkdirSync(paths.meta, { recursive: true });
|
|
@@ -116,7 +118,7 @@ export function rebuildMetadata(paths: VaultPaths): ProjectionResult {
|
|
|
116
118
|
atomicWriteFile(join(paths.meta, "registry.json"), registryJson);
|
|
117
119
|
atomicWriteFile(join(paths.meta, "backlinks.json"), backlinksJson);
|
|
118
120
|
atomicWriteFile(join(paths.meta, "index.md"), metaIndex);
|
|
119
|
-
atomicWriteFile(join(paths.meta, "log.md"), metaLog);
|
|
121
|
+
if (metaLog !== undefined) atomicWriteFile(join(paths.meta, "log.md"), metaLog);
|
|
120
122
|
|
|
121
123
|
// Step 9: Write OKF projections if applicable
|
|
122
124
|
if (okfIndexes && okfIndexes.size > 0) {
|
|
@@ -128,7 +130,7 @@ export function rebuildMetadata(paths: VaultPaths): ProjectionResult {
|
|
|
128
130
|
pruneObsoleteIndexes(paths, okfIndexes);
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
if (okfLog !==
|
|
133
|
+
if (okfLog !== undefined) {
|
|
132
134
|
mkdirSync(paths.wiki, { recursive: true });
|
|
133
135
|
atomicWriteFile(join(paths.wiki, "log.md"), okfLog);
|
|
134
136
|
}
|
|
@@ -302,23 +304,19 @@ function buildIndexMarkdown(registry: Registry): string {
|
|
|
302
304
|
return `${sections.join("\n")}\n`;
|
|
303
305
|
}
|
|
304
306
|
|
|
305
|
-
|
|
306
|
-
function buildLogMarkdown(paths: VaultPaths): string {
|
|
307
|
-
const eventsPath = join(paths.meta, "events.jsonl");
|
|
307
|
+
function buildLogMarkdown(eventsJsonl: string): string {
|
|
308
308
|
const events: WikiEvent[] = [];
|
|
309
|
+
const raw = eventsJsonl.trim();
|
|
309
310
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
317
|
-
events.push(candidate as WikiEvent);
|
|
318
|
-
}
|
|
319
|
-
} catch {
|
|
320
|
-
// skip malformed
|
|
311
|
+
for (const line of raw.split("\n")) {
|
|
312
|
+
if (!line.trim()) continue;
|
|
313
|
+
try {
|
|
314
|
+
const candidate: unknown = JSON.parse(line);
|
|
315
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
316
|
+
events.push(candidate as WikiEvent);
|
|
321
317
|
}
|
|
318
|
+
} catch {
|
|
319
|
+
// Keep backward-compatible rich-log behavior: malformed lines are omitted.
|
|
322
320
|
}
|
|
323
321
|
}
|
|
324
322
|
|
|
@@ -338,10 +336,7 @@ function buildLogMarkdown(paths: VaultPaths): string {
|
|
|
338
336
|
lines.push("");
|
|
339
337
|
}
|
|
340
338
|
|
|
341
|
-
if (events.length === 0)
|
|
342
|
-
lines.push("_No events recorded yet._\n");
|
|
343
|
-
}
|
|
344
|
-
|
|
339
|
+
if (events.length === 0) lines.push("_No events recorded yet._\n");
|
|
345
340
|
return `${lines.join("\n")}\n`;
|
|
346
341
|
}
|
|
347
342
|
|
|
@@ -365,13 +360,25 @@ function atomicWriteFile(path: string, content: string): void {
|
|
|
365
360
|
renameSync(temporary, path);
|
|
366
361
|
}
|
|
367
362
|
|
|
368
|
-
|
|
369
|
-
|
|
363
|
+
type EventSourceRead =
|
|
364
|
+
| { available: true; content: string }
|
|
365
|
+
| { available: false; diagnostic: KnowledgeDiagnostic };
|
|
366
|
+
|
|
367
|
+
function readEventSource(filePath: string, diagnosticPath = "meta/events.jsonl"): EventSourceRead {
|
|
370
368
|
try {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
369
|
+
return { available: true, content: readFileSync(filePath, "utf8") };
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
372
|
+
// ENOENT = missing; ENOTDIR/EISDIR = not a regular file (FreeBSD dir hazard); else unreadable
|
|
373
|
+
const diagnosticCode = code === "ENOENT" ? "event_source_missing" : "event_source_unreadable";
|
|
374
|
+
const message =
|
|
375
|
+
diagnosticCode === "event_source_missing"
|
|
376
|
+
? "Authoritative event source is missing; existing log projections were preserved"
|
|
377
|
+
: "Authoritative event source is unreadable; existing log projections were preserved";
|
|
378
|
+
return {
|
|
379
|
+
available: false,
|
|
380
|
+
diagnostic: okfDiag("warning", diagnosticCode, diagnosticPath, message),
|
|
381
|
+
};
|
|
375
382
|
}
|
|
376
383
|
}
|
|
377
384
|
|
|
@@ -63,7 +63,6 @@ export function registerWikiModelCommand(pi: ExtensionAPI, runtime: Runtime): vo
|
|
|
63
63
|
const apply = (model: { provider: string; id: string } | undefined): void => {
|
|
64
64
|
persistTaskModel(ctx.cwd, model);
|
|
65
65
|
runtime.config = { ...runtime.config, taskModel: model };
|
|
66
|
-
runtime.configLoaded = true;
|
|
67
66
|
const label = formatActiveModelLabel(runtime.config, sessionId);
|
|
68
67
|
ctx.ui.setStatus(MODEL_STATUS_KEY, `🧠 wiki model: ${label}`);
|
|
69
68
|
ctx.ui.notify(`LLM Wiki: background tasks now use ${label}`, "info");
|
|
@@ -51,7 +51,6 @@ export interface LaunchCtx {
|
|
|
51
51
|
|
|
52
52
|
export class Runtime {
|
|
53
53
|
config: TaskConfig = { ...TASK_DEFAULTS };
|
|
54
|
-
configLoaded = false;
|
|
55
54
|
|
|
56
55
|
/**
|
|
57
56
|
* Extension API handle, attached at registration. Used by `report()` to emit
|
|
@@ -68,9 +67,7 @@ export class Runtime {
|
|
|
68
67
|
resolveFailureNotified = false;
|
|
69
68
|
|
|
70
69
|
ensureConfig(cwd: string): void {
|
|
71
|
-
if (this.configLoaded) return;
|
|
72
70
|
this.config = loadTaskConfig(cwd);
|
|
73
|
-
this.configLoaded = true;
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
/** True if a task with this label is currently running. */
|
|
@@ -147,7 +147,7 @@ function fileCaptureSource(pi: ExecApi, filePath: string, signal?: AbortSignal):
|
|
|
147
147
|
file_path: filePath,
|
|
148
148
|
format: extractor.format,
|
|
149
149
|
}),
|
|
150
|
-
event: () => ({
|
|
150
|
+
event: () => ({ format: extractor.format }),
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -100,6 +100,14 @@ export interface TaskConfig {
|
|
|
100
100
|
* they cost nothing in the system prompt for the ~95% who don't use them.
|
|
101
101
|
*/
|
|
102
102
|
trajectories?: boolean;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Language for background ingest synthesis narrative content (issue #124).
|
|
106
|
+
* BCP 47 language tag (e.g. "ru", "fr"). When unset, synthesis defaults to
|
|
107
|
+
* English. Applies to titles, summaries, takeaways, descriptions, etc.; raw
|
|
108
|
+
* source content and technical identifiers remain unchanged.
|
|
109
|
+
*/
|
|
110
|
+
synthesisLanguage?: string;
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
export const TASK_DEFAULTS: TaskConfig = {};
|
|
@@ -175,6 +183,13 @@ function readNamespacedConfig(path: string): Partial<TaskConfig> {
|
|
|
175
183
|
if (typeof section.trajectories === "boolean") {
|
|
176
184
|
out.trajectories = section.trajectories;
|
|
177
185
|
}
|
|
186
|
+
|
|
187
|
+
const lang = section.synthesisLanguage;
|
|
188
|
+
if (typeof lang === "string" && lang.trim()) {
|
|
189
|
+
const canonical = validateSynthesisLanguage(lang.trim());
|
|
190
|
+
if (canonical) out.synthesisLanguage = canonical;
|
|
191
|
+
}
|
|
192
|
+
|
|
178
193
|
return out;
|
|
179
194
|
} catch {
|
|
180
195
|
return {};
|
|
@@ -198,6 +213,27 @@ export function parseModelRef(ref: string): { provider: string; id: string } | u
|
|
|
198
213
|
return { provider, id };
|
|
199
214
|
}
|
|
200
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Validate and canonicalize a BCP 47 language tag for synthesisLanguage (issue #124).
|
|
218
|
+
* Returns the canonical tag, or undefined if invalid or suspicious.
|
|
219
|
+
* Uses Intl.getCanonicalLocales for validation; rejects tags containing
|
|
220
|
+
* newlines, quotes, or other prompt-injection candidates.
|
|
221
|
+
*/
|
|
222
|
+
export function validateSynthesisLanguage(tag: string): string | undefined {
|
|
223
|
+
// Reject obvious injection attempts: newlines, quotes, braces, angle brackets
|
|
224
|
+
if (/\n|\r|"|'|<|>|{|}/.test(tag)) return undefined;
|
|
225
|
+
// Reject if it looks like an instruction (contains "write", "ignore", "translate", etc.)
|
|
226
|
+
const lower = tag.toLowerCase();
|
|
227
|
+
if (/\b(write|ignore|translate|system|prompt|instruction)\b/.test(lower)) return undefined;
|
|
228
|
+
// Basic BCP 47 pattern: language[-script][-region][-variant]*
|
|
229
|
+
// Must start with 2-3 letter language code
|
|
230
|
+
if (!/^[a-z]{2,3}(?:-[A-Za-z]{1,8})*$/.test(tag)) return undefined;
|
|
231
|
+
|
|
232
|
+
const canonical = Intl.getCanonicalLocales(tag);
|
|
233
|
+
if (canonical.length === 0) return undefined;
|
|
234
|
+
return canonical[0];
|
|
235
|
+
}
|
|
236
|
+
|
|
201
237
|
/**
|
|
202
238
|
* Read a settings JSON file as a plain object, or `{}` when it is absent or
|
|
203
239
|
* corrupt. Reads directly (no `existsSync` pre-check) so there is no
|
|
@@ -414,6 +414,7 @@ export function registerWikiIngest(pi: ExtensionAPI, runtime?: Runtime): void {
|
|
|
414
414
|
sourceId: s.id,
|
|
415
415
|
manifest: s.manifest,
|
|
416
416
|
extracted: s.extracted,
|
|
417
|
+
synthesisLanguage: runtime.config.synthesisLanguage,
|
|
417
418
|
});
|
|
418
419
|
if (committed) {
|
|
419
420
|
// Background semantic embeddings (#66): embed the pages this
|
|
@@ -1112,6 +1113,14 @@ export function registerWikiRebuildMeta(pi: ExtensionAPI, runtime?: Runtime): vo
|
|
|
1112
1113
|
if (!result.ok) {
|
|
1113
1114
|
return `⚠️ LLM Wiki: rebuild had issues — ${result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("; ")}`;
|
|
1114
1115
|
}
|
|
1116
|
+
const warnings = result.diagnostics.filter(
|
|
1117
|
+
(diagnostic) => diagnostic.severity === "warning",
|
|
1118
|
+
);
|
|
1119
|
+
if (warnings.length > 0) {
|
|
1120
|
+
return `⚠️ LLM Wiki: metadata rebuilt with warnings — ${warnings
|
|
1121
|
+
.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`)
|
|
1122
|
+
.join("; ")}`;
|
|
1123
|
+
}
|
|
1115
1124
|
const registry = readJson<Registry>(join(paths.meta, "registry.json"), {
|
|
1116
1125
|
version: "1.0",
|
|
1117
1126
|
last_updated: "",
|
|
@@ -397,6 +397,13 @@ export function isProtectedPath(
|
|
|
397
397
|
reason: "Raw sources are immutable. Use wiki_capture_source to add sources.",
|
|
398
398
|
};
|
|
399
399
|
}
|
|
400
|
+
if (relativePhysicalPath(paths.meta, absPath) === "events.jsonl") {
|
|
401
|
+
return {
|
|
402
|
+
protected: true,
|
|
403
|
+
reason:
|
|
404
|
+
"Event history is append-only authoritative state. Use wiki_log_event or an owning wiki operation instead.",
|
|
405
|
+
};
|
|
406
|
+
}
|
|
400
407
|
if (isPathWithin(paths.meta, absPath)) {
|
|
401
408
|
return {
|
|
402
409
|
protected: true,
|
package/mcp/index.ts
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
-
import { McpServer
|
|
15
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
16
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
16
17
|
import * as z from "zod/v4";
|
|
17
18
|
import { resolveVaultPaths } from "../extensions/llm-wiki/lib/utils.js";
|
|
18
19
|
import { createExecApi } from "./exec.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zosmaai/pi-llm-wiki",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
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",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@cfworker/json-schema": "^4.1.1",
|
|
76
|
-
"@modelcontextprotocol/server": "^2.0.0
|
|
76
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
77
77
|
"mdast-util-from-markdown": "^2.0.3",
|
|
78
78
|
"node-html-markdown": "^2.0.0",
|
|
79
79
|
"yaml": "^2.9.0",
|
package/skills/llm-wiki/SKILL.md
CHANGED
|
@@ -32,7 +32,7 @@ WIKI_ROOT/
|
|
|
32
32
|
│ ├── analyses/ # Durable query answers
|
|
33
33
|
│ ├── cases/ # One specific past task per trajectory
|
|
34
34
|
│ └── skills/ # Reusable patterns distilled from trajectories
|
|
35
|
-
├── meta/ #
|
|
35
|
+
├── meta/ # Durable events + generated projections (extension-owned)
|
|
36
36
|
│ ├── registry.json # Master page catalog
|
|
37
37
|
│ ├── backlinks.json # Inbound link map
|
|
38
38
|
│ ├── index.md # Human-readable catalog
|
|
@@ -45,13 +45,15 @@ WIKI_ROOT/
|
|
|
45
45
|
## Golden Rules
|
|
46
46
|
|
|
47
47
|
1. **RAW IS IMMUTABLE.** Never edit `raw/`. Use `wiki_capture_source` to add sources.
|
|
48
|
-
2. **META IS
|
|
48
|
+
2. **META IS EXTENSION-OWNED.** Never edit `meta/` directly. `events.jsonl` is append-only authoritative activity state; other metadata files are generated projections.
|
|
49
49
|
3. **YOU OWN THE WIKI.** Create, update, and cross-reference everything in `wiki/`.
|
|
50
50
|
4. **ONE FILE PER THING.** Each entity, concept, source gets its own `.md` file.
|
|
51
51
|
5. **CROSS-REFERENCE EVERYTHING.** Every page needs at least 2 links. Prefer standard Markdown: `[label](/folder/page.md)`. Legacy wikilinks `[[folder/page]]` remain readable.
|
|
52
52
|
6. **CITE SOURCES.** Every claim links back to its raw source packet.
|
|
53
53
|
7. **FLAG CONTRADICTIONS.** When sources disagree, document both sides.
|
|
54
54
|
|
|
55
|
+
> Preserve `meta/events.jsonl` in full-vault backups. `meta/log.md` and `wiki/log.md` cannot reconstruct it. Do not place secrets or private machine paths in manual event details.
|
|
56
|
+
|
|
55
57
|
## Agent Working-Memory (Trajectories)
|
|
56
58
|
|
|
57
59
|
The wiki captures not only what you *read* (sources) but what you *do*
|