pi-vault-mind 0.16.5 → 0.16.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.7 / 0.6.10 — 2026-07-18
4
+
5
+ ### Fixed
6
+
7
+ - **Obsidian Markdown lifecycle.** Assistant Markdown renders through Obsidian's native renderer, renderer components unload on panel close or remount, stale asynchronous opens cannot remount a closed panel, and deferred message fills cannot hijack a replacement message placeholder.
8
+ - **Generated sandbox constants.** Root builds now format the sandbox extension-package catalog they regenerate, so the release build cannot invalidate the sandbox verification suite.
9
+ - **Repository-only documentation.** Removed the obsolete MkDocs configuration, GitHub Pages deployment workflow, publication badges, and generated-site instructions while retaining maintained Markdown documentation in the repository.
10
+
11
+ ### Tests
12
+
13
+ - Added focused regressions for Markdown cleanup ownership, direct remount disposal, close-during-refresh cancellation, and deferred placeholder isolation.
14
+
15
+ ## 0.6.9 — 2026-07-18
16
+
17
+ ### Fixed
18
+
19
+ - **First-run personalization recovery.** The configured-vault onboarding card now retains its active personalization state until durable runtime status confirms completion, including when the prompt acknowledgement fails or a permission request is pending. The chat feed and composer remain correctly gated until then.
20
+ - **Portable model selection.** The shared panel contract now passes opaque model IDs end-to-end; the Obsidian adapter resolves each ID to Pi's provider/model RPC payload internally, preserving IDs that contain `/` or `:`.
21
+
22
+ ### Tests
23
+
24
+ - Added focused regressions for acknowledgement failure, pending permission delivery, re-entry protection, and exact model-ID selection ordering.
25
+
26
+ ## 0.16.6 / 0.6.8 — 2026-07-18
27
+
28
+ ### Fixed
29
+
30
+ - **First-run personalization handoff.** Configured vaults now remain on a dedicated personalization card until `/vm personalize` durably completes, including the valid all-suggestions-rejected path. The chat feed and composer are revealed only after the persisted completion status is observed.
31
+ - **Live Pi model selection.** The first-run card now loads the active model and catalog from typed Pi RPC state, preserves provider and model ID for selection, and starts Pi only when a configured panel refresh needs that catalog.
32
+
33
+ ### Tests
34
+
35
+ - Added regression coverage for first-run gating, model selection ordering, delayed personalization completion, and demand-driven Pi model loading.
36
+
3
37
  ## 0.16.5 / 0.6.7 — 2026-07-18
4
38
 
5
39
  ### Fixed
