@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.de.md +19 -1
  3. package/README.es.md +19 -1
  4. package/README.fr.md +19 -1
  5. package/README.hi.md +19 -1
  6. package/README.ja.md +19 -1
  7. package/README.ko.md +19 -1
  8. package/README.md +38 -3
  9. package/README.pt.md +19 -1
  10. package/README.ru.md +19 -1
  11. package/README.zh.md +19 -1
  12. package/dist/extensions/llm-wiki/lib/bootstrap.js +4 -1
  13. package/dist/extensions/llm-wiki/lib/ingest-worker.js +129 -29
  14. package/dist/extensions/llm-wiki/lib/metadata.js +37 -31
  15. package/dist/extensions/llm-wiki/lib/model-command.js +0 -1
  16. package/dist/extensions/llm-wiki/lib/runtime.js +0 -4
  17. package/dist/extensions/llm-wiki/lib/source-packet.js +1 -1
  18. package/dist/extensions/llm-wiki/lib/task-config.js +29 -0
  19. package/dist/extensions/llm-wiki/lib/tools.js +7 -0
  20. package/dist/extensions/llm-wiki/lib/utils.js +6 -0
  21. package/dist/mcp/index.js +2 -1
  22. package/docs/api.md +5 -2
  23. package/docs/architecture.md +5 -2
  24. package/docs/configuration.md +25 -0
  25. package/docs/superpowers/plans/2026-08-06-authoritative-event-history-phase-1-foundation-hardening.md +937 -0
  26. package/docs/superpowers/plans/2026-08-07-synthesis-language.md +98 -0
  27. package/docs/superpowers/specs/2026-08-02-okf-foundation-design.md +17 -2
  28. package/docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md +6 -2
  29. package/docs/superpowers/specs/2026-08-07-synthesis-language-design.md +94 -0
  30. package/extensions/llm-wiki/lib/bootstrap.ts +4 -1
  31. package/extensions/llm-wiki/lib/ingest-worker.ts +161 -26
  32. package/extensions/llm-wiki/lib/knowledge-document.ts +2 -0
  33. package/extensions/llm-wiki/lib/metadata.ts +38 -31
  34. package/extensions/llm-wiki/lib/model-command.ts +0 -1
  35. package/extensions/llm-wiki/lib/runtime.ts +0 -3
  36. package/extensions/llm-wiki/lib/source-packet.ts +1 -1
  37. package/extensions/llm-wiki/lib/task-config.ts +36 -0
  38. package/extensions/llm-wiki/lib/tools.ts +9 -0
  39. package/extensions/llm-wiki/lib/utils.ts +7 -0
  40. package/mcp/index.ts +2 -1
  41. package/package.json +2 -2
  42. package/skills/llm-wiki/SKILL.md +4 -2
