@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.
- package/dist/extensions/llm-wiki/lib/dashboard-command.js +1 -1
- package/dist/extensions/llm-wiki/lib/guardrails.js +1 -1
- package/dist/extensions/llm-wiki/lib/host.js +1 -1
- package/dist/extensions/llm-wiki/lib/knowledge-document.js +11 -2
- package/dist/extensions/llm-wiki/lib/model-command.js +1 -1
- package/dist/extensions/llm-wiki/lib/settings-command.js +1 -1
- package/dist/extensions/llm-wiki/lib/source-packet.js +10 -2
- package/dist/extensions/llm-wiki/lib/subagent.js +1 -1
- package/dist/extensions/llm-wiki/lib/tools.js +3 -0
- package/docs/superpowers/plans/2026-09-02-wiki-lint-fresh-vault-and-windows-capture-fixes.md +284 -0
- package/extensions/llm-wiki/index.ts +1 -1
- package/extensions/llm-wiki/lib/dashboard-command.ts +2 -2
- package/extensions/llm-wiki/lib/guardrails.ts +2 -2
- package/extensions/llm-wiki/lib/host.ts +3 -3
- package/extensions/llm-wiki/lib/ingest-worker.ts +2 -2
- package/extensions/llm-wiki/lib/knowledge-document.ts +12 -2
- package/extensions/llm-wiki/lib/model-command.ts +2 -2
- package/extensions/llm-wiki/lib/observation.ts +1 -1
- package/extensions/llm-wiki/lib/recall.ts +1 -1
- package/extensions/llm-wiki/lib/retro.ts +1 -1
- package/extensions/llm-wiki/lib/runtime.ts +1 -1
- package/extensions/llm-wiki/lib/settings-command.ts +2 -2
- package/extensions/llm-wiki/lib/source-packet.ts +11 -2
- package/extensions/llm-wiki/lib/subagent.ts +2 -2
- package/extensions/llm-wiki/lib/tools.ts +4 -1
- package/extensions/llm-wiki/lib/trajectories-command.ts +1 -1
- package/extensions/llm-wiki/lib/trajectory.ts +1 -1
- package/extensions/llm-wiki/lib/utils.ts +1 -1
- package/mcp/operations.ts +1 -1
- package/package.json +6 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isToolCallEventType } from "@
|
|
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 "@
|
|
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
|
-
|
|
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
|
|
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 "@
|
|
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 "@
|
|
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
|
|
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
|
}
|
|
@@ -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 "@
|
|
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 "@
|
|
10
|
-
import { Container, matchesKey, Text } from "@
|
|
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 "@
|
|
2
|
-
import { isToolCallEventType } from "@
|
|
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 "@
|
|
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** — `@
|
|
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 `@
|
|
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 "@
|
|
4
|
-
import type { Api, Model } from "@
|
|
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
|
-
|
|
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
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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 "@
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import type { ExtensionAPI, Theme } from "@
|
|
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 "@
|
|
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
|
|
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 "@
|
|
7
|
-
import type { Api, Message, Model } from "@
|
|
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 "@
|
|
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,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 "@
|
|
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 "@
|
|
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 "@
|
|
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.
|
|
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
|
-
"@
|
|
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
|
-
"@
|
|
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
|
-
"@
|
|
97
|
-
"@
|
|
98
|
-
"@
|
|
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",
|