@zosmaai/pi-llm-wiki 0.11.8 → 0.12.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.
Files changed (30) hide show
  1. package/dist/extensions/llm-wiki/lib/dashboard-command.js +1 -1
  2. package/dist/extensions/llm-wiki/lib/guardrails.js +1 -1
  3. package/dist/extensions/llm-wiki/lib/host.js +1 -1
  4. package/dist/extensions/llm-wiki/lib/knowledge-document.js +11 -2
  5. package/dist/extensions/llm-wiki/lib/model-command.js +1 -1
  6. package/dist/extensions/llm-wiki/lib/settings-command.js +1 -1
  7. package/dist/extensions/llm-wiki/lib/source-packet.js +10 -2
  8. package/dist/extensions/llm-wiki/lib/subagent.js +1 -1
  9. package/dist/extensions/llm-wiki/lib/tools.js +3 -0
  10. package/docs/superpowers/plans/2026-09-02-wiki-lint-fresh-vault-and-windows-capture-fixes.md +284 -0
  11. package/extensions/llm-wiki/index.ts +1 -1
  12. package/extensions/llm-wiki/lib/dashboard-command.ts +2 -2
  13. package/extensions/llm-wiki/lib/guardrails.ts +2 -2
  14. package/extensions/llm-wiki/lib/host.ts +3 -3
  15. package/extensions/llm-wiki/lib/ingest-worker.ts +2 -2
  16. package/extensions/llm-wiki/lib/knowledge-document.ts +12 -2
  17. package/extensions/llm-wiki/lib/model-command.ts +2 -2
  18. package/extensions/llm-wiki/lib/observation.ts +1 -1
  19. package/extensions/llm-wiki/lib/recall.ts +1 -1
  20. package/extensions/llm-wiki/lib/retro.ts +1 -1
  21. package/extensions/llm-wiki/lib/runtime.ts +1 -1
  22. package/extensions/llm-wiki/lib/settings-command.ts +2 -2
  23. package/extensions/llm-wiki/lib/source-packet.ts +11 -2
  24. package/extensions/llm-wiki/lib/subagent.ts +2 -2
  25. package/extensions/llm-wiki/lib/tools.ts +4 -1
  26. package/extensions/llm-wiki/lib/trajectories-command.ts +1 -1
  27. package/extensions/llm-wiki/lib/trajectory.ts +1 -1
  28. package/extensions/llm-wiki/lib/utils.ts +1 -1
  29. package/mcp/operations.ts +1 -1
  30. package/package.json +6 -6
@@ -1,4 +1,4 @@
1
- import { Container, matchesKey, Text } from "@mariozechner/pi-tui";
1
+ import { Container, matchesKey, Text } from "@earendil-works/pi-tui";
2
2
  import { collectDashboardStats } from "./dashboard.js";
3
3
  const TYPE_ORDER = [
4
4
  "concept",
@@ -1,4 +1,4 @@
1
- import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
1
+ import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
2
2
  import { scheduleReindex } from "./indexing.js";
3
3
  import { rebuildMetadataLight } from "./metadata.js";
4
4
  import { isPathWithin, isProtectedPath, resolveVaultPaths } from "./utils.js";
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
4
  /** Project config directory name per host. */
5
5
  const CONFIG_DIR = { pi: ".pi", omp: ".omp" };
6
6
  /** Settings file names inside a config directory, lowest → highest precedence. */
@@ -383,7 +383,14 @@ export function serializeKnowledgeDocument(document) {
383
383
  const body = document.body.replace(/\r\n?/g, "\n").replace(/\n*$/, "");
384
384
  return body ? `---\n${yaml}---\n\n${body}\n` : `---\n${yaml}---\n`;
385
385
  }
386
- /** Escape wikilink alias pipes so generated content remains valid in Markdown tables. */
386
+ const TABLE_ROW = /^\s*\|.*\|\s*$/;
387
+ /** Escape wikilink alias pipes only inside Markdown table rows.
388
+ *
389
+ * A bare `|` inside `[[target|alias]]` would be read as a table cell
390
+ * delimiter when the row renders, so table rows need the pipe escaped as
391
+ * `[[target\|alias]]`. Prose, lists, headings, and fenced code keep their
392
+ * pipes literal, so the written file stays valid for external readers
393
+ * (Obsidian, VS Code Wiki Links) that don't understand the escaped form. */
387
394
  function escapeWikilinkAliasPipes(body) {
388
395
  let inFence = false;
389
396
  return body
@@ -395,7 +402,9 @@ function escapeWikilinkAliasPipes(body) {
395
402
  }
396
403
  if (inFence)
397
404
  return line;
398
- return line.replace(/\[\[([^\]\n]*?)(?<!\\)\|([^\]\n]*?)\]\]/g, "[[$1\\|$2]]");
405
+ return TABLE_ROW.test(line)
406
+ ? line.replace(/\[\[([^\]\n]*?)(?<!\\)\|([^\]\n]*?)\]\]/g, "[[$1\\|$2]]")
407
+ : line;
399
408
  })
