pi-vault-mind 0.16.27 → 0.16.28

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,6 +1,18 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.16.28 — 2026-08-09
5
+
6
+ ### Fixed
7
+
8
+ - **CLI setup no longer invents vault folder structure.** `/vm setup` (and `scaffold.ts`'s fallback scaffolding) used to default `vaultMind.folders` to a guessed `Agent/Inbox`/`Agent/Library`/`Agent/Presentations`/`Agent/Journal` convention. In a real vault with its own organization scheme (PARA-style numbered folders, an existing inbox elsewhere, etc.) this silently created a stray top-level `Agent/` folder and misfiled real auto-synced content into it. The interactive wizard now shows a real folder picker — built from the vault's actual existing directories (walked up to 2 levels deep) — with explicit "Type a custom path..." and "Skip — configure later" options, matching the Obsidian plugin's existing `FoldersStep` folder-browser behavior. Headless `/vm setup` (CLI flags) and `scaffoldVaultConfig()` no longer populate a guessed folders block at all; `folders` stays unset until deliberately configured.
9
+
10
+ ### Verification
11
+
12
+ - Full build + `node --test dist/test/**/*.test.js`: 894/894 passing.
13
+ - Added a regression test asserting a fresh headless `/vm setup` leaves `vaultMind.folders` unset rather than reinventing the old default.
14
+ - Reproduced the failure mode live: a real vault's auto-sync wrote 5 real notes into a newly-created `Agent/Inbox/` instead of the vault's actual `00-system/00.01-inbox/`.
15
+
4
16
  ## 0.16.27 — 2026-08-09
5
17
 
6
18
  ### Fixed
@@ -169,12 +169,6 @@ export const scaffoldVaultConfig = (vaultPath, collectionOverride) => {
169
169
  dataDir: ".vault-mind/.lancedb",
170
170
  ftsEnabled: true,
171
171
  graph: { enabled: true, canvasSync: false },
172
- folders: {
173
- inbox: "Agent/Inbox",
174
- library: "Agent/Library",
175
- presentations: "Agent/Presentations",
176
- journal: "Agent/Journal",
177
- },
178
172
  },
179
173
  };
180
174
  ensureDir(cfgDest);