package/README.md CHANGED
@@ -3,8 +3,6 @@
3
3
  [![npm version](https://img.shields.io/npm/v/pi-vault-mind)](https://www.npmjs.com/package/pi-vault-mind)
4
4
  [![license](https://img.shields.io/npm/l/pi-vault-mind)](LICENSE)
5
5
  [![pi-extension](https://img.shields.io/badge/pi-extension-blue)](https://github.com/mariozechner/pi)
6
- [![docs](https://img.shields.io/badge/docs-pi--vault--mind-blue)](https://kylebrodeur.github.io/pi-vault-mind/)
7
- [![docs build](https://github.com/kylebrodeur/pi-vault-mind/actions/workflows/docs.yml/badge.svg)](https://github.com/kylebrodeur/pi-vault-mind/actions/workflows/docs.yml)
8
6
 
9
7
  Passive Obsidian vault extension for the [pi](https://github.com/mariozechner/pi) agent ecosystem. Watches `@agent` markers in your vault, dispatches forked subagents, and stores results in LanceDB with vector + FTS + graph. Multi-agent "Drop & Forget" workflow.
10
8
 
@@ -363,19 +361,16 @@ Edit `pi-vault-mind.config.json` to match your domain:
363
361
 
364
362
  ## Documentation
365
363
 
366
- > **Full docs site:** [kylebrodeur.github.io/pi-vault-mind](https://kylebrodeur.github.io/pi-vault-mind/) (GH Pages, built from [`docs/`](docs/)).
367
- > The links below point to the source files in this repository.
364
+ Documentation is maintained directly in this repository.
368
365
 
369
366
  ### Getting started
370
367
 
371
368
  | Doc | Description |
372
369
  |---|---|
373
- | [docs/getting-started/QUICKSTART.md](docs/getting-started/QUICKSTART.md) | **Fastest path** 60-second install, setup, first append |
374
- | [docs/getting-started/NEW_VAULT_WALKTHROUGH.md](docs/getting-started/NEW_VAULT_WALKTHROUGH.md) | **Fresh Mac new vault Modal sync → verified**, one linear path (incl. multi-vault storage isolation) |
375
- | [docs/getting-started/INSTALL.md](docs/getting-started/INSTALL.md) | **Canonical install playbook** all 5 layers (pi ext, skills, Obsidian, config, external CLIs) |
376
- | [docs/getting-started/GETTING_STARTED.md](docs/getting-started/GETTING_STARTED.md) | End-to-end workflow + daily "drop & forget" usage |
377
- | [docs/getting-started/CLI_ONLY_WALKTHROUGH.md](docs/getting-started/CLI_ONLY_WALKTHROUGH.md) | Same setup + daily commands, entirely CLI — no Obsidian plugin required |
378
- | [docs/getting-started/WALKTHROUGH_PROMPT.md](docs/getting-started/WALKTHROUGH_PROMPT.md) | Paste-into-pi guided setup with checkpoints between phases |
370
+ | [Install playbook](docs/_reference/reference_archive/getting-started/INSTALL.md) | Canonical install procedure for the extension, skills, Obsidian plugin, configuration, and external CLIs |
371
+ | [Getting started](docs/_reference/reference_archive/getting-started/GETTING_STARTED.md) | End-to-end setup and daily “drop and forget” workflow |
372
+ | [CLI-only walkthrough](docs/_reference/reference_archive/getting-started/CLI_ONLY_WALKTHROUGH.md) | Setup and daily commands without the Obsidian plugin |
373
+ | [ReturnVape walkthrough](docs/getting-started/WALKTHROUGH.md) | Current guided Obsidian test-vault procedure |
379
374
 
380
375
  ### Architecture & design
381
376
 
@@ -384,8 +379,6 @@ Edit `pi-vault-mind.config.json` to match your domain:
384
379
  | [docs/architecture/AGENTS.md](docs/architecture/AGENTS.md) | Agent Roster and Multi-Agent Architecture ("Fork & Review" model) |
385
380
  | [docs/architecture/EXTENSION_WIRING.md](docs/architecture/EXTENSION_WIRING.md) | Extension dependencies, runtime wiring, auto-install patterns |
386
381
  | [docs/architecture/DISPATCHER_SPEC.md](docs/architecture/DISPATCHER_SPEC.md) | Technical spec for the passive file-watcher and subagent routing — incl. the "Fork & Dispatch" rationale and thread resume |
387
- | [docs/architecture/AGENTS.md](docs/architecture/AGENTS.md) | Agent roster + the Miner's "Drop & Forget" ingestion pipeline |
388
- | [docs/integrations/OBSIDIAN_SETUP.md](docs/integrations/OBSIDIAN_SETUP.md) | Recommended Obsidian vault structure, plugins, and CLI |
389
382
 
390
383
  ### Modal embedding service (local integration done)
391
384
 
@@ -401,8 +394,6 @@ full design.
401
394
  | Doc | Description |
402
395
  |---|---|
403
396
  | [docs/integrations/MODAL_EMBEDDING.md](docs/integrations/MODAL_EMBEDDING.md) | Design of record: ADRs, HTTP contract, sync protocol, roadmap for the cloud embedding service |
404
- | [docs/plans/modal-embedding-plan.md](docs/plans/modal-embedding-plan.md) | **Consolidated plan & next steps** — phases, owners, status, decision gate (in repo, not on the docs site) |
405
- | [docs/plans/embedding-open-questions.md](docs/plans/embedding-open-questions.md) | Embedding-strategy decision log (decided + open questions) |
406
397
  | [modal/](modal/) | The deployable Modal app: embedding service + bulk worker + sync + dataset generator |
407
398
  | [eval/](eval/) | Retrieval eval harness + labeled benchmark datasets for picking the canonical model |
408
399
 
@@ -412,17 +403,10 @@ full design.
412
403
  |---|---|
413
404
  | [skills/vault-mind/SKILL.md](skills/vault-mind/SKILL.md) | The Manager skill — what pi auto-loads about this extension |
414
405
  | [docs/CHANGELOG.md](docs/CHANGELOG.md) | Version history (rename from `pi-knowledge-store` to `pi-vault-mind` was v0.7.0) |
415
- | [docs/reference/tools.md](docs/reference/tools.md) | The 11 `vm_*` and `vm_query`/`vm_append`/etc. tools parameters, return shapes |
416
- | [docs/reference/commands.md](docs/reference/commands.md) | Full `/vm` slash command tree |
417
- | [docs/reference/configuration.md](docs/reference/configuration.md) | The complete `pi-vault-mind.config.json` schema |
418
- | [docs/reference/skill.md](docs/reference/skill.md) | Manifest of all bundled skills and their trigger phrases |
419
-
420
- ### Testing
421
-
422
- | Doc | Description |
423
- |---|---|
424
- | [docs/testing/TESTING.md](docs/testing/TESTING.md) | Test plan for agent / human / HITL personas, regression suite |
425
- | [docs/testing/E2E_MANUAL_TEST.md](docs/testing/E2E_MANUAL_TEST.md) | Manual end-to-end verification procedure (watcher → dispatch → vault) |
406
+ | [Tools reference](docs/_reference/reference_archive/tools.md) | Registered Vault Mind tools, parameters, and return shapes |
407
+ | [Commands reference](docs/_reference/reference_archive/commands.md) | Full `/vm` slash command tree |
408
+ | [Configuration reference](docs/_reference/reference_archive/configuration.md) | Complete `pi-vault-mind.config.json` schema |
409
+ | [Skill manifest](docs/_reference/reference_archive/skill.md) | Bundled skills and their trigger phrases |
426
410
 
427
411
  ### Development
428
412
 
@@ -430,22 +414,13 @@ full design.
430
414
  |---|---|
431
415
  | [docs/development/CONTRIBUTING.md](docs/development/CONTRIBUTING.md) | Dev setup, testing, and commit conventions |
432
416
  | [docs/development/PUBLISHING.md](docs/development/PUBLISHING.md) | How to publish this extension to npm |
433
- | [docs/development/FUTURE_WORK.md](docs/development/FUTURE_WORK.md) | Roadmap — codegraph integration, pagination, TUI rendering, etc. |
434
-
435
- ### Research
436
-
437
- | Doc | Description |
438
- |---|---|
439
- | [docs/research/COMPETITOR_COMPARISON.md](docs/research/COMPETITOR_COMPARISON.md) | Tier 1/2/3 comparison vs. other Obsidian-LLM tools (22 competitors) |
440
- | [docs/research/naming-decisions.md](docs/research/naming-decisions.md) | Historical record of the 2026-06-06 decision to name the project `pi-vault-mind` |
441
- | [docs/research/obsidian-links-reviewed.csv](docs/research/obsidian-links-reviewed.csv) | Curated subset of starred Obsidian repos with adoption verdicts |
442
417
 
443
418
  ### Archive
444
419
 
445
420
  | Doc | Description |
446
421
  |---|---|
447
422
  | [docs/_archive/](docs/_archive/) | Historical docs kept for context (e.g. the `pi-knowledge-store` → `pi-vault-mind` rename audit) |
448
- | [docs/_archive/legacy-audit.md](docs/_archive/legacy-audit.md) | The 2026-06-08 legacy-terminology audit (139 findings, 13 blockers) and its resolution log. Resolved 2026-06-09; archived 2026-06-16 (in the repo, not on the docs site) |
423
+ | [docs/_archive/legacy-audit.md](docs/_archive/legacy-audit.md) | The 2026-06-08 legacy-terminology audit (139 findings, 13 blockers) and its resolution log. Resolved 2026-06-09 and archived 2026-06-16. |
449
424
 
450
425
  ## Contributing
451
426
 
@@ -1760,7 +1760,8 @@ export const registerCommands = (pi) => {
1760
1760
  case "remote":
1761
1761
  return handleModal(rest, ctx, pi);
1762
1762
  case "personalize":
1763
- return runPersonalize(ctx, pi);
1763
+ await runPersonalize(ctx, pi);
1764
+ return;
1764
1765
  case "setup":
1765
1766
  return handleSetup(rest, ctx, pi);
1766
1767
  case "token":
@@ -44,4 +44,6 @@ export declare const presentAndApplyDiff: (suggestions: SuggestionResult, ctx: E
44
44
  rejected: string[];
45
45
  }>;
46
46
  /** Orchestrate /vm personalize: analyze, suggest, confirm, apply. */
47
- export declare const runPersonalize: (ctx: ExtensionContext, pi: ExtensionAPI) => Promise<void>;
47
+ export declare const runPersonalize: (ctx: ExtensionContext, pi: ExtensionAPI) => Promise<{
48
+ completed: boolean;
49
+ }>;
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
4
4
  import { getStatus } from "./lance.js";
5
- import { expandHome, loadConfig, resolveAgentDir } from "./utils.js";
5
+ import { expandHome, getPersonalizedMarkerPath, loadConfig, resolveAgentDir } from "./utils.js";
6
6
  /** Resolve the vault path: configured default vault wins, otherwise ctx.cwd. */
7
7
  export const resolveVaultPath = (ctx) => {
8
8
  const cfg = loadConfig(ctx.cwd);
@@ -359,8 +359,14 @@ export const runPersonalize = async (ctx, pi) => {
359
359
  try {
360
360
  const suggestions = await generateSuggestions(pi, profile, currentConfig);
361
361
  await presentAndApplyDiff(suggestions, ctx, currentConfig);
362
+ const markerPath = getPersonalizedMarkerPath(vaultPath);
363
+ await fs.promises.mkdir(path.dirname(markerPath), { recursive: true });
364
+ await fs.promises.writeFile(markerPath, JSON.stringify({ completed: true, completedAt: new Date().toISOString() }, null, 2));
365
+ return { completed: true };
362
366
  }
363
367
  catch (err) {
364
- ctx.ui.notify(`Personalization failed: ${err.message}`, "error");
368
+ const message = err instanceof Error ? err.message : String(err);
369
+ ctx.ui.notify(`Personalization failed: ${message}`, "error");
370
+ return { completed: false };
365
371
  }
366
372
  };
@@ -45,7 +45,7 @@ import { readModelProviders } from "./models.js";
45
45
  import { scaffoldVaultConfig } from "./scaffold.js";
46
46
  import { archiveSession, deleteSession, exportSession, getSession, listSessions, renameSession, resolveSessionsDir, searchSessions, } from "./session-search.js";
47
47
  import { listAllTools } from "./tool-catalog.js";
48
- import { ensureDir, expandHome, findConfig, getConfigPath, loadConfig, resolveVaultFile, resolveVaultMindPaths, shrinkHome, } from "./utils.js";
48
+ import { ensureDir, expandHome, findConfig, getConfigPath, isPersonalized, loadConfig, resolveVaultFile, resolveVaultMindPaths, shrinkHome, } from "./utils.js";
49
49
  import { classifySearchMode, normalizeGraphResult, searchVaultNative } from "./vault-tools.js";
50
50
  import { createManualDispatch, processQueue, scanFile, startWatcher, stopWatcher, } from "./watcher.js";
51
51
  export function createServerState(port = 11435) {
@@ -1100,12 +1100,14 @@ function handleVmStatus(res, serverState) {
1100
1100
  }
1101
1101
  const cfg = loadConfig(vaultPath);
1102
1102
  const configured = !!findConfig(vaultPath).project;
1103
+ const personalized = isPersonalized(vaultPath);
1103
1104
  const model = cfg.vaultMind.embedding[EMBEDDING_FLAT_KEYS[2]] || "embeddinggemma";
1104
1105
  const dim = cfg.vaultMind.embedding[EMBEDDING_FLAT_KEYS[4]] ?? 768;
1105
1106
  res.writeHead(200);
1106
1107
  res.end(JSON.stringify({
1107
1108
  ok: true,
1108
1109
  configured,
1110
+ personalized,
1109
1111
  vaultPath,
1110
1112
  watcher: ws?.running ?? false,
1111
1113
  embedding: {
@@ -1635,30 +1637,32 @@ async function handleVmSearch(req, res, serverState, overrideBody) {
1635
1637
  res.end(JSON.stringify({ error: `Unknown mode: ${mode}` }));
1636
1638
  return;
1637
1639
  }
1640
+ const sources = Array.isArray(body.sources) ? body.sources : ["vault"];
1641
+ const includeVault = sources.includes("vault");
1638
1642
  try {
1639
- let results;
1643
+ let results = [];
1640
1644
  let relations;
1641
1645
  let message;
1642
- if (resolvedMode === "hybrid") {
1646
+ if (includeVault && resolvedMode === "hybrid") {
1643
1647
  results = await searchHybridRanked(cfg.vaultMind.dataDir, collection, query, limit, cfg.vaultMind, identity);
1644
1648
  }
1645
- else if (resolvedMode === "semantic") {
1649
+ else if (includeVault && resolvedMode === "semantic") {
1646
1650
  results = await searchHybrid(cfg.vaultMind.dataDir, collection, query, limit, cfg.vaultMind, identity);
1647
1651
  }
1648
- else if (resolvedMode === "fts") {
1652
+ else if (includeVault && resolvedMode === "fts") {
1649
1653
  results = await searchFts(cfg.vaultMind.dataDir, collection, query, limit, cfg.vaultMind, identity);
1650
1654
  }
1651
- else if (resolvedMode === "graph") {
1655
+ else if (includeVault && resolvedMode === "graph") {
1652
1656
  const depth = body.depth ?? 1;
1653
1657
  const norm = normalizeGraphResult(await queryGraph(cfg.vaultMind.dataDir, cfg.vaultMind, entity, depth, identity));
1654
1658
  results = norm.hits;
1655
1659
  relations = norm.relations;
1656
1660
  message = norm.message;
1657
1661
  }
1658
- else if (resolvedMode === "vault") {
1662
+ else if (includeVault && resolvedMode === "vault") {
1659
1663
  results = await searchVaultNative(vaultPath, query, limit, body.tags);
1660
1664
  }
1661
- else {
1665
+ else if (includeVault) {
1662
1666
  // mode === "full"
1663
1667
  const depth = body.depth ?? 1;
1664
1668
  const [hybrid, semantic, fts, graph, vault] = await Promise.all([
@@ -1707,7 +1711,6 @@ async function handleVmSearch(req, res, serverState, overrideBody) {
1707
1711
  addResults(vault, "vault");
1708
1712
  results = merged.slice(0, limit);
1709
1713
  }
1710
- const sources = Array.isArray(body.sources) ? body.sources : ["vault"];
1711
1714
  if (sources.includes("sessions")) {
1712
1715
  results = [...results, ...searchSessions(resolveSessionsDir(), query, limit)];
1713
1716
  }
@@ -13,6 +13,9 @@ export declare const resolveAgentDir: (cwd: string) => string;
13
13
  export declare const resolveVaultMindDir: (cwd: string) => string;
14
14
  /** Active pi-vault-mind config: `<vault>/.vault-mind/vault-mind.config.json`. */
15
15
  export declare const getConfigPath: (cwd: string) => string;
16
+ export declare const getPersonalizedMarkerPath: (cwd: string) => string;
17
+ /** Check if a vault has a valid personalization completion marker. */
18
+ export declare function isPersonalized(cwd: string): boolean;
16
19
  /** Token env file: `<vault>/.vault-mind/vault-mind.env`. */
17
20
  export declare const getTokenEnvPath: (cwd: string) => string;
18
21
  export declare const EXT_ROOT: string;
package/dist/src/utils.js CHANGED
@@ -18,6 +18,19 @@ export const resolveAgentDir = (cwd) => {
18
18
  export const resolveVaultMindDir = (cwd) => path.join(cwd, VAULT_MIND_DIR);
19
19
  /** Active pi-vault-mind config: `<vault>/.vault-mind/vault-mind.config.json`. */
20
20
  export const getConfigPath = (cwd) => path.join(resolveVaultMindDir(cwd), "vault-mind.config.json");
21
+ export const getPersonalizedMarkerPath = (cwd) => path.join(resolveVaultMindDir(cwd), "personalized.json");
22
+ /** Check if a vault has a valid personalization completion marker. */
23
+ export function isPersonalized(cwd) {
24
+ const p = getPersonalizedMarkerPath(cwd);
25
+ try {
26
+ const content = fs.readFileSync(p, "utf8");
27
+ const marker = JSON.parse(content);
28
+ return marker && typeof marker === "object" && marker.completed === true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
21
34
  /** Token env file: `<vault>/.vault-mind/vault-mind.env`. */
22
35
  export const getTokenEnvPath = (cwd) => path.join(resolveVaultMindDir(cwd), "vault-mind.env");
23
36
  export const EXT_ROOT = path.join(import.meta.dirname, "..", "..");
@@ -192,6 +192,69 @@ describe("personalize", () => {
192
192
  // AGENTS.md was never created, so rejection means it still does not exist.
193
193
  assert.equal(fs.existsSync(path.join(testDir, "AGENTS.md")), false);
194
194
  });
195
+ it("persists a completion marker even when all suggestions are rejected", async () => {
196
+ const { runPersonalize } = await import("../src/personalize.js");
197
+ const pi = makePi(JSON.stringify({
198
+ commentary: "No changes needed.",
199
+ files: [
200
+ { path: ".pi/agent/system.md", action: "create", content: "# System" },
201
+ { path: "AGENTS.md", action: "create", content: "# Agents" },
202
+ ],
203
+ }));
204
+ const ctx = makeCtx(testDir, [false, false]);
205
+ await runPersonalize(ctx, pi);
206
+ const markerPath = path.join(testDir, ".vault-mind", "personalized.json");
207
+ assert.ok(fs.existsSync(markerPath), "runPersonalize must write a durable completion marker after the flow finishes, even when the user rejects every actionable suggestion");
208
+ const marker = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
209
+ assert.strictEqual(marker.completed, true);
210
+ assert.ok(typeof marker.completedAt === "string");
211
+ });
212
+ it("persists a completion marker after approved suggestions", async () => {
213
+ const { runPersonalize } = await import("../src/personalize.js");
214
+ const pi = makePi(JSON.stringify({
215
+ commentary: "Updated tuning.",
216
+ files: [{ path: ".pi/agent/system.md", action: "create", content: "# New system" }],
217
+ }));
218
+ const ctx = makeCtx(testDir, [true]);
219
+ await runPersonalize(ctx, pi);
220
+ const markerPath = path.join(testDir, ".vault-mind", "personalized.json");
221
+ assert.ok(fs.existsSync(markerPath), "runPersonalize must write a durable completion marker after approved suggestions are applied");
222
+ const marker = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
223
+ assert.strictEqual(marker.completed, true);
224
+ assert.ok(typeof marker.completedAt === "string");
225
+ });
226
+ it("returns a successful result when all suggestions are rejected", async () => {
227
+ const { runPersonalize: raw } = await import("../src/personalize.js");
228
+ const runPersonalize = raw;
229
+ const pi = makePi(JSON.stringify({
230
+ commentary: "No changes needed.",
231
+ files: [
232
+ { path: ".pi/agent/system.md", action: "create", content: "# System" },
233
+ { path: "AGENTS.md", action: "create", content: "# Agents" },
234
+ ],
235
+ }));
236
+ const ctx = makeCtx(testDir, [false, false]);
237
+ const result = await runPersonalize(ctx, pi);
238
+ assert.ok(result, "runPersonalize must return a result, not void");
239
+ assert.strictEqual(result.completed, true, "all-rejected personalization must resolve as a successful completion");
240
+ });
241
+ it("returns a failed result when suggestion generation fails", async () => {
242
+ const { runPersonalize: raw } = await import("../src/personalize.js");
243
+ const runPersonalize = raw;
244
+ const pi = makePi("not valid JSON");
245
+ const ctx = makeCtx(testDir, []);
246
+ const result = await runPersonalize(ctx, pi);
247
+ assert.ok(result, "runPersonalize must return a result, not void");
248
+ assert.strictEqual(result.completed, false, "a generation failure must resolve as a failed personalization, distinguishable from success");
249
+ });
250
+ it("does not persist a completion marker when suggestion generation fails", async () => {
251
+ const { runPersonalize } = await import("../src/personalize.js");
252
+ const pi = makePi("not valid JSON");
253
+ const ctx = makeCtx(testDir, []);
254
+ await runPersonalize(ctx, pi);
255
+ const markerPath = path.join(testDir, ".vault-mind", "personalized.json");
256
+ assert.strictEqual(fs.existsSync(markerPath), false, "runPersonalize must not leave a completion marker after a failed personalization");
257
+ });
195
258
  it("presentAndApplyDiff creates missing directories for new files", async () => {
196
259
  const { presentAndApplyDiff } = await import("../src/personalize.js");
197
260
  const ctx = makeCtx(testDir, [true]);
@@ -4,6 +4,7 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { after, before, describe, it } from "node:test";
6
6
  import { _resetAuthCache } from "../src/auth.js";
7
+ import { resetConnection } from "../src/lance.js";
7
8
  import { fakePi, fetchJson, makeVault, startTestServer, TEST_TOKEN, } from "./helpers/server.js";
8
9
  const envVar = "PVM_API_TOKEN";
9
10
  describe("REST vm routes", () => {
@@ -265,8 +266,8 @@ describe("REST vm routes", () => {
265
266
  assert.ok(Array.isArray(d.sources), "sources should be echoed");
266
267
  }
267
268
  });
268
- it("POST /vm/search with sources sessions merges session hits", async () => {
269
- // Seed a temp sessions dir
269
+ it("POST /vm/search with only sessions succeeds without an available vault index", async () => {
270
+ // Seed a temp sessions dir while the configured LanceDB index remains unavailable.
270
271
  const tmpSessions = fs.mkdtempSync(path.join(os.tmpdir(), "pvm-rest-sessions-"));
271
272
  const slugDir = path.join(tmpSessions, "--test--");
272
273
  fs.mkdirSync(slugDir, { recursive: true });
@@ -284,18 +285,24 @@ describe("REST vm routes", () => {
284
285
  const savedDir = process.env.PI_SESSIONS_DIR;
285
286
  try {
286
287
  process.env.PI_SESSIONS_DIR = tmpSessions;
288
+ resetConnection();
289
+ const vaultIndexPath = path.join(vaultPath, ".lancedb");
290
+ fs.rmSync(vaultIndexPath, { recursive: true, force: true });
291
+ assert.equal(fs.existsSync(vaultIndexPath), false, "the vault index fixture must be absent");
287
292
  const { status, body } = await fetchJson(port, "POST", "/vm/search", {
288
- body: { query: "searchtestquery", mode: "vault", sources: ["sessions"] },
293
+ body: { query: "searchtestquery", mode: "hybrid", sources: ["sessions"] },
289
294
  token: TEST_TOKEN,
290
295
  });
291
- assert.ok(status === 200 || status === 500, `expected 200 or 500, got ${status}`);
292
- if (status === 200) {
293
- const d = body;
294
- const hits = d.hits;
295
- assert.ok(hits.some((h) => h._source === "session"), "should contain at least one session hit");
296
- }
296
+ assert.equal(status, 200, "sessions-only search must not initialize or query the vault index");
297
+ assert.equal(fs.existsSync(vaultIndexPath), false, "sessions-only search must leave the unavailable vault index untouched");
298
+ const d = body;
299
+ assert.deepEqual(d.sources, ["sessions"], "the response must preserve the isolated source");
300
+ const hits = d.hits;
301
+ assert.ok(hits.length > 0, "sessions-only search should return the seeded session hit");
302
+ assert.ok(hits.every((hit) => hit._source === "session"), "sessions-only search must not merge vault hits");
297
303
  }
298
304
  finally {
305
+ resetConnection();
299
306
  if (savedDir === undefined)
300
307
  delete process.env.PI_SESSIONS_DIR;
301
308
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-vault-mind",
3
- "version": "0.16.5",
3
+ "version": "0.16.7",
4
4
  "description": "Passive Obsidian vault extension for pi. Watches @agent markers, dispatches forked subagents (Miner, Broadcaster, Heavy-Lifter), stores in LanceDB with vector + FTS + graph. Multi-agent 'Drop & Forget' workflow for the pi agent ecosystem.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -64,7 +64,7 @@
64
64
  "scripts": {
65
65
  "prepare": "husky",
66
66
  "check": "biome check --write . && tsc --noEmit",
67
- "generate-constants": "node scripts/generate-extension-packages-json.mjs && node scripts/generate-config-keys-json.mjs && biome check --write extension-packages.json config-keys.json packages/obsidian/src/extension-packages.json packages/obsidian/src/config-keys.json",
67
+ "generate-constants": "node scripts/generate-extension-packages-json.mjs && node scripts/generate-config-keys-json.mjs && biome check --write extension-packages.json config-keys.json packages/obsidian/src/extension-packages.json packages/obsidian/src/config-keys.json packages/obsidian-ui/src/extension-packages.json",
68
68
  "build": "npm run generate-constants && tsc && tsc -p tsconfig.test.json",
69
69
  "dev": "tsc --watch",
70
70
  "test": "node --test \"dist/test/**/*.test.js\"",