400
409
  .join("\n");
401
410
  }
@@ -1,4 +1,4 @@
1
- import { Container, matchesKey, SelectList, Spacer, Text, } from "@mariozechner/pi-tui";
1
+ import { Container, matchesKey, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
2
2
  import { parseModelRef, persistTaskModel } from "./task-config.js";
3
3
  /** Words that clear the override and revert to the session model. */
4
4
  const CLEAR_WORDS = new Set(["session", "default", "reset", "clear", "none", "unset"]);
@@ -1,5 +1,5 @@
1
1
  import { homedir } from "node:os";
2
- import { Container, Input, SettingsList, Spacer, Text, } from "@mariozechner/pi-tui";
2
+ import { Container, Input, SettingsList, Spacer, Text, } from "@earendil-works/pi-tui";
3
3
  import { loadTaskConfig, loadTaskConfigSources, parseModelRef, persistSetting, trajectoriesEnabled, } from "./task-config.js";
4
4
  const SETTINGS = [
5
5
  {
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { copyFile } from "node:fs/promises";
3
- import { extname, join } from "node:path";
3
+ import { basename, extname, join } from "node:path";
4
4
  import { createKnowledgeDocument, serializeKnowledgeDocument } from "./knowledge-document.js";
5
5
  import { appendEvent } from "./metadata.js";
6
6
  import { binaryExtractionFailureMessage, detectBinaryMagicBytes, extractUrlContent, fileExtractorFor, } from "./source-extractors.js";
@@ -47,7 +47,7 @@ function urlCaptureSource(pi, url, signal) {
47
47
  };
48
48
  }
49
49
  function fileCaptureSource(pi, filePath, signal) {
50
- const fileName = filePath.split("/").pop() || "unknown";
50
+ const fileName = originalFileNameForPath(filePath);
51
51
  const extractor = fileExtractorFor(filePath);
52
52
  const content = extractor.shouldReadText ? readText(filePath) : "";
53
53
  return {
@@ -168,6 +168,14 @@ function originalFileNameForUrl(url) {
168
168
  }
169
169
  return "source.html";
170
170
  }
171
+ /**
172
+ * Original-artifact name for a local file capture. Uses platform basename so
173
+ * Windows absolute paths (drive colon + backslashes) cannot leak into the
174
+ * packet/original file name (issue #204).
175
+ */
176
+ export function originalFileNameForPath(filePath) {
177
+ return basename(filePath) || "unknown";
178
+ }
171
179
  function contentExtractionFailureMessage(source) {
172
180
  return `_Content could not be extracted from ${source}_\n`;
173
181
  }
@@ -1,4 +1,4 @@
1
- import { agentLoop, } from "@mariozechner/pi-agent-core";
1
+ import { agentLoop, } from "@earendil-works/pi-agent-core";
2
2
  /**
3
3
  * Run a sub-agent loop to completion.
4
4
  *
@@ -871,6 +871,9 @@ function runWikiLint(paths, autoFix) {
871
871
  // The gap snapshot is generated discovery metadata consumed by wiki_status:
872
872
  // persist it on every successful lint so status never reports a stale count.
873
873
  // Corrective actions below (report, event, meta rebuild) stay autoFix-only.
874
+ // mkdir mirrors the autoFix report write below: on a fresh checkout the
875
+ // gitignored .discoveries dir does not exist yet (issue #203).
876
+ mkdirSync(paths.discoveries, { recursive: true });
874
877
  writeJson(join(paths.discoveries, "gaps.json"), {
875
878
  gaps,
876
879
  generated: new Date().toISOString(),
@@ -0,0 +1,284 @@
1
+ # Wiki Lint Fresh-Vault + Windows Capture Fixes Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use /skill:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Fix two one-line bugs — `wiki_lint` crashing with ENOENT on fresh checkouts (issue #203) and `wiki_capture_source` corrupting the `original/` artifact name on Windows absolute paths (issue #204) — each with a regression test.
6
+
7
+ **Architecture:** Both fixes slot into existing code with no new modules. #203: `runWikiLint` (private in `tools.ts`) persists the gap snapshot via `writeJson` without ensuring `.llm-wiki/.discoveries/` exists; it gets the same `mkdirSync(recursive)` treatment the `autoFix` report write in the same function already uses. #204: `fileCaptureSource` derives the artifact name with `filePath.split("/").pop()`, which is a no-op on Windows paths; replace with a small exported, platform-aware `originalFileNameForPath()` helper modeled on the existing `originalFileNameForUrl()` sibling so the behavior is unit-testable cross-platform.
8
+
9
+ **Tech Stack:** TypeScript (ES2022, ESM), Vitest, Biome, pnpm.
10
+
11
+ **Roadmap:** None
12
+
13
+ **Phase:** Single-plan implementation (two independent one-line fixes, one branch, one PR)
14
+
15
+ ---
16
+
17
+ ## Background (what the engineer must know)
18
+
19
+ - Repo: `@zosmaai/pi-llm-wiki`. Two bugs, both confirmed present on current `main`:
20
+ - **#203** — `runWikiLint()` (`extensions/llm-wiki/lib/tools.ts:879`) unconditionally writes `gaps.json` to `paths.discoveries` at ~line 1018. `ensureVaultStructure` (`lib/utils.ts:262`) does create `.discoveries` — but only when bootstrapping a vault. A fresh git checkout/worktree of an existing vault has `.discoveries` gitignored → absent → `writeJson` → `writeFileSync` throws ENOENT → the whole lint run is discarded and no report is ever delivered.
21
+ - **#204** — `fileCaptureSource()` (`extensions/llm-wiki/lib/source-packet.ts:114`) computes `const fileName = filePath.split("/").pop() || "unknown"`. On Windows an absolute path like `D:\any\dir\doc.md` contains no `/`, so `fileName` becomes the whole string; `preserveFileOriginal` then writes `join(packetPath, "original", "D:\any\dir\doc.md")` — a drive colon inside a filename segment, illegal on Windows → capture fails, empty packet left behind.
22
+ - Test harness facts (verified, so the tests below compile as-is):
23
+ - `test/lint-okf.test.ts` drives the tool via `registerWikiLint({ registerTool: ... } as unknown as ExtensionAPI)` and `tool.execute("test", params, undefined, undefined, { cwd: root, hasUI: false })`. It imports `existsSync`, `rmSync`, `writeFileSync`, `readFileSync` from `node:fs` and `getVaultPaths`, `ensureVaultStructure` from `lib/utils.js` — everything this plan's test needs is already imported.
24
+ - Without a `Runtime` (the test case), `dispatchReported` (`tools.ts:82`) runs `work()` inline with **no catch** — so the ENOENT rejection propagates out of `tool.execute()`. That is why the failing test below fails by throwing.
25
+ - `test/source-capture.test.ts` already imports `{ captureFile, captureText, captureUrl }` from `lib/source-packet.js` and has a `makePaths()` helper + `mockPi()` from `./helpers.js`.
26
+ - Commands: `pnpm test` (vitest), `pnpm typecheck`, `pnpm lint` (biome). Node/pnpm available; `pnpm --version` = 9.15.4.
27
+ - Issue #171 is explicitly out of scope.
28
+
29
+ ## File Structure
30
+
31
+ | File | Change | Responsibility in this plan |
32
+ |---|---|---|
33
+ | `extensions/llm-wiki/lib/tools.ts` | Modify (~line 1015, inside `runWikiLint`) | One-line `mkdirSync(paths.discoveries, { recursive: true })` before the gaps write |
34
+ | `extensions/llm-wiki/lib/source-packet.ts` | Modify (line 3 import, line 114, new helper after `originalFileNameForUrl` at ~line 256) | Export `originalFileNameForPath(filePath)`; use it in `fileCaptureSource` |
35
+ | `test/lint-okf.test.ts` | Modify (append one `it(...)` after the "refreshes a stale gap snapshot to empty on audit-only lint" test, ~line 183) | Regression test: lint on a vault without `.discoveries` completes and persists `gaps.json` |
36
+ | `test/source-capture.test.ts` | Modify (extend the `source-packet.js` import; add one `it(...)` after the "should copy local non-PDF file content to extracted.md" test) | Unit test: `originalFileNameForPath` strips the platform's directory prefix |
37
+
38
+ No new files. No config, docs, or schema changes.
39
+
40
+ ---
41
+
42
+ ### Task 1: Fix #203 — `wiki_lint` ENOENT on fresh vault
43
+
44
+ **Files:**
45
+ - Modify: `extensions/llm-wiki/lib/tools.ts` (inside `runWikiLint`, the gaps write at ~lines 1015-1020)
46
+ - Test: `test/lint-okf.test.ts`
47
+
48
+ - [x] **Step 1: Write the failing test**
49
+
50
+ Append this `it(...)` to `test/lint-okf.test.ts`, immediately after the closing `});` of the test named `"refreshes a stale gap snapshot to empty on audit-only lint"` (that test ends with `expect(status.content[0].text).toContain("Gaps: 0");` + `});`, ~line 183). All symbols used are already imported in this file:
51
+
52
+ ```ts
53
+ it("completes on a fresh checkout where .discoveries is not yet created (issue #203)", async () => {
54
+ const paths = getVaultPaths(root);
55
+ ensureVaultStructure(paths);
56
+ // Fresh checkouts / worktrees of an existing vault do not have the
57
+ // gitignored .discoveries directory — simulate that.
58
+ rmSync(paths.discoveries, { recursive: true, force: true });
59
+ writeFileSync(join(paths.dotWiki, "config.json"), JSON.stringify({ name: "Fresh lint" }));
60
+ writeFileSync(
61
+ join(paths.wiki, "concepts", "valid.md"),
62
+ "---\ntype: concept\n---\n\nValid.\n",
63
+ );
64
+
65
+ let tool: TestTool | undefined;
66
+ registerWikiLint({
67
+ registerTool: (definition: unknown) => {
68
+ tool = definition as TestTool;
69
+ },
70
+ } as unknown as ExtensionAPI);
71
+ if (!tool) throw new Error("wiki_lint was not registered");
72
+ const result = await tool.execute("test", { auto_fix: false }, undefined, undefined, {
73
+ cwd: root,
74
+ hasUI: false,
75
+ });
76
+
77
+ expect(result.isError).not.toBe(true);
78
+ // auto_fix:false returns the run summary (not the reportLines file content).
79
+ expect(result.content[0].text).toContain("LLM Wiki lint complete");
80
+ expect(existsSync(join(paths.discoveries, "gaps.json"))).toBe(true);
81
+ const snapshot = JSON.parse(readFileSync(join(paths.discoveries, "gaps.json"), "utf8"));
82
+ expect(snapshot.gaps).toEqual([]);
83
+ });
84
+ ```
85
+
86
+ - [x] **Step 2: Run the test to verify it fails**
87
+
88
+ Run: `pnpm vitest run test/lint-okf.test.ts -t "fresh checkout"`
89
+
90
+ Expected: FAIL — the `await tool.execute(...)` line throws `Error: ENOENT: no such file or directory, open '<root>/.llm-wiki/.discoveries/gaps.json'` (rejection propagates because `dispatchReported` has no runtime in tests).
91
+
92
+ - [x] **Step 3: Write the minimal implementation**
93
+
94
+ In `extensions/llm-wiki/lib/tools.ts`, inside `runWikiLint`, change this block (~lines 1015-1020):
95
+
96
+ ```ts
97
+ // The gap snapshot is generated discovery metadata consumed by wiki_status:
98
+ // persist it on every successful lint so status never reports a stale count.
99
+ // Corrective actions below (report, event, meta rebuild) stay autoFix-only.
100
+ writeJson(join(paths.discoveries, "gaps.json"), {
101
+ gaps,
102
+ generated: new Date().toISOString(),
103
+ });
104
+ ```
105
+
106
+ to:
107
+
108
+ ```ts
109
+ // The gap snapshot is generated discovery metadata consumed by wiki_status:
110
+ // persist it on every successful lint so status never reports a stale count.
111
+ // Corrective actions below (report, event, meta rebuild) stay autoFix-only.
112
+ // mkdir mirrors the autoFix report write below: on a fresh checkout the
113
+ // gitignored .discoveries dir does not exist yet (issue #203).
114
+ mkdirSync(paths.discoveries, { recursive: true });
115
+ writeJson(join(paths.discoveries, "gaps.json"), {
116
+ gaps,
117
+ generated: new Date().toISOString(),
118
+ });
119
+ ```
120
+
121
+ `mkdirSync` is already imported at the top of `tools.ts` (line 1: `import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";`) — no import change.
122
+
123
+ - [x] **Step 4: Run the test to verify it passes**
124
+
125
+ Run: `pnpm vitest run test/lint-okf.test.ts`
126
+
127
+ Expected: PASS — all tests in the file green, including the new one.
128
+
129
+ - [x] **Step 5: Run the full gate**
130
+
131
+ Run: `pnpm test && pnpm typecheck && pnpm lint`
132
+
133
+ Expected: all green (no unrelated breakage).
134
+
135
+ - [x] **Step 6: Commit**
136
+
137
+ ```bash
138
+ git add test/lint-okf.test.ts extensions/llm-wiki/lib/tools.ts
139
+ git commit -m "fix: ensure .discoveries exists before lint persists the gap snapshot (#203)"
140
+ ```
141
+
142
+ ---
143
+
144
+ ### Task 2: Fix #204 — Windows drive colon in captured file name
145
+
146
+ **Files:**
147
+ - Modify: `extensions/llm-wiki/lib/source-packet.ts` (line 3 import; line 114 in `fileCaptureSource`; new exported helper after `originalFileNameForUrl`, which ends ~line 256)
148
+ - Test: `test/source-capture.test.ts`
149
+
150
+ - [x] **Step 1: Write the failing test**
151
+
152
+ First, extend the existing import at line 5 of `test/source-capture.test.ts`:
153
+
154
+ ```ts
155
+ import { captureFile, captureText, captureUrl } from "../extensions/llm-wiki/lib/source-packet.js";
156
+ ```
157
+
158
+ becomes:
159
+
160
+ ```ts
161
+ import {
162
+ captureFile,
163
+ captureText,
164
+ captureUrl,
165
+ originalFileNameForPath,
166
+ } from "../extensions/llm-wiki/lib/source-packet.js";
167
+ ```
168
+
169
+ Then append this `it(...)` inside the `describe("source packet capture", ...)` block, immediately after the test named `"should copy local non-PDF file content to extracted.md"` (ends with `expect(existsSync(join(result.packetPath, "original", "notes.md"))).toBe(true);` + `});`):
170
+
171
+ ```ts
172
+ it("derives the original artifact name with platform basename so drive letters stay out of file names (issue #204)", () => {
173
+ const absPath =
174
+ process.platform === "win32" ? "D:\\any\\dir\\doc.md" : "/home/user/any/dir/doc.md";
175
+ expect(originalFileNameForPath(absPath)).toBe("doc.md");
176
+ });
177
+ ```
178
+
179
+ - [x] **Step 2: Run the test to verify it fails**
180
+
181
+ Run: `pnpm vitest run test/source-capture.test.ts -t "drive letters"`
182
+
183
+ Expected: FAIL — module load error: `No export named "originalFileNameForPath"` (the named ESM import does not exist yet).
184
+
185
+ - [x] **Step 3: Write the minimal implementation**
186
+
187
+ In `extensions/llm-wiki/lib/source-packet.ts`, make three edits:
188
+
189
+ 1. Line 3 import — change:
190
+
191
+ ```ts
192
+ import { extname, join } from "node:path";
193
+ ```
194
+
195
+ to:
196
+
197
+ ```ts
198
+ import { basename, extname, join } from "node:path";
199
+ ```
200
+
201
+ 2. After `originalFileNameForUrl` (the function ending `return "source.html";` + `});`/`}`, ~line 256), add:
202
+
203
+ ```ts
204
+ /**
205
+ * Original-artifact name for a local file capture. Uses platform basename so
206
+ * Windows absolute paths (drive colon + backslashes) cannot leak into the
207
+ * packet/original file name (issue #204).
208
+ */
209
+ export function originalFileNameForPath(filePath: string): string {
210
+ return basename(filePath) || "unknown";
211
+ }
212
+ ```
213
+
214
+ 3. Inside `fileCaptureSource` (~line 114) — change:
215
+
216
+ ```ts
217
+ const fileName = filePath.split("/").pop() || "unknown";
218
+ ```
219
+
220
+ to:
221
+
222
+ ```ts
223
+ const fileName = originalFileNameForPath(filePath);
224
+ ```
225
+
226
+ `fileName` flows into `preserveFileOriginal(packetPath, filePath, fileName, content)` and the manifest `title` — both now get the clean basename.
227
+
228
+ - [x] **Step 4: Run the test to verify it passes**
229
+
230
+ Run: `pnpm vitest run test/source-capture.test.ts`
231
+
232
+ Expected: PASS — all tests in the file green, including the new one.
233
+
234
+ Note (honest ceiling): on Linux, POSIX `basename("D:\\any\\dir\\doc.md")` returns the string unchanged, so this assertion only has teeth on `win32` — it pins the operation choice (platform basename vs. slash-split) rather than reproducing Windows. Full Windows coverage needs a Windows CI leg or manual repro.
235
+
236
+ - [x] **Step 5: Run the full gate**
237
+
238
+ Run: `pnpm test && pnpm typecheck && pnpm lint`
239
+
240
+ Expected: all green.
241
+
242
+ - [x] **Step 6: Commit**
243
+
244
+ ```bash
245
+ git add test/source-capture.test.ts extensions/llm-wiki/lib/source-packet.ts
246
+ git commit -m "fix: use platform basename for file capture artifact names (#204)"
247
+ ```
248
+
249
+ ---
250
+
251
+ ### Task 3: Push branch, open PR
252
+
253
+ **Files:** none (git/gh only)
254
+
255
+ - [x] **Step 1: Push and open the PR**
256
+
257
+ ```bash
258
+ git push -u origin fix/lint-fresh-vault-and-windows-basename
259
+ gh pr create \
260
+ --title "fix: lint ENOENT on fresh vaults (#203) + Windows capture basename (#204)" \
261
+ --body "Fixes #203. Fixes #204.
262
+
263
+ Two one-line fixes with regression tests:
264
+ - wiki_lint: mkdirSync the gitignored .discoveries dir before persisting the gap snapshot (mirrors the autoFix report write in the same function).
265
+ - wiki_capture_source: derive the original artifact name with platform basename instead of split('/').pop() so Windows absolute paths no longer embed a drive colon in the file name."
266
+ ```
267
+
268
+ Expected: PR created; `Fixes #203` / `Fixes #204` auto-close the issues on merge.
269
+
270
+ ---
271
+
272
+ ## Setup (before Task 1)
273
+
274
+ - [x] **Step 0: Branch from main**
275
+
276
+ Working tree is clean (verified 2026-09-02); current branch is `qmd-phase-1` — do the work on a dedicated branch off `main`:
277
+
278
+ ```bash
279
+ git checkout main
280
+ git pull --ff-only
281
+ git checkout -b fix/lint-fresh-vault-and-windows-basename
282
+ ```
283
+
284
+ Expected: `Switched to a new branch 'fix/lint-fresh-vault-and-windows-basename'`.
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { basename, join } from "node:path";
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { bootstrapVault } from "./lib/bootstrap.js";
5
5
  import { registerWikiDashboardCommand } from "./lib/dashboard-command.js";
6
6
  import { installGuardrails } from "./lib/guardrails.js";
@@ -6,8 +6,8 @@
6
6
  * from on-disk vault state (see lib/dashboard.ts) and rendered as plain
7
7
  * text through a Container + Text.
8
8
  */
9
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
10
- import { Container, matchesKey, Text } from "@mariozechner/pi-tui";
9
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
10
+ import { Container, matchesKey, Text } from "@earendil-works/pi-tui";
11
11
  import { collectDashboardStats, type DashboardStats } from "./dashboard.js";
12
12
  import type { Runtime } from "./runtime.js";
13
13
 
@@ -1,5 +1,5 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
3
3
  import { scheduleReindex } from "./indexing.js";
4
4
  import { rebuildMetadataLight } from "./metadata.js";
5
5
  import type { Runtime } from "./runtime.js";
@@ -1,14 +1,14 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
4
 
5
5
  /**
6
6
  * Host adapter for the two coding agents that can load this extension:
7
7
  *
8
- * - **pi** — `@mariozechner/pi-coding-agent`, config dir `.pi`
8
+ * - **pi** — `@earendil-works/pi-coding-agent`, config dir `.pi`
9
9
  * - **omp** — oh-my-pi (`@oh-my-pi/pi-coding-agent`), config dir `.omp`
10
10
  *
11
- * omp rewrites `@mariozechner/pi-*` (and bare `typebox`) imports onto its own
11
+ * omp rewrites `@earendil-works/pi-*` (and bare `typebox`) imports onto its own
12
12
  * bundled packages at load time (its `legacy-pi-compat.ts`), so the *module
13
13
  * graph* needs no changes. What does differ is the on-disk config layout:
14
14
  *
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import type { AgentTool } from "@mariozechner/pi-agent-core";
4
- import type { Api, Model } from "@mariozechner/pi-ai";
3
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
4
+ import type { Api, Model } from "@earendil-works/pi-ai";
5
5
  import type { Static } from "typebox";
6
6
  import { Type } from "typebox";
7
7
  import {
@@ -589,7 +589,15 @@ export function serializeKnowledgeDocument(document: KnowledgeDocument): string
589
589
  return body ? `---\n${yaml}---\n\n${body}\n` : `---\n${yaml}---\n`;
590
590
  }
591
591
 
592
- /** Escape wikilink alias pipes so generated content remains valid in Markdown tables. */
592
+ const TABLE_ROW = /^\s*\|.*\|\s*$/;
593
+
594
+ /** Escape wikilink alias pipes only inside Markdown table rows.
595
+ *
596
+ * A bare `|` inside `[[target|alias]]` would be read as a table cell
597
+ * delimiter when the row renders, so table rows need the pipe escaped as
598
+ * `[[target\|alias]]`. Prose, lists, headings, and fenced code keep their
599
+ * pipes literal, so the written file stays valid for external readers
600
+ * (Obsidian, VS Code Wiki Links) that don't understand the escaped form. */
593
601
  function escapeWikilinkAliasPipes(body: string): string {
594
602
  let inFence = false;
595
603
  return body
@@ -600,7 +608,9 @@ function escapeWikilinkAliasPipes(body: string): string {
600
608
  return line;
601
609
  }
602
610
  if (inFence) return line;
603
- return line.replace(/\[\[([^\]\n]*?)(?<!\\)\|([^\]\n]*?)\]\]/g, "[[$1\\|$2]]");
611
+ return TABLE_ROW.test(line)
612
+ ? line.replace(/\[\[([^\]\n]*?)(?<!\\)\|([^\]\n]*?)\]\]/g, "[[$1\\|$2]]")
613
+ : line;
604
614
  })