@@ -0,0 +1,937 @@
1
+ # Authoritative Event History 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:** Make `meta/events.jsonl` an explicitly durable, locally authoritative event source, prevent metadata rebuilds from erasing logs when that source is unavailable, and keep host-local file paths out of the public OKF log projection.
6
+
7
+ **Architecture:** Preserve the existing one-way model: extension operations append authoritative local events, while `meta/log.md` and `wiki/log.md` remain projections. Replace the current missing-file-to-empty coercion with a shared event-source read result; unavailable source data produces a warning and suppresses only log publication, while an explicitly empty file remains a valid empty history. Keep OKF import/export and cross-bundle history continuity out of Foundation: this phase documents that `wiki/log.md` is a portable snapshot, not a recovery format, and leaves Interchange to define imported-history composition.
8
+
9
+ **Tech Stack:** TypeScript ES2022, Node.js filesystem APIs, Vitest, Markdown documentation, Biome
10
+
11
+ **Roadmap:** `docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md`
12
+
13
+ **Phase:** Phase 1 follow-up: OKF Foundation event-history hardening
14
+
15
+ ---
16
+
17
+ ## Scope and decisions
18
+
19
+ This plan resolves issue #123 inside the already-shipped Foundation boundary.
20
+
21
+ 1. `.llm-wiki/meta/events.jsonl` is durable extension-owned state and the authoritative source for recorded activity. It is not a rebuildable metadata projection.
22
+ 2. Full-vault backup or Git synchronization must retain `events.jsonl` to continue activity history.
23
+ 3. `.llm-wiki/wiki/log.md` remains a generated, portable OKF snapshot. It cannot reconstruct `events.jsonl` and does not by itself continue local history after import.
24
+ 4. A missing or unreadable event source is different from a present, zero-byte event source. Rebuild warns and leaves existing logs byte-identical when the source is unavailable; a present empty stream intentionally generates empty logs.
25
+ 5. Registry, backlinks, and indexes continue rebuilding when event history is unavailable. Event-source loss must not stop unrelated projections.
26
+ 6. Local file capture keeps the exact input path in the extension-owned raw manifest, where it is already required for provenance, but no longer duplicates that host-specific path into the event stream or public `wiki/log.md`.
27
+ 7. Event scope remains selected extension operations, not a complete file-revision audit. Manual page edits still trigger projection rebuilds without fabricated events.
28
+
29
+ ### Explicitly out of scope
30
+
31
+ - `wiki_okf_import`, `wiki_okf_export`, `wiki_okf_migrate`, review staging, or transaction journals
32
+ - reconstructing JSONL events from Markdown prose
33
+ - merging imported `log.md` history with new local events
34
+ - moving `events.jsonl` to a new directory
35
+ - adding event hashes, signatures, sequence IDs, rotation, or retention policy
36
+ - logging manual file edits without actor and intent information
37
+ - changing malformed-line behavior; malformed individual lines remain non-blocking diagnostics and are omitted from projections
38
+ - hand-editing localized README translations without a reviewed localization workflow
39
+
40
+ Interchange must receive its own child spec and detailed phase plan before implementing portable history continuity.
41
+
42
+ ## File responsibility map
43
+
44
+ ### Created planning file
45
+
46
+ - `docs/superpowers/plans/2026-08-06-authoritative-event-history-phase-1-foundation-hardening.md` — executable plan, scope boundary, and acceptance checklist for issue #123.
47
+
48
+ ### Modified production files
49
+
50
+ - `extensions/llm-wiki/lib/knowledge-document.ts` — add stable diagnostics for missing and unreadable authoritative event sources.
51
+ - `extensions/llm-wiki/lib/metadata.ts` — read the event source once, distinguish unavailable from explicitly empty, and suppress only log writes when unavailable.
52
+ - `extensions/llm-wiki/lib/tools.ts` — surface non-blocking rebuild diagnostics instead of reporting unconditional success.
53
+ - `extensions/llm-wiki/lib/source-packet.ts` — stop copying local `file_path` into capture events while retaining it in raw manifests.
54
+ - `extensions/llm-wiki/lib/utils.ts` — give `events.jsonl` a guardrail message describing append-only authoritative state instead of auto-generated metadata.
55
+ - `extensions/llm-wiki/lib/bootstrap.ts` — generate accurate in-vault ownership rules.
56
+
57
+ ### Modified tests
58
+
59
+ - `test/okf-projections.test.ts` — cover missing, unreadable, and explicitly empty event-source semantics.
60
+ - `test/background-tools.test.ts` — prove `wiki_rebuild_meta` reports non-blocking event-source warnings to users.
61
+ - `test/source-capture.test.ts` — prove raw manifests retain local paths while event and OKF log projections do not.
62
+ - `test/e2e-guardrails.test.ts` — distinguish authoritative event-state protection from generated metadata protection.
63
+ - `test/bootstrap.test.ts` — verify new vault schemas classify event state correctly.
64
+
65
+ ### Modified specifications and user documentation
66
+
67
+ - `docs/superpowers/specs/2026-08-02-okf-foundation-design.md` — make local authority, durability, missing-source behavior, event scope, and Foundation portability boundary normative.
68
+ - `docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md` — record the Interchange requirement without implementing that phase.
69
+ - `README.md` — document backup/Git rules and distinguish authoritative state from projections.
70
+ - `docs/architecture.md` — update vault-layer ownership.
71
+ - `docs/api.md` — document event durability, arbitrary-detail privacy, and projection behavior.
72
+ - `skills/llm-wiki/SKILL.md` — stop teaching agents that every `meta/**` file is rebuildable.
73
+ - `CHANGELOG.md` — record event-history preservation and path-redaction behavior under `Unreleased`.
74
+
75
+ No new production file or dependency is needed.
76
+
77
+ ---
78
+
79
+ ### Task 1: Make the event-history contract normative
80
+
81
+ **Files:**
82
+ - Modify: `docs/superpowers/specs/2026-08-02-okf-foundation-design.md:40-47`
83
+ - Modify: `docs/superpowers/specs/2026-08-02-okf-foundation-design.md:349-401`
84
+ - Modify: `docs/superpowers/specs/2026-08-02-okf-foundation-design.md:477-490`
85
+ - Modify: `docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md:91-117`
86
+ - Modify: `docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md:248-337`
87
+
88
+ - [ ] **Step 1: Create the implementation branch and commit the plan**
89
+
90
+ Run:
91
+
92
+ ```bash
93
+ git fetch origin
94
+ git switch -c fix/authoritative-event-history origin/main
95
+ git add docs/superpowers/plans/2026-08-06-authoritative-event-history-phase-1-foundation-hardening.md
96
+ git commit -m "docs: plan authoritative event history hardening"
97
+ ```
98
+
99
+ Expected: branch starts from current `origin/main`, plan is its first commit, and `git status --short` is empty.
100
+
101
+ - [ ] **Step 2: Clarify the Foundation design principle**
102
+
103
+ Replace Foundation design principle 5 with:
104
+
105
+ ```markdown
106
+ 5. **Generated files are projections; authoritative extension state is not.** Registry, backlinks, indexes, and logs derive from authoritative pages and events. `meta/events.jsonl` is extension-written state, but it is not generated metadata because no rebuild can reconstruct it.
107
+ ```
108
+
109
+ - [ ] **Step 3: Add the durable local-state contract to Deterministic Root Log**
110
+
111
+ Immediately after the opening paragraph under `## Deterministic Root Log`, add:
112
+
113
+ ```markdown
114
+ `meta/events.jsonl` is durable extension-owned vault state. Users who need activity continuity must preserve it when backing up or synchronizing a complete pi-llm-wiki vault. It is not derivable from canonical pages, raw source packets, `meta/log.md`, or `wiki/log.md`.
115
+
116
+ Foundation does not make the JSONL event source part of the distributable OKF bundle. `wiki/log.md` is a portable snapshot of recorded activity at projection time, not a recovery format and not a promise that an imported bundle can continue the originating vault's event stream. Import, export, and imported-history composition belong to the later Interchange child specification.
117
+
118
+ The event stream records selected extension operations. It is not a complete revision history: manual file edits do not fabricate events, while extension-owned operational actions may emit events. Documentation and UI text must call it an activity history rather than a complete content audit trail.
119
+ ```
120
+
121
+ - [ ] **Step 4: Specify event-detail portability and privacy**
122
+
123
+ After the paragraph ending `because the extension cannot infer actor or intent safely.`, add:
124
+
125
+ ```markdown
126
+ Fields projected into `wiki/log.md` must be safe for a distributable bundle. A local file capture event records its stable `source_id` and format but not the caller-supplied `file_path`; the exact path remains in the extension-owned raw source manifest. Manual event details are user-controlled and documentation must warn callers not to include secrets or machine-local paths intended to remain private.
127
+ ```
128
+
129
+ - [ ] **Step 5: Specify unavailable-source rebuild behavior**
130
+
131
+ Replace the event-diagnostic paragraph under `## Projection Rebuild Semantics` with:
132
+
133
+ ```markdown
134
+ Unresolved links and malformed event lines are non-blocking projection diagnostics: valid concepts may still be indexed, and valid event lines may still be projected. A missing or unreadable `meta/events.jsonl` is different from a present empty stream. Rebuild reports `event_source_missing` or `event_source_unreadable`, continues publishing registry, backlink, and index projections, and leaves existing `meta/log.md` and `wiki/log.md` byte-identical. A present zero-byte event file is an explicitly empty authoritative stream and generates the normal empty log projections.
135
+ ```
136
+
137
+ Add these codes to the diagnostics list:
138
+
139
+ ```markdown
140
+ - `event_source_missing`
141
+ - `event_source_unreadable`
142
+ ```
143
+
144
+ - [ ] **Step 6: Clarify the umbrella architecture and Interchange handoff**
145
+
146
+ Change the `meta/` tree comment to:
147
+
148
+ ```text
149
+ ├── meta/ # durable local events + generated internal projections
150
+ ```
151
+
152
+ After “`.llm-wiki/wiki/` is the distributable OKF bundle...”, add:
153
+
154
+ ```markdown
155
+ `meta/events.jsonl` is durable local pi-llm-wiki state but is not part of the base OKF bundle. A full-vault backup preserves it; an OKF-only export does not. The exported `wiki/log.md` is therefore a readable history snapshot, not a lossless or resumable event source.
156
+ ```
157
+
158
+ Replace the final sentence of the Generated Indexes and Logs section with:
159
+
160
+ ```markdown
161
+ Foreign concept paths and document-level metadata are preserved; arbitrary foreign index prose is not merged into the live generated index. Before Interchange implementation, its normative child spec must define whether an imported `log.md` is archived, retained as a separate historical baseline, or replaced when a new local event stream begins. It must not imply that Markdown prose can reconstruct the originating JSONL stream.
162
+ ```
163
+
164
+ After the Export Design paragraph that excludes `meta/**`, add:
165
+
166
+ ```markdown
167
+ Because `meta/events.jsonl` is excluded, exported `log.md` is a deterministic snapshot rather than a resumable event ledger. Export must use only bundle-safe projected fields. Portable event continuity or a machine-readable event sidecar requires a separately reviewed Interchange decision and is not implied by Foundation.
168
+ ```
169
+
170
+ - [ ] **Step 7: Review scope language**
171
+
172
+ Run:
173
+
174
+ ```bash
175
+ grep -nE "events.jsonl|event_source_|portable snapshot|complete revision" \
176
+ docs/superpowers/specs/2026-08-02-okf-foundation-design.md \
177
+ docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md
178
+ ```
179
+
180
+ Expected: output shows local authority and unavailable-source semantics in Foundation, plus explicit Interchange handoff; no text claims import/export is implemented.
181
+
182
+ - [ ] **Step 8: Commit the contract**
183
+
184
+ ```bash
185
+ git add \
186
+ docs/superpowers/specs/2026-08-02-okf-foundation-design.md \
187
+ docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md
188
+ git commit -m "docs: define authoritative event history contract"
189
+ ```
190
+
191
+ ---
192
+
193
+ ### Task 2: Preserve logs when the authoritative event source is unavailable
194
+
195
+ **Files:**
196
+ - Modify: `test/okf-projections.test.ts:1-45`
197
+ - Modify: `test/okf-projections.test.ts:180-230`
198
+ - Modify: `test/background-tools.test.ts:34-100`
199
+ - Modify: `extensions/llm-wiki/lib/knowledge-document.ts:5-27`
200
+ - Modify: `extensions/llm-wiki/lib/metadata.ts:66-145`
201
+ - Modify: `extensions/llm-wiki/lib/metadata.ts:306-377`
202
+ - Modify: `extensions/llm-wiki/lib/tools.ts:1080-1130`
203
+
204
+ - [ ] **Step 1: Make integration fixtures distinguish known-empty from missing**
205
+
206
+ In `createVault()` inside `test/okf-projections.test.ts`, add an explicit empty event source after writing `config.json`:
207
+
208
+ ```ts
209
+ writeFileSync(join(paths.dotWiki, "config.json"), `${JSON.stringify(config)}\n`);
210
+ writeFileSync(join(paths.meta, "events.jsonl"), "");
211
+ return paths;
212
+ ```
213
+
214
+ This preserves existing fixture intent: ordinary projection tests start from a known-empty stream, not a missing authoritative source.
215
+
216
+ - [ ] **Step 2: Write the missing-source regression test**
217
+
218
+ Add under `describe("OKF rebuild integration", ...)`:
219
+
220
+ ```ts
221
+ it("preserves existing logs and warns when the authoritative event source is missing", () => {
222
+ const paths = createVault({ knowledge_format: "okf-0.2" });
223
+ writeFileSync(
224
+ join(paths.meta, "events.jsonl"),
225
+ '{"timestamp":"2026-08-06T10:00:00.000Z","kind":"before-loss"}\n',
226
+ );
227
+ expect(rebuildMetadata(paths).ok).toBe(true);
228
+
229
+ const metaLog = readFileSync(join(paths.meta, "log.md"), "utf8");
230
+ const wikiLog = readFileSync(join(paths.wiki, "log.md"), "utf8");
231
+ rmSync(join(paths.meta, "events.jsonl"));
232
+
233
+ const result = rebuildMetadata(paths);
234
+
235
+ expect(result.ok).toBe(true);
236
+ expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toContain(
237
+ "event_source_missing",
238
+ );
239
+ expect(readFileSync(join(paths.meta, "log.md"), "utf8")).toBe(metaLog);
240
+ expect(readFileSync(join(paths.wiki, "log.md"), "utf8")).toBe(wikiLog);
241
+ expect(existsSync(join(paths.meta, "registry.json"))).toBe(true);
242
+ expect(existsSync(join(paths.meta, "backlinks.json"))).toBe(true);
243
+ });
244
+ ```
245
+
246
+ - [ ] **Step 3: Write the unreadable-source regression test**
247
+
248
+ Add immediately after the missing-source test:
249
+
250
+ ```ts
251
+ it("preserves existing logs and warns when the authoritative event source is unreadable", () => {
252
+ const paths = createVault({ knowledge_format: "okf-0.2" });
253
+ writeFileSync(
254
+ join(paths.meta, "events.jsonl"),
255
+ '{"timestamp":"2026-08-06T10:00:00.000Z","kind":"before-read-error"}\n',
256
+ );
257
+ expect(rebuildMetadata(paths).ok).toBe(true);
258
+
259
+ const metaLog = readFileSync(join(paths.meta, "log.md"), "utf8");
260
+ const wikiLog = readFileSync(join(paths.wiki, "log.md"), "utf8");
261
+ rmSync(join(paths.meta, "events.jsonl"));
262
+ mkdirSync(join(paths.meta, "events.jsonl"));
263
+
264
+ const result = rebuildMetadata(paths);
265
+
266
+ expect(result.ok).toBe(true);
267
+ expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toContain(
268
+ "event_source_unreadable",
269
+ );
270
+ expect(readFileSync(join(paths.meta, "log.md"), "utf8")).toBe(metaLog);
271
+ expect(readFileSync(join(paths.wiki, "log.md"), "utf8")).toBe(wikiLog);
272
+ });
273
+ ```
274
+
275
+ Using a directory at the event-file path deterministically produces a read failure on supported CI platforms without permission-dependent tests.
276
+
277
+ - [ ] **Step 4: Write the explicit-empty regression test**
278
+
279
+ Add:
280
+
281
+ ```ts
282
+ it("treats a present zero-byte event source as intentionally empty", () => {
283
+ const paths = createVault({ knowledge_format: "okf-0.2" });
284
+
285
+ const result = rebuildMetadata(paths);
286
+
287
+ expect(result.ok).toBe(true);
288
+ expect(result.diagnostics.map((diagnostic) => diagnostic.code)).not.toContain(
289
+ "event_source_missing",
290
+ );
291
+ expect(readFileSync(join(paths.meta, "log.md"), "utf8")).toContain(
292
+ "_No events recorded yet._",
293
+ );
294
+ expect(readFileSync(join(paths.wiki, "log.md"), "utf8")).toBe("# Wiki Update Log\n");
295
+ });
296
+ ```
297
+
298
+ - [ ] **Step 5: Run focused tests and verify failure**
299
+
300
+ Run:
301
+
302
+ ```bash
303
+ pnpm vitest run test/okf-projections.test.ts
304
+ ```
305
+
306
+ Expected: new missing/unreadable tests fail because rebuild currently replaces logs and reports no source diagnostic. Existing tests may remain green.
307
+
308
+ - [ ] **Step 6: Add stable diagnostic codes**
309
+
310
+ Extend `DiagnosticCode` in `extensions/llm-wiki/lib/knowledge-document.ts`:
311
+
312
+ ```ts
313
+ | "event_source_missing"
314
+ | "event_source_unreadable"
315
+ | "event_invalid_json"
316
+ ```
317
+
318
+ Keep existing event diagnostic members after these entries.
319
+
320
+ - [ ] **Step 7: Replace silent event reads with one discriminated source read**
321
+
322
+ In `extensions/llm-wiki/lib/metadata.ts`, replace the existing `readText()` helper with:
323
+
324
+ ```ts
325
+ type EventSourceRead =
326
+ | { available: true; content: string }
327
+ | { available: false; diagnostic: KnowledgeDiagnostic };
328
+
329
+ function readEventSource(
330
+ filePath: string,
331
+ diagnosticPath = "meta/events.jsonl",
332
+ ): EventSourceRead {
333
+ if (!existsSync(filePath)) {
334
+ return {
335
+ available: false,
336
+ diagnostic: okfDiag(
337
+ "warning",
338
+ "event_source_missing",
339
+ diagnosticPath,
340
+ "Authoritative event source is missing; existing log projections were preserved",
341
+ ),
342
+ };
343
+ }
344
+
345
+ try {
346
+ return { available: true, content: readFileSync(filePath, "utf8") };
347
+ } catch {
348
+ return {
349
+ available: false,
350
+ diagnostic: okfDiag(
351
+ "warning",
352
+ "event_source_unreadable",
353
+ diagnosticPath,
354
+ "Authoritative event source is unreadable; existing log projections were preserved",
355
+ ),
356
+ };
357
+ }
358
+ }
359
+ ```
360
+
361
+ Function declarations are hoisted, so this helper may call the existing `okfDiag()` declaration later in the module without moving rendering helpers.
362
+
363
+ - [ ] **Step 8: Parse the event source once during rebuild**
364
+
365
+ Replace the direct `buildOkfLog(readText(...))` call with:
366
+
367
+ ```ts
368
+ const eventSource = readEventSource(join(paths.meta, "events.jsonl"));
369
+ const eventLogResult = eventSource.available ? buildOkfLog(eventSource.content) : undefined;
370
+ if (eventLogResult) allDiagnostics.push(...eventLogResult.diagnostics);
371
+ else allDiagnostics.push(eventSource.diagnostic);
372
+ ```
373
+
374
+ Replace meta-log construction with:
375
+
376
+ ```ts
377
+ const metaLog = eventSource.available ? buildLogMarkdown(eventSource.content) : undefined;
378
+ ```
379
+
380
+ Replace OKF-log construction with:
381
+
382
+ ```ts
383
+ const okfLog: string | undefined =
384
+ vaultState.knowledgeFormat === "okf-0.2" ? eventLogResult?.markdown : undefined;
385
+ ```
386
+
387
+ - [ ] **Step 9: Suppress only unavailable log writes**
388
+
389
+ Keep registry, backlinks, and index writes unconditional. Replace the meta-log write with:
390
+
391
+ ```ts
392
+ if (metaLog !== undefined) atomicWriteFile(join(paths.meta, "log.md"), metaLog);
393
+ ```
394
+
395
+ Replace the OKF-log write block with:
396
+
397
+ ```ts
398
+ if (okfLog !== undefined) {
399
+ mkdirSync(paths.wiki, { recursive: true });
400
+ atomicWriteFile(join(paths.wiki, "log.md"), okfLog);
401
+ }
402
+ ```
403
+
404
+ - [ ] **Step 10: Make the rich log renderer consume the same bytes**
405
+
406
+ Replace `buildLogMarkdown(paths: VaultPaths)` and its filesystem read with:
407
+
408
+ ```ts
409
+ function buildLogMarkdown(eventsJsonl: string): string {
410
+ const events: WikiEvent[] = [];
411
+ const raw = eventsJsonl.trim();
412
+
413
+ for (const line of raw.split("\n")) {
414
+ if (!line.trim()) continue;
415
+ try {
416
+ const candidate: unknown = JSON.parse(line);
417
+ if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
418
+ events.push(candidate as WikiEvent);
419
+ }
420
+ } catch {
421
+ // Keep backward-compatible rich-log behavior: malformed lines are omitted.
422
+ }
423
+ }
424
+
425
+ const lines: string[] = [];
426
+ lines.push("# Activity Log\n\n> Auto-generated from meta/events.jsonl. Do not edit manually.\n");
427
+
428
+ for (const ev of events) {
429
+ const ts = ev.timestamp || "unknown";
430
+ const kind = ev.kind || "event";
431
+ const details = Object.entries(ev)
432
+ .filter(([k]) => k !== "timestamp" && k !== "kind")
433
+ .map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
434
+ .join(", ");
435
+
436
+ lines.push(`## [${ts}] ${kind}`);
437
+ if (details) lines.push(`- ${details}`);
438
+ lines.push("");
439
+ }
440
+
441
+ if (events.length === 0) lines.push("_No events recorded yet._\n");
442
+ return `${lines.join("\n")}\n`;
443
+ }
444
+ ```
445
+
446
+ Delete the now-unused `readText()` helper.
447
+
448
+ - [ ] **Step 11: Give existing background-tool fixtures an explicit empty stream**
449
+
450
+ In `test/background-tools.test.ts` `beforeEach()`, add after writing `config.json`:
451
+
452
+ ```ts
453
+ writeFileSync(join(paths.meta, "events.jsonl"), "");
454
+ ```
455
+
456
+ This keeps existing success-message tests focused on background dispatch rather than missing-history behavior.
457
+
458
+ - [ ] **Step 12: Add a user-visible warning test**
459
+
460
+ Add to `test/background-tools.test.ts`:
461
+
462
+ ```ts
463
+ it("reports a missing authoritative event source without failing unrelated projections", async () => {
464
+ const paths = getVaultPaths(wikiDir);
465
+ rmSync(join(paths.meta, "events.jsonl"));
466
+ const tool = captureRebuildTool();
467
+
468
+ const res = await tool.execute("id", {}, undefined, undefined, {
469
+ cwd: wikiDir,
470
+ hasUI: false,
471
+ } as unknown);
472
+
473
+ expect(res.details.background).toBe(false);
474
+ expect(res.content[0].text).toContain("metadata rebuilt with warnings");
475
+ expect(res.content[0].text).toContain("event_source_missing");
476
+ expect(existsSync(join(paths.meta, "registry.json"))).toBe(true);
477
+ });
478
+ ```
479
+
480
+ Add `existsSync` to that test file's `node:fs` import.
481
+
482
+ - [ ] **Step 13: Surface successful rebuild warnings**
483
+
484
+ In `registerWikiRebuildMeta()` after the existing `if (!result.ok)` block, add:
485
+
486
+ ```ts
487
+ const warnings = result.diagnostics.filter(
488
+ (diagnostic) => diagnostic.severity === "warning",
489
+ );
490
+ if (warnings.length > 0) {
491
+ return `⚠️ LLM Wiki: metadata rebuilt with warnings — ${warnings
492
+ .map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`)
493
+ .join("; ")}`;
494
+ }
495
+ ```
496
+
497
+ Keep the existing successful page-count message after this block. This surfaces missing/unreadable sources and existing non-blocking malformed-event/link diagnostics without marking successfully rebuilt registry/index projections as failed.
498
+
499
+ - [ ] **Step 14: Run focused tests**
500
+
501
+ ```bash
502
+ pnpm vitest run test/okf-projections.test.ts test/background-tools.test.ts
503
+ ```
504
+
505
+ Expected: both test files pass, including missing, unreadable, explicit-empty, and user-visible warning cases.
506
+
507
+ - [ ] **Step 15: Type-check and commit**
508
+
509
+ ```bash
510
+ pnpm typecheck
511
+ git add \
512
+ extensions/llm-wiki/lib/knowledge-document.ts \
513
+ extensions/llm-wiki/lib/metadata.ts \
514
+ extensions/llm-wiki/lib/tools.ts \
515
+ test/okf-projections.test.ts \
516
+ test/background-tools.test.ts
517
+ git commit -m "fix: preserve logs without authoritative events"
518
+ ```
519
+
520
+ Expected: typecheck passes and commit contains only event-source read/rebuild behavior, warning propagation, and tests.
521
+
522
+ ---
523
+
524
+ ### Task 3: Keep machine-local capture paths out of portable logs
525
+
526
+ **Files:**
527
+ - Modify: `test/source-capture.test.ts:17-34`
528
+ - Modify: `test/source-capture.test.ts:140-190`
529
+ - Modify: `extensions/llm-wiki/lib/source-packet.ts:145-151`
530
+
531
+ - [ ] **Step 1: Let source-capture tests choose vault mode**
532
+
533
+ Replace the local `makePaths()` helper with:
534
+
535
+ ```ts
536
+ function makePaths(config: Record<string, unknown> = { name: "Capture test" }) {
537
+ const p = getVaultPaths(join(tmpDir, `wiki-${Math.random().toString(36).slice(2)}`));
538
+ ensureVaultStructure(p);
539
+ writeFileSync(join(p.dotWiki, "config.json"), JSON.stringify(config));
540
+ return p;
541
+ }
542
+ ```
543
+
544
+ - [ ] **Step 2: Write the local-path projection regression test**
545
+
546
+ Add after the local non-PDF capture test:
547
+
548
+ ```ts
549
+ it("keeps a local capture path in the raw manifest but out of events and the OKF log", async () => {
550
+ const paths = makePaths({ name: "Portable log test", knowledge_format: "okf-0.2" });
551
+ const localPath = join(tmpDir, "private", "notes.md");
552
+ mkdirSync(join(localPath, ".."), { recursive: true });
553
+ writeFileSync(localPath, "# Private notes\n", "utf8");
554
+
555
+ const result = await captureFile(mockPi() as never, paths, localPath);
556
+ const manifest = JSON.parse(readFile(join(result.packetPath, "manifest.json")));
557
+ expect(manifest.file_path).toBe(localPath);
558
+
559
+ const eventStream = readFile(join(paths.meta, "events.jsonl"));
560
+ expect(eventStream).not.toContain(localPath);
561
+ expect(eventStream).toContain(`"source_id":"${result.sourceId}"`);
562
+ expect(eventStream).toContain('"format":"markdown"');
563
+
564
+ expect(rebuildMetadata(paths).ok).toBe(true);
565
+ expect(readFile(join(paths.wiki, "log.md"))).not.toContain(localPath);
566
+ });
567
+ ```
568
+
569
+ - [ ] **Step 3: Run the regression test and verify failure**
570
+
571
+ ```bash
572
+ pnpm vitest run test/source-capture.test.ts -t "keeps a local capture path"
573
+ ```
574
+
575
+ Expected: FAIL because current capture event contains `file_path`.
576
+
577
+ - [ ] **Step 4: Remove the duplicated local path from capture events**
578
+
579
+ In `fileCaptureSource()`, retain the manifest exactly and change only the event payload:
580
+
581
+ ```ts
582
+ manifest: () => ({
583
+ title: fileName,
584
+ file_path: filePath,
585
+ format: extractor.format,
586
+ }),
587
+ event: () => ({ format: extractor.format }),
588
+ ```
589
+
590
+ `finalizeCapture()` already adds stable `source_id`, so no replacement path field is needed.
591
+
592
+ - [ ] **Step 5: Run source capture and projection tests**
593
+
594
+ ```bash
595
+ pnpm vitest run test/source-capture.test.ts test/okf-projections.test.ts
596
+ ```
597
+
598
+ Expected: both files pass; raw manifest assertion proves provenance was retained, while event and public log assertions prove host path was removed.
599
+
600
+ - [ ] **Step 6: Commit the privacy boundary**
601
+
602
+ ```bash
603
+ git add extensions/llm-wiki/lib/source-packet.ts test/source-capture.test.ts
604
+ git commit -m "fix: omit local paths from activity events"
605
+ ```
606
+
607
+ ---
608
+
609
+ ### Task 4: Correct ownership guardrails and generated vault schema
610
+
611
+ **Files:**
612
+ - Modify: `test/e2e-guardrails.test.ts:82-110`
613
+ - Modify: `test/bootstrap.test.ts:42-75`
614
+ - Modify: `extensions/llm-wiki/lib/utils.ts:389-405`
615
+ - Modify: `extensions/llm-wiki/lib/bootstrap.ts:8-25`
616
+
617
+ - [ ] **Step 1: Tighten guardrail expectations**
618
+
619
+ Replace the `meta/events.jsonl` assertion in `test/e2e-guardrails.test.ts` with:
620
+
621
+ ```ts
622
+ it("blocks direct writes to authoritative event history", () => {
623
+ const paths = makePaths();
624
+ const target = join(paths.meta, "events.jsonl");
625
+
626
+ const result = isProtectedPath(target, paths);
627
+
628
+ expect(result.protected).toBe(true);
629
+ expect(result.reason).toContain("append-only authoritative state");
630
+ expect(result.reason).toContain("wiki_log_event");
631
+ expect(result.reason).not.toContain("auto-generated");
632
+ });
633
+ ```
634
+
635
+ Leave registry/backlink expectations unchanged; they remain generated metadata.
636
+
637
+ - [ ] **Step 2: Assert generated schema ownership**
638
+
639
+ In the successful silent-bootstrap test, after reading config, add:
640
+
641
+ ```ts
642
+ const schema = readFileSync(join(paths.dotWiki, "WIKI_SCHEMA.md"), "utf8");
643
+ expect(schema).toContain("meta/events.jsonl | extension tools | append-only authoritative state");
644
+ expect(schema).toContain("meta/* except events.jsonl | extension | generated projections");
645
+ expect(schema).toContain("Back up events.jsonl to preserve activity history");
646
+ ```
647
+
648
+ - [ ] **Step 3: Run tests and verify failure**
649
+
650
+ ```bash
651
+ pnpm vitest run test/e2e-guardrails.test.ts test/bootstrap.test.ts
652
+ ```
653
+
654
+ Expected: event guardrail and schema assertions fail against current “auto-generated” wording.
655
+
656
+ - [ ] **Step 4: Special-case event-state guardrail text**
657
+
658
+ In `isProtectedPath()` before the generic `paths.meta` branch, add:
659
+
660
+ ```ts
661
+ if (relativePhysicalPath(paths.meta, absPath) === "events.jsonl") {
662
+ return {
663
+ protected: true,
664
+ reason:
665
+ "Event history is append-only authoritative state. Use wiki_log_event or an owning wiki operation instead.",
666
+ };
667
+ }
668
+ ```
669
+
670
+ Keep the existing generic metadata branch unchanged for registry, backlinks, indexes, logs, lint reports, and embeddings.
671
+
672
+ - [ ] **Step 5: Replace generated schema ownership rows**
673
+
674
+ In `WIKI_SCHEMA`, replace the single `meta/*` row with:
675
+
676
+ ```ts
677
+ "| meta/events.jsonl | extension tools | append-only authoritative state |",
678
+ "| meta/* except events.jsonl | extension | generated projections |",
679
+ ```
680
+
681
+ After the ownership table, add:
682
+
683
+ ```ts
684
+ "",
685
+ "Back up `meta/events.jsonl` to preserve activity history. Generated logs cannot reconstruct it.",
686
+ ```
687
+
688
+ - [ ] **Step 6: Run tests and commit**
689
+
690
+ ```bash
691
+ pnpm vitest run test/e2e-guardrails.test.ts test/bootstrap.test.ts
692
+ pnpm typecheck
693
+ git add \
694
+ extensions/llm-wiki/lib/utils.ts \
695
+ extensions/llm-wiki/lib/bootstrap.ts \
696
+ test/e2e-guardrails.test.ts \
697
+ test/bootstrap.test.ts
698
+ git commit -m "docs: distinguish event state in vault guardrails"
699
+ ```
700
+
701
+ Expected: focused tests and typecheck pass.
702
+
703
+ ---
704
+
705
+ ### Task 5: Align public docs, agent guidance, and release notes
706
+
707
+ **Files:**
708
+ - Modify: `README.md:240-313`
709
+ - Modify: `README.md:380-390`
710
+ - Modify: `docs/architecture.md:45-78`
711
+ - Modify: `docs/api.md:291-316`
712
+ - Modify: `skills/llm-wiki/SKILL.md:25-55`
713
+ - Modify: `skills/llm-wiki/SKILL.md:72-110`
714
+ - Modify: `CHANGELOG.md:1-12`
715
+
716
+ - [ ] **Step 1: Correct README ownership language**
717
+
718
+ Change the event ownership row to:
719
+
720
+ ```markdown
721
+ | `.llm-wiki/meta/events.jsonl` | Extension / tool | Authoritative append-only state; back up for activity continuity |
722
+ ```
723
+
724
+ Replace the Four-Layer Page Model meta comment with:
725
+
726
+ ```text
727
+ .llm-wiki/meta/ # Durable event source + generated internal projections
728
+ ```
729
+
730
+ After the ownership table, add:
731
+
732
+ ```markdown
733
+ ### Activity history, backup, and portability
734
+
735
+ `meta/events.jsonl` is the authoritative source for recorded extension activity. Unlike registry, backlinks, indexes, logs, and embeddings, it cannot be rebuilt from wiki pages or raw packets. Preserve it when backing up or Git-synchronizing a complete pi-llm-wiki vault.
736
+
737
+ `meta/log.md` and OKF-mode `wiki/log.md` are generated views. `wiki/log.md` can travel with the OKF bundle as a readable snapshot, but it cannot reconstruct or resume the originating JSONL stream. Manual page edits are intentionally absent, so this is selected extension activity rather than a complete revision audit.
738
+
739
+ File-capture events omit machine-local paths from the public log projection. Callers of `wiki_log_event` still control arbitrary detail fields and must not record secrets or private host paths.
740
+ ```
741
+
742
+ - [ ] **Step 2: Correct architecture ownership**
743
+
744
+ Replace the meta tree comment and ownership row with:
745
+
746
+ ```markdown
747
+ ├── meta/ # Durable event source + generated internal projections
748
+ ```
749
+
750
+ ```markdown
751
+ | `.llm-wiki/meta/events.jsonl` | Extension tools | Authoritative, append-only; preserve in full-vault backups |
752
+ | `.llm-wiki/meta/**` except `events.jsonl` | Extension | Generated projections |
753
+ ```
754
+
755
+ After the ownership table, add:
756
+
757
+ ```markdown
758
+ `events.jsonl` records selected extension operations, not every filesystem edit. `meta/log.md` and OKF-mode `wiki/log.md` are one-way projections; neither can recover the event stream.
759
+ ```
760
+
761
+ - [ ] **Step 3: Correct skill guidance**
762
+
763
+ Replace Golden Rule 2 with:
764
+
765
+ ```markdown
766
+ 2. **META IS EXTENSION-OWNED.** Never edit `meta/` directly. `events.jsonl` is append-only authoritative activity state; other metadata files are generated projections.
767
+ ```
768
+
769
+ Under the vault tree, change the meta comment to:
770
+
771
+ ```text
772
+ ├── meta/ # Durable events + generated projections (extension-owned)
773
+ ```
774
+
775
+ Add beneath Golden Rules:
776
+
777
+ ```markdown
778
+ > 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.
779
+ ```
780
+
781
+ - [ ] **Step 4: Document API semantics and privacy boundary**
782
+
783
+ Replace the opening `wiki_log_event` paragraph with:
784
+
785
+ ```markdown
786
+ Append a structured event to the authoritative, append-only `meta/events.jsonl` stream and regenerate available log projections. Every event is timestamped automatically. The event stream must be preserved in full-vault backups; generated Markdown logs cannot reconstruct it.
787
+
788
+ `details` is user-controlled and may appear in OKF-mode `wiki/log.md`. Do not include secrets, credentials, or private machine-local paths. Built-in local-file capture records its stable source ID and format in events while retaining the exact input path only in the extension-owned raw manifest.
789
+ ```
790
+
791
+ After the `wiki_rebuild_meta` paragraph, add:
792
+
793
+ ```markdown
794
+ If `meta/events.jsonl` is missing or unreadable, rebuild reports a warning and preserves existing log projections while continuing to rebuild registry, backlinks, and indexes. A present zero-byte event file is an intentional empty history.
795
+ ```
796
+
797
+ - [ ] **Step 5: Add changelog entries**
798
+
799
+ Under `Unreleased`, add:
800
+
801
+ ```markdown
802
+ - **Authoritative activity history**: `meta/events.jsonl` is now documented as durable append-only extension state rather than rebuildable metadata. Missing or unreadable event sources warn and preserve existing Markdown logs while unrelated projections continue rebuilding.
803
+ - **Portable log privacy**: local-file capture events no longer duplicate caller-supplied filesystem paths into `events.jsonl` or OKF `wiki/log.md`; exact paths remain in extension-owned raw manifests.
804
+ ```
805
+
806
+ - [ ] **Step 6: Scan for contradictory canonical guidance**
807
+
808
+ Run:
809
+
810
+ ```bash
811
+ grep -InE "META IS AUTO-GENERATED|events.jsonl.*Auto-generated|events.jsonl.*Generated" \
812
+ README.md \
813
+ docs/architecture.md \
814
+ docs/api.md \
815
+ skills/llm-wiki/SKILL.md \
816
+ extensions/llm-wiki/lib/bootstrap.ts
817
+ ```
818
+
819
+ Expected: no output; current canonical English guidance no longer classifies `events.jsonl` as rebuildable or auto-generated. Historical plans and changelog entries continue describing behavior at their recorded time and must not be rewritten.
820
+
821
+ Also run:
822
+
823
+ ```bash
824
+ grep -RIn "events.jsonl" README.md docs/architecture.md docs/api.md skills/llm-wiki/SKILL.md \
825
+ extensions/llm-wiki/lib/bootstrap.ts
826
+ ```
827
+
828
+ Expected: every current operating document consistently describes authoritative append-only state, one-way log projections, and backup requirements.
829
+
830
+ - [ ] **Step 7: Commit documentation**
831
+
832
+ ```bash
833
+ git add \
834
+ README.md \
835
+ docs/architecture.md \
836
+ docs/api.md \
837
+ skills/llm-wiki/SKILL.md \
838
+ CHANGELOG.md
839
+ git commit -m "docs: explain activity history backup and portability"
840
+ ```
841
+
842
+ ---
843
+
844
+ ### Task 6: Run release gates and inspect the final diff
845
+
846
+ **Files:**
847
+ - Verify all files changed in Tasks 1-5
848
+
849
+ - [ ] **Step 1: Run focused behavior tests**
850
+
851
+ ```bash
852
+ pnpm vitest run \
853
+ test/okf-projections.test.ts \
854
+ test/background-tools.test.ts \
855
+ test/source-capture.test.ts \
856
+ test/e2e-guardrails.test.ts \
857
+ test/bootstrap.test.ts
858
+ ```
859
+
860
+ Expected: all focused tests pass.
861
+
862
+ - [ ] **Step 2: Run complete test suite**
863
+
864
+ ```bash
865
+ pnpm test
866
+ ```
867
+
868
+ Expected: all tests pass; no snapshot or fixture changes outside planned files.
869
+
870
+ - [ ] **Step 3: Run static gates**
871
+
872
+ ```bash
873
+ pnpm typecheck
874
+ pnpm lint
875
+ ```
876
+
877
+ Expected: TypeScript and Biome pass with no errors.
878
+
879
+ - [ ] **Step 4: Run coverage gate**
880
+
881
+ ```bash
882
+ pnpm test:coverage
883
+ ```
884
+
885
+ Expected: suite passes and repository coverage thresholds remain satisfied.
886
+
887
+ - [ ] **Step 5: Inspect behavioral diff and commit history**
888
+
889
+ ```bash
890
+ git diff origin/main...HEAD --check
891
+ git diff --stat origin/main...HEAD
892
+ git log --oneline origin/main..HEAD
893
+ git status --short
894
+ ```
895
+
896
+ Expected:
897
+
898
+ - no whitespace errors;
899
+ - only files listed in the responsibility map changed;
900
+ - commits are plan, contract, missing-source preservation, path privacy, ownership/schema, and docs;
901
+ - working tree is clean.
902
+
903
+ - [ ] **Step 6: Verify issue #123 acceptance cases manually from tests and docs**
904
+
905
+ Confirm each statement with a direct file/test reference:
906
+
907
+ ```text
908
+ [ ] events.jsonl is explicitly authoritative and non-reconstructible
909
+ [ ] full-vault backup/Git guidance names events.jsonl
910
+ [ ] wiki/log.md is documented as a snapshot, not event recovery
911
+ [ ] missing source preserves prior logs and warns
912
+ [ ] unreadable source preserves prior logs and warns
913
+ [ ] present empty source generates empty logs
914
+ [ ] registry/backlinks/indexes still rebuild without event source
915
+ [ ] file capture path remains in raw manifest
916
+ [ ] file capture path is absent from events and wiki/log.md
917
+ [ ] manual event details carry a privacy warning
918
+ [ ] import/export implementation remains out of scope
919
+ ```
920
+
921
+ Expected: all boxes can be checked without relying on future code.
922
+
923
+ If release gates require corrections, return to the task that owns those files, apply its test-first sequence again, and amend that task with a concrete follow-up commit. Do not create an empty or catch-all verification commit.
924
+
925
+ ---
926
+
927
+ ## Phase boundary
928
+
929
+ Executing this plan leaves Foundation coherent and green:
930
+
931
+ - local event authority and backup semantics are explicit;
932
+ - unavailable authoritative history cannot silently erase existing logs;
933
+ - public OKF logs no longer receive built-in local file paths;
934
+ - existing event rendering and valid/malformed-line behavior remain intact;
935
+ - no import/export surface or partial Interchange implementation is introduced.
936
+
937
+ Next roadmap work requires a separate normative **OKF Interchange** child spec. That spec must choose imported-history composition before planning import/export implementation.