@@ -291,27 +291,10 @@ export const setupWizard = async (ctx, cliArgs) => {
291
291
  if (cliArgs) {
292
292
  const config = existingConfigFile
293
293
  ? JSON.parse(fs.readFileSync(getConfigPath(ctx.cwd), "utf-8"))
294
- : {
295
- vaultMind: {
296
- embedding: {},
297
- vaults: {},
298
- folders: {
299
- inbox: "Agent/Inbox",
300
- library: "Agent/Library",
301
- presentations: "Agent/Presentations",
302
- journal: "Agent/Journal",
303
- },
304
- },
305
- };
294
+ : { vaultMind: { embedding: {}, vaults: {} } };
306
295
  config.vaultMind = config.vaultMind || {};
307
296
  config.vaultMind.embedding = config.vaultMind.embedding || {};
308
297
  config.vaultMind.vaults = config.vaultMind.vaults || {};
309
- config.vaultMind.folders = config.vaultMind.folders || {
310
- inbox: "Agent/Inbox",
311
- library: "Agent/Library",
312
- presentations: "Agent/Presentations",
313
- journal: "Agent/Journal",
314
- };
315
298
  const effectiveVaultPath = cliArgs.vault || detectedVaultPath || undefined;
316
299
  if (effectiveVaultPath) {
317
300
  config.vaultMind.vaults.default = { path: shrinkHome(effectiveVaultPath), autoSync: true };
@@ -532,13 +515,61 @@ export const setupWizard = async (ctx, cliArgs) => {
532
515
  dim = 384;
533
516
  }
534
517
  // ── Step 2.5: Vault folders ──────────────────────────────────────────────
518
+ // Lists real existing folders rather than guessing a convention (e.g.
519
+ // "Agent/Inbox") — a blind default silently creates the wrong structure
520
+ // in vaults that already have their own organization scheme.
521
+ const FOLDER_IGNORE = new Set([
522
+ ".git",
523
+ ".obsidian",
524
+ ".vault-mind",
525
+ ".pi",
526
+ "node_modules",
527
+ ".trash",
528
+ ".DS_Store",
529
+ ]);
530
+ const listVaultFolders = (root) => {
531
+ const results = [];
532
+ const walk = (dir, rel, depth) => {
533
+ if (depth > 2)
534
+ return;
535
+ let entries;
536
+ try {
537
+ entries = fs.readdirSync(dir, { withFileTypes: true });
538
+ }
539
+ catch {
540
+ return;
541
+ }
542
+ for (const entry of entries) {
543
+ if (!entry.isDirectory() || entry.name.startsWith(".") || FOLDER_IGNORE.has(entry.name))
544
+ continue;
545
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
546
+ results.push(relPath);
547
+ walk(path.join(dir, entry.name), relPath, depth + 1);
548
+ }
549
+ };
550
+ walk(root, "", 0);
551
+ return results.sort();
552
+ };
553
+ const SKIP_FOLDER = "(skip — configure later)";
554
+ const CUSTOM_FOLDER = "→ Type a custom path...";
555
+ const pickFolder = async (label, current) => {
556
+ const options = [SKIP_FOLDER, CUSTOM_FOLDER, ...vaultFolders];
557
+ const prompt = current ? `${label} (current: ${current})` : label;
558
+ const choice = await ctx.ui.select(prompt, options);
559
+ if (!choice || choice === SKIP_FOLDER)
560
+ return "";
561
+ if (choice === CUSTOM_FOLDER)
562
+ return (await ctx.ui.input(`${label} — path:`, current || "")) || "";
563
+ return choice;
564
+ };
535
565
  const existingFolders = existingConfigFile
536
566
  ? (JSON.parse(fs.readFileSync(getConfigPath(ctx.cwd), "utf-8")).vaultMind?.folders ?? {})
537
567
  : {};
538
- const inboxFolder = await ctx.ui.input("Inbox folder (agent capture target):", existingFolders.inbox || "Agent/Inbox");
539
- const libraryFolder = await ctx.ui.input("Library folder (durable knowledge notes):", existingFolders.library || "Agent/Library");
540
- const presentationsFolder = await ctx.ui.input("Presentations folder:", existingFolders.presentations || "Agent/Presentations");
541
- const journalFolder = await ctx.ui.input("Journal folder:", existingFolders.journal || "Agent/Journal");
568
+ const vaultFolders = fs.existsSync(vaultPath) ? listVaultFolders(vaultPath) : [];
569
+ const inboxFolder = await pickFolder("Inbox folder (agent capture target)", existingFolders.inbox);
570
+ const libraryFolder = await pickFolder("Library folder (durable knowledge notes)", existingFolders.library);
571
+ const presentationsFolder = await pickFolder("Presentations folder", existingFolders.presentations);
572
+ const journalFolder = await pickFolder("Journal folder", existingFolders.journal);
542
573
  // ── Step 3: Deterministic runtime settings ──────────────────────────────
543
574
  const enableContextAutomation = await ctx.ui.confirm("Context automation", "Enable pi-context integration with auto-/acm trigger and event indexing?");
544
575
  const enableAutoStart = await ctx.ui.confirm("Vault auto-start", "Auto-start watcher/server for this vault when pi session starts?");
@@ -547,7 +578,7 @@ export const setupWizard = async (ctx, cliArgs) => {
547
578
  `Remote URL: ${remoteUrl || "none"}`,
548
579
  `Local URL: ${localUrl || "none"}`,
549
580
  `Model: ${model || "(auto/server default)"}`,
550
- `Folders: inbox=${inboxFolder}, library=${libraryFolder}, presentations=${presentationsFolder}, journal=${journalFolder}`,
581
+ `Folders: inbox=${inboxFolder || "(unset)"}, library=${libraryFolder || "(unset)"}, presentations=${presentationsFolder || "(unset)"}, journal=${journalFolder || "(unset)"}`,
551
582
  `Context automation: ${enableContextAutomation ? "enabled" : "disabled"}`,
552
583
  `Vault auto-start: ${enableAutoStart ? "enabled" : "disabled"}`,
553
584
  ].join("\n"));
@@ -578,12 +609,22 @@ export const setupWizard = async (ctx, cliArgs) => {
578
609
  autoSync: true,
579
610
  autoStart: enableAutoStart,
580
611
  };
581
- config.vaultMind.folders = {
582
- inbox: inboxFolder || "Agent/Inbox",
583
- library: libraryFolder || "Agent/Library",
584
- presentations: presentationsFolder || "Agent/Presentations",
585
- journal: journalFolder || "Agent/Journal",
586
- };
612
+ const chosenFolders = {};
613
+ if (inboxFolder)
614
+ chosenFolders.inbox = inboxFolder;
615
+ if (libraryFolder)
616
+ chosenFolders.library = libraryFolder;
617
+ if (presentationsFolder)
618
+ chosenFolders.presentations = presentationsFolder;
619
+ if (journalFolder)
620
+ chosenFolders.journal = journalFolder;
621
+ if (Object.keys(chosenFolders).length > 0) {
622
+ config.vaultMind.folders = chosenFolders;
623
+ }
624
+ else if (Object.keys(existingFolders).length > 0) {
625
+ // All skipped during a reconfigure — preserve what was already set.
626
+ config.vaultMind.folders = existingFolders;
627
+ }
587
628
  config.vaultMind.graph = config.vaultMind.graph || { enabled: true, canvasSync: true };
588
629
  config.vaultMind.ftsEnabled = config.vaultMind.ftsEnabled !== false;
589
630
  // Extension-compatibility (pi-context) now lives in the single consolidated
@@ -182,6 +182,11 @@ describe("/vm setup collection scaffolding (regression)", () => {
182
182
  assert.equal(cfg.vaultMind?.embedding?.remoteUrl, "https://example.test");
183
183
  assert.equal(cfg.vaultMind?.embedding?.model, "embeddinggemma");
184
184
  assert.ok(cfg.vaultMind?.vaults?.default?.path, "vaults.default.path should be written");
185
+ // Regression: headless setup used to invent an "Agent/Inbox" etc.
186
+ // folders block on a fresh vault. Guessing a folder convention that
187
+ // doesn't match the vault's actual structure silently misfiles
188
+ // content — folders must stay unset until deliberately configured.
189
+ assert.equal(cfg.vaultMind?.folders, undefined, "fresh headless setup should not invent a folders block");
185
190
  fs.rmSync(vault, { recursive: true, force: true });
186
191
  });
187
192
  it("preserves an explicit empty injector array during subsequent setup runs", async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-vault-mind",
3
- "version": "0.16.27",
3
+ "version": "0.16.28",
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",