605
615
  .join("\n");
606
616
  }
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
3
  Container,
4
4
  matchesKey,
@@ -7,7 +7,7 @@ import {
7
7
  type SelectListTheme,
8
8
  Spacer,
9
9
  Text,
10
- } from "@mariozechner/pi-tui";
10
+ } from "@earendil-works/pi-tui";
11
11
  import type { Runtime } from "./runtime.js";
12
12
  import { parseModelRef, persistTaskModel, type TaskConfig } from "./task-config.js";
13
13
 
@@ -1,5 +1,5 @@
1
1
  import { join } from "node:path";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import { Type } from "typebox";
4
4
  import { scheduleReindex } from "./indexing.js";
5
5
  import { createKnowledgeDocument, writeKnowledgeDocumentFile } from "./knowledge-document.js";
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { Type } from "typebox";
5
5
  import {
6
6
  cosineSimilarity,
@@ -1,5 +1,5 @@
1
1
  import { dirname, join, resolve } from "node:path";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import { Type } from "typebox";
4
4
  import { scheduleReindex } from "./indexing.js";
5
5
  import { createKnowledgeDocument, writeKnowledgeDocumentFile } from "./knowledge-document.js";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { loadTaskConfig, noticesEnabled, TASK_DEFAULTS, type TaskConfig } from "./task-config.js";
3
3
 
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  import { homedir } from "node:os";
2
- import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
4
  Container,
5
5
  Input,
@@ -8,7 +8,7 @@ import {
8
8
  type SettingsListTheme,
9
9
  Spacer,
10
10
  Text,
11
- } from "@mariozechner/pi-tui";
11
+ } from "@earendil-works/pi-tui";
12
12
  import type { Runtime } from "./runtime.js";
