@zosmaai/pi-llm-wiki 0.9.3 → 0.10.1

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 CHANGED
@@ -3,6 +3,8 @@
3
3
  ## [Unreleased]
4
4
 
5
5
  ### Fixed
6
+ - **`buildIngestedSourcePage` omits folder prefix on entity/concept wikilinks** (Issue #89): `buildIngestedSourcePage()` in `lib/ingest-worker.ts` generated entity and concept wikilinks in ingested source pages as `[[entity-name]]` instead of `[[entities/entity-name]]`. The bare target never matched the page ID (which carries the folder prefix, e.g. `entities/entity-name`), leaving every entity and concept with zero inbound links and producing 177 orphans out of 180 pages in real vaults. The concept/entity page templates already used the correct `[[sources/...]]` prefix — only the source page template was broken. Fixed by adding the `entities/` and `concepts/` prefix to the generated wikilinks.
7
+
6
8
  - **`wiki_watch` referred to a fictional `schedule_prompt` tool** (Issue #81): `wiki_watch` emitted instructions to run `schedule_prompt action=add ...` — a tool that doesn't exist in pi-coding-agent or any pi extension — so `/wiki-run --schedule weekly` printed a command the user couldn't execute. The emitted payload also contained the typo `/wiki:run` (real slash-command is `/wiki-run`), and the tool description claimed it scheduled jobs when it only ever printed instructions. Now emits a real, copy-pasteable **POSIX 5-field crontab line** wrapped in `/bin/bash -lc` (so npm-global / bun / nvm PATH is imported under cron's minimal env), with defensive `mkdir -p` of the log dir and a `# llm-wiki-autoupdate` removal tag. The tool description, output text, `prompts/wiki-run.md`, `docs/api.md` and all 10 i18n READMEs now explicitly say "prints — does not install". `details` now carries `cronLine` and `installed: false`. New `test/wiki-watch.test.ts` (10 tests) locks down all three regressions plus the portability contract (login-shell wrapper, defensive `mkdir`, quoted `$HOME`).
7
9
  - **Personal wiki created at doubled path `~/.llm-wiki/.llm-wiki/…`**: `getPersonalWikiRoot()` returned the dot-dir itself (`~/.llm-wiki`) while `getVaultPaths()` then appended another `.llm-wiki/` segment, so the personal vault was written to `~/.llm-wiki/.llm-wiki/wiki/…`. Fixed by aligning `getPersonalWikiRoot()` with the same "root = parent of `.llm-wiki/`" contract used by project vaults. `WIKI_HOME` continues to override the parent.
8
10
 
package/README.md CHANGED
@@ -422,17 +422,17 @@ Thanks to everyone who has contributed! This list is regenerated automatically b
422
422
  </a>
423
423
  </td>
424
424
  <td align="center">
425
- <a href="https://github.com/jfraser">
426
- <img src="https://avatars.githubusercontent.com/u/165964?v=4" width="64;" alt="jfraser"/>
425
+ <a href="https://github.com/Shanvit7">
426
+ <img src="https://avatars.githubusercontent.com/u/64424817?v=4" width="64;" alt="Shanvit7"/>
427
427
  <br />
428
- <sub><b>James Fraser</b></sub>
428
+ <sub><b>Shanvit S Shetty</b></sub>
429
429
  </a>
430
430
  </td>
431
431
  <td align="center">
432
- <a href="https://github.com/Shanvit7">
433
- <img src="https://avatars.githubusercontent.com/u/64424817?v=4" width="64;" alt="Shanvit7"/>
432
+ <a href="https://github.com/jfraser">
433
+ <img src="https://avatars.githubusercontent.com/u/165964?v=4" width="64;" alt="jfraser"/>
434
434
  <br />
435
- <sub><b>Shanvit S Shetty</b></sub>
435
+ <sub><b>James Fraser</b></sub>
436
436
  </a>
437
437
  </td>
438
438
  <td align="center">
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, join } from "node:path";
3
3
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
4
  import { installGuardrails } from "./lib/guardrails.js";
5
- import { appendWikiStatus } from "./lib/inject.js";
5
+ import { buildAgentStartInjection } from "./lib/inject.js";
6
6
  import { registerWikiModelCommand } from "./lib/model-command.js";
7
7
  import {
8
8
  buildSessionNotice,
@@ -216,7 +216,14 @@ export default function (pi: ExtensionAPI) {
216
216
  }
217
217
 
218
218
  const prompt = event.prompt || "";
219
- let injectedContext = event.systemPrompt || "";
219
+ // Volatile, per-turn content (recall results + one-time topic-inference
220
+ // directive) MUST NOT go into the system prompt (issue #92). The system
221
+ // prompt is the provider's primary cache prefix; rewriting it every turn
222
+ // (recall is keyed on event.prompt, so it differs each turn) invalidates
223
+ // the whole prompt cache, forcing a full re-prompt-fill every turn. Collect
224
+ // it here and deliver it as a tail conversation message instead, which
225
+ // leaves the cached system-prompt prefix stable.
226
+ let dynamicContext = "";
220
227
 
221
228
  // Topic inference on first turn after auto-creation
222
229
  if (needsTopicInference && prompt.trim()) {
@@ -238,7 +245,7 @@ export default function (pi: ExtensionAPI) {
238
245
  // ignore
239
246
  }
240
247
 
241
- injectedContext += `
248
+ dynamicContext += `
242
249
 
243
250
  ## Wiki Setup Required
244
251
  The LLM Wiki was just auto-created but needs its topic and mode configured. Before responding to the user, analyze their prompt and this project's context to infer:
@@ -279,7 +286,7 @@ Then call wiki_bootstrap with the inferred topic and mode to finalize the setup.
279
286
  skillInlineMax: runtime.config?.recallSkillInlineMax,
280
287
  });
281
288
  if (recallContext) {
282
- injectedContext += `\n\n${recallContext}`;
289
+ dynamicContext += `\n\n${recallContext}`;
283
290
  }
284
291
  // Recall-aware status line (issue #77): make it visible that recall
285
292
  // actually fired and how many pages matched. Purely a UI signal — no
@@ -294,11 +301,19 @@ Then call wiki_bootstrap with the inferred topic and mode to finalize the setup.
294
301
  }
