@zosmaai/pi-llm-wiki 0.12.0 → 0.12.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 (49) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +16 -8
  3. package/dist/extensions/llm-wiki/lib/bootstrap.js +2 -0
  4. package/dist/extensions/llm-wiki/lib/indexing.js +24 -1
  5. package/dist/extensions/llm-wiki/lib/ingest-worker.js +3 -1
  6. package/dist/extensions/llm-wiki/lib/knowledge-document.js +11 -2
  7. package/dist/extensions/llm-wiki/lib/knowledge-links.js +41 -6
  8. package/dist/extensions/llm-wiki/lib/model-command.js +45 -8
  9. package/dist/extensions/llm-wiki/lib/qmd-indexing.js +1024 -0
  10. package/dist/extensions/llm-wiki/lib/qmd-mirror.js +418 -0
  11. package/dist/extensions/llm-wiki/lib/qmd-store.js +112 -0
  12. package/dist/extensions/llm-wiki/lib/recall.js +77 -3
  13. package/dist/extensions/llm-wiki/lib/runtime.js +25 -1
  14. package/dist/extensions/llm-wiki/lib/subagent.js +47 -7
  15. package/dist/extensions/llm-wiki/lib/tools.js +165 -5
  16. package/dist/extensions/llm-wiki/lib/utils.js +16 -2
  17. package/dist/extensions/llm-wiki/lib/wiki-service.js +104 -5
  18. package/dist/mcp/index.js +66 -2
  19. package/dist/mcp/operations.js +26 -2
  20. package/docs/api.md +43 -1
  21. package/docs/architecture.md +28 -0
  22. package/docs/commands.md +1 -0
  23. package/docs/qmd-compatibility.md +47 -0
  24. package/docs/retrieval-benchmark.md +47 -0
  25. package/docs/superpowers/benchmarks/phase-1-current-baseline.json +53 -0
  26. package/docs/superpowers/plans/2026-08-09-qmd-retrieval-phase-2-remediation.md +549 -0
  27. package/docs/superpowers/plans/2026-08-09-qmd-retrieval-phase-2-validated-indexing.md +1493 -0
  28. package/docs/superpowers/plans/2026-08-11-qmd-retrieval-phase-3-retrieval-modes-and-recall-cutover.md +678 -0
  29. package/docs/superpowers/plans/2026-09-05-wikilink-alias-pipe-table-only.md +257 -0
  30. package/extensions/llm-wiki/index.ts +14 -1
  31. package/extensions/llm-wiki/lib/bootstrap.ts +2 -0
  32. package/extensions/llm-wiki/lib/indexing.ts +24 -1
  33. package/extensions/llm-wiki/lib/ingest-worker.ts +10 -2
  34. package/extensions/llm-wiki/lib/knowledge-document.ts +20 -3
  35. package/extensions/llm-wiki/lib/knowledge-links.ts +39 -7
  36. package/extensions/llm-wiki/lib/model-command.ts +57 -12
  37. package/extensions/llm-wiki/lib/qmd-indexing.ts +1304 -0
  38. package/extensions/llm-wiki/lib/qmd-mirror.ts +496 -0
  39. package/extensions/llm-wiki/lib/qmd-store.ts +222 -0
  40. package/extensions/llm-wiki/lib/recall.ts +77 -3
  41. package/extensions/llm-wiki/lib/runtime.ts +57 -5
  42. package/extensions/llm-wiki/lib/subagent.ts +73 -10
  43. package/extensions/llm-wiki/lib/tools.ts +188 -4
  44. package/extensions/llm-wiki/lib/utils.ts +21 -2
  45. package/extensions/llm-wiki/lib/wiki-service.ts +160 -4
  46. package/mcp/index.ts +78 -1
  47. package/mcp/operations.ts +41 -2
  48. package/package.json +9 -6
  49. package/skills/llm-wiki/SKILL.md +7 -1
