@velvetmonkey/flywheel-memory 2.0.1 → 2.0.3

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 (2) hide show
  1. package/dist/index.js +488 -320
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var init_constants = __esm({
24
24
 
25
25
  // src/core/write/writer.ts
26
26
  import fs14 from "fs/promises";
27
- import path16 from "path";
27
+ import path15 from "path";
28
28
  import matter5 from "gray-matter";
29
29
  function isSensitivePath(filePath) {
30
30
  const normalizedPath = filePath.replace(/\\/g, "/");
@@ -345,8 +345,8 @@ function validatePath(vaultPath2, notePath) {
345
345
  if (notePath.startsWith("\\")) {
346
346
  return false;
347
347
  }
348
- const resolvedVault = path16.resolve(vaultPath2);
349
- const resolvedNote = path16.resolve(vaultPath2, notePath);
348
+ const resolvedVault = path15.resolve(vaultPath2);
349
+ const resolvedNote = path15.resolve(vaultPath2, notePath);
350
350
  return resolvedNote.startsWith(resolvedVault);
351
351
  }
352
352
  async function validatePathSecure(vaultPath2, notePath) {
@@ -374,8 +374,8 @@ async function validatePathSecure(vaultPath2, notePath) {
374
374
  reason: "Path traversal not allowed"
375
375
  };
376
376
  }
377
- const resolvedVault = path16.resolve(vaultPath2);
378
- const resolvedNote = path16.resolve(vaultPath2, notePath);
377
+ const resolvedVault = path15.resolve(vaultPath2);
378
+ const resolvedNote = path15.resolve(vaultPath2, notePath);
379
379
  if (!resolvedNote.startsWith(resolvedVault)) {
380
380
  return {
381
381
  valid: false,
@@ -389,7 +389,7 @@ async function validatePathSecure(vaultPath2, notePath) {
389
389
  };
390
390
  }
391
391
  try {
392
- const fullPath = path16.join(vaultPath2, notePath);
392
+ const fullPath = path15.join(vaultPath2, notePath);
393
393
  try {
394
394
  await fs14.access(fullPath);
395
395
  const realPath = await fs14.realpath(fullPath);
@@ -400,7 +400,7 @@ async function validatePathSecure(vaultPath2, notePath) {
400
400
  reason: "Symlink target is outside vault"
401
401
  };
402
402
  }
403
- const relativePath = path16.relative(realVaultPath, realPath);
403
+ const relativePath = path15.relative(realVaultPath, realPath);
404
404
  if (isSensitivePath(relativePath)) {
405
405
  return {
406
406
  valid: false,
@@ -408,7 +408,7 @@ async function validatePathSecure(vaultPath2, notePath) {
408
408
  };
409
409
  }
410
410
  } catch {
411
- const parentDir = path16.dirname(fullPath);
411
+ const parentDir = path15.dirname(fullPath);
412
412
  try {
413
413
  await fs14.access(parentDir);
414
414
  const realParentPath = await fs14.realpath(parentDir);
@@ -434,7 +434,7 @@ async function readVaultFile(vaultPath2, notePath) {
434
434
  if (!validatePath(vaultPath2, notePath)) {
435
435
  throw new Error("Invalid path: path traversal not allowed");
436
436
  }
437
- const fullPath = path16.join(vaultPath2, notePath);
437
+ const fullPath = path15.join(vaultPath2, notePath);
438
438
  const rawContent = await fs14.readFile(fullPath, "utf-8");
439
439
  const lineEnding = detectLineEnding(rawContent);
440
440
  const normalizedContent = normalizeLineEndings(rawContent);
@@ -483,7 +483,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
483
483
  if (!validation.valid) {
484
484
  throw new Error(`Invalid path: ${validation.reason}`);
485
485
  }
486
- const fullPath = path16.join(vaultPath2, notePath);
486
+ const fullPath = path15.join(vaultPath2, notePath);
487
487
  let output = matter5.stringify(content, frontmatter);
488
488
  output = normalizeTrailingNewline(output);
489
489
  output = convertLineEndings(output, lineEnding);
@@ -710,8 +710,8 @@ function createContext(variables = {}) {
710
710
  }
711
711
  };
712
712
  }
713
- function resolvePath(obj, path25) {
714
- const parts = path25.split(".");
713
+ function resolvePath(obj, path24) {
714
+ const parts = path24.split(".");
715
715
  let current = obj;
716
716
  for (const part of parts) {
717
717
  if (current === void 0 || current === null) {
@@ -1150,7 +1150,7 @@ __export(conditions_exports, {
1150
1150
  shouldStepExecute: () => shouldStepExecute
1151
1151
  });
1152
1152
  import fs20 from "fs/promises";
1153
- import path22 from "path";
1153
+ import path21 from "path";
1154
1154
  async function evaluateCondition(condition, vaultPath2, context) {
1155
1155
  const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
1156
1156
  const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
@@ -1203,7 +1203,7 @@ async function evaluateCondition(condition, vaultPath2, context) {
1203
1203
  }
1204
1204
  }
1205
1205
  async function evaluateFileExists(vaultPath2, notePath, expectExists) {
1206
- const fullPath = path22.join(vaultPath2, notePath);
1206
+ const fullPath = path21.join(vaultPath2, notePath);
1207
1207
  try {
1208
1208
  await fs20.access(fullPath);
1209
1209
  return {
@@ -1218,7 +1218,7 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
1218
1218
  }
1219
1219
  }
1220
1220
  async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
1221
- const fullPath = path22.join(vaultPath2, notePath);
1221
+ const fullPath = path21.join(vaultPath2, notePath);
1222
1222
  try {
1223
1223
  await fs20.access(fullPath);
1224
1224
  } catch {
@@ -1249,7 +1249,7 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
1249
1249
  }
1250
1250
  }
1251
1251
  async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
1252
- const fullPath = path22.join(vaultPath2, notePath);
1252
+ const fullPath = path21.join(vaultPath2, notePath);
1253
1253
  try {
1254
1254
  await fs20.access(fullPath);
1255
1255
  } catch {
@@ -1280,7 +1280,7 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
1280
1280
  }
1281
1281
  }
1282
1282
  async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
1283
- const fullPath = path22.join(vaultPath2, notePath);
1283
+ const fullPath = path21.join(vaultPath2, notePath);
1284
1284
  try {
1285
1285
  await fs20.access(fullPath);
1286
1286
  } catch {
@@ -1689,8 +1689,8 @@ function updateIndexProgress(parsed, total) {
1689
1689
  function normalizeTarget(target) {
1690
1690
  return target.toLowerCase().replace(/\.md$/, "");
1691
1691
  }
1692
- function normalizeNotePath(path25) {
1693
- return path25.toLowerCase().replace(/\.md$/, "");
1692
+ function normalizeNotePath(path24) {
1693
+ return path24.toLowerCase().replace(/\.md$/, "");
1694
1694
  }
1695
1695
  async function buildVaultIndex(vaultPath2, options = {}) {
1696
1696
  const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
@@ -1884,7 +1884,7 @@ function findSimilarEntity(index, target) {
1884
1884
  }
1885
1885
  const maxDist = normalizedLen <= 10 ? 1 : 2;
1886
1886
  let bestMatch;
1887
- for (const [entity, path25] of index.entities) {
1887
+ for (const [entity, path24] of index.entities) {
1888
1888
  const lenDiff = Math.abs(entity.length - normalizedLen);
1889
1889
  if (lenDiff > maxDist) {
1890
1890
  continue;
@@ -1892,7 +1892,7 @@ function findSimilarEntity(index, target) {
1892
1892
  const dist = levenshteinDistance(normalized, entity);
1893
1893
  if (dist > 0 && dist <= maxDist) {
1894
1894
  if (!bestMatch || dist < bestMatch.distance) {
1895
- bestMatch = { path: path25, entity, distance: dist };
1895
+ bestMatch = { path: path24, entity, distance: dist };
1896
1896
  if (dist === 1) {
1897
1897
  return bestMatch;
1898
1898
  }
@@ -2322,30 +2322,30 @@ var EventQueue = class {
2322
2322
  * Add a new event to the queue
2323
2323
  */
2324
2324
  push(type, rawPath) {
2325
- const path25 = normalizePath(rawPath);
2325
+ const path24 = normalizePath(rawPath);
2326
2326
  const now = Date.now();
2327
2327
  const event = {
2328
2328
  type,
2329
- path: path25,
2329
+ path: path24,
2330
2330
  timestamp: now
2331
2331
  };
2332
- let pending = this.pending.get(path25);
2332
+ let pending = this.pending.get(path24);
2333
2333
  if (!pending) {
2334
2334
  pending = {
2335
2335
  events: [],
2336
2336
  timer: null,
2337
2337
  lastEvent: now
2338
2338
  };
2339
- this.pending.set(path25, pending);
2339
+ this.pending.set(path24, pending);
2340
2340
  }
2341
2341
  pending.events.push(event);
2342
2342
  pending.lastEvent = now;
2343
- console.error(`[flywheel] QUEUE: pushed ${type} for ${path25}, pending=${this.pending.size}`);
2343
+ console.error(`[flywheel] QUEUE: pushed ${type} for ${path24}, pending=${this.pending.size}`);
2344
2344
  if (pending.timer) {
2345
2345
  clearTimeout(pending.timer);
2346
2346
  }
2347
2347
  pending.timer = setTimeout(() => {
2348
- this.flushPath(path25);
2348
+ this.flushPath(path24);
2349
2349
  }, this.config.debounceMs);
2350
2350
  if (this.pending.size >= this.config.batchSize) {
2351
2351
  this.flush();
@@ -2366,10 +2366,10 @@ var EventQueue = class {
2366
2366
  /**
2367
2367
  * Flush a single path's events
2368
2368
  */
2369
- flushPath(path25) {
2370
- const pending = this.pending.get(path25);
2369
+ flushPath(path24) {
2370
+ const pending = this.pending.get(path24);
2371
2371
  if (!pending || pending.events.length === 0) return;
2372
- console.error(`[flywheel] QUEUE: flushing ${path25}, events=${pending.events.length}`);
2372
+ console.error(`[flywheel] QUEUE: flushing ${path24}, events=${pending.events.length}`);
2373
2373
  if (pending.timer) {
2374
2374
  clearTimeout(pending.timer);
2375
2375
  pending.timer = null;
@@ -2378,7 +2378,7 @@ var EventQueue = class {
2378
2378
  if (coalescedType) {
2379
2379
  const coalesced = {
2380
2380
  type: coalescedType,
2381
- path: path25,
2381
+ path: path24,
2382
2382
  originalEvents: [...pending.events]
2383
2383
  };
2384
2384
  this.onBatch({
@@ -2386,7 +2386,7 @@ var EventQueue = class {
2386
2386
  timestamp: Date.now()
2387
2387
  });
2388
2388
  }
2389
- this.pending.delete(path25);
2389
+ this.pending.delete(path24);
2390
2390
  }
2391
2391
  /**
2392
2392
  * Flush all pending events
@@ -2398,7 +2398,7 @@ var EventQueue = class {
2398
2398
  }
2399
2399
  if (this.pending.size === 0) return;
2400
2400
  const events = [];
2401
- for (const [path25, pending] of this.pending) {
2401
+ for (const [path24, pending] of this.pending) {
2402
2402
  if (pending.timer) {
2403
2403
  clearTimeout(pending.timer);
2404
2404
  }
@@ -2406,7 +2406,7 @@ var EventQueue = class {
2406
2406
  if (coalescedType) {
2407
2407
  events.push({
2408
2408
  type: coalescedType,
2409
- path: path25,
2409
+ path: path24,
2410
2410
  originalEvents: [...pending.events]
2411
2411
  });
2412
2412
  }
@@ -2555,31 +2555,31 @@ function createVaultWatcher(options) {
2555
2555
  usePolling: config.usePolling,
2556
2556
  interval: config.usePolling ? config.pollInterval : void 0
2557
2557
  });
2558
- watcher.on("add", (path25) => {
2559
- console.error(`[flywheel] RAW EVENT: add ${path25}`);
2560
- if (shouldWatch(path25, vaultPath2)) {
2561
- console.error(`[flywheel] ACCEPTED: add ${path25}`);
2562
- eventQueue.push("add", path25);
2558
+ watcher.on("add", (path24) => {
2559
+ console.error(`[flywheel] RAW EVENT: add ${path24}`);
2560
+ if (shouldWatch(path24, vaultPath2)) {
2561
+ console.error(`[flywheel] ACCEPTED: add ${path24}`);
2562
+ eventQueue.push("add", path24);
2563
2563
  } else {
2564
- console.error(`[flywheel] FILTERED: add ${path25}`);
2564
+ console.error(`[flywheel] FILTERED: add ${path24}`);
2565
2565
  }
2566
2566
  });
2567
- watcher.on("change", (path25) => {
2568
- console.error(`[flywheel] RAW EVENT: change ${path25}`);
2569
- if (shouldWatch(path25, vaultPath2)) {
2570
- console.error(`[flywheel] ACCEPTED: change ${path25}`);
2571
- eventQueue.push("change", path25);
2567
+ watcher.on("change", (path24) => {
2568
+ console.error(`[flywheel] RAW EVENT: change ${path24}`);
2569
+ if (shouldWatch(path24, vaultPath2)) {
2570
+ console.error(`[flywheel] ACCEPTED: change ${path24}`);
2571
+ eventQueue.push("change", path24);
2572
2572
  } else {
2573
- console.error(`[flywheel] FILTERED: change ${path25}`);
2573
+ console.error(`[flywheel] FILTERED: change ${path24}`);
2574
2574
  }
2575
2575
  });
2576
- watcher.on("unlink", (path25) => {
2577
- console.error(`[flywheel] RAW EVENT: unlink ${path25}`);
2578
- if (shouldWatch(path25, vaultPath2)) {
2579
- console.error(`[flywheel] ACCEPTED: unlink ${path25}`);
2580
- eventQueue.push("unlink", path25);
2576
+ watcher.on("unlink", (path24) => {
2577
+ console.error(`[flywheel] RAW EVENT: unlink ${path24}`);
2578
+ if (shouldWatch(path24, vaultPath2)) {
2579
+ console.error(`[flywheel] ACCEPTED: unlink ${path24}`);
2580
+ eventQueue.push("unlink", path24);
2581
2581
  } else {
2582
- console.error(`[flywheel] FILTERED: unlink ${path25}`);
2582
+ console.error(`[flywheel] FILTERED: unlink ${path24}`);
2583
2583
  }
2584
2584
  });
2585
2585
  watcher.on("ready", () => {
@@ -2689,7 +2689,10 @@ import {
2689
2689
  applyWikilinks,
2690
2690
  resolveAliasWikilinks,
2691
2691
  getEntityIndexFromDb,
2692
- getStateDbMetadata
2692
+ getStateDbMetadata,
2693
+ getEntityByName,
2694
+ getEntitiesByAlias,
2695
+ searchEntities as searchEntitiesDb
2693
2696
  } from "@velvetmonkey/vault-core";
2694
2697
 
2695
2698
  // src/core/write/git.ts
@@ -4532,6 +4535,7 @@ function suggestRelatedLinks(content, options = {}) {
4532
4535
  if (SUGGESTION_PATTERN.test(content)) {
4533
4536
  return emptyResult;
4534
4537
  }
4538
+ checkAndRefreshIfStale();
4535
4539
  if (!isEntityIndexReady() || !entityIndex) {
4536
4540
  return emptyResult;
4537
4541
  }
@@ -4650,6 +4654,126 @@ function suggestRelatedLinks(content, options = {}) {
4650
4654
  suffix
4651
4655
  };
4652
4656
  }
4657
+ function detectAliasCollisions(noteName, aliases = []) {
4658
+ if (!moduleStateDb4) return [];
4659
+ const collisions = [];
4660
+ const nameAsAlias = getEntitiesByAlias(moduleStateDb4, noteName);
4661
+ for (const entity of nameAsAlias) {
4662
+ if (entity.name.toLowerCase() === noteName.toLowerCase()) continue;
4663
+ collisions.push({
4664
+ term: noteName,
4665
+ source: "name",
4666
+ collidedWith: {
4667
+ name: entity.name,
4668
+ path: entity.path,
4669
+ matchType: "alias"
4670
+ }
4671
+ });
4672
+ }
4673
+ for (const alias of aliases) {
4674
+ const existingByName = getEntityByName(moduleStateDb4, alias);
4675
+ if (existingByName && existingByName.name.toLowerCase() !== noteName.toLowerCase()) {
4676
+ collisions.push({
4677
+ term: alias,
4678
+ source: "alias",
4679
+ collidedWith: {
4680
+ name: existingByName.name,
4681
+ path: existingByName.path,
4682
+ matchType: "name"
4683
+ }
4684
+ });
4685
+ }
4686
+ const existingByAlias = getEntitiesByAlias(moduleStateDb4, alias);
4687
+ for (const entity of existingByAlias) {
4688
+ if (entity.name.toLowerCase() === noteName.toLowerCase()) continue;
4689
+ if (existingByName && existingByName.name.toLowerCase() === entity.name.toLowerCase()) continue;
4690
+ collisions.push({
4691
+ term: alias,
4692
+ source: "alias",
4693
+ collidedWith: {
4694
+ name: entity.name,
4695
+ path: entity.path,
4696
+ matchType: "alias"
4697
+ }
4698
+ });
4699
+ }
4700
+ }
4701
+ return collisions;
4702
+ }
4703
+ function suggestAliases(noteName, existingAliases = [], category) {
4704
+ const suggestions = [];
4705
+ const existingLower = new Set(existingAliases.map((a) => a.toLowerCase()));
4706
+ const words = noteName.split(/\s+/).filter((w) => w.length > 0);
4707
+ function isSafe(alias) {
4708
+ if (existingLower.has(alias.toLowerCase())) return false;
4709
+ if (alias.toLowerCase() === noteName.toLowerCase()) return false;
4710
+ if (!moduleStateDb4) return true;
4711
+ const existing = getEntityByName(moduleStateDb4, alias);
4712
+ return !existing;
4713
+ }
4714
+ const inferredCategory = category || inferCategoryFromName(noteName);
4715
+ if (inferredCategory === "people" && words.length >= 2) {
4716
+ const firstName = words[0];
4717
+ const lastName = words[words.length - 1];
4718
+ if (firstName.length >= 2 && isSafe(firstName)) {
4719
+ suggestions.push({ alias: firstName, reason: "First name for quick reference" });
4720
+ }
4721
+ if (lastName.length >= 2 && lastName !== firstName && isSafe(lastName)) {
4722
+ suggestions.push({ alias: lastName, reason: "Last name for quick reference" });
4723
+ }
4724
+ }
4725
+ if (words.length >= 3) {
4726
+ const acronym = words.map((w) => w[0]).join("").toUpperCase();
4727
+ if (acronym.length >= 3 && isSafe(acronym)) {
4728
+ suggestions.push({ alias: acronym, reason: `Acronym for "${noteName}"` });
4729
+ }
4730
+ }
4731
+ if (noteName.includes("-")) {
4732
+ const unhyphenated = noteName.replace(/-/g, "");
4733
+ if (unhyphenated !== noteName && isSafe(unhyphenated)) {
4734
+ suggestions.push({ alias: unhyphenated, reason: "Unhyphenated form" });
4735
+ }
4736
+ const spaced = noteName.replace(/-/g, " ");
4737
+ if (spaced !== noteName && isSafe(spaced)) {
4738
+ suggestions.push({ alias: spaced, reason: "Space-separated form" });
4739
+ }
4740
+ }
4741
+ return suggestions;
4742
+ }
4743
+ function inferCategoryFromName(name) {
4744
+ const words = name.split(/\s+/);
4745
+ if (words.length === 2 || words.length === 3) {
4746
+ const allCapitalized = words.every((w) => /^[A-Z][a-z]/.test(w));
4747
+ if (allCapitalized) return "people";
4748
+ }
4749
+ return void 0;
4750
+ }
4751
+ function checkPreflightSimilarity(noteName) {
4752
+ const result = { similarEntities: [] };
4753
+ if (!moduleStateDb4) return result;
4754
+ const exact = getEntityByName(moduleStateDb4, noteName);
4755
+ if (exact) {
4756
+ result.existingEntity = {
4757
+ name: exact.name,
4758
+ path: exact.path,
4759
+ category: exact.category
4760
+ };
4761
+ }
4762
+ try {
4763
+ const searchResults = searchEntitiesDb(moduleStateDb4, noteName, 5);
4764
+ for (const sr of searchResults) {
4765
+ if (sr.name.toLowerCase() === noteName.toLowerCase()) continue;
4766
+ result.similarEntities.push({
4767
+ name: sr.name,
4768
+ path: sr.path,
4769
+ category: sr.category,
4770
+ rank: sr.rank
4771
+ });
4772
+ }
4773
+ } catch {
4774
+ }
4775
+ return result;
4776
+ }
4653
4777
 
4654
4778
  // src/core/write/logging.ts
4655
4779
  import {
@@ -4672,11 +4796,153 @@ async function flushLogs() {
4672
4796
  if (logger2) await logger2.flush();
4673
4797
  }
4674
4798
 
4799
+ // src/core/read/fts5.ts
4800
+ import * as fs5 from "fs";
4801
+ var EXCLUDED_DIRS2 = /* @__PURE__ */ new Set([
4802
+ ".obsidian",
4803
+ ".trash",
4804
+ ".git",
4805
+ "node_modules",
4806
+ "templates",
4807
+ ".claude",
4808
+ ".flywheel"
4809
+ ]);
4810
+ var MAX_INDEX_FILE_SIZE = 5 * 1024 * 1024;
4811
+ var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
4812
+ var db = null;
4813
+ var state = {
4814
+ ready: false,
4815
+ lastBuilt: null,
4816
+ noteCount: 0,
4817
+ error: null
4818
+ };
4819
+ function setFTS5Database(database) {
4820
+ db = database;
4821
+ try {
4822
+ const row = db.prepare(
4823
+ "SELECT value FROM fts_metadata WHERE key = ?"
4824
+ ).get("last_built");
4825
+ if (row) {
4826
+ const lastBuilt = new Date(row.value);
4827
+ const countRow = db.prepare("SELECT COUNT(*) as count FROM notes_fts").get();
4828
+ state = {
4829
+ ready: countRow.count > 0,
4830
+ lastBuilt,
4831
+ noteCount: countRow.count,
4832
+ error: null
4833
+ };
4834
+ }
4835
+ } catch {
4836
+ }
4837
+ }
4838
+ function shouldIndexFile(filePath) {
4839
+ const parts = filePath.split("/");
4840
+ return !parts.some((part) => EXCLUDED_DIRS2.has(part));
4841
+ }
4842
+ async function buildFTS5Index(vaultPath2) {
4843
+ try {
4844
+ state.error = null;
4845
+ if (!db) {
4846
+ throw new Error("FTS5 database not initialized. Call setFTS5Database() first.");
4847
+ }
4848
+ db.exec("DELETE FROM notes_fts");
4849
+ const files = await scanVault(vaultPath2);
4850
+ const indexableFiles = files.filter((f) => shouldIndexFile(f.path));
4851
+ const insert = db.prepare(
4852
+ "INSERT INTO notes_fts (path, title, content) VALUES (?, ?, ?)"
4853
+ );
4854
+ const insertMany = db.transaction((filesToIndex) => {
4855
+ let indexed2 = 0;
4856
+ for (const file of filesToIndex) {
4857
+ try {
4858
+ const stats = fs5.statSync(file.absolutePath);
4859
+ if (stats.size > MAX_INDEX_FILE_SIZE) {
4860
+ continue;
4861
+ }
4862
+ const content = fs5.readFileSync(file.absolutePath, "utf-8");
4863
+ const title = file.path.replace(/\.md$/, "").split("/").pop() || file.path;
4864
+ insert.run(file.path, title, content);
4865
+ indexed2++;
4866
+ } catch (err) {
4867
+ console.error(`[FTS5] Skipping ${file.path}:`, err);
4868
+ }
4869
+ }
4870
+ return indexed2;
4871
+ });
4872
+ const indexed = insertMany(indexableFiles);
4873
+ const now = /* @__PURE__ */ new Date();
4874
+ db.prepare(
4875
+ "INSERT OR REPLACE INTO fts_metadata (key, value) VALUES (?, ?)"
4876
+ ).run("last_built", now.toISOString());
4877
+ state = {
4878
+ ready: true,
4879
+ lastBuilt: now,
4880
+ noteCount: indexed,
4881
+ error: null
4882
+ };
4883
+ console.error(`[FTS5] Indexed ${indexed} notes`);
4884
+ return state;
4885
+ } catch (err) {
4886
+ state = {
4887
+ ready: false,
4888
+ lastBuilt: null,
4889
+ noteCount: 0,
4890
+ error: err instanceof Error ? err.message : String(err)
4891
+ };
4892
+ throw err;
4893
+ }
4894
+ }
4895
+ function isIndexStale(_vaultPath) {
4896
+ if (!db) {
4897
+ return true;
4898
+ }
4899
+ try {
4900
+ const row = db.prepare(
4901
+ "SELECT value FROM fts_metadata WHERE key = ?"
4902
+ ).get("last_built");
4903
+ if (!row) {
4904
+ return true;
4905
+ }
4906
+ const lastBuilt = new Date(row.value);
4907
+ const age = Date.now() - lastBuilt.getTime();
4908
+ return age > STALE_THRESHOLD_MS;
4909
+ } catch {
4910
+ return true;
4911
+ }
4912
+ }
4913
+ function searchFTS5(_vaultPath, query, limit = 10) {
4914
+ if (!db) {
4915
+ throw new Error("FTS5 database not initialized. Call setFTS5Database() first.");
4916
+ }
4917
+ try {
4918
+ const stmt = db.prepare(`
4919
+ SELECT
4920
+ path,
4921
+ title,
4922
+ snippet(notes_fts, 2, '[', ']', '...', 20) as snippet
4923
+ FROM notes_fts
4924
+ WHERE notes_fts MATCH ?
4925
+ ORDER BY rank
4926
+ LIMIT ?
4927
+ `);
4928
+ const results = stmt.all(query, limit);
4929
+ return results;
4930
+ } catch (err) {
4931
+ if (err instanceof Error && err.message.includes("fts5: syntax error")) {
4932
+ throw new Error(`Invalid search query: ${query}. Check FTS5 syntax.`);
4933
+ }
4934
+ throw err;
4935
+ }
4936
+ }
4937
+ function getFTS5State() {
4938
+ return { ...state };
4939
+ }
4940
+
4675
4941
  // src/index.ts
4676
4942
  import { openStateDb, scanVaultEntities as scanVaultEntities3 } from "@velvetmonkey/vault-core";
4677
4943
 
4678
4944
  // src/tools/read/graph.ts
4679
- import * as fs5 from "fs";
4945
+ import * as fs6 from "fs";
4680
4946
  import * as path8 from "path";
4681
4947
  import { z } from "zod";
4682
4948
 
@@ -4701,7 +4967,7 @@ function requireIndex() {
4701
4967
  async function getContext(vaultPath2, sourcePath, line, contextLines = 1) {
4702
4968
  try {
4703
4969
  const fullPath = path8.join(vaultPath2, sourcePath);
4704
- const content = await fs5.promises.readFile(fullPath, "utf-8");
4970
+ const content = await fs6.promises.readFile(fullPath, "utf-8");
4705
4971
  const lines = content.split("\n");
4706
4972
  const startLine = Math.max(0, line - 1 - contextLines);
4707
4973
  const endLine = Math.min(lines.length, line + contextLines);
@@ -5091,14 +5357,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
5091
5357
  };
5092
5358
  function findSimilarEntity2(target, entities) {
5093
5359
  const targetLower = target.toLowerCase();
5094
- for (const [name, path25] of entities) {
5360
+ for (const [name, path24] of entities) {
5095
5361
  if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
5096
- return path25;
5362
+ return path24;
5097
5363
  }
5098
5364
  }
5099
- for (const [name, path25] of entities) {
5365
+ for (const [name, path24] of entities) {
5100
5366
  if (name.includes(targetLower) || targetLower.includes(name)) {
5101
- return path25;
5367
+ return path24;
5102
5368
  }
5103
5369
  }
5104
5370
  return void 0;
@@ -5178,7 +5444,7 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
5178
5444
  }
5179
5445
 
5180
5446
  // src/tools/read/health.ts
5181
- import * as fs6 from "fs";
5447
+ import * as fs7 from "fs";
5182
5448
  import { z as z3 } from "zod";
5183
5449
  var STALE_THRESHOLD_SECONDS = 300;
5184
5450
  function registerHealthTools(server2, getIndex, getVaultPath) {
@@ -5218,7 +5484,7 @@ function registerHealthTools(server2, getIndex, getVaultPath) {
5218
5484
  const indexErrorObj = getIndexError();
5219
5485
  let vaultAccessible = false;
5220
5486
  try {
5221
- fs6.accessSync(vaultPath2, fs6.constants.R_OK);
5487
+ fs7.accessSync(vaultPath2, fs7.constants.R_OK);
5222
5488
  vaultAccessible = true;
5223
5489
  } catch {
5224
5490
  vaultAccessible = false;
@@ -5395,8 +5661,8 @@ function registerHealthTools(server2, getIndex, getVaultPath) {
5395
5661
  top_tags: z3.array(TagStatSchema).describe("Top 20 most used tags"),
5396
5662
  folders: z3.array(FolderStatSchema).describe("Note counts by top-level folder")
5397
5663
  };
5398
- function isPeriodicNote(path25) {
5399
- const filename = path25.split("/").pop() || "";
5664
+ function isPeriodicNote(path24) {
5665
+ const filename = path24.split("/").pop() || "";
5400
5666
  const nameWithoutExt = filename.replace(/\.md$/, "");
5401
5667
  const patterns = [
5402
5668
  /^\d{4}-\d{2}-\d{2}$/,
@@ -5411,7 +5677,7 @@ function registerHealthTools(server2, getIndex, getVaultPath) {
5411
5677
  // YYYY (yearly)
5412
5678
  ];
5413
5679
  const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
5414
- const folder = path25.split("/")[0]?.toLowerCase() || "";
5680
+ const folder = path24.split("/")[0]?.toLowerCase() || "";
5415
5681
  return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
5416
5682
  }
5417
5683
  server2.registerTool(
@@ -5504,166 +5770,6 @@ function registerHealthTools(server2, getIndex, getVaultPath) {
5504
5770
 
5505
5771
  // src/tools/read/query.ts
5506
5772
  import { z as z4 } from "zod";
5507
-
5508
- // src/core/read/fts5.ts
5509
- import Database from "better-sqlite3";
5510
- import * as fs7 from "fs";
5511
- import * as path9 from "path";
5512
- var EXCLUDED_DIRS2 = /* @__PURE__ */ new Set([
5513
- ".obsidian",
5514
- ".trash",
5515
- ".git",
5516
- "node_modules",
5517
- "templates",
5518
- ".claude"
5519
- ]);
5520
- var MAX_INDEX_FILE_SIZE = 5 * 1024 * 1024;
5521
- var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
5522
- var db = null;
5523
- var state = {
5524
- ready: false,
5525
- lastBuilt: null,
5526
- noteCount: 0,
5527
- error: null
5528
- };
5529
- function getDbPath(vaultPath2) {
5530
- const claudeDir = path9.join(vaultPath2, ".claude");
5531
- if (!fs7.existsSync(claudeDir)) {
5532
- fs7.mkdirSync(claudeDir, { recursive: true });
5533
- }
5534
- return path9.join(claudeDir, "vault-search.db");
5535
- }
5536
- function initDatabase(vaultPath2) {
5537
- const dbPath = getDbPath(vaultPath2);
5538
- const database = new Database(dbPath);
5539
- database.exec(`
5540
- CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(
5541
- path,
5542
- title,
5543
- content,
5544
- tokenize='porter'
5545
- );
5546
-
5547
- CREATE TABLE IF NOT EXISTS fts_metadata (
5548
- key TEXT PRIMARY KEY,
5549
- value TEXT
5550
- );
5551
- `);
5552
- return database;
5553
- }
5554
- function shouldIndexFile(filePath) {
5555
- const parts = filePath.split("/");
5556
- return !parts.some((part) => EXCLUDED_DIRS2.has(part));
5557
- }
5558
- async function buildFTS5Index(vaultPath2) {
5559
- try {
5560
- state.error = null;
5561
- db = initDatabase(vaultPath2);
5562
- db.exec("DELETE FROM notes_fts");
5563
- const files = await scanVault(vaultPath2);
5564
- const indexableFiles = files.filter((f) => shouldIndexFile(f.path));
5565
- const insert = db.prepare(
5566
- "INSERT INTO notes_fts (path, title, content) VALUES (?, ?, ?)"
5567
- );
5568
- const insertMany = db.transaction((filesToIndex) => {
5569
- let indexed2 = 0;
5570
- for (const file of filesToIndex) {
5571
- try {
5572
- const stats = fs7.statSync(file.absolutePath);
5573
- if (stats.size > MAX_INDEX_FILE_SIZE) {
5574
- continue;
5575
- }
5576
- const content = fs7.readFileSync(file.absolutePath, "utf-8");
5577
- const title = file.path.replace(/\.md$/, "").split("/").pop() || file.path;
5578
- insert.run(file.path, title, content);
5579
- indexed2++;
5580
- } catch (err) {
5581
- console.error(`[FTS5] Skipping ${file.path}:`, err);
5582
- }
5583
- }
5584
- return indexed2;
5585
- });
5586
- const indexed = insertMany(indexableFiles);
5587
- const now = /* @__PURE__ */ new Date();
5588
- db.prepare(
5589
- "INSERT OR REPLACE INTO fts_metadata (key, value) VALUES (?, ?)"
5590
- ).run("last_built", now.toISOString());
5591
- state = {
5592
- ready: true,
5593
- lastBuilt: now,
5594
- noteCount: indexed,
5595
- error: null
5596
- };
5597
- console.error(`[FTS5] Indexed ${indexed} notes`);
5598
- return state;
5599
- } catch (err) {
5600
- state = {
5601
- ready: false,
5602
- lastBuilt: null,
5603
- noteCount: 0,
5604
- error: err instanceof Error ? err.message : String(err)
5605
- };
5606
- throw err;
5607
- }
5608
- }
5609
- function isIndexStale(vaultPath2) {
5610
- const dbPath = getDbPath(vaultPath2);
5611
- if (!fs7.existsSync(dbPath)) {
5612
- return true;
5613
- }
5614
- try {
5615
- const database = new Database(dbPath, { readonly: true });
5616
- const row = database.prepare(
5617
- "SELECT value FROM fts_metadata WHERE key = ?"
5618
- ).get("last_built");
5619
- database.close();
5620
- if (!row) {
5621
- return true;
5622
- }
5623
- const lastBuilt = new Date(row.value);
5624
- const age = Date.now() - lastBuilt.getTime();
5625
- return age > STALE_THRESHOLD_MS;
5626
- } catch {
5627
- return true;
5628
- }
5629
- }
5630
- function ensureDb(vaultPath2) {
5631
- if (!db) {
5632
- const dbPath = getDbPath(vaultPath2);
5633
- if (!fs7.existsSync(dbPath)) {
5634
- throw new Error("Search index not built. Call rebuild_search_index first.");
5635
- }
5636
- db = new Database(dbPath);
5637
- }
5638
- return db;
5639
- }
5640
- function searchFTS5(vaultPath2, query, limit = 10) {
5641
- const database = ensureDb(vaultPath2);
5642
- try {
5643
- const stmt = database.prepare(`
5644
- SELECT
5645
- path,
5646
- title,
5647
- snippet(notes_fts, 2, '[', ']', '...', 20) as snippet
5648
- FROM notes_fts
5649
- WHERE notes_fts MATCH ?
5650
- ORDER BY rank
5651
- LIMIT ?
5652
- `);
5653
- const results = stmt.all(query, limit);
5654
- return results;
5655
- } catch (err) {
5656
- if (err instanceof Error && err.message.includes("fts5: syntax error")) {
5657
- throw new Error(`Invalid search query: ${query}. Check FTS5 syntax.`);
5658
- }
5659
- throw err;
5660
- }
5661
- }
5662
- function getFTS5State() {
5663
- return { ...state };
5664
- }
5665
-
5666
- // src/tools/read/query.ts
5667
5773
  import {
5668
5774
  searchEntities,
5669
5775
  searchEntitiesPrefix
@@ -6033,7 +6139,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
6033
6139
 
6034
6140
  // src/tools/read/system.ts
6035
6141
  import * as fs8 from "fs";
6036
- import * as path10 from "path";
6142
+ import * as path9 from "path";
6037
6143
  import { z as z5 } from "zod";
6038
6144
  import { scanVaultEntities as scanVaultEntities2 } from "@velvetmonkey/vault-core";
6039
6145
  function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfig, getStateDb) {
@@ -6311,7 +6417,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
6311
6417
  continue;
6312
6418
  }
6313
6419
  try {
6314
- const fullPath = path10.join(vaultPath2, note.path);
6420
+ const fullPath = path9.join(vaultPath2, note.path);
6315
6421
  const content = await fs8.promises.readFile(fullPath, "utf-8");
6316
6422
  const lines = content.split("\n");
6317
6423
  for (let i = 0; i < lines.length; i++) {
@@ -6427,7 +6533,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
6427
6533
  let wordCount;
6428
6534
  if (include_word_count) {
6429
6535
  try {
6430
- const fullPath = path10.join(vaultPath2, resolvedPath);
6536
+ const fullPath = path9.join(vaultPath2, resolvedPath);
6431
6537
  const content = await fs8.promises.readFile(fullPath, "utf-8");
6432
6538
  wordCount = content.split(/\s+/).filter((w) => w.length > 0).length;
6433
6539
  } catch {
@@ -6593,8 +6699,8 @@ function getStaleNotes(index, days, minBacklinks = 0) {
6593
6699
  return b.days_since_modified - a.days_since_modified;
6594
6700
  });
6595
6701
  }
6596
- function getContemporaneousNotes(index, path25, hours = 24) {
6597
- const targetNote = index.notes.get(path25);
6702
+ function getContemporaneousNotes(index, path24, hours = 24) {
6703
+ const targetNote = index.notes.get(path24);
6598
6704
  if (!targetNote) {
6599
6705
  return [];
6600
6706
  }
@@ -6602,7 +6708,7 @@ function getContemporaneousNotes(index, path25, hours = 24) {
6602
6708
  const windowMs = hours * 60 * 60 * 1e3;
6603
6709
  const results = [];
6604
6710
  for (const note of index.notes.values()) {
6605
- if (note.path === path25) continue;
6711
+ if (note.path === path24) continue;
6606
6712
  const timeDiff = Math.abs(note.modified.getTime() - targetTime);
6607
6713
  if (timeDiff <= windowMs) {
6608
6714
  results.push({
@@ -6651,7 +6757,7 @@ function getActivitySummary(index, days) {
6651
6757
 
6652
6758
  // src/tools/read/structure.ts
6653
6759
  import * as fs9 from "fs";
6654
- import * as path11 from "path";
6760
+ import * as path10 from "path";
6655
6761
  var HEADING_REGEX = /^(#{1,6})\s+(.+)$/;
6656
6762
  function extractHeadings(content) {
6657
6763
  const lines = content.split("\n");
@@ -6705,7 +6811,7 @@ function buildSections(headings, totalLines) {
6705
6811
  async function getNoteStructure(index, notePath, vaultPath2) {
6706
6812
  const note = index.notes.get(notePath);
6707
6813
  if (!note) return null;
6708
- const absolutePath = path11.join(vaultPath2, notePath);
6814
+ const absolutePath = path10.join(vaultPath2, notePath);
6709
6815
  let content;
6710
6816
  try {
6711
6817
  content = await fs9.promises.readFile(absolutePath, "utf-8");
@@ -6728,7 +6834,7 @@ async function getNoteStructure(index, notePath, vaultPath2) {
6728
6834
  async function getHeadings(index, notePath, vaultPath2) {
6729
6835
  const note = index.notes.get(notePath);
6730
6836
  if (!note) return null;
6731
- const absolutePath = path11.join(vaultPath2, notePath);
6837
+ const absolutePath = path10.join(vaultPath2, notePath);
6732
6838
  let content;
6733
6839
  try {
6734
6840
  content = await fs9.promises.readFile(absolutePath, "utf-8");
@@ -6740,7 +6846,7 @@ async function getHeadings(index, notePath, vaultPath2) {
6740
6846
  async function getSectionContent(index, notePath, headingText, vaultPath2, includeSubheadings = true) {
6741
6847
  const note = index.notes.get(notePath);
6742
6848
  if (!note) return null;
6743
- const absolutePath = path11.join(vaultPath2, notePath);
6849
+ const absolutePath = path10.join(vaultPath2, notePath);
6744
6850
  let content;
6745
6851
  try {
6746
6852
  content = await fs9.promises.readFile(absolutePath, "utf-8");
@@ -6782,7 +6888,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
6782
6888
  const results = [];
6783
6889
  for (const note of index.notes.values()) {
6784
6890
  if (folder && !note.path.startsWith(folder)) continue;
6785
- const absolutePath = path11.join(vaultPath2, note.path);
6891
+ const absolutePath = path10.join(vaultPath2, note.path);
6786
6892
  let content;
6787
6893
  try {
6788
6894
  content = await fs9.promises.readFile(absolutePath, "utf-8");
@@ -6806,7 +6912,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
6806
6912
 
6807
6913
  // src/tools/read/tasks.ts
6808
6914
  import * as fs10 from "fs";
6809
- import * as path12 from "path";
6915
+ import * as path11 from "path";
6810
6916
  var TASK_REGEX = /^(\s*)- \[([ xX\-])\]\s+(.+)$/;
6811
6917
  var TAG_REGEX2 = /#([a-zA-Z][a-zA-Z0-9_/-]*)/g;
6812
6918
  var DATE_REGEX = /\b(\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4})\b/;
@@ -6875,7 +6981,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
6875
6981
  const allTasks = [];
6876
6982
  for (const note of index.notes.values()) {
6877
6983
  if (folder && !note.path.startsWith(folder)) continue;
6878
- const absolutePath = path12.join(vaultPath2, note.path);
6984
+ const absolutePath = path11.join(vaultPath2, note.path);
6879
6985
  const tasks = await extractTasksFromNote(note.path, absolutePath);
6880
6986
  allTasks.push(...tasks);
6881
6987
  }
@@ -6906,7 +7012,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
6906
7012
  async function getTasksFromNote(index, notePath, vaultPath2, excludeTags = []) {
6907
7013
  const note = index.notes.get(notePath);
6908
7014
  if (!note) return null;
6909
- const absolutePath = path12.join(vaultPath2, notePath);
7015
+ const absolutePath = path11.join(vaultPath2, notePath);
6910
7016
  let tasks = await extractTasksFromNote(notePath, absolutePath);
6911
7017
  if (excludeTags.length > 0) {
6912
7018
  tasks = tasks.filter(
@@ -7394,14 +7500,14 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
7394
7500
  offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
7395
7501
  }
7396
7502
  },
7397
- async ({ path: path25, hours, limit: requestedLimit, offset }) => {
7503
+ async ({ path: path24, hours, limit: requestedLimit, offset }) => {
7398
7504
  const limit = Math.min(requestedLimit ?? 50, MAX_LIMIT);
7399
7505
  const index = getIndex();
7400
- const allResults = getContemporaneousNotes(index, path25, hours);
7506
+ const allResults = getContemporaneousNotes(index, path24, hours);
7401
7507
  const result = allResults.slice(offset, offset + limit);
7402
7508
  return {
7403
7509
  content: [{ type: "text", text: JSON.stringify({
7404
- reference_note: path25,
7510
+ reference_note: path24,
7405
7511
  window_hours: hours,
7406
7512
  total_count: allResults.length,
7407
7513
  returned_count: result.length,
@@ -7439,13 +7545,13 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
7439
7545
  path: z6.string().describe("Path to the note")
7440
7546
  }
7441
7547
  },
7442
- async ({ path: path25 }) => {
7548
+ async ({ path: path24 }) => {
7443
7549
  const index = getIndex();
7444
7550
  const vaultPath2 = getVaultPath();
7445
- const result = await getNoteStructure(index, path25, vaultPath2);
7551
+ const result = await getNoteStructure(index, path24, vaultPath2);
7446
7552
  if (!result) {
7447
7553
  return {
7448
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path25 }, null, 2) }]
7554
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path24 }, null, 2) }]
7449
7555
  };
7450
7556
  }
7451
7557
  return {
@@ -7462,18 +7568,18 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
7462
7568
  path: z6.string().describe("Path to the note")
7463
7569
  }
7464
7570
  },
7465
- async ({ path: path25 }) => {
7571
+ async ({ path: path24 }) => {
7466
7572
  const index = getIndex();
7467
7573
  const vaultPath2 = getVaultPath();
7468
- const result = await getHeadings(index, path25, vaultPath2);
7574
+ const result = await getHeadings(index, path24, vaultPath2);
7469
7575
  if (!result) {
7470
7576
  return {
7471
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path25 }, null, 2) }]
7577
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path24 }, null, 2) }]
7472
7578
  };
7473
7579
  }
7474
7580
  return {
7475
7581
  content: [{ type: "text", text: JSON.stringify({
7476
- path: path25,
7582
+ path: path24,
7477
7583
  heading_count: result.length,
7478
7584
  headings: result
7479
7585
  }, null, 2) }]
@@ -7491,15 +7597,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
7491
7597
  include_subheadings: z6.boolean().default(true).describe("Include content under subheadings")
7492
7598
  }
7493
7599
  },
7494
- async ({ path: path25, heading, include_subheadings }) => {
7600
+ async ({ path: path24, heading, include_subheadings }) => {
7495
7601
  const index = getIndex();
7496
7602
  const vaultPath2 = getVaultPath();
7497
- const result = await getSectionContent(index, path25, heading, vaultPath2, include_subheadings);
7603
+ const result = await getSectionContent(index, path24, heading, vaultPath2, include_subheadings);
7498
7604
  if (!result) {
7499
7605
  return {
7500
7606
  content: [{ type: "text", text: JSON.stringify({
7501
7607
  error: "Section not found",
7502
- path: path25,
7608
+ path: path24,
7503
7609
  heading
7504
7610
  }, null, 2) }]
7505
7611
  };
@@ -7576,19 +7682,19 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
7576
7682
  path: z6.string().describe("Path to the note")
7577
7683
  }
7578
7684
  },
7579
- async ({ path: path25 }) => {
7685
+ async ({ path: path24 }) => {
7580
7686
  const index = getIndex();
7581
7687
  const vaultPath2 = getVaultPath();
7582
7688
  const config = getConfig();
7583
- const result = await getTasksFromNote(index, path25, vaultPath2, config.exclude_task_tags || []);
7689
+ const result = await getTasksFromNote(index, path24, vaultPath2, config.exclude_task_tags || []);
7584
7690
  if (!result) {
7585
7691
  return {
7586
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path25 }, null, 2) }]
7692
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path24 }, null, 2) }]
7587
7693
  };
7588
7694
  }
7589
7695
  return {
7590
7696
  content: [{ type: "text", text: JSON.stringify({
7591
- path: path25,
7697
+ path: path24,
7592
7698
  task_count: result.length,
7593
7699
  open: result.filter((t) => t.status === "open").length,
7594
7700
  completed: result.filter((t) => t.status === "completed").length,
@@ -7720,14 +7826,14 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
7720
7826
  offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
7721
7827
  }
7722
7828
  },
7723
- async ({ path: path25, limit: requestedLimit, offset }) => {
7829
+ async ({ path: path24, limit: requestedLimit, offset }) => {
7724
7830
  const limit = Math.min(requestedLimit ?? 50, MAX_LIMIT);
7725
7831
  const index = getIndex();
7726
- const allResults = findBidirectionalLinks(index, path25);
7832
+ const allResults = findBidirectionalLinks(index, path24);
7727
7833
  const result = allResults.slice(offset, offset + limit);
7728
7834
  return {
7729
7835
  content: [{ type: "text", text: JSON.stringify({
7730
- scope: path25 || "all",
7836
+ scope: path24 || "all",
7731
7837
  total_count: allResults.length,
7732
7838
  returned_count: result.length,
7733
7839
  pairs: result
@@ -8135,13 +8241,13 @@ function registerPeriodicTools(server2, getIndex) {
8135
8241
  // src/tools/read/bidirectional.ts
8136
8242
  import { z as z8 } from "zod";
8137
8243
  import * as fs11 from "fs/promises";
8138
- import * as path13 from "path";
8244
+ import * as path12 from "path";
8139
8245
  import matter2 from "gray-matter";
8140
8246
  var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
8141
8247
  var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
8142
8248
  var WIKILINK_REGEX2 = /\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|[^\]]+)?\]\]/g;
8143
8249
  async function readFileContent(notePath, vaultPath2) {
8144
- const fullPath = path13.join(vaultPath2, notePath);
8250
+ const fullPath = path12.join(vaultPath2, notePath);
8145
8251
  try {
8146
8252
  return await fs11.readFile(fullPath, "utf-8");
8147
8253
  } catch {
@@ -8877,10 +8983,10 @@ function registerSchemaTools(server2, getIndex, getVaultPath) {
8877
8983
  // src/tools/read/computed.ts
8878
8984
  import { z as z10 } from "zod";
8879
8985
  import * as fs12 from "fs/promises";
8880
- import * as path14 from "path";
8986
+ import * as path13 from "path";
8881
8987
  import matter3 from "gray-matter";
8882
8988
  async function readFileContent2(notePath, vaultPath2) {
8883
- const fullPath = path14.join(vaultPath2, notePath);
8989
+ const fullPath = path13.join(vaultPath2, notePath);
8884
8990
  try {
8885
8991
  return await fs12.readFile(fullPath, "utf-8");
8886
8992
  } catch {
@@ -8888,7 +8994,7 @@ async function readFileContent2(notePath, vaultPath2) {
8888
8994
  }
8889
8995
  }
8890
8996
  async function getFileStats(notePath, vaultPath2) {
8891
- const fullPath = path14.join(vaultPath2, notePath);
8997
+ const fullPath = path13.join(vaultPath2, notePath);
8892
8998
  try {
8893
8999
  const stats = await fs12.stat(fullPath);
8894
9000
  return {
@@ -9047,7 +9153,7 @@ function registerComputedTools(server2, getIndex, getVaultPath) {
9047
9153
  // src/tools/read/migrations.ts
9048
9154
  import { z as z11 } from "zod";
9049
9155
  import * as fs13 from "fs/promises";
9050
- import * as path15 from "path";
9156
+ import * as path14 from "path";
9051
9157
  import matter4 from "gray-matter";
9052
9158
  function getNotesInFolder2(index, folder) {
9053
9159
  const notes = [];
@@ -9060,7 +9166,7 @@ function getNotesInFolder2(index, folder) {
9060
9166
  return notes;
9061
9167
  }
9062
9168
  async function readFileContent3(notePath, vaultPath2) {
9063
- const fullPath = path15.join(vaultPath2, notePath);
9169
+ const fullPath = path14.join(vaultPath2, notePath);
9064
9170
  try {
9065
9171
  return await fs13.readFile(fullPath, "utf-8");
9066
9172
  } catch {
@@ -9068,7 +9174,7 @@ async function readFileContent3(notePath, vaultPath2) {
9068
9174
  }
9069
9175
  }
9070
9176
  async function writeFileContent(notePath, vaultPath2, content) {
9071
- const fullPath = path15.join(vaultPath2, notePath);
9177
+ const fullPath = path14.join(vaultPath2, notePath);
9072
9178
  try {
9073
9179
  await fs13.writeFile(fullPath, content, "utf-8");
9074
9180
  return true;
@@ -9452,7 +9558,7 @@ function runValidationPipeline(content, format, options = {}) {
9452
9558
  // src/core/write/mutation-helpers.ts
9453
9559
  init_writer();
9454
9560
  import fs15 from "fs/promises";
9455
- import path17 from "path";
9561
+ import path16 from "path";
9456
9562
  init_constants();
9457
9563
  init_writer();
9458
9564
  function formatMcpResult(result) {
@@ -9501,7 +9607,7 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
9501
9607
  return info;
9502
9608
  }
9503
9609
  async function ensureFileExists(vaultPath2, notePath) {
9504
- const fullPath = path17.join(vaultPath2, notePath);
9610
+ const fullPath = path16.join(vaultPath2, notePath);
9505
9611
  try {
9506
9612
  await fs15.access(fullPath);
9507
9613
  return null;
@@ -10060,8 +10166,8 @@ function registerFrontmatterTools(server2, vaultPath2) {
10060
10166
  init_writer();
10061
10167
  import { z as z15 } from "zod";
10062
10168
  import fs16 from "fs/promises";
10063
- import path18 from "path";
10064
- function registerNoteTools(server2, vaultPath2) {
10169
+ import path17 from "path";
10170
+ function registerNoteTools(server2, vaultPath2, getIndex) {
10065
10171
  server2.tool(
10066
10172
  "vault_create_note",
10067
10173
  "Create a new note in the vault with optional frontmatter and content",
@@ -10082,13 +10188,39 @@ function registerNoteTools(server2, vaultPath2) {
10082
10188
  if (!validatePath(vaultPath2, notePath)) {
10083
10189
  return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
10084
10190
  }
10085
- const fullPath = path18.join(vaultPath2, notePath);
10191
+ const fullPath = path17.join(vaultPath2, notePath);
10086
10192
  const existsCheck = await ensureFileExists(vaultPath2, notePath);
10087
10193
  if (existsCheck === null && !overwrite) {
10088
10194
  return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
10089
10195
  }
10090
- const dir = path18.dirname(fullPath);
10196
+ const dir = path17.dirname(fullPath);
10091
10197
  await fs16.mkdir(dir, { recursive: true });
10198
+ const warnings = [];
10199
+ const noteName = path17.basename(notePath, ".md");
10200
+ const existingAliases = Array.isArray(frontmatter?.aliases) ? frontmatter.aliases.filter((a) => typeof a === "string") : [];
10201
+ const preflight = checkPreflightSimilarity(noteName);
10202
+ if (preflight.existingEntity) {
10203
+ warnings.push({
10204
+ type: "similar_note_exists",
10205
+ message: `An entity "${preflight.existingEntity.name}" already exists at ${preflight.existingEntity.path}`,
10206
+ suggestion: `Consider linking to the existing note instead, or choose a different name`
10207
+ });
10208
+ }
10209
+ for (const similar of preflight.similarEntities.slice(0, 3)) {
10210
+ warnings.push({
10211
+ type: "similar_note_exists",
10212
+ message: `Similar entity "${similar.name}" exists at ${similar.path}`,
10213
+ suggestion: `Check if this is a duplicate`
10214
+ });
10215
+ }
10216
+ const collisions = detectAliasCollisions(noteName, existingAliases);
10217
+ for (const collision of collisions) {
10218
+ warnings.push({
10219
+ type: "alias_collision",
10220
+ message: `${collision.source === "name" ? "Note name" : "Alias"} "${collision.term}" collides with ${collision.collidedWith.matchType} of "${collision.collidedWith.name}" (${collision.collidedWith.path})`,
10221
+ suggestion: `This may cause ambiguous wikilink resolution`
10222
+ });
10223
+ }
10092
10224
  let { content: processedContent, wikilinkInfo } = maybeApplyWikilinks(content, skipWikilinks, notePath);
10093
10225
  let suggestInfo;
10094
10226
  if (suggestOutgoingLinks && !skipWikilinks) {
@@ -10114,12 +10246,22 @@ function registerNoteTools(server2, vaultPath2) {
10114
10246
  }
10115
10247
  const hasAliases = frontmatter && "aliases" in frontmatter;
10116
10248
  if (!hasAliases) {
10117
- previewLines.push("");
10118
- previewLines.push('Tip: Add aliases to frontmatter for flexible wikilink matching (e.g., aliases: ["Short Name"])');
10249
+ const aliasSuggestions = suggestAliases(noteName, existingAliases);
10250
+ if (aliasSuggestions.length > 0) {
10251
+ previewLines.push("");
10252
+ previewLines.push("Suggested aliases:");
10253
+ for (const s of aliasSuggestions) {
10254
+ previewLines.push(` - "${s.alias}" (${s.reason})`);
10255
+ }
10256
+ } else {
10257
+ previewLines.push("");
10258
+ previewLines.push('Tip: Add aliases to frontmatter for flexible wikilink matching (e.g., aliases: ["Short Name"])');
10259
+ }
10119
10260
  }
10120
10261
  return formatMcpResult(
10121
10262
  successResult(notePath, `Created note: ${notePath}`, gitInfo, {
10122
- preview: previewLines.join("\n")
10263
+ preview: previewLines.join("\n"),
10264
+ warnings: warnings.length > 0 ? warnings : void 0
10123
10265
  })
10124
10266
  );
10125
10267
  } catch (error) {
@@ -10139,9 +10281,6 @@ function registerNoteTools(server2, vaultPath2) {
10139
10281
  },
10140
10282
  async ({ path: notePath, confirm, commit }) => {
10141
10283
  try {
10142
- if (!confirm) {
10143
- return formatMcpResult(errorResult(notePath, "Deletion requires explicit confirmation (confirm=true)"));
10144
- }
10145
10284
  if (!validatePath(vaultPath2, notePath)) {
10146
10285
  return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
10147
10286
  }
@@ -10149,10 +10288,38 @@ function registerNoteTools(server2, vaultPath2) {
10149
10288
  if (existsError) {
10150
10289
  return formatMcpResult(existsError);
10151
10290
  }
10152
- const fullPath = path18.join(vaultPath2, notePath);
10291
+ let backlinkWarning;
10292
+ if (getIndex) {
10293
+ try {
10294
+ const index = getIndex();
10295
+ const backlinks = getBacklinksForNote(index, notePath);
10296
+ if (backlinks.length > 0) {
10297
+ const sources = backlinks.slice(0, 10).map((bl) => ` - ${bl.source}${bl.context ? ` ("${bl.context.slice(0, 60)}")` : ""}`).join("\n");
10298
+ backlinkWarning = `This note is referenced from ${backlinks.length} other note(s):
10299
+ ${sources}`;
10300
+ if (backlinks.length > 10) {
10301
+ backlinkWarning += `
10302
+ ... and ${backlinks.length - 10} more`;
10303
+ }
10304
+ }
10305
+ } catch {
10306
+ }
10307
+ }
10308
+ if (!confirm) {
10309
+ const previewLines = ["Deletion requires explicit confirmation (confirm=true)"];
10310
+ if (backlinkWarning) {
10311
+ previewLines.push("");
10312
+ previewLines.push("Warning: " + backlinkWarning);
10313
+ }
10314
+ return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
10315
+ }
10316
+ const fullPath = path17.join(vaultPath2, notePath);
10153
10317
  await fs16.unlink(fullPath);
10154
10318
  const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Crank:Delete]");
10155
- return formatMcpResult(successResult(notePath, `Deleted note: ${notePath}`, gitInfo));
10319
+ const message = backlinkWarning ? `Deleted note: ${notePath}
10320
+
10321
+ Warning: ${backlinkWarning}` : `Deleted note: ${notePath}`;
10322
+ return formatMcpResult(successResult(notePath, message, gitInfo));
10156
10323
  } catch (error) {
10157
10324
  return formatMcpResult(
10158
10325
  errorResult(notePath, `Failed to delete note: ${error instanceof Error ? error.message : String(error)}`)
@@ -10166,7 +10333,7 @@ function registerNoteTools(server2, vaultPath2) {
10166
10333
  init_writer();
10167
10334
  import { z as z16 } from "zod";
10168
10335
  import fs17 from "fs/promises";
10169
- import path19 from "path";
10336
+ import path18 from "path";
10170
10337
  import matter6 from "gray-matter";
10171
10338
  function escapeRegex(str) {
10172
10339
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -10185,7 +10352,7 @@ function extractWikilinks2(content) {
10185
10352
  return wikilinks;
10186
10353
  }
10187
10354
  function getTitleFromPath(filePath) {
10188
- return path19.basename(filePath, ".md");
10355
+ return path18.basename(filePath, ".md");
10189
10356
  }
10190
10357
  async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
10191
10358
  const results = [];
@@ -10194,7 +10361,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
10194
10361
  const files = [];
10195
10362
  const entries = await fs17.readdir(dir, { withFileTypes: true });
10196
10363
  for (const entry of entries) {
10197
- const fullPath = path19.join(dir, entry.name);
10364
+ const fullPath = path18.join(dir, entry.name);
10198
10365
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
10199
10366
  files.push(...await scanDir(fullPath));
10200
10367
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -10205,7 +10372,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
10205
10372
  }
10206
10373
  const allFiles = await scanDir(vaultPath2);
10207
10374
  for (const filePath of allFiles) {
10208
- const relativePath = path19.relative(vaultPath2, filePath);
10375
+ const relativePath = path18.relative(vaultPath2, filePath);
10209
10376
  const content = await fs17.readFile(filePath, "utf-8");
10210
10377
  const wikilinks = extractWikilinks2(content);
10211
10378
  const matchingLinks = [];
@@ -10225,7 +10392,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
10225
10392
  return results;
10226
10393
  }
10227
10394
  async function updateBacklinksInFile(vaultPath2, filePath, oldTitles, newTitle) {
10228
- const fullPath = path19.join(vaultPath2, filePath);
10395
+ const fullPath = path18.join(vaultPath2, filePath);
10229
10396
  const raw = await fs17.readFile(fullPath, "utf-8");
10230
10397
  const parsed = matter6(raw);
10231
10398
  let content = parsed.content;
@@ -10292,8 +10459,8 @@ function registerMoveNoteTools(server2, vaultPath2) {
10292
10459
  };
10293
10460
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
10294
10461
  }
10295
- const oldFullPath = path19.join(vaultPath2, oldPath);
10296
- const newFullPath = path19.join(vaultPath2, newPath);
10462
+ const oldFullPath = path18.join(vaultPath2, oldPath);
10463
+ const newFullPath = path18.join(vaultPath2, newPath);
10297
10464
  try {
10298
10465
  await fs17.access(oldFullPath);
10299
10466
  } catch {
@@ -10343,7 +10510,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
10343
10510
  }
10344
10511
  }
10345
10512
  }
10346
- const destDir = path19.dirname(newFullPath);
10513
+ const destDir = path18.dirname(newFullPath);
10347
10514
  await fs17.mkdir(destDir, { recursive: true });
10348
10515
  await fs17.rename(oldFullPath, newFullPath);
10349
10516
  let gitCommit;
@@ -10429,10 +10596,10 @@ function registerMoveNoteTools(server2, vaultPath2) {
10429
10596
  if (sanitizedTitle !== newTitle) {
10430
10597
  console.error(`[Crank] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
10431
10598
  }
10432
- const fullPath = path19.join(vaultPath2, notePath);
10433
- const dir = path19.dirname(notePath);
10434
- const newPath = dir === "." ? `${sanitizedTitle}.md` : path19.join(dir, `${sanitizedTitle}.md`);
10435
- const newFullPath = path19.join(vaultPath2, newPath);
10599
+ const fullPath = path18.join(vaultPath2, notePath);
10600
+ const dir = path18.dirname(notePath);
10601
+ const newPath = dir === "." ? `${sanitizedTitle}.md` : path18.join(dir, `${sanitizedTitle}.md`);
10602
+ const newFullPath = path18.join(vaultPath2, newPath);
10436
10603
  try {
10437
10604
  await fs17.access(fullPath);
10438
10605
  } catch {
@@ -10544,7 +10711,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
10544
10711
  init_writer();
10545
10712
  import { z as z17 } from "zod";
10546
10713
  import fs18 from "fs/promises";
10547
- import path20 from "path";
10714
+ import path19 from "path";
10548
10715
  function registerSystemTools2(server2, vaultPath2) {
10549
10716
  server2.tool(
10550
10717
  "vault_list_sections",
@@ -10564,7 +10731,7 @@ function registerSystemTools2(server2, vaultPath2) {
10564
10731
  };
10565
10732
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
10566
10733
  }
10567
- const fullPath = path20.join(vaultPath2, notePath);
10734
+ const fullPath = path19.join(vaultPath2, notePath);
10568
10735
  try {
10569
10736
  await fs18.access(fullPath);
10570
10737
  } catch {
@@ -10718,7 +10885,7 @@ init_schema();
10718
10885
  // src/core/write/policy/parser.ts
10719
10886
  init_schema();
10720
10887
  import fs19 from "fs/promises";
10721
- import path21 from "path";
10888
+ import path20 from "path";
10722
10889
  import matter7 from "gray-matter";
10723
10890
  function parseYaml(content) {
10724
10891
  const parsed = matter7(`---
@@ -10767,13 +10934,13 @@ async function loadPolicyFile(filePath) {
10767
10934
  }
10768
10935
  }
10769
10936
  async function loadPolicy(vaultPath2, policyName) {
10770
- const policiesDir = path21.join(vaultPath2, ".claude", "policies");
10771
- const policyPath = path21.join(policiesDir, `${policyName}.yaml`);
10937
+ const policiesDir = path20.join(vaultPath2, ".claude", "policies");
10938
+ const policyPath = path20.join(policiesDir, `${policyName}.yaml`);
10772
10939
  try {
10773
10940
  await fs19.access(policyPath);
10774
10941
  return loadPolicyFile(policyPath);
10775
10942
  } catch {
10776
- const ymlPath = path21.join(policiesDir, `${policyName}.yml`);
10943
+ const ymlPath = path20.join(policiesDir, `${policyName}.yml`);
10777
10944
  try {
10778
10945
  await fs19.access(ymlPath);
10779
10946
  return loadPolicyFile(ymlPath);
@@ -10914,7 +11081,7 @@ init_conditions();
10914
11081
  init_schema();
10915
11082
  init_writer();
10916
11083
  import fs21 from "fs/promises";
10917
- import path23 from "path";
11084
+ import path22 from "path";
10918
11085
  init_constants();
10919
11086
  async function executeStep(step, vaultPath2, context, conditionResults) {
10920
11087
  const { execute, reason } = shouldStepExecute(step.when, conditionResults);
@@ -10983,7 +11150,7 @@ async function executeAddToSection(params, vaultPath2, context) {
10983
11150
  const preserveListNesting = params.preserveListNesting !== false;
10984
11151
  const suggestOutgoingLinks = params.suggestOutgoingLinks !== false;
10985
11152
  const maxSuggestions = Number(params.maxSuggestions) || 3;
10986
- const fullPath = path23.join(vaultPath2, notePath);
11153
+ const fullPath = path22.join(vaultPath2, notePath);
10987
11154
  try {
10988
11155
  await fs21.access(fullPath);
10989
11156
  } catch {
@@ -11023,7 +11190,7 @@ async function executeRemoveFromSection(params, vaultPath2) {
11023
11190
  const pattern = String(params.pattern || "");
11024
11191
  const mode = params.mode || "first";
11025
11192
  const useRegex = Boolean(params.useRegex);
11026
- const fullPath = path23.join(vaultPath2, notePath);
11193
+ const fullPath = path22.join(vaultPath2, notePath);
11027
11194
  try {
11028
11195
  await fs21.access(fullPath);
11029
11196
  } catch {
@@ -11054,7 +11221,7 @@ async function executeReplaceInSection(params, vaultPath2, context) {
11054
11221
  const mode = params.mode || "first";
11055
11222
  const useRegex = Boolean(params.useRegex);
11056
11223
  const skipWikilinks = Boolean(params.skipWikilinks);
11057
- const fullPath = path23.join(vaultPath2, notePath);
11224
+ const fullPath = path22.join(vaultPath2, notePath);
11058
11225
  try {
11059
11226
  await fs21.access(fullPath);
11060
11227
  } catch {
@@ -11097,7 +11264,7 @@ async function executeCreateNote(params, vaultPath2, context) {
11097
11264
  if (!validatePath(vaultPath2, notePath)) {
11098
11265
  return { success: false, message: "Invalid path: path traversal not allowed", path: notePath };
11099
11266
  }
11100
- const fullPath = path23.join(vaultPath2, notePath);
11267
+ const fullPath = path22.join(vaultPath2, notePath);
11101
11268
  try {
11102
11269
  await fs21.access(fullPath);
11103
11270
  if (!overwrite) {
@@ -11105,7 +11272,7 @@ async function executeCreateNote(params, vaultPath2, context) {
11105
11272
  }
11106
11273
  } catch {
11107
11274
  }
11108
- const dir = path23.dirname(fullPath);
11275
+ const dir = path22.dirname(fullPath);
11109
11276
  await fs21.mkdir(dir, { recursive: true });
11110
11277
  const { content: processedContent } = maybeApplyWikilinks(content, skipWikilinks, notePath);
11111
11278
  await writeVaultFile(vaultPath2, notePath, processedContent, frontmatter);
@@ -11125,7 +11292,7 @@ async function executeDeleteNote(params, vaultPath2) {
11125
11292
  if (!validatePath(vaultPath2, notePath)) {
11126
11293
  return { success: false, message: "Invalid path: path traversal not allowed", path: notePath };
11127
11294
  }
11128
- const fullPath = path23.join(vaultPath2, notePath);
11295
+ const fullPath = path22.join(vaultPath2, notePath);
11129
11296
  try {
11130
11297
  await fs21.access(fullPath);
11131
11298
  } catch {
@@ -11142,7 +11309,7 @@ async function executeToggleTask(params, vaultPath2) {
11142
11309
  const notePath = String(params.path || "");
11143
11310
  const task = String(params.task || "");
11144
11311
  const section = params.section ? String(params.section) : void 0;
11145
- const fullPath = path23.join(vaultPath2, notePath);
11312
+ const fullPath = path22.join(vaultPath2, notePath);
11146
11313
  try {
11147
11314
  await fs21.access(fullPath);
11148
11315
  } catch {
@@ -11185,7 +11352,7 @@ async function executeAddTask(params, vaultPath2, context) {
11185
11352
  const completed = Boolean(params.completed);
11186
11353
  const skipWikilinks = Boolean(params.skipWikilinks);
11187
11354
  const preserveListNesting = params.preserveListNesting !== false;
11188
- const fullPath = path23.join(vaultPath2, notePath);
11355
+ const fullPath = path22.join(vaultPath2, notePath);
11189
11356
  try {
11190
11357
  await fs21.access(fullPath);
11191
11358
  } catch {
@@ -11222,7 +11389,7 @@ async function executeAddTask(params, vaultPath2, context) {
11222
11389
  async function executeUpdateFrontmatter(params, vaultPath2) {
11223
11390
  const notePath = String(params.path || "");
11224
11391
  const updates = params.frontmatter || {};
11225
- const fullPath = path23.join(vaultPath2, notePath);
11392
+ const fullPath = path22.join(vaultPath2, notePath);
11226
11393
  try {
11227
11394
  await fs21.access(fullPath);
11228
11395
  } catch {
@@ -11244,7 +11411,7 @@ async function executeAddFrontmatterField(params, vaultPath2) {
11244
11411
  const notePath = String(params.path || "");
11245
11412
  const key = String(params.key || "");
11246
11413
  const value = params.value;
11247
- const fullPath = path23.join(vaultPath2, notePath);
11414
+ const fullPath = path22.join(vaultPath2, notePath);
11248
11415
  try {
11249
11416
  await fs21.access(fullPath);
11250
11417
  } catch {
@@ -11404,7 +11571,7 @@ async function executePolicy(policy, vaultPath2, variables, commit = false) {
11404
11571
  async function rollbackChanges(vaultPath2, originalContents, filesModified) {
11405
11572
  for (const filePath of filesModified) {
11406
11573
  const original = originalContents.get(filePath);
11407
- const fullPath = path23.join(vaultPath2, filePath);
11574
+ const fullPath = path22.join(vaultPath2, filePath);
11408
11575
  if (original === null) {
11409
11576
  try {
11410
11577
  await fs21.unlink(fullPath);
@@ -11459,9 +11626,9 @@ async function previewPolicy(policy, vaultPath2, variables) {
11459
11626
 
11460
11627
  // src/core/write/policy/storage.ts
11461
11628
  import fs22 from "fs/promises";
11462
- import path24 from "path";
11629
+ import path23 from "path";
11463
11630
  function getPoliciesDir(vaultPath2) {
11464
- return path24.join(vaultPath2, ".claude", "policies");
11631
+ return path23.join(vaultPath2, ".claude", "policies");
11465
11632
  }
11466
11633
  async function ensurePoliciesDir(vaultPath2) {
11467
11634
  const dir = getPoliciesDir(vaultPath2);
@@ -11476,7 +11643,7 @@ async function listPolicies(vaultPath2) {
11476
11643
  if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
11477
11644
  continue;
11478
11645
  }
11479
- const filePath = path24.join(dir, file);
11646
+ const filePath = path23.join(dir, file);
11480
11647
  const stat3 = await fs22.stat(filePath);
11481
11648
  const content = await fs22.readFile(filePath, "utf-8");
11482
11649
  const metadata = extractPolicyMetadata(content);
@@ -11499,8 +11666,8 @@ async function listPolicies(vaultPath2) {
11499
11666
  }
11500
11667
  async function getPolicyPath(vaultPath2, policyName) {
11501
11668
  const dir = getPoliciesDir(vaultPath2);
11502
- const yamlPath = path24.join(dir, `${policyName}.yaml`);
11503
- const ymlPath = path24.join(dir, `${policyName}.yml`);
11669
+ const yamlPath = path23.join(dir, `${policyName}.yaml`);
11670
+ const ymlPath = path23.join(dir, `${policyName}.yml`);
11504
11671
  try {
11505
11672
  await fs22.access(yamlPath);
11506
11673
  return yamlPath;
@@ -11517,7 +11684,7 @@ async function savePolicy(vaultPath2, policy, overwrite = false) {
11517
11684
  const dir = getPoliciesDir(vaultPath2);
11518
11685
  await ensurePoliciesDir(vaultPath2);
11519
11686
  const filename = `${policy.name}.yaml`;
11520
- const filePath = path24.join(dir, filename);
11687
+ const filePath = path23.join(dir, filename);
11521
11688
  if (!overwrite) {
11522
11689
  try {
11523
11690
  await fs22.access(filePath);
@@ -11556,7 +11723,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
11556
11723
  const dir = getPoliciesDir(vaultPath2);
11557
11724
  await ensurePoliciesDir(vaultPath2);
11558
11725
  const filename = `${policyName}.yaml`;
11559
- const filePath = path24.join(dir, filename);
11726
+ const filePath = path23.join(dir, filename);
11560
11727
  if (!overwrite) {
11561
11728
  try {
11562
11729
  await fs22.access(filePath);
@@ -12554,7 +12721,7 @@ registerMigrationTools(server, () => vaultIndex, () => vaultPath);
12554
12721
  registerMutationTools(server, vaultPath);
12555
12722
  registerTaskTools(server, vaultPath);
12556
12723
  registerFrontmatterTools(server, vaultPath);
12557
- registerNoteTools(server, vaultPath);
12724
+ registerNoteTools(server, vaultPath, () => vaultIndex);
12558
12725
  registerMoveNoteTools(server, vaultPath);
12559
12726
  registerSystemTools2(server, vaultPath);
12560
12727
  registerPolicyTools(server, vaultPath);
@@ -12566,6 +12733,7 @@ async function main() {
12566
12733
  try {
12567
12734
  stateDb = openStateDb(vaultPath);
12568
12735
  console.error("[Memory] StateDb initialized");
12736
+ setFTS5Database(stateDb.db);
12569
12737
  setCrankStateDb(stateDb);
12570
12738
  await initializeEntityIndex(vaultPath);
12571
12739
  } catch (err) {
@@ -12723,8 +12891,8 @@ async function runPostIndexWork(index) {
12723
12891
  }
12724
12892
  });
12725
12893
  let rebuildTimer;
12726
- legacyWatcher.on("all", (event, path25) => {
12727
- if (!path25.endsWith(".md")) return;
12894
+ legacyWatcher.on("all", (event, path24) => {
12895
+ if (!path24.endsWith(".md")) return;
12728
12896
  clearTimeout(rebuildTimer);
12729
12897
  rebuildTimer = setTimeout(() => {
12730
12898
  console.error("[Memory] Rebuilding index (file changed)");