295
302
  }
296
303
 
297
- // Always inject a visible wiki status footer, even when empty
298
- // This ensures the model knows the wiki is active and can use it
299
- injectedContext = appendWikiStatus(injectedContext);
304
+ // Split into a cache-stable system prompt (static footer only) and a
305
+ // volatile tail message (issue #92). See lib/inject.ts for the contract.
306
+ const { systemPrompt, message } = buildAgentStartInjection(event.systemPrompt || "", [
307
+ dynamicContext,
308
+ ]);
300
309
 
301
- if (injectedContext === event.systemPrompt) return;
302
- return { systemPrompt: injectedContext };
310
+ // Only claim a systemPrompt change when the footer actually altered the
311
+ // string (a carry-forward turn already carries it, so this no-ops).
312
+ const systemPromptChanged = systemPrompt !== event.systemPrompt;
313
+ if (!systemPromptChanged && !message) return;
314
+ return {
315
+ ...(systemPromptChanged ? { systemPrompt } : {}),
316
+ ...(message ? { message } : {}),
317
+ };
303
318
  });
304
319
  }
@@ -116,11 +116,11 @@ export function buildIngestedSourcePage(
116
116
  : "- [None recorded]";
117
117
  const entities =
118
118
  data.entities.length > 0
119
- ? data.entities.map((e) => `- [[${slugify(e.title)}]]`).join("\n")
119
+ ? data.entities.map((e) => `- [[entities/${slugify(e.title)}]]`).join("\n")
120
120
  : "- [None]";
121
121
  const concepts =
122
122
  data.concepts.length > 0
123
- ? data.concepts.map((c) => `- [[${slugify(c.title)}]]`).join("\n")
123
+ ? data.concepts.map((c) => `- [[concepts/${slugify(c.title)}]]`).join("\n")
124
124
  : "- [None]";
125
125
  const quotes =
126
126
  data.quotes && data.quotes.length > 0
@@ -1,11 +1,18 @@
1
1
  /**
2
- * System-prompt context injection primitives (issue #87).
2
+ * System-prompt context injection primitives (issues #87, #92).
3
3
  *
4
4
  * The `before_agent_start` hook augments the chained system prompt with a
5
5
  * visible wiki-status footer. This module isolates that append so it can be
6
6
  * unit-tested for idempotency — a turn that aborts (network error / ESC) and is
7
7
  * retried can carry the prior injection forward, and a naive append stacks the
8
8
  * footer 2x, 3x, ...
9
+ *
10
+ * It also owns the cache-safety split (issue #92): VOLATILE per-turn context
11
+ * (recall results, one-time topic-inference directive) must never enter the
12
+ * system prompt — that is the provider's primary cache prefix, so per-turn
13
+ * variation there forces a full cache miss every turn. Volatile content is
14
+ * routed into a tail conversation message instead. `buildAgentStartInjection`
15
+ * is the single, pure decision point for that split.
9
16
  */
10
17
 
11
18
  /** The always-injected wiki-status footer (sans surrounding whitespace). */
@@ -25,3 +32,54 @@ export function appendWikiStatus(systemPrompt: string): string {
25
32
  const base = systemPrompt.split(`\n\n${WIKI_STATUS_BLOCK}`).join("");
26
33
  return `${base}\n\n${WIKI_STATUS_BLOCK}`;
27
34
  }
35
+
36
+ /** customType of the hidden tail message carrying volatile per-turn context. */
37
+ export const WIKI_RECALL_MESSAGE_TYPE = "wiki-recall-context";
38
+
39
+ /** A hidden, LLM-visible tail message carrying volatile per-turn wiki context. */
40
+ export interface WikiRecallMessage {
41
+ customType: typeof WIKI_RECALL_MESSAGE_TYPE;
42
+ content: string;
43
+ display: false;
44
+ }
45
+
46
+ /** Result of the cache-safe split for one `before_agent_start` turn. */
47
+ export interface AgentStartInjection {
48
+ /**
49
+ * The system prompt to return. ONLY ever the base plus the static footer —
50
+ * never any volatile content — so it is byte-identical turn over turn and
51
+ * the provider's prompt cache stays warm (issue #92).
52
+ */
53
+ systemPrompt: string;
54
+ /** Volatile blocks, delivered as a tail message. Omitted when empty. */
55
+ message?: WikiRecallMessage;
56
+ }
57
+
58
+ /**
59
+ * Split a turn's injection into a cache-stable system prompt and a volatile
60
+ * tail message (issue #92).
61
+ *
62
+ * - `systemPrompt` is always `appendWikiStatus(baseSystemPrompt)` — the static
63
+ * footer only. It carries NONE of `dynamicBlocks`, so it does not vary with
64
+ * recall results and never breaks the provider cache prefix.
65
+ * - `dynamicBlocks` (recall context, topic-inference directive, ...) are
66
+ * trimmed, emptied entries dropped, and joined with a blank line into the
67
+ * message body. When nothing survives, no message is emitted.
68
+ *
69
+ * Pure and side-effect free — see test/agent-start-injection.test.ts.
70
+ */
71
+ export function buildAgentStartInjection(
72
+ baseSystemPrompt: string,
73
+ dynamicBlocks: Array<string | undefined>,
74
+ ): AgentStartInjection {
75
+ const systemPrompt = appendWikiStatus(baseSystemPrompt);
76
+ const content = dynamicBlocks
77
+ .map((b) => b?.trim())
78
+ .filter((b): b is string => Boolean(b))
79
+ .join("\n\n");
80
+ if (!content) return { systemPrompt };
81
+ return {
82
+ systemPrompt,
83
+ message: { customType: WIKI_RECALL_MESSAGE_TYPE, content, display: false },
84
+ };
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zosmaai/pi-llm-wiki",
3
- "version": "0.9.3",
3
+ "version": "0.10.1",
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",
@@ -47,22 +47,16 @@
47
47
  "publishConfig": {
48
48
  "access": "public"
49
49
  },
50
- "scripts": {
51
- "test": "vitest run",
52
- "test:watch": "vitest",
53
- "test:coverage": "vitest run --coverage",
54
- "typecheck": "tsc --noEmit",
55
- "lint": "biome check .",
56
- "lint:fix": "biome check --apply .",
57
- "release:patch": "node scripts/release.js patch",
58
- "release:minor": "node scripts/release.js minor",
59
- "release:major": "node scripts/release.js major",
60
- "release:push": "git push origin main --tags"
61
- },
62
50
  "pi": {
63
- "extensions": ["./extensions"],
64
- "skills": ["./skills"],
65
- "prompts": ["./prompts"],
51
+ "extensions": [
52
+ "./extensions"
53
+ ],
54
+ "skills": [
55
+ "./skills"
56
+ ],
57
+ "prompts": [
58
+ "./prompts"
59
+ ],
66
60
  "image": "https://raw.githubusercontent.com/zosmaai/pi-llm-wiki/main/assets/screenshot.png",
67
61
  "mcpservers": {
68
62
  "llm-wiki": "node ./mcp/index.js"
@@ -81,11 +75,26 @@
81
75
  },
82
76
  "devDependencies": {
83
77
  "@biomejs/biome": "^1.9.4",
78
+ "@mariozechner/pi-agent-core": "^0.70.2",
79
+ "@mariozechner/pi-ai": "^0.70.2",
84
80
  "@mariozechner/pi-coding-agent": "^0.70.2",
85
81
  "@mermaid-js/mermaid-cli": "^11.12.0",
82
+ "@types/node": "^22",
86
83
  "@vitest/coverage-v8": "^3.2.4",
87
84
  "typebox": "^1.1.34",
88
85
  "typescript": "^5.7.0",
89
86
  "vitest": "^3.0.0"
87
+ },
88
+ "scripts": {
89
+ "test": "vitest run",
90
+ "test:watch": "vitest",
91
+ "test:coverage": "vitest run --coverage",
92
+ "typecheck": "tsc --noEmit",
93
+ "lint": "biome check .",
94
+ "lint:fix": "biome check --apply .",
95
+ "release:patch": "node scripts/release.js patch",
96
+ "release:minor": "node scripts/release.js minor",
97
+ "release:major": "node scripts/release.js major",
98
+ "release:push": "git push origin main --tags"
90
99
  }
91
- }
100
+ }