13
13
  import {
14
14
  loadTaskConfig,
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { copyFile } from "node:fs/promises";
3
- import { extname, join } from "node:path";
3
+ import { basename, extname, join } from "node:path";
4
4
  import { createKnowledgeDocument, serializeKnowledgeDocument } from "./knowledge-document.js";
5
5
  import { appendEvent } from "./metadata.js";
6
6
  import {
@@ -111,7 +111,7 @@ function urlCaptureSource(pi: ExecApi, url: string, signal?: AbortSignal): Captu
111
111
  }
112
112
 
113
113
  function fileCaptureSource(pi: ExecApi, filePath: string, signal?: AbortSignal): CaptureSource {
114
- const fileName = filePath.split("/").pop() || "unknown";
114
+ const fileName = originalFileNameForPath(filePath);
115
115
  const extractor = fileExtractorFor(filePath);
116
116
  const content = extractor.shouldReadText ? readText(filePath) : "";
117
117
 
@@ -255,6 +255,15 @@ function originalFileNameForUrl(url: string): string {
255
255
  return "source.html";
256
256
  }
257
257
 
258
+ /**
259
+ * Original-artifact name for a local file capture. Uses platform basename so
260
+ * Windows absolute paths (drive colon + backslashes) cannot leak into the
261
+ * packet/original file name (issue #204).
262
+ */
263
+ export function originalFileNameForPath(filePath: string): string {
264
+ return basename(filePath) || "unknown";
265
+ }
266
+
258
267
  function contentExtractionFailureMessage(source: string): string {
259
268
  return `_Content could not be extracted from ${source}_\n`;
260
269
  }
@@ -3,8 +3,8 @@ import {
3
3
  type AgentLoopConfig,
4
4
  type AgentTool,
5
5
  agentLoop,
6
- } from "@mariozechner/pi-agent-core";
7
- import type { Api, Message, Model } from "@mariozechner/pi-ai";
6
+ } from "@earendil-works/pi-agent-core";
7
+ import type { Api, Message, Model } from "@earendil-works/pi-ai";
8
8
 
9
9
  /**
10
10
  * Thin sub-agent runner for the LLM Wiki background lane (issue #64, part of #63).
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { Type } from "typebox";
5
5
  import { bootstrapVault } from "./bootstrap.js";
6
6
  import { launchEmbedPages, reindexEmbeddings, resolveEmbedder } from "./embeddings.js";
@@ -1013,6 +1013,9 @@ function runWikiLint(paths: VaultPaths, autoFix: boolean): string {
1013
1013
  // The gap snapshot is generated discovery metadata consumed by wiki_status:
1014
1014
  // persist it on every successful lint so status never reports a stale count.
1015
1015
  // Corrective actions below (report, event, meta rebuild) stay autoFix-only.
1016
+ // mkdir mirrors the autoFix report write below: on a fresh checkout the
1017
+ // gitignored .discoveries dir does not exist yet (issue #203).
1018
+ mkdirSync(paths.discoveries, { recursive: true });
1016
1019
  writeJson(join(paths.discoveries, "gaps.json"), {
1017
1020
  gaps,
1018
1021
  generated: new Date().toISOString(),
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { loadTaskConfig, persistTrajectoriesEnabled, trajectoriesEnabled } from "./task-config.js";
3
3
 
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
4
4
  import { Type } from "typebox";
5
5
  import { appendEvent, rebuildMetadataLight } from "./metadata.js";
6
6
  import { searchWikiLayered } from "./recall.js";
@@ -12,7 +12,7 @@ import {
12
12
  } from "node:fs";
13
13
  import { homedir } from "node:os";
14
14
  import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
15
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
15
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
16
16
 
17
17
  /**
18
18
  * Vault utility functions for the LLM Wiki extension.
package/mcp/operations.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { join } from "node:path";
10
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
10
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
11
  import { bootstrapVault } from "../extensions/llm-wiki/lib/bootstrap.js";
12
12
  import {
13
13
  applyWikilinkGate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zosmaai/pi-llm-wiki",
3
- "version": "0.11.8",
3
+ "version": "0.12.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",
@@ -75,14 +75,14 @@
75
75
  ]
76
76
  },
77
77
  "peerDependencies": {
78
- "@mariozechner/pi-coding-agent": "*"
78
+ "@earendil-works/pi-coding-agent": ">=0.78.1"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=18"
82
82
  },
83
83
  "dependencies": {
84
84
  "@cfworker/json-schema": "^4.1.1",
85
- "@mariozechner/pi-tui": "0.73.1",
85
+ "@earendil-works/pi-tui": "0.84.4",
86
86
  "@modelcontextprotocol/server": "^2.0.0",
87
87
  "mdast-util-from-markdown": "^2.0.3",
88
88
  "node-html-markdown": "^2.0.0",
@@ -93,9 +93,9 @@
93
93
  "devDependencies": {
94
94
  "@astrojs/starlight": "^0.41.10",
95
95
  "@biomejs/biome": "^2.5.11",
96
- "@mariozechner/pi-agent-core": "^0.73.1",
97
- "@mariozechner/pi-ai": "^0.73.1",
98
- "@mariozechner/pi-coding-agent": "^0.70.2",
96
+ "@earendil-works/pi-agent-core": "^0.78.1",
97
+ "@earendil-works/pi-ai": "^0.78.1",
98
+ "@earendil-works/pi-coding-agent": "^0.78.1",
99
99
  "@mermaid-js/mermaid-cli": "^11.12.0",
100
100
  "@types/mdast": "^4.0.4",
101
101
  "@types/node": "^26",