@@ -0,0 +1,549 @@
1
+ # QMD Retrieval Phase 2 Remediation Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use `/skill:executing-plans` to implement this plan task-by-task. Track progress with the checkboxes below. Do not start Phase 3 until every required gate passes.
4
+
5
+ **Goal:** Close the crash-recovery, status-integrity, generated-state cleanup, configuration-safety, accounting, and operator-guidance gaps found in the completed Phase 2 implementation.
6
+
7
+ **Architecture:** Keep authoritative Markdown and existing recall unchanged. Make the QMD directory swap a write-ahead, state-verifiable operation shared by normal reindexing and fail-closed invalidation. Treat generated status as a strict parser over independent artifacts rather than conflating missing, malformed, and unreadable files. Clean only recognizable extension-owned staging artifacts while holding the existing per-vault lock. Preserve existing QMD SDK isolation and public tool shape.
8
+
9
+ **Tech Stack:** TypeScript 5.9, Node.js 22 `node:fs/promises`, `@tobilu/qmd` 2.5.3 public SDK, TypeBox, Vitest, Biome, pnpm.
10
+
11
+ **Normative inputs:**
12
+
13
+ - `docs/superpowers/specs/2026-08-08-qmd-retrieval-design.md`
14
+ - `docs/superpowers/plans/2026-08-09-qmd-retrieval-phase-2-validated-indexing.md`
15
+ - Audit range: `32b7983..2ff868c`
16
+
17
+ **Phase:** Phase 2 remediation. Phase 3 remains blocked until this plan passes.
18
+
19
+ ---
20
+
21
+ ## Baseline and Reviewed Defect Disposition
22
+
23
+ Baseline verified before this plan:
24
+
25
+ ```text
26
+ Focused QMD suite: 43 passed, 1 model smoke skipped
27
+ Full suite: 654 passed, 1 model smoke skipped
28
+ Typecheck: pass
29
+ Biome lint: pass
30
+ MCP build: pass
31
+ Retrieval baseline: unchanged from Phase 1
32
+ Mechanical scope: recall.ts and inject.ts unchanged; QMD import isolated to qmd-store.ts
33
+ Known test noise: okf-integration may print a background QMD recovery ENOENT during teardown
34
+ ```
35
+
36
+ | Audit finding | Classification | Planned fix |
37
+ |---|---|---|
38
+ | Crash after `current → previous` but before journal update can leave no `current` | Critical implementation defect | Task 1 |
39
+ | Normal and fail-closed promotion duplicate the same unsafe ordering | Root-cause duplication | Task 1 |
40
+ | Malformed manifest/state can report `missing` or `ready` | Important implementation defect | Task 2 |
41
+ | `last-error.json` without a usable current reports `missing` | Important implementation defect | Task 2 |
42
+ | State file can report `ready` while `current/index.sqlite` is absent | Important implementation defect | Task 2 |
43
+ | Status cannot express which component needs repair | Operator-contract defect | Tasks 2 and 4 |
44
+ | Pre-journal failures leave full staging copies behind | Important generated-state lifecycle defect | Task 3 |
45
+ | Existing unreferenced staging directories are never reconciled | Important generated-state lifecycle defect | Task 3 |
46
+ | `ensureVaultId` treats malformed/unreadable config as `{}` and may overwrite it | Important data-safety defect | Task 3 |
47
+ | `scope="all"` omits unchanged rewritten pages from counts | Reporting defect | Task 4 |
48
+ | Lint can emit invalid `components=["lexical, vectors"]` | Operator-guidance defect | Task 4 |
49
+ | Existing vector index can cause stale vectors to receive lexical-only guidance | Operator-guidance defect | Task 4 |
50
+ | Integration test deletes a vault before fire-and-forget recovery drains | Test isolation defect | Task 4 |
51
+ | Real model-backed vector smoke remains unrun | Optional verification gap | Task 5 |
52
+
53
+ ---
54
+
55
+ ## Scope Boundaries
56
+
57
+ This remediation must not:
58
+
59
+ - change heuristic recall ranking, rendering, or `before_agent_start` injection;
60
+ - add Phase 3 lexical, hybrid, adaptive, quality, fallback, or cutover behavior;
61
+ - change QMD tables or import QMD outside `extensions/llm-wiki/lib/qmd-store.ts`;
62
+ - delete unknown entries under `.llm-wiki/meta/qmd/**`;
63
+ - break another host's or a malformed owner lock;
64
+ - load or download models during ordinary tests, status, lint, recovery, or lexical indexing;
65
+ - replace an invalid existing `vault_id` or reconstruct malformed configuration;
66
+ - change `wiki_reindex` input parameters.
67
+
68
+ A failed or cancelled operation must retain or restore the last usable current store. If no prior current exists, failure may leave the index missing, but never falsely ready. This plan covers process termination and interrupted filesystem operations using atomic file/directory replacement; fsync-level sudden-power-loss durability remains outside the existing Phase 2 contract.
69
+
70
+ ---
71
+
72
+ ## File Responsibility Map
73
+
74
+ ### Production files
75
+
76
+ - `extensions/llm-wiki/lib/qmd-indexing.ts` — write-ahead promotion, state-aware recovery, strict status, fail-closed config parsing, staging cleanup, component repair metadata.
77
+ - `extensions/llm-wiki/lib/qmd-mirror.ts` — strict manifest read behavior and accurate full-scope counts.
78
+ - `extensions/llm-wiki/lib/wiki-service.ts` — complete blocked-reindex status objects after adding required repair metadata.
79
+ - `extensions/llm-wiki/lib/tools.ts` — exact component-specific lint repair commands.
80
+
81
+ ### Tests
82
+
83
+ - `test/qmd-indexing-recovery.test.ts` — rename/journal crash windows and orphan recovery.
84
+ - `test/qmd-indexing.test.ts` — malformed state/config, missing current DB, last-error precedence, staging cleanup, component repair metadata.
85
+ - `test/qmd-mirror.test.ts` — malformed manifest and `scope="all"` accounting.
86
+ - `test/lint-okf.test.ts` — exact valid lexical/vector repair commands.
87
+ - `test/okf-integration.test.ts` — drain startup recovery before teardown.
88
+
89
+ ### Documentation
90
+
91
+ - `docs/api.md` — strict status meanings and component-specific repair metadata/guidance.
92
+ - `docs/architecture.md` — write-ahead swap and generated staging cleanup semantics.
93
+ - `docs/commands.md` — valid repair examples.
94
+
95
+ No new production module is planned. Extract only one small shared promotion helper inside `qmd-indexing.ts`; both existing promotion paths must use it.
96
+
97
+ ---
98
+
99
+ ## Task 1: Make Store Promotion Crash-Safe Once
100
+
101
+ **Files:**
102
+
103
+ - Modify: `extensions/llm-wiki/lib/qmd-indexing.ts`
104
+ - Modify: `test/qmd-indexing-recovery.test.ts`
105
+ - Modify: `test/qmd-indexing.test.ts`
106
+
107
+ - [ ] **Step 1: Add failing tests for the uncovered crash windows**
108
+
109
+ Add on-disk recovery cases for:
110
+
111
+ 1. journal says `prepared`, `current` is absent, `previous` exists, and staging exists — equivalent to a crash after moving current but before publishing `previous-moved`; recovery restores `previous` to `current`;
112
+ 2. journal says `previous-moved`, both `current` and `previous` exist, and staging is absent — equivalent to a crash after promotion but before publishing `current-promoted`; recovery validates current and removes previous, or rolls back when current validation fails;
113
+ 3. journal says `current-promoted`, current is absent, previous exists, and staging exists — recovery restores previous and removes staging;
114
+ 4. no prior current, journal says `current-promoted`, promoted current is invalid, and no previous exists — recovery removes invalid generated current and reports a missing/error state rather than inventing a store;
115
+ 5. write-ahead journal says `previous-moved`, but the crash occurred before `rename(current, previous)` — current and staging exist, previous does not; recovery keeps old current and removes staging;
116
+ 6. write-ahead journal says `current-promoted`, and the crash occurred after `rename(staging, current)` but before `validated` — current and previous exist, staging does not; recovery validates current before choosing it over previous;
117
+ 7. successful recovery to a validated current removes an earlier `last-error.json`, while recovery with no validated current preserves it.
118
+
119
+ Use the existing fake store factory and real temporary directories. Include a promoted store that opens successfully but reports the wrong document count; recovery must roll it back rather than treating mere openability as validity. Do not mock QMD internals.
120
+
121
+ - [ ] **Step 2: Assert write-ahead ordering during real promotion**
122
+
123
+ Wrap the existing injected filesystem adapter. Immediately before delegating each destructive rename, read `swap.json` and assert:
124
+
125
+ ```text
126
+ rename(current, previous) -> persisted phase is previous-moved
127
+ rename(staging, current) -> persisted phase is current-promoted
128
+ ```
129
+
130
+ The phase is durable intent: it must be published before the corresponding rename. A failed rename therefore leaves recovery enough information to choose cleanup or rollback.
131
+
132
+ - [ ] **Step 3: Run focused tests and verify failure**
133
+
134
+ ```bash
135
+ pnpm exec vitest run test/qmd-indexing-recovery.test.ts --reporter=verbose
136
+ ```
137
+
138
+ Expected: at least the current-to-previous crash-window test fails against `2ff868c`.
139
+
140
+ - [ ] **Step 4: Extract one shared promotion helper**
141
+
142
+ Inside `qmd-indexing.ts`, replace the duplicated rename/journal sequences in `reindexQmdVault` and `invalidateQmdAfterProjectionFailure` with one private helper. It must:
143
+
144
+ 1. publish `prepared` after staging is closed and validated;
145
+ 2. if current exists, publish `previous-moved` before moving current to previous;
146
+ 3. publish `current-promoted` before moving staging to current;
147
+ 4. reopen and validate promoted current as openable **and** require `status().totalDocuments` to equal the validated manifest entry count; if the manifest is unavailable during recovery, use the structurally valid `current/index-state.json` recorded count as the fallback expectation;
148
+ 5. publish `validated` only after count validation succeeds;
149
+ 6. remove previous and journal only after `validated` is durable;
150
+ 7. never rename a directory while its store handle is open.
151
+
152
+ Keep SQLite/WAL/SHM/native sidecars together by renaming only complete artifact directories.
153
+
154
+ - [ ] **Step 5: Make recovery phase-and-filesystem aware**
155
+
156
+ Recovery must handle both old post-operation journals and new write-ahead journals. For each phase, inspect the existence of current, previous, and the journal-referenced staging directory. Rules:
157
+
158
+ - a current is valid only when it opens and its document count equals the manifest entry count, falling back to the current state file's recorded count only when the manifest cannot be read;
159
+ - if current is absent or invalid and previous exists, restore previous;
160
+ - if current is valid and previous exists, retain current and remove previous;
161
+ - remove only the journal-referenced staging directory;
162
+ - never remove previous until current passes count validation;
163
+ - after recovery leaves a validated current, remove `last-error.json`; if no validated current remains, retain the error artifact;
164
+ - leave malformed journals untouched for inspection.
165
+
166
+ This preserves compatibility with interrupted stores produced before remediation and prevents an openable-but-incomplete promoted store from displacing the last known-good index.
167
+
168
+ - [ ] **Step 6: Run focused recovery and indexing tests**
169
+
170
+ ```bash
171
+ QMD_FORCE_CPU=1 pnpm exec vitest run test/qmd-indexing-recovery.test.ts test/qmd-indexing.test.ts --reporter=verbose
172
+ pnpm typecheck
173
+ ```
174
+
175
+ Expected: every stable phase and every transition-window state recovers to a usable current or an explicit missing/error state.
176
+
177
+ - [ ] **Step 7: Commit**
178
+
179
+ ```bash
180
+ git add extensions/llm-wiki/lib/qmd-indexing.ts test/qmd-indexing-recovery.test.ts test/qmd-indexing.test.ts
181
+ git commit -m "fix: make QMD store promotion crash-safe"
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Task 2: Make Generated Status Strict and Actionable
187
+
188
+ **Files:**
189
+
190
+ - Modify: `extensions/llm-wiki/lib/qmd-indexing.ts`
191
+ - Modify: `extensions/llm-wiki/lib/qmd-mirror.ts`
192
+ - Modify: `extensions/llm-wiki/lib/wiki-service.ts`
193
+ - Modify: `test/qmd-indexing.test.ts`
194
+ - Modify: `test/qmd-mirror.test.ts`
195
+
196
+ - [ ] **Step 1: Add failing artifact-state matrix tests**
197
+
198
+ Cover these states without opening a QMD store or loading a model:
199
+
200
+ | Artifacts | Expected state |
201
+ |---|---|
202
+ | no manifest, state, DB, error, or journal | `missing` |
203
+ | malformed manifest JSON | `error` with `qmd_manifest_invalid` |
204
+ | structurally invalid manifest entry | `error` with `qmd_manifest_invalid` |
205
+ | malformed state JSON | `error` with `qmd_index_error` |
206
+ | state object missing required status/model/hash fields | `error` with `qmd_index_error` |
207
+ | valid state but missing manifest | `stale` with `qmd_index_stale` |
208
+ | valid state but missing config | `error` because vault identity cannot be confirmed |
209
+ | valid state but missing `current/index.sqlite` | `error` with `qmd_index_error` |
210
+ | `last-error.json` but no usable current | `error` with `qmd_index_error` |
211
+ | usable current plus last error | `stale`, preserving last usable counts |
212
+ | valid swap journal | `recovering` with its phase |
213
+ | malformed swap journal | `error` with `qmd_swap_interrupted` |
214
+ | manifest hash mismatch, no vector index | `stale`, repair component `lexical` |
215
+ | embedding model mismatch | `stale`, repair component `vectors` |
216
+ | manifest mismatch with an existing vector index | `stale`, repair component `vectors` because vector reindex refreshes documents first |
217
+ | QMD package or vault identity mismatch, no vector index | `stale`, repair component `lexical` |
218
+ | QMD package or vault identity mismatch with vector index | `stale`, repair component `vectors` |
219
+
220
+ Use a required explicit status field named `repairComponents: QmdComponent[]`. It is empty for `ready`, `missing`, and `recovering`; it contains only valid `lexical`/`vectors` values for stale/error states where reindex can repair the condition. For malformed/error states, use `vectors` when a prior state proves a vector index exists (one vector pass refreshes documents first); otherwise use `lexical`. Update every `QmdGeneratedStatus` constructor, including `blockedReindexResult` in `wiki-service.ts`.
221
+
222
+ - [ ] **Step 2: Add strict manifest tests**
223
+
224
+ `readQmdManifest` must distinguish absence from corruption:
225
+
226
+ - `ENOENT` returns an empty manifest for the expected vault;
227
+ - malformed JSON or other read failures throw `qmd_manifest_invalid`;
228
+ - each entry must have a safe deterministic key, matching vault ID, matching role/page ID, an absolute source path contained under authoritative `paths.wiki` (therefore under `paths.root` and outside `paths.qmd`), a non-empty type, and a lowercase 64-hex SHA-256 content hash;
229
+ - unsafe or inconsistent entries never become trusted prior state;
230
+ - when the projection-failure invalidation path encounters a corrupt manifest, fail closed in the removal direction: publish an empty validated manifest and remove only generated mirror entries that can be enumerated safely, so stale deleted-page candidates cannot survive silently.
231
+
232
+ Tests should mutate one field at a time and assert rejection. Add an invalidation regression for a corrupt prior manifest. Do not follow symlinks or read source content while merely parsing status.
233
+
234
+ - [ ] **Step 3: Run tests and verify failure**
235
+
236
+ ```bash
237
+ pnpm exec vitest run test/qmd-indexing.test.ts test/qmd-mirror.test.ts --reporter=verbose
238
+ ```
239
+
240
+ Expected: malformed state/manifest and missing-DB cases fail against current status handling.
241
+
242
+ - [ ] **Step 4: Separate missing, invalid, and readable JSON states**
243
+
244
+ Replace broad status-path catches with an internal discriminated read result:
245
+
246
+ ```ts
247
+ type JsonArtifact<T> =
248
+ | { kind: "missing" }
249
+ | { kind: "valid"; value: T }
250
+ | { kind: "invalid"; message: string };
251
+ ```
252
+
253
+ Only `ENOENT` is `missing`. JSON parse failures, permission failures, directories at file paths, and invalid shapes are `invalid`. Keep permissive lock-owner parsing separate; malformed foreign locks must remain unbroken.
254
+
255
+ Validate the complete `QmdIndexStateFile` shape before reading nested fields. Verify `current/index.sqlite` exists before `ready` or `stale` can be returned. Status remains model-free and must not call `openQmdIndexStore`.
256
+
257
+ - [ ] **Step 5: Implement deterministic precedence and repair components**
258
+
259
+ Apply status precedence in this order:
260
+
261
+ 1. valid interrupted journal → `recovering`;
262
+ 2. malformed journal, manifest, existing config identity, state, or error artifact → `error`;
263
+ 3. no state and no last error → `missing` (a valid legacy config with no `vault_id` remains backfillable, not invalid);
264
+ 4. state exists but `current/index.sqlite` is absent → `error`;
265
+ 5. no structurally usable current with last error → `error`;
266
+ 6. usable current with missing manifest, model/version/vault mismatch, or last error → `stale`;
267
+ 7. otherwise → `ready`.
268
+
269
+ An absent config beside an existing state is `error`, because the indexed vault identity cannot be confirmed. Collect all applicable safe issues, not only the first mismatch. Derive `repairComponents` from every applicable reason using the rules above. Never expose model-cache paths or authoritative absolute source paths in chat rendering.
270
+
271
+ - [ ] **Step 6: Run focused tests**
272
+
273
+ ```bash
274
+ QMD_FORCE_CPU=1 pnpm exec vitest run test/qmd-mirror.test.ts test/qmd-indexing.test.ts test/lint-okf.test.ts test/mcp-parity.test.ts --reporter=verbose
275
+ pnpm typecheck
276
+ pnpm lint
277
+ ```
278
+
279
+ - [ ] **Step 7: Commit**
280
+
281
+ ```bash
282
+ git add extensions/llm-wiki/lib/qmd-indexing.ts extensions/llm-wiki/lib/qmd-mirror.ts extensions/llm-wiki/lib/wiki-service.ts test/qmd-indexing.test.ts test/qmd-mirror.test.ts
283
+ git commit -m "fix: report QMD artifact health strictly"
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Task 3: Fail Closed on Config and Reconcile Staging Artifacts
289
+
290
+ **Files:**
291
+
292
+ - Modify: `extensions/llm-wiki/lib/qmd-indexing.ts`
293
+ - Modify: `test/qmd-indexing.test.ts`
294
+ - Modify: `test/qmd-indexing-recovery.test.ts`
295
+
296
+ - [ ] **Step 1: Add failing configuration-preservation tests**
297
+
298
+ For malformed JSON and JSON arrays/null:
299
+
300
+ - `ensureVaultId` and `reindexQmdVault` return a structured error;
301
+ - the original config bytes remain unchanged;
302
+ - no `vault_id` is generated;
303
+ - no mirror, staging store, or current store is created.
304
+
305
+ Use a deterministic read-error case by placing a directory at `config.json`; assert it remains a directory and no generated indexing state appears. Do not depend on chmod behavior, which differs across CI platforms.
306
+
307
+ A valid object without `vault_id` is still backfilled once and preserves every unrelated key. A valid but invalid existing `vault_id` remains unchanged and errors as today.
308
+
309
+ - [ ] **Step 2: Add failing staging cleanup tests**
310
+
311
+ Cover:
312
+
313
+ 1. store update failure before journal publication;
314
+ 2. staging validation failure before journal publication;
315
+ 3. cancellation triggered from an update progress callback;
316
+ 4. startup recovery with multiple unreferenced `staging-<uuid>` directories and no journal;
317
+ 5. recovery with a valid journal referencing one staging directory plus unrelated stale staging directories;
318
+ 6. malformed journal, where no staging directory is removed;
319
+ 7. arbitrary unknown files/directories under `meta/qmd`, which are never removed.
320
+
321
+ After recoverable cases, all unreferenced exact-pattern staging directories are gone. Current, manifest/documents, known state/error files, a valid journal-referenced staging artifact, and arbitrary unknown entries remain untouched.
322
+
323
+ - [ ] **Step 3: Run tests and verify failure**
324
+
325
+ ```bash
326
+ pnpm exec vitest run test/qmd-indexing.test.ts test/qmd-indexing-recovery.test.ts --reporter=verbose
327
+ ```
328
+
329
+ - [ ] **Step 4: Parse configuration fail-closed**
330
+
331
+ `ensureVaultId` must read and parse `config.json` directly. Missing, unreadable, malformed, null, or array configuration is an error; it must never become `{}`. Atomic backfill occurs only after a valid object and absent `vault_id` are confirmed.
332
+
333
+ Preserve the existing UUID validation and atomic replacement. Do not add a UUID dependency.
334
+
335
+ - [ ] **Step 5: Clean staging safely under the existing lock**
336
+
337
+ Keep the active staging path available to `catch/finally`:
338
+
339
+ - before a journal is published, failure or cancellation removes that operation's staging directory;
340
+ - after a journal is published, leave state for the recovery routine;
341
+ - during recovery with no malformed journal, scan only direct children matching the exact `STAGING_NAME` pattern and remove those not referenced by the active journal;
342
+ - do not recurse into or delete arbitrary names;
343
+ - perform cleanup only while holding the per-vault lock.
344
+
345
+ Use `Dirent` checks so a matching symlink or non-directory is not followed. Cleanup failures become safe diagnostics; they must not delete current or hide the original indexing error.
346
+
347
+ - [ ] **Step 6: Run focused tests**
348
+
349
+ ```bash
350
+ QMD_FORCE_CPU=1 pnpm exec vitest run test/qmd-indexing.test.ts test/qmd-indexing-recovery.test.ts test/indexing.test.ts test/indexing-fail-closed.test.ts --reporter=verbose
351
+ pnpm typecheck
352
+ pnpm lint
353
+ ```
354
+
355
+ - [ ] **Step 7: Commit**
356
+
357
+ ```bash
358
+ git add extensions/llm-wiki/lib/qmd-indexing.ts test/qmd-indexing.test.ts test/qmd-indexing-recovery.test.ts
359
+ git commit -m "fix: preserve QMD config and clean staging"
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Task 4: Correct Counts, Repair Commands, and Test Teardown
365
+
366
+ **Files:**
367
+
368
+ - Modify: `extensions/llm-wiki/lib/qmd-mirror.ts`
369
+ - Modify: `extensions/llm-wiki/lib/tools.ts`
370
+ - Modify: `test/qmd-mirror.test.ts`
371
+ - Modify: `test/lint-okf.test.ts`
372
+ - Modify: `test/okf-integration.test.ts`
373
+ - Modify: `docs/api.md`
374
+ - Modify: `docs/architecture.md`
375
+ - Modify: `docs/commands.md`
376
+
377
+ - [ ] **Step 1: Add failing full-scope accounting test**
378
+
379
+ Run `reconcileQmdMirror(paths, vaultId, "all")` twice over unchanged content. The second result must count every accepted page as `unchanged` even though full scope rewrites it. Assert:
380
+
381
+ ```ts
382
+ indexed + updated + unchanged === Object.keys(manifest.entries).length
383
+ ```
384
+
385
+ `removed` remains independent because deleted prior entries are not in the final manifest.
386
+
387
+ - [ ] **Step 2: Add exact lint command tests**
388
+
389
+ Assert complete command fragments, not only the word `wiki_reindex`:
390
+
391
+ ```text
392
+ wiki_reindex(scope="changed", components=["lexical"], vault="active")
393
+ wiki_reindex(scope="changed", components=["vectors"], vault="active")
394
+ ```
395
+
396
+ Also assert lint never emits:
397
+
398
+ ```text
399
+ components=["lexical, vectors"]
400
+ ```
401
+
402
+ Cases:
403
+
404
+ - manifest stale without a vector index → lexical;
405
+ - embedding model mismatch → vectors;
406
+ - document change with an existing vector index → vectors, because vector selection performs document update first;
407
+ - malformed/error state with no safe component inference → lexical fallback.
408
+
409
+ Build arrays with `JSON.stringify(qmdStatus.repairComponents)`; do not hand-build comma-delimited strings.
410
+
411
+ - [ ] **Step 3: Reproduce and fix integration teardown noise**
412
+
413
+ First reproduce the existing ENOENT warning with the current harness and confirm it is emitted by the `index.ts` startup-recovery wrapper. Then spy on `console.warn`, emit `session_shutdown`, and await `registerBackgroundRuntime`'s existing drain handler before temporary-vault cleanup. Assert no QMD recovery warning. Do not silence production warnings globally or bypass the harness lifecycle.
414
+
415
+ - [ ] **Step 4: Implement minimal reporting fixes**
416
+
417
+ - Increment `unchanged` based on content identity even when `scope="all"` forces a rewrite.
418
+ - Render lint guidance from validated `repairComponents`.
419
+ - Keep `missing` informational.
420
+ - Keep lint read-only for QMD.
421
+
422
+ - [ ] **Step 5: Update operator documentation**
423
+
424
+ Document:
425
+
426
+ - journal phases are write-ahead intent and recovery also checks filesystem state;
427
+ - recognizable stale staging directories are extension-owned and cleaned while locked;
428
+ - malformed generated artifacts report `error`, not `missing`/`ready`;
429
+ - `repairComponents` contains valid tool component values;
430
+ - vectors refresh documents before embedding, so `components=["vectors"]` repairs stale vectors and their document index together;
431
+ - active recall is still heuristic until Phase 3.
432
+
433
+ - [ ] **Step 6: Run focused parity and documentation tests**
434
+
435
+ ```bash
436
+ pnpm exec vitest run test/qmd-mirror.test.ts test/lint-okf.test.ts test/okf-integration.test.ts test/background-tools.test.ts test/mcp-parity.test.ts test/package-structure.test.ts --reporter=verbose
437
+ pnpm typecheck
438
+ pnpm lint
439
+ ```
440
+
441
+ - [ ] **Step 7: Commit**
442
+
443
+ ```bash
444
+ git add extensions/llm-wiki/lib/qmd-mirror.ts extensions/llm-wiki/lib/tools.ts test/qmd-mirror.test.ts test/lint-okf.test.ts test/okf-integration.test.ts docs/api.md docs/architecture.md docs/commands.md
445
+ git commit -m "fix: make QMD diagnostics actionable"
446
+ ```
447
+
448
+ ---
449
+
450
+ ## Task 5: Final Phase 2 Certification
451
+
452
+ **Files:**
453
+
454
+ - Modify only if verification exposes a remediation regression.
455
+
456
+ - [ ] **Step 1: Run the complete model-free Phase 2 suite**
457
+
458
+ ```bash
459
+ QMD_FORCE_CPU=1 pnpm exec vitest run \
460
+ test/qmd-mirror.test.ts \
461
+ test/qmd-contract.test.ts \
462
+ test/qmd-indexing.test.ts \
463
+ test/qmd-indexing-recovery.test.ts \
464
+ test/qmd-reindex-tool.test.ts \
465
+ test/lint-okf.test.ts \
466
+ test/indexing.test.ts \
467
+ test/indexing-fail-closed.test.ts \
468
+ --reporter=verbose
469
+ ```
470
+
471
+ Expected: all model-free tests pass; model smoke remains skipped.
472
+
473
+ - [ ] **Step 2: Run all required repository gates**
474
+
475
+ ```bash
476
+ pnpm test
477
+ pnpm typecheck
478
+ pnpm lint
479
+ pnpm build:mcp
480
+ pnpm benchmark:retrieval
481
+ git diff --check 2ff868c..HEAD
482
+ git status --short
483
+ ```
484
+
485
+ Expected:
486
+
487
+ - no unexpected stderr recovery warning;
488
+ - MCP still exposes seven tools;
489
+ - retrieval benchmark exactly matches the Phase 1 baseline;
490
+ - no whitespace errors;
491
+ - clean implementation tree after commits.
492
+
493
+ - [ ] **Step 3: Verify phase scope mechanically**
494
+
495
+ ```bash
496
+ git diff 2ff868c..HEAD -- extensions/llm-wiki/lib/recall.ts extensions/llm-wiki/lib/inject.ts
497
+ git grep -n "@tobilu/qmd" -- extensions/llm-wiki | grep -v "lib/qmd-store.ts"
498
+ git diff --name-only 2ff868c..HEAD
499
+ ```
500
+
501
+ Expected:
502
+
503
+ - no recall/injection diff;
504
+ - no production QMD import outside `qmd-store.ts`;
505
+ - only files listed in this plan changed, unless a failing gate required a documented addition.
506
+
507
+ - [ ] **Step 4: Run optional real vector smoke**
508
+
509
+ Only when pinned models are already cached or the operator explicitly accepts the approximately 2 GB first-use download:
510
+
511
+ ```bash
512
+ QMD_MODEL_SMOKE=1 QMD_FORCE_CPU=1 pnpm exec vitest run test/qmd-contract.test.ts test/qmd-indexing.test.ts --reporter=verbose
513
+ ```
514
+
515
+ Expected: embedding succeeds, vector status becomes fresh, and no query/reranking path is wired into recall.
516
+
517
+ If not run, record it as an explicit release-risk note rather than claiming model-backed verification.
518
+
519
+ - [ ] **Step 5: Inspect final history**
520
+
521
+ ```bash
522
+ git log --oneline 2ff868c..HEAD
523
+ git diff --stat 2ff868c..HEAD
524
+ git diff --check 2ff868c..HEAD
525
+ git status --short
526
+ ```
527
+
528
+ Expected: one reviewed planning commit plus four focused remediation commits after `2ff868c`, and a clean tree.
529
+
530
+ ---
531
+
532
+ ## Certification Criteria
533
+
534
+ Phase 2 is certified only when all statements are true:
535
+
536
+ - every destructive directory rename is preceded by durable journal intent;
537
+ - every journal phase recovers correctly across pre-operation and post-operation filesystem states;
538
+ - failed/cancelled pre-journal work leaves no staging copy;
539
+ - stale staging cleanup removes only exact extension-owned staging directories while locked;
540
+ - malformed or unreadable configuration is never overwritten;
541
+ - malformed generated artifacts and missing current DB cannot report `ready`;
542
+ - status supplies valid component-specific repair guidance;
543
+ - full-scope accounting includes every final manifest entry;
544
+ - Pi and MCP status/reindex parity remains green;
545
+ - current heuristic recall and Phase 1 retrieval metrics remain unchanged;
546
+ - all required model-free tests, typecheck, lint, MCP build, and diff checks pass;
547
+ - optional model smoke is either passed or explicitly recorded as unrun.
548
+
549
+ After certification, create the separate Phase 3 Retrieval Modes and Recall Cutover plan. Do not fold Phase 3 work into this remediation.