@velvetmonkey/flywheel-memory 2.0.35 → 2.0.36

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 +438 -196
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ var init_constants = __esm({
59
59
 
60
60
  // src/core/write/writer.ts
61
61
  import fs18 from "fs/promises";
62
- import path18 from "path";
62
+ import path19 from "path";
63
63
  import matter5 from "gray-matter";
64
64
  function isSensitivePath(filePath) {
65
65
  const normalizedPath = filePath.replace(/\\/g, "/");
@@ -386,8 +386,8 @@ function validatePath(vaultPath2, notePath) {
386
386
  if (notePath.startsWith("\\")) {
387
387
  return false;
388
388
  }
389
- const resolvedVault = path18.resolve(vaultPath2);
390
- const resolvedNote = path18.resolve(vaultPath2, notePath);
389
+ const resolvedVault = path19.resolve(vaultPath2);
390
+ const resolvedNote = path19.resolve(vaultPath2, notePath);
391
391
  return resolvedNote.startsWith(resolvedVault);
392
392
  }
393
393
  async function validatePathSecure(vaultPath2, notePath) {
@@ -415,8 +415,8 @@ async function validatePathSecure(vaultPath2, notePath) {
415
415
  reason: "Path traversal not allowed"
416
416
  };
417
417
  }
418
- const resolvedVault = path18.resolve(vaultPath2);
419
- const resolvedNote = path18.resolve(vaultPath2, notePath);
418
+ const resolvedVault = path19.resolve(vaultPath2);
419
+ const resolvedNote = path19.resolve(vaultPath2, notePath);
420
420
  if (!resolvedNote.startsWith(resolvedVault)) {
421
421
  return {
422
422
  valid: false,
@@ -430,7 +430,7 @@ async function validatePathSecure(vaultPath2, notePath) {
430
430
  };
431
431
  }
432
432
  try {
433
- const fullPath = path18.join(vaultPath2, notePath);
433
+ const fullPath = path19.join(vaultPath2, notePath);
434
434
  try {
435
435
  await fs18.access(fullPath);
436
436
  const realPath = await fs18.realpath(fullPath);
@@ -441,7 +441,7 @@ async function validatePathSecure(vaultPath2, notePath) {
441
441
  reason: "Symlink target is outside vault"
442
442
  };
443
443
  }
444
- const relativePath = path18.relative(realVaultPath, realPath);
444
+ const relativePath = path19.relative(realVaultPath, realPath);
445
445
  if (isSensitivePath(relativePath)) {
446
446
  return {
447
447
  valid: false,
@@ -449,7 +449,7 @@ async function validatePathSecure(vaultPath2, notePath) {
449
449
  };
450
450
  }
451
451
  } catch {
452
- const parentDir = path18.dirname(fullPath);
452
+ const parentDir = path19.dirname(fullPath);
453
453
  try {
454
454
  await fs18.access(parentDir);
455
455
  const realParentPath = await fs18.realpath(parentDir);
@@ -475,7 +475,7 @@ async function readVaultFile(vaultPath2, notePath) {
475
475
  if (!validatePath(vaultPath2, notePath)) {
476
476
  throw new Error("Invalid path: path traversal not allowed");
477
477
  }
478
- const fullPath = path18.join(vaultPath2, notePath);
478
+ const fullPath = path19.join(vaultPath2, notePath);
479
479
  const [rawContent, stat3] = await Promise.all([
480
480
  fs18.readFile(fullPath, "utf-8"),
481
481
  fs18.stat(fullPath)
@@ -528,7 +528,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
528
528
  if (!validation.valid) {
529
529
  throw new Error(`Invalid path: ${validation.reason}`);
530
530
  }
531
- const fullPath = path18.join(vaultPath2, notePath);
531
+ const fullPath = path19.join(vaultPath2, notePath);
532
532
  let output = matter5.stringify(content, frontmatter);
533
533
  output = normalizeTrailingNewline(output);
534
534
  output = convertLineEndings(output, lineEnding);
@@ -836,8 +836,8 @@ function createContext(variables = {}) {
836
836
  steps: {}
837
837
  };
838
838
  }
839
- function resolvePath(obj, path30) {
840
- const parts = path30.split(".");
839
+ function resolvePath(obj, path31) {
840
+ const parts = path31.split(".");
841
841
  let current = obj;
842
842
  for (const part of parts) {
843
843
  if (current === void 0 || current === null) {
@@ -1279,7 +1279,7 @@ __export(conditions_exports, {
1279
1279
  shouldStepExecute: () => shouldStepExecute
1280
1280
  });
1281
1281
  import fs25 from "fs/promises";
1282
- import path24 from "path";
1282
+ import path25 from "path";
1283
1283
  async function evaluateCondition(condition, vaultPath2, context) {
1284
1284
  const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
1285
1285
  const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
@@ -1332,7 +1332,7 @@ async function evaluateCondition(condition, vaultPath2, context) {
1332
1332
  }
1333
1333
  }
1334
1334
  async function evaluateFileExists(vaultPath2, notePath, expectExists) {
1335
- const fullPath = path24.join(vaultPath2, notePath);
1335
+ const fullPath = path25.join(vaultPath2, notePath);
1336
1336
  try {
1337
1337
  await fs25.access(fullPath);
1338
1338
  return {
@@ -1347,7 +1347,7 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
1347
1347
  }
1348
1348
  }
1349
1349
  async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
1350
- const fullPath = path24.join(vaultPath2, notePath);
1350
+ const fullPath = path25.join(vaultPath2, notePath);
1351
1351
  try {
1352
1352
  await fs25.access(fullPath);
1353
1353
  } catch {
@@ -1378,7 +1378,7 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
1378
1378
  }
1379
1379
  }
1380
1380
  async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
1381
- const fullPath = path24.join(vaultPath2, notePath);
1381
+ const fullPath = path25.join(vaultPath2, notePath);
1382
1382
  try {
1383
1383
  await fs25.access(fullPath);
1384
1384
  } catch {
@@ -1409,7 +1409,7 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
1409
1409
  }
1410
1410
  }
1411
1411
  async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
1412
- const fullPath = path24.join(vaultPath2, notePath);
1412
+ const fullPath = path25.join(vaultPath2, notePath);
1413
1413
  try {
1414
1414
  await fs25.access(fullPath);
1415
1415
  } catch {
@@ -1552,7 +1552,7 @@ var init_taskHelpers = __esm({
1552
1552
  });
1553
1553
 
1554
1554
  // src/index.ts
1555
- import * as path29 from "path";
1555
+ import * as path30 from "path";
1556
1556
  import { readFileSync as readFileSync4, realpathSync } from "fs";
1557
1557
  import { fileURLToPath } from "url";
1558
1558
  import { dirname as dirname4, join as join16 } from "path";
@@ -2216,8 +2216,8 @@ function updateIndexProgress(parsed, total) {
2216
2216
  function normalizeTarget(target) {
2217
2217
  return target.toLowerCase().replace(/\.md$/, "");
2218
2218
  }
2219
- function normalizeNotePath(path30) {
2220
- return path30.toLowerCase().replace(/\.md$/, "");
2219
+ function normalizeNotePath(path31) {
2220
+ return path31.toLowerCase().replace(/\.md$/, "");
2221
2221
  }
2222
2222
  async function buildVaultIndex(vaultPath2, options = {}) {
2223
2223
  const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
@@ -2386,7 +2386,7 @@ function findSimilarEntity(index, target) {
2386
2386
  }
2387
2387
  const maxDist = normalizedLen <= 10 ? 1 : 2;
2388
2388
  let bestMatch;
2389
- for (const [entity, path30] of index.entities) {
2389
+ for (const [entity, path31] of index.entities) {
2390
2390
  const lenDiff = Math.abs(entity.length - normalizedLen);
2391
2391
  if (lenDiff > maxDist) {
2392
2392
  continue;
@@ -2394,7 +2394,7 @@ function findSimilarEntity(index, target) {
2394
2394
  const dist = levenshteinDistance(normalized, entity);
2395
2395
  if (dist > 0 && dist <= maxDist) {
2396
2396
  if (!bestMatch || dist < bestMatch.distance) {
2397
- bestMatch = { path: path30, entity, distance: dist };
2397
+ bestMatch = { path: path31, entity, distance: dist };
2398
2398
  if (dist === 1) {
2399
2399
  return bestMatch;
2400
2400
  }
@@ -2864,30 +2864,30 @@ var EventQueue = class {
2864
2864
  * Add a new event to the queue
2865
2865
  */
2866
2866
  push(type, rawPath) {
2867
- const path30 = normalizePath(rawPath);
2867
+ const path31 = normalizePath(rawPath);
2868
2868
  const now = Date.now();
2869
2869
  const event = {
2870
2870
  type,
2871
- path: path30,
2871
+ path: path31,
2872
2872
  timestamp: now
2873
2873
  };
2874
- let pending = this.pending.get(path30);
2874
+ let pending = this.pending.get(path31);
2875
2875
  if (!pending) {
2876
2876
  pending = {
2877
2877
  events: [],
2878
2878
  timer: null,
2879
2879
  lastEvent: now
2880
2880
  };
2881
- this.pending.set(path30, pending);
2881
+ this.pending.set(path31, pending);
2882
2882
  }
2883
2883
  pending.events.push(event);
2884
2884
  pending.lastEvent = now;
2885
- console.error(`[flywheel] QUEUE: pushed ${type} for ${path30}, pending=${this.pending.size}`);
2885
+ console.error(`[flywheel] QUEUE: pushed ${type} for ${path31}, pending=${this.pending.size}`);
2886
2886
  if (pending.timer) {
2887
2887
  clearTimeout(pending.timer);
2888
2888
  }
2889
2889
  pending.timer = setTimeout(() => {
2890
- this.flushPath(path30);
2890
+ this.flushPath(path31);
2891
2891
  }, this.config.debounceMs);
2892
2892
  if (this.pending.size >= this.config.batchSize) {
2893
2893
  this.flush();
@@ -2908,10 +2908,10 @@ var EventQueue = class {
2908
2908
  /**
2909
2909
  * Flush a single path's events
2910
2910
  */
2911
- flushPath(path30) {
2912
- const pending = this.pending.get(path30);
2911
+ flushPath(path31) {
2912
+ const pending = this.pending.get(path31);
2913
2913
  if (!pending || pending.events.length === 0) return;
2914
- console.error(`[flywheel] QUEUE: flushing ${path30}, events=${pending.events.length}`);
2914
+ console.error(`[flywheel] QUEUE: flushing ${path31}, events=${pending.events.length}`);
2915
2915
  if (pending.timer) {
2916
2916
  clearTimeout(pending.timer);
2917
2917
  pending.timer = null;
@@ -2920,7 +2920,7 @@ var EventQueue = class {
2920
2920
  if (coalescedType) {
2921
2921
  const coalesced = {
2922
2922
  type: coalescedType,
2923
- path: path30,
2923
+ path: path31,
2924
2924
  originalEvents: [...pending.events]
2925
2925
  };
2926
2926
  this.onBatch({
@@ -2928,7 +2928,7 @@ var EventQueue = class {
2928
2928
  timestamp: Date.now()
2929
2929
  });
2930
2930
  }
2931
- this.pending.delete(path30);
2931
+ this.pending.delete(path31);
2932
2932
  }
2933
2933
  /**
2934
2934
  * Flush all pending events
@@ -2940,7 +2940,7 @@ var EventQueue = class {
2940
2940
  }
2941
2941
  if (this.pending.size === 0) return;
2942
2942
  const events = [];
2943
- for (const [path30, pending] of this.pending) {
2943
+ for (const [path31, pending] of this.pending) {
2944
2944
  if (pending.timer) {
2945
2945
  clearTimeout(pending.timer);
2946
2946
  }
@@ -2948,7 +2948,7 @@ var EventQueue = class {
2948
2948
  if (coalescedType) {
2949
2949
  events.push({
2950
2950
  type: coalescedType,
2951
- path: path30,
2951
+ path: path31,
2952
2952
  originalEvents: [...pending.events]
2953
2953
  });
2954
2954
  }
@@ -3023,6 +3023,208 @@ function parseWatcherConfig() {
3023
3023
  };
3024
3024
  }
3025
3025
 
3026
+ // src/core/read/watch/incrementalIndex.ts
3027
+ import path6 from "path";
3028
+ function normalizeTarget2(target) {
3029
+ return target.toLowerCase().replace(/\.md$/, "");
3030
+ }
3031
+ function normalizeNotePath2(notePath) {
3032
+ return notePath.toLowerCase().replace(/\.md$/, "");
3033
+ }
3034
+ function removeNoteFromIndex(index, notePath) {
3035
+ const note = index.notes.get(notePath);
3036
+ if (!note) {
3037
+ return false;
3038
+ }
3039
+ index.notes.delete(notePath);
3040
+ const normalizedTitle = normalizeTarget2(note.title);
3041
+ const normalizedPath = normalizeNotePath2(notePath);
3042
+ if (index.entities.get(normalizedTitle) === notePath) {
3043
+ index.entities.delete(normalizedTitle);
3044
+ }
3045
+ if (index.entities.get(normalizedPath) === notePath) {
3046
+ index.entities.delete(normalizedPath);
3047
+ }
3048
+ for (const alias of note.aliases) {
3049
+ const normalizedAlias = normalizeTarget2(alias);
3050
+ if (index.entities.get(normalizedAlias) === notePath) {
3051
+ index.entities.delete(normalizedAlias);
3052
+ }
3053
+ }
3054
+ for (const tag of note.tags) {
3055
+ const tagPaths = index.tags.get(tag);
3056
+ if (tagPaths) {
3057
+ tagPaths.delete(notePath);
3058
+ if (tagPaths.size === 0) {
3059
+ index.tags.delete(tag);
3060
+ }
3061
+ }
3062
+ }
3063
+ for (const link of note.outlinks) {
3064
+ const normalizedTarget = normalizeTarget2(link.target);
3065
+ const targetPath = index.entities.get(normalizedTarget);
3066
+ const key = targetPath ? normalizeNotePath2(targetPath) : normalizedTarget;
3067
+ const backlinks = index.backlinks.get(key);
3068
+ if (backlinks) {
3069
+ const filtered = backlinks.filter((bl) => bl.source !== notePath);
3070
+ if (filtered.length === 0) {
3071
+ index.backlinks.delete(key);
3072
+ } else {
3073
+ index.backlinks.set(key, filtered);
3074
+ }
3075
+ }
3076
+ }
3077
+ return true;
3078
+ }
3079
+ function addNoteToIndex(index, note) {
3080
+ index.notes.set(note.path, note);
3081
+ const normalizedTitle = normalizeTarget2(note.title);
3082
+ const normalizedPath = normalizeNotePath2(note.path);
3083
+ if (!index.entities.has(normalizedTitle)) {
3084
+ index.entities.set(normalizedTitle, note.path);
3085
+ }
3086
+ index.entities.set(normalizedPath, note.path);
3087
+ for (const alias of note.aliases) {
3088
+ const normalizedAlias = normalizeTarget2(alias);
3089
+ if (!index.entities.has(normalizedAlias)) {
3090
+ index.entities.set(normalizedAlias, note.path);
3091
+ }
3092
+ }
3093
+ for (const tag of note.tags) {
3094
+ if (!index.tags.has(tag)) {
3095
+ index.tags.set(tag, /* @__PURE__ */ new Set());
3096
+ }
3097
+ index.tags.get(tag).add(note.path);
3098
+ }
3099
+ for (const link of note.outlinks) {
3100
+ const normalizedTarget = normalizeTarget2(link.target);
3101
+ const targetPath = index.entities.get(normalizedTarget);
3102
+ const key = targetPath ? normalizeNotePath2(targetPath) : normalizedTarget;
3103
+ if (!index.backlinks.has(key)) {
3104
+ index.backlinks.set(key, []);
3105
+ }
3106
+ index.backlinks.get(key).push({
3107
+ source: note.path,
3108
+ line: link.line
3109
+ });
3110
+ }
3111
+ }
3112
+ async function upsertNote(index, vaultPath2, notePath) {
3113
+ try {
3114
+ const existed = index.notes.has(notePath);
3115
+ if (existed) {
3116
+ removeNoteFromIndex(index, notePath);
3117
+ }
3118
+ const fullPath = path6.join(vaultPath2, notePath);
3119
+ const fs31 = await import("fs/promises");
3120
+ const stats = await fs31.stat(fullPath);
3121
+ const vaultFile = {
3122
+ path: notePath,
3123
+ absolutePath: fullPath,
3124
+ modified: stats.mtime
3125
+ };
3126
+ const note = await parseNote(vaultFile);
3127
+ addNoteToIndex(index, note);
3128
+ return {
3129
+ success: true,
3130
+ action: existed ? "updated" : "added",
3131
+ path: notePath
3132
+ };
3133
+ } catch (error) {
3134
+ return {
3135
+ success: false,
3136
+ action: "unchanged",
3137
+ path: notePath,
3138
+ error: error instanceof Error ? error : new Error(String(error))
3139
+ };
3140
+ }
3141
+ }
3142
+ function deleteNote(index, notePath) {
3143
+ const removed = removeNoteFromIndex(index, notePath);
3144
+ return {
3145
+ success: removed,
3146
+ action: removed ? "removed" : "unchanged",
3147
+ path: notePath
3148
+ };
3149
+ }
3150
+
3151
+ // src/core/read/watch/batchProcessor.ts
3152
+ var DEFAULT_CONCURRENCY = 4;
3153
+ var YIELD_INTERVAL = 10;
3154
+ async function processBatch(index, vaultPath2, batch, options = {}) {
3155
+ const { concurrency = DEFAULT_CONCURRENCY, onProgress, onError } = options;
3156
+ const startTime = Date.now();
3157
+ const results = [];
3158
+ let successful = 0;
3159
+ let failed = 0;
3160
+ let processed = 0;
3161
+ const events = batch.events;
3162
+ const total = events.length;
3163
+ if (total === 0) {
3164
+ return {
3165
+ total: 0,
3166
+ successful: 0,
3167
+ failed: 0,
3168
+ results: [],
3169
+ durationMs: 0
3170
+ };
3171
+ }
3172
+ console.error(`[flywheel] Processing ${total} file events`);
3173
+ for (let i = 0; i < events.length; i += concurrency) {
3174
+ const chunk = events.slice(i, i + concurrency);
3175
+ const chunkResults = await Promise.allSettled(
3176
+ chunk.map(async (event) => {
3177
+ const relativePath = getRelativePath(vaultPath2, event.path);
3178
+ if (event.type === "delete") {
3179
+ return deleteNote(index, relativePath);
3180
+ } else {
3181
+ return upsertNote(index, vaultPath2, relativePath);
3182
+ }
3183
+ })
3184
+ );
3185
+ for (let j = 0; j < chunkResults.length; j++) {
3186
+ const result = chunkResults[j];
3187
+ processed++;
3188
+ if (result.status === "fulfilled") {
3189
+ results.push(result.value);
3190
+ if (result.value.success) {
3191
+ successful++;
3192
+ } else {
3193
+ failed++;
3194
+ if (result.value.error && onError) {
3195
+ onError(result.value.path, result.value.error);
3196
+ }
3197
+ }
3198
+ } else {
3199
+ failed++;
3200
+ const event = chunk[j];
3201
+ const relativePath = getRelativePath(vaultPath2, event.path);
3202
+ const error = result.reason instanceof Error ? result.reason : new Error(String(result.reason));
3203
+ results.push({
3204
+ success: false,
3205
+ action: "unchanged",
3206
+ path: relativePath,
3207
+ error
3208
+ });
3209
+ onError?.(relativePath, error);
3210
+ }
3211
+ }
3212
+ onProgress?.(processed, total);
3213
+ if (processed % YIELD_INTERVAL === 0 && processed < total) {
3214
+ await new Promise((resolve2) => setImmediate(resolve2));
3215
+ }
3216
+ }
3217
+ const durationMs = Date.now() - startTime;
3218
+ console.error(`[flywheel] Processed ${successful}/${total} files in ${durationMs}ms`);
3219
+ return {
3220
+ total,
3221
+ successful,
3222
+ failed,
3223
+ results,
3224
+ durationMs
3225
+ };
3226
+ }
3227
+
3026
3228
  // src/core/read/watch/index.ts
3027
3229
  function createVaultWatcher(options) {
3028
3230
  const { vaultPath: vaultPath2, onBatch, onStateChange, onError } = options;
@@ -3097,31 +3299,31 @@ function createVaultWatcher(options) {
3097
3299
  usePolling: config.usePolling,
3098
3300
  interval: config.usePolling ? config.pollInterval : void 0
3099
3301
  });
3100
- watcher.on("add", (path30) => {
3101
- console.error(`[flywheel] RAW EVENT: add ${path30}`);
3102
- if (shouldWatch(path30, vaultPath2)) {
3103
- console.error(`[flywheel] ACCEPTED: add ${path30}`);
3104
- eventQueue.push("add", path30);
3302
+ watcher.on("add", (path31) => {
3303
+ console.error(`[flywheel] RAW EVENT: add ${path31}`);
3304
+ if (shouldWatch(path31, vaultPath2)) {
3305
+ console.error(`[flywheel] ACCEPTED: add ${path31}`);
3306
+ eventQueue.push("add", path31);
3105
3307
  } else {
3106
- console.error(`[flywheel] FILTERED: add ${path30}`);
3308
+ console.error(`[flywheel] FILTERED: add ${path31}`);
3107
3309
  }
3108
3310
  });
3109
- watcher.on("change", (path30) => {
3110
- console.error(`[flywheel] RAW EVENT: change ${path30}`);
3111
- if (shouldWatch(path30, vaultPath2)) {
3112
- console.error(`[flywheel] ACCEPTED: change ${path30}`);
3113
- eventQueue.push("change", path30);
3311
+ watcher.on("change", (path31) => {
3312
+ console.error(`[flywheel] RAW EVENT: change ${path31}`);
3313
+ if (shouldWatch(path31, vaultPath2)) {
3314
+ console.error(`[flywheel] ACCEPTED: change ${path31}`);
3315
+ eventQueue.push("change", path31);
3114
3316
  } else {
3115
- console.error(`[flywheel] FILTERED: change ${path30}`);
3317
+ console.error(`[flywheel] FILTERED: change ${path31}`);
3116
3318
  }
3117
3319
  });
3118
- watcher.on("unlink", (path30) => {
3119
- console.error(`[flywheel] RAW EVENT: unlink ${path30}`);
3120
- if (shouldWatch(path30, vaultPath2)) {
3121
- console.error(`[flywheel] ACCEPTED: unlink ${path30}`);
3122
- eventQueue.push("unlink", path30);
3320
+ watcher.on("unlink", (path31) => {
3321
+ console.error(`[flywheel] RAW EVENT: unlink ${path31}`);
3322
+ if (shouldWatch(path31, vaultPath2)) {
3323
+ console.error(`[flywheel] ACCEPTED: unlink ${path31}`);
3324
+ eventQueue.push("unlink", path31);
3123
3325
  } else {
3124
- console.error(`[flywheel] FILTERED: unlink ${path30}`);
3326
+ console.error(`[flywheel] FILTERED: unlink ${path31}`);
3125
3327
  }
3126
3328
  });
3127
3329
  watcher.on("ready", () => {
@@ -3725,7 +3927,7 @@ function getExtendedDashboardData(stateDb2) {
3725
3927
 
3726
3928
  // src/core/write/git.ts
3727
3929
  import { simpleGit, CheckRepoActions } from "simple-git";
3728
- import path6 from "path";
3930
+ import path7 from "path";
3729
3931
  import fs6 from "fs/promises";
3730
3932
  import {
3731
3933
  setWriteState,
@@ -3779,7 +3981,7 @@ function clearLastMutationCommit() {
3779
3981
  }
3780
3982
  }
3781
3983
  async function checkGitLock(vaultPath2) {
3782
- const lockPath = path6.join(vaultPath2, ".git/index.lock");
3984
+ const lockPath = path7.join(vaultPath2, ".git/index.lock");
3783
3985
  try {
3784
3986
  const stat3 = await fs6.stat(lockPath);
3785
3987
  const ageMs = Date.now() - stat3.mtimeMs;
@@ -3802,7 +4004,7 @@ async function isGitRepo(vaultPath2) {
3802
4004
  }
3803
4005
  }
3804
4006
  async function checkLockFile(vaultPath2) {
3805
- const lockPath = path6.join(vaultPath2, ".git/index.lock");
4007
+ const lockPath = path7.join(vaultPath2, ".git/index.lock");
3806
4008
  try {
3807
4009
  const stat3 = await fs6.stat(lockPath);
3808
4010
  const ageMs = Date.now() - stat3.mtimeMs;
@@ -3852,7 +4054,7 @@ async function commitChange(vaultPath2, filePath, messagePrefix, retryConfig = D
3852
4054
  }
3853
4055
  }
3854
4056
  await git.add(filePath);
3855
- const fileName = path6.basename(filePath);
4057
+ const fileName = path7.basename(filePath);
3856
4058
  const commitMessage = `${messagePrefix} Update ${fileName}`;
3857
4059
  const result = await git.commit(commitMessage);
3858
4060
  if (result.commit) {
@@ -4046,7 +4248,7 @@ function setHintsStateDb(stateDb2) {
4046
4248
 
4047
4249
  // src/core/shared/recency.ts
4048
4250
  import { readdir, readFile, stat } from "fs/promises";
4049
- import path7 from "path";
4251
+ import path8 from "path";
4050
4252
  import {
4051
4253
  getEntityName,
4052
4254
  recordEntityMention,
@@ -4068,9 +4270,9 @@ async function* walkMarkdownFiles(dir, baseDir) {
4068
4270
  try {
4069
4271
  const entries = await readdir(dir, { withFileTypes: true });
4070
4272
  for (const entry of entries) {
4071
- const fullPath = path7.join(dir, entry.name);
4072
- const relativePath = path7.relative(baseDir, fullPath);
4073
- const topFolder = relativePath.split(path7.sep)[0];
4273
+ const fullPath = path8.join(dir, entry.name);
4274
+ const relativePath = path8.relative(baseDir, fullPath);
4275
+ const topFolder = relativePath.split(path8.sep)[0];
4074
4276
  if (EXCLUDED_FOLDERS.has(topFolder)) {
4075
4277
  continue;
4076
4278
  }
@@ -4950,7 +5152,7 @@ function tokenize(text) {
4950
5152
 
4951
5153
  // src/core/shared/cooccurrence.ts
4952
5154
  import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
4953
- import path8 from "path";
5155
+ import path9 from "path";
4954
5156
  var DEFAULT_MIN_COOCCURRENCE = 2;
4955
5157
  var EXCLUDED_FOLDERS2 = /* @__PURE__ */ new Set([
4956
5158
  "templates",
@@ -4984,9 +5186,9 @@ async function* walkMarkdownFiles2(dir, baseDir) {
4984
5186
  try {
4985
5187
  const entries = await readdir2(dir, { withFileTypes: true });
4986
5188
  for (const entry of entries) {
4987
- const fullPath = path8.join(dir, entry.name);
4988
- const relativePath = path8.relative(baseDir, fullPath);
4989
- const topFolder = relativePath.split(path8.sep)[0];
5189
+ const fullPath = path9.join(dir, entry.name);
5190
+ const relativePath = path9.relative(baseDir, fullPath);
5191
+ const topFolder = relativePath.split(path9.sep)[0];
4990
5192
  if (EXCLUDED_FOLDERS2.has(topFolder)) {
4991
5193
  continue;
4992
5194
  }
@@ -5249,9 +5451,11 @@ function processWikilinks(content, notePath) {
5249
5451
  const resolved = resolveAliasWikilinks(content, sortedEntities, {
5250
5452
  caseInsensitive: true
5251
5453
  });
5454
+ const step1LinkedEntities = new Set(resolved.linkedEntities.map((e) => e.toLowerCase()));
5252
5455
  const result = applyWikilinks(resolved.content, sortedEntities, {
5253
5456
  firstOccurrenceOnly: true,
5254
- caseInsensitive: true
5457
+ caseInsensitive: true,
5458
+ alreadyLinked: step1LinkedEntities
5255
5459
  });
5256
5460
  const implicitEnabled = moduleConfig?.implicit_detection !== false;
5257
5461
  const validPatterns = new Set(ALL_IMPLICIT_PATTERNS);
@@ -6298,11 +6502,11 @@ function countFTS5Mentions(term) {
6298
6502
  }
6299
6503
 
6300
6504
  // src/core/read/taskCache.ts
6301
- import * as path10 from "path";
6505
+ import * as path11 from "path";
6302
6506
 
6303
6507
  // src/tools/read/tasks.ts
6304
6508
  import * as fs8 from "fs";
6305
- import * as path9 from "path";
6509
+ import * as path10 from "path";
6306
6510
  var TASK_REGEX = /^(\s*)- \[([ xX\-])\]\s+(.+)$/;
6307
6511
  var TAG_REGEX2 = /#([a-zA-Z][a-zA-Z0-9_/-]*)/g;
6308
6512
  var DATE_REGEX = /\b(\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4})\b/;
@@ -6371,7 +6575,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
6371
6575
  const allTasks = [];
6372
6576
  for (const note of index.notes.values()) {
6373
6577
  if (folder && !note.path.startsWith(folder)) continue;
6374
- const absolutePath = path9.join(vaultPath2, note.path);
6578
+ const absolutePath = path10.join(vaultPath2, note.path);
6375
6579
  const tasks = await extractTasksFromNote(note.path, absolutePath);
6376
6580
  allTasks.push(...tasks);
6377
6581
  }
@@ -6415,7 +6619,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
6415
6619
  async function getTasksFromNote(index, notePath, vaultPath2, excludeTags = []) {
6416
6620
  const note = index.notes.get(notePath);
6417
6621
  if (!note) return null;
6418
- const absolutePath = path9.join(vaultPath2, notePath);
6622
+ const absolutePath = path10.join(vaultPath2, notePath);
6419
6623
  let tasks = await extractTasksFromNote(notePath, absolutePath);
6420
6624
  if (excludeTags.length > 0) {
6421
6625
  tasks = tasks.filter(
@@ -6507,7 +6711,7 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
6507
6711
  }
6508
6712
  const allRows = [];
6509
6713
  for (const notePath of notePaths) {
6510
- const absolutePath = path10.join(vaultPath2, notePath);
6714
+ const absolutePath = path11.join(vaultPath2, notePath);
6511
6715
  const tasks = await extractTasksFromNote(notePath, absolutePath);
6512
6716
  for (const task of tasks) {
6513
6717
  if (excludeTags?.length && excludeTags.some((t) => task.tags.includes(t))) {
@@ -6549,7 +6753,7 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
6549
6753
  async function updateTaskCacheForFile(vaultPath2, relativePath) {
6550
6754
  if (!db3) return;
6551
6755
  db3.prepare("DELETE FROM tasks WHERE path = ?").run(relativePath);
6552
- const absolutePath = path10.join(vaultPath2, relativePath);
6756
+ const absolutePath = path11.join(vaultPath2, relativePath);
6553
6757
  const tasks = await extractTasksFromNote(relativePath, absolutePath);
6554
6758
  if (tasks.length > 0) {
6555
6759
  const insertStmt = db3.prepare(`
@@ -6689,7 +6893,7 @@ import { openStateDb, scanVaultEntities as scanVaultEntities3, getSessionId, get
6689
6893
 
6690
6894
  // src/tools/read/graph.ts
6691
6895
  import * as fs9 from "fs";
6692
- import * as path11 from "path";
6896
+ import * as path12 from "path";
6693
6897
  import { z } from "zod";
6694
6898
 
6695
6899
  // src/core/read/constants.ts
@@ -6973,7 +7177,7 @@ function requireIndex() {
6973
7177
  // src/tools/read/graph.ts
6974
7178
  async function getContext(vaultPath2, sourcePath, line, contextLines = 1) {
6975
7179
  try {
6976
- const fullPath = path11.join(vaultPath2, sourcePath);
7180
+ const fullPath = path12.join(vaultPath2, sourcePath);
6977
7181
  const content = await fs9.promises.readFile(fullPath, "utf-8");
6978
7182
  const allLines = content.split("\n");
6979
7183
  let fmLines = 0;
@@ -7287,14 +7491,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
7287
7491
  };
7288
7492
  function findSimilarEntity2(target, entities) {
7289
7493
  const targetLower = target.toLowerCase();
7290
- for (const [name, path30] of entities) {
7494
+ for (const [name, path31] of entities) {
7291
7495
  if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
7292
- return path30;
7496
+ return path31;
7293
7497
  }
7294
7498
  }
7295
- for (const [name, path30] of entities) {
7499
+ for (const [name, path31] of entities) {
7296
7500
  if (name.includes(targetLower) || targetLower.includes(name)) {
7297
- return path30;
7501
+ return path31;
7298
7502
  }
7299
7503
  }
7300
7504
  return void 0;
@@ -8271,8 +8475,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
8271
8475
  daily_counts: z3.record(z3.number())
8272
8476
  }).describe("Activity summary for the last 7 days")
8273
8477
  };
8274
- function isPeriodicNote2(path30) {
8275
- const filename = path30.split("/").pop() || "";
8478
+ function isPeriodicNote2(path31) {
8479
+ const filename = path31.split("/").pop() || "";
8276
8480
  const nameWithoutExt = filename.replace(/\.md$/, "");
8277
8481
  const patterns = [
8278
8482
  /^\d{4}-\d{2}-\d{2}$/,
@@ -8287,7 +8491,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
8287
8491
  // YYYY (yearly)
8288
8492
  ];
8289
8493
  const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
8290
- const folder = path30.split("/")[0]?.toLowerCase() || "";
8494
+ const folder = path31.split("/")[0]?.toLowerCase() || "";
8291
8495
  return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
8292
8496
  }
8293
8497
  server2.registerTool(
@@ -8695,7 +8899,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
8695
8899
 
8696
8900
  // src/tools/read/system.ts
8697
8901
  import * as fs11 from "fs";
8698
- import * as path12 from "path";
8902
+ import * as path13 from "path";
8699
8903
  import { z as z5 } from "zod";
8700
8904
  import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
8701
8905
 
@@ -8995,7 +9199,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
8995
9199
  continue;
8996
9200
  }
8997
9201
  try {
8998
- const fullPath = path12.join(vaultPath2, note.path);
9202
+ const fullPath = path13.join(vaultPath2, note.path);
8999
9203
  const content = await fs11.promises.readFile(fullPath, "utf-8");
9000
9204
  const lines = content.split("\n");
9001
9205
  for (let i = 0; i < lines.length; i++) {
@@ -9111,7 +9315,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
9111
9315
  let wordCount;
9112
9316
  if (include_word_count) {
9113
9317
  try {
9114
- const fullPath = path12.join(vaultPath2, resolvedPath);
9318
+ const fullPath = path13.join(vaultPath2, resolvedPath);
9115
9319
  const content = await fs11.promises.readFile(fullPath, "utf-8");
9116
9320
  wordCount = content.split(/\s+/).filter((w) => w.length > 0).length;
9117
9321
  } catch {
@@ -9342,7 +9546,7 @@ import { z as z6 } from "zod";
9342
9546
 
9343
9547
  // src/tools/read/structure.ts
9344
9548
  import * as fs12 from "fs";
9345
- import * as path13 from "path";
9549
+ import * as path14 from "path";
9346
9550
  var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
9347
9551
  function extractHeadings(content) {
9348
9552
  const lines = content.split("\n");
@@ -9396,7 +9600,7 @@ function buildSections(headings, totalLines) {
9396
9600
  async function getNoteStructure(index, notePath, vaultPath2) {
9397
9601
  const note = index.notes.get(notePath);
9398
9602
  if (!note) return null;
9399
- const absolutePath = path13.join(vaultPath2, notePath);
9603
+ const absolutePath = path14.join(vaultPath2, notePath);
9400
9604
  let content;
9401
9605
  try {
9402
9606
  content = await fs12.promises.readFile(absolutePath, "utf-8");
@@ -9419,7 +9623,7 @@ async function getNoteStructure(index, notePath, vaultPath2) {
9419
9623
  async function getSectionContent(index, notePath, headingText, vaultPath2, includeSubheadings = true) {
9420
9624
  const note = index.notes.get(notePath);
9421
9625
  if (!note) return null;
9422
- const absolutePath = path13.join(vaultPath2, notePath);
9626
+ const absolutePath = path14.join(vaultPath2, notePath);
9423
9627
  let content;
9424
9628
  try {
9425
9629
  content = await fs12.promises.readFile(absolutePath, "utf-8");
@@ -9461,7 +9665,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
9461
9665
  const results = [];
9462
9666
  for (const note of index.notes.values()) {
9463
9667
  if (folder && !note.path.startsWith(folder)) continue;
9464
- const absolutePath = path13.join(vaultPath2, note.path);
9668
+ const absolutePath = path14.join(vaultPath2, note.path);
9465
9669
  let content;
9466
9670
  try {
9467
9671
  content = await fs12.promises.readFile(absolutePath, "utf-8");
@@ -9495,18 +9699,18 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
9495
9699
  include_content: z6.boolean().default(false).describe("Include the text content under each top-level section")
9496
9700
  }
9497
9701
  },
9498
- async ({ path: path30, include_content }) => {
9702
+ async ({ path: path31, include_content }) => {
9499
9703
  const index = getIndex();
9500
9704
  const vaultPath2 = getVaultPath();
9501
- const result = await getNoteStructure(index, path30, vaultPath2);
9705
+ const result = await getNoteStructure(index, path31, vaultPath2);
9502
9706
  if (!result) {
9503
9707
  return {
9504
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path30 }, null, 2) }]
9708
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path31 }, null, 2) }]
9505
9709
  };
9506
9710
  }
9507
9711
  if (include_content) {
9508
9712
  for (const section of result.sections) {
9509
- const sectionResult = await getSectionContent(index, path30, section.heading.text, vaultPath2, true);
9713
+ const sectionResult = await getSectionContent(index, path31, section.heading.text, vaultPath2, true);
9510
9714
  if (sectionResult) {
9511
9715
  section.content = sectionResult.content;
9512
9716
  }
@@ -9528,15 +9732,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
9528
9732
  include_subheadings: z6.boolean().default(true).describe("Include content under subheadings")
9529
9733
  }
9530
9734
  },
9531
- async ({ path: path30, heading, include_subheadings }) => {
9735
+ async ({ path: path31, heading, include_subheadings }) => {
9532
9736
  const index = getIndex();
9533
9737
  const vaultPath2 = getVaultPath();
9534
- const result = await getSectionContent(index, path30, heading, vaultPath2, include_subheadings);
9738
+ const result = await getSectionContent(index, path31, heading, vaultPath2, include_subheadings);
9535
9739
  if (!result) {
9536
9740
  return {
9537
9741
  content: [{ type: "text", text: JSON.stringify({
9538
9742
  error: "Section not found",
9539
- path: path30,
9743
+ path: path31,
9540
9744
  heading
9541
9745
  }, null, 2) }]
9542
9746
  };
@@ -9590,16 +9794,16 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
9590
9794
  offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
9591
9795
  }
9592
9796
  },
9593
- async ({ path: path30, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
9797
+ async ({ path: path31, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
9594
9798
  const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
9595
9799
  const index = getIndex();
9596
9800
  const vaultPath2 = getVaultPath();
9597
9801
  const config = getConfig();
9598
- if (path30) {
9599
- const result2 = await getTasksFromNote(index, path30, vaultPath2, config.exclude_task_tags || []);
9802
+ if (path31) {
9803
+ const result2 = await getTasksFromNote(index, path31, vaultPath2, config.exclude_task_tags || []);
9600
9804
  if (!result2) {
9601
9805
  return {
9602
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path30 }, null, 2) }]
9806
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path31 }, null, 2) }]
9603
9807
  };
9604
9808
  }
9605
9809
  let filtered = result2;
@@ -9609,7 +9813,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
9609
9813
  const paged2 = filtered.slice(offset, offset + limit);
9610
9814
  return {
9611
9815
  content: [{ type: "text", text: JSON.stringify({
9612
- path: path30,
9816
+ path: path31,
9613
9817
  total_count: filtered.length,
9614
9818
  returned_count: paged2.length,
9615
9819
  open: result2.filter((t) => t.status === "open").length,
@@ -9765,7 +9969,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
9765
9969
  // src/tools/read/migrations.ts
9766
9970
  import { z as z7 } from "zod";
9767
9971
  import * as fs13 from "fs/promises";
9768
- import * as path14 from "path";
9972
+ import * as path15 from "path";
9769
9973
  import matter2 from "gray-matter";
9770
9974
  function getNotesInFolder(index, folder) {
9771
9975
  const notes = [];
@@ -9778,7 +9982,7 @@ function getNotesInFolder(index, folder) {
9778
9982
  return notes;
9779
9983
  }
9780
9984
  async function readFileContent(notePath, vaultPath2) {
9781
- const fullPath = path14.join(vaultPath2, notePath);
9985
+ const fullPath = path15.join(vaultPath2, notePath);
9782
9986
  try {
9783
9987
  return await fs13.readFile(fullPath, "utf-8");
9784
9988
  } catch {
@@ -9786,7 +9990,7 @@ async function readFileContent(notePath, vaultPath2) {
9786
9990
  }
9787
9991
  }
9788
9992
  async function writeFileContent(notePath, vaultPath2, content) {
9789
- const fullPath = path14.join(vaultPath2, notePath);
9993
+ const fullPath = path15.join(vaultPath2, notePath);
9790
9994
  try {
9791
9995
  await fs13.writeFile(fullPath, content, "utf-8");
9792
9996
  return true;
@@ -9967,7 +10171,7 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
9967
10171
 
9968
10172
  // src/tools/read/graphAnalysis.ts
9969
10173
  import fs14 from "node:fs";
9970
- import path15 from "node:path";
10174
+ import path16 from "node:path";
9971
10175
  import { z as z8 } from "zod";
9972
10176
 
9973
10177
  // src/tools/read/schema.ts
@@ -10729,7 +10933,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb,
10729
10933
  const scored = allNotes.map((note) => {
10730
10934
  let wordCount = 0;
10731
10935
  try {
10732
- const content = fs14.readFileSync(path15.join(vaultPath2, note.path), "utf-8");
10936
+ const content = fs14.readFileSync(path16.join(vaultPath2, note.path), "utf-8");
10733
10937
  const body = content.replace(/^---[\s\S]*?---\n?/, "");
10734
10938
  wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
10735
10939
  } catch {
@@ -11309,12 +11513,12 @@ import { z as z10 } from "zod";
11309
11513
 
11310
11514
  // src/tools/read/bidirectional.ts
11311
11515
  import * as fs15 from "fs/promises";
11312
- import * as path16 from "path";
11516
+ import * as path17 from "path";
11313
11517
  import matter3 from "gray-matter";
11314
11518
  var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
11315
11519
  var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
11316
11520
  async function readFileContent2(notePath, vaultPath2) {
11317
- const fullPath = path16.join(vaultPath2, notePath);
11521
+ const fullPath = path17.join(vaultPath2, notePath);
11318
11522
  try {
11319
11523
  return await fs15.readFile(fullPath, "utf-8");
11320
11524
  } catch {
@@ -11493,10 +11697,10 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
11493
11697
 
11494
11698
  // src/tools/read/computed.ts
11495
11699
  import * as fs16 from "fs/promises";
11496
- import * as path17 from "path";
11700
+ import * as path18 from "path";
11497
11701
  import matter4 from "gray-matter";
11498
11702
  async function readFileContent3(notePath, vaultPath2) {
11499
- const fullPath = path17.join(vaultPath2, notePath);
11703
+ const fullPath = path18.join(vaultPath2, notePath);
11500
11704
  try {
11501
11705
  return await fs16.readFile(fullPath, "utf-8");
11502
11706
  } catch {
@@ -11504,7 +11708,7 @@ async function readFileContent3(notePath, vaultPath2) {
11504
11708
  }
11505
11709
  }
11506
11710
  async function getFileStats(notePath, vaultPath2) {
11507
- const fullPath = path17.join(vaultPath2, notePath);
11711
+ const fullPath = path18.join(vaultPath2, notePath);
11508
11712
  try {
11509
11713
  const stats = await fs16.stat(fullPath);
11510
11714
  return {
@@ -11775,7 +11979,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
11775
11979
  init_writer();
11776
11980
  import { z as z11 } from "zod";
11777
11981
  import fs20 from "fs/promises";
11778
- import path20 from "path";
11982
+ import path21 from "path";
11779
11983
 
11780
11984
  // src/core/write/validator.ts
11781
11985
  var TIMESTAMP_PATTERN = /^\*\*\d{2}:\d{2}\*\*/;
@@ -11978,7 +12182,7 @@ function runValidationPipeline(content, format, options = {}) {
11978
12182
  // src/core/write/mutation-helpers.ts
11979
12183
  init_writer();
11980
12184
  import fs19 from "fs/promises";
11981
- import path19 from "path";
12185
+ import path20 from "path";
11982
12186
  init_constants();
11983
12187
  init_writer();
11984
12188
  function formatMcpResult(result) {
@@ -12027,7 +12231,7 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
12027
12231
  return info;
12028
12232
  }
12029
12233
  async function ensureFileExists(vaultPath2, notePath) {
12030
- const fullPath = path19.join(vaultPath2, notePath);
12234
+ const fullPath = path20.join(vaultPath2, notePath);
12031
12235
  try {
12032
12236
  await fs19.access(fullPath);
12033
12237
  return null;
@@ -12086,7 +12290,7 @@ async function withVaultFile(options, operation) {
12086
12290
  if ("error" in result) {
12087
12291
  return formatMcpResult(result.error);
12088
12292
  }
12089
- const fullPath = path19.join(vaultPath2, notePath);
12293
+ const fullPath = path20.join(vaultPath2, notePath);
12090
12294
  const statBefore = await fs19.stat(fullPath);
12091
12295
  if (statBefore.mtimeMs !== result.mtimeMs) {
12092
12296
  console.warn(`[withVaultFile] External modification detected on ${notePath}, re-reading and retrying`);
@@ -12149,10 +12353,10 @@ async function withVaultFrontmatter(options, operation) {
12149
12353
 
12150
12354
  // src/tools/write/mutations.ts
12151
12355
  async function createNoteFromTemplate(vaultPath2, notePath, config) {
12152
- const fullPath = path20.join(vaultPath2, notePath);
12153
- await fs20.mkdir(path20.dirname(fullPath), { recursive: true });
12356
+ const fullPath = path21.join(vaultPath2, notePath);
12357
+ await fs20.mkdir(path21.dirname(fullPath), { recursive: true });
12154
12358
  const templates = config.templates || {};
12155
- const filename = path20.basename(notePath, ".md").toLowerCase();
12359
+ const filename = path21.basename(notePath, ".md").toLowerCase();
12156
12360
  let templatePath;
12157
12361
  const dailyPattern = /^\d{4}-\d{2}-\d{2}/;
12158
12362
  const weeklyPattern = /^\d{4}-W\d{2}/;
@@ -12173,10 +12377,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
12173
12377
  let templateContent;
12174
12378
  if (templatePath) {
12175
12379
  try {
12176
- const absTemplatePath = path20.join(vaultPath2, templatePath);
12380
+ const absTemplatePath = path21.join(vaultPath2, templatePath);
12177
12381
  templateContent = await fs20.readFile(absTemplatePath, "utf-8");
12178
12382
  } catch {
12179
- const title = path20.basename(notePath, ".md");
12383
+ const title = path21.basename(notePath, ".md");
12180
12384
  templateContent = `---
12181
12385
  ---
12182
12386
 
@@ -12185,7 +12389,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
12185
12389
  templatePath = void 0;
12186
12390
  }
12187
12391
  } else {
12188
- const title = path20.basename(notePath, ".md");
12392
+ const title = path21.basename(notePath, ".md");
12189
12393
  templateContent = `---
12190
12394
  ---
12191
12395
 
@@ -12194,7 +12398,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
12194
12398
  }
12195
12399
  const now = /* @__PURE__ */ new Date();
12196
12400
  const dateStr = now.toISOString().split("T")[0];
12197
- templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path20.basename(notePath, ".md"));
12401
+ templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path21.basename(notePath, ".md"));
12198
12402
  const matter9 = (await import("gray-matter")).default;
12199
12403
  const parsed = matter9(templateContent);
12200
12404
  if (!parsed.data.date) {
@@ -12234,7 +12438,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
12234
12438
  let noteCreated = false;
12235
12439
  let templateUsed;
12236
12440
  if (create_if_missing) {
12237
- const fullPath = path20.join(vaultPath2, notePath);
12441
+ const fullPath = path21.join(vaultPath2, notePath);
12238
12442
  try {
12239
12443
  await fs20.access(fullPath);
12240
12444
  } catch {
@@ -12693,7 +12897,7 @@ Example: vault_update_frontmatter({ path: "projects/alpha.md", frontmatter: { st
12693
12897
  init_writer();
12694
12898
  import { z as z14 } from "zod";
12695
12899
  import fs21 from "fs/promises";
12696
- import path21 from "path";
12900
+ import path22 from "path";
12697
12901
  function registerNoteTools(server2, vaultPath2, getIndex) {
12698
12902
  server2.tool(
12699
12903
  "vault_create_note",
@@ -12716,23 +12920,23 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
12716
12920
  if (!validatePath(vaultPath2, notePath)) {
12717
12921
  return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
12718
12922
  }
12719
- const fullPath = path21.join(vaultPath2, notePath);
12923
+ const fullPath = path22.join(vaultPath2, notePath);
12720
12924
  const existsCheck = await ensureFileExists(vaultPath2, notePath);
12721
12925
  if (existsCheck === null && !overwrite) {
12722
12926
  return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
12723
12927
  }
12724
- const dir = path21.dirname(fullPath);
12928
+ const dir = path22.dirname(fullPath);
12725
12929
  await fs21.mkdir(dir, { recursive: true });
12726
12930
  let effectiveContent = content;
12727
12931
  let effectiveFrontmatter = frontmatter;
12728
12932
  if (template) {
12729
- const templatePath = path21.join(vaultPath2, template);
12933
+ const templatePath = path22.join(vaultPath2, template);
12730
12934
  try {
12731
12935
  const raw = await fs21.readFile(templatePath, "utf-8");
12732
12936
  const matter9 = (await import("gray-matter")).default;
12733
12937
  const parsed = matter9(raw);
12734
12938
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
12735
- const title = path21.basename(notePath, ".md");
12939
+ const title = path22.basename(notePath, ".md");
12736
12940
  let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
12737
12941
  if (content) {
12738
12942
  templateContent = templateContent.trimEnd() + "\n\n" + content;
@@ -12751,7 +12955,7 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
12751
12955
  effectiveFrontmatter.created = now.toISOString();
12752
12956
  }
12753
12957
  const warnings = [];
12754
- const noteName = path21.basename(notePath, ".md");
12958
+ const noteName = path22.basename(notePath, ".md");
12755
12959
  const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
12756
12960
  const preflight = await checkPreflightSimilarity(noteName);
12757
12961
  if (preflight.existingEntity) {
@@ -12868,7 +13072,7 @@ ${sources}`;
12868
13072
  }
12869
13073
  return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
12870
13074
  }
12871
- const fullPath = path21.join(vaultPath2, notePath);
13075
+ const fullPath = path22.join(vaultPath2, notePath);
12872
13076
  await fs21.unlink(fullPath);
12873
13077
  const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
12874
13078
  const message = backlinkWarning ? `Deleted note: ${notePath}
@@ -12888,7 +13092,7 @@ Warning: ${backlinkWarning}` : `Deleted note: ${notePath}`;
12888
13092
  init_writer();
12889
13093
  import { z as z15 } from "zod";
12890
13094
  import fs22 from "fs/promises";
12891
- import path22 from "path";
13095
+ import path23 from "path";
12892
13096
  import matter6 from "gray-matter";
12893
13097
  function escapeRegex(str) {
12894
13098
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -12907,7 +13111,7 @@ function extractWikilinks2(content) {
12907
13111
  return wikilinks;
12908
13112
  }
12909
13113
  function getTitleFromPath(filePath) {
12910
- return path22.basename(filePath, ".md");
13114
+ return path23.basename(filePath, ".md");
12911
13115
  }
12912
13116
  async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
12913
13117
  const results = [];
@@ -12916,7 +13120,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
12916
13120
  const files = [];
12917
13121
  const entries = await fs22.readdir(dir, { withFileTypes: true });
12918
13122
  for (const entry of entries) {
12919
- const fullPath = path22.join(dir, entry.name);
13123
+ const fullPath = path23.join(dir, entry.name);
12920
13124
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
12921
13125
  files.push(...await scanDir(fullPath));
12922
13126
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -12927,7 +13131,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
12927
13131
  }
12928
13132
  const allFiles = await scanDir(vaultPath2);
12929
13133
  for (const filePath of allFiles) {
12930
- const relativePath = path22.relative(vaultPath2, filePath);
13134
+ const relativePath = path23.relative(vaultPath2, filePath);
12931
13135
  const content = await fs22.readFile(filePath, "utf-8");
12932
13136
  const wikilinks = extractWikilinks2(content);
12933
13137
  const matchingLinks = [];
@@ -12947,7 +13151,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
12947
13151
  return results;
12948
13152
  }
12949
13153
  async function updateBacklinksInFile(vaultPath2, filePath, oldTitles, newTitle) {
12950
- const fullPath = path22.join(vaultPath2, filePath);
13154
+ const fullPath = path23.join(vaultPath2, filePath);
12951
13155
  const raw = await fs22.readFile(fullPath, "utf-8");
12952
13156
  const parsed = matter6(raw);
12953
13157
  let content = parsed.content;
@@ -13014,8 +13218,8 @@ function registerMoveNoteTools(server2, vaultPath2) {
13014
13218
  };
13015
13219
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
13016
13220
  }
13017
- const oldFullPath = path22.join(vaultPath2, oldPath);
13018
- const newFullPath = path22.join(vaultPath2, newPath);
13221
+ const oldFullPath = path23.join(vaultPath2, oldPath);
13222
+ const newFullPath = path23.join(vaultPath2, newPath);
13019
13223
  try {
13020
13224
  await fs22.access(oldFullPath);
13021
13225
  } catch {
@@ -13065,7 +13269,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
13065
13269
  }
13066
13270
  }
13067
13271
  }
13068
- const destDir = path22.dirname(newFullPath);
13272
+ const destDir = path23.dirname(newFullPath);
13069
13273
  await fs22.mkdir(destDir, { recursive: true });
13070
13274
  await fs22.rename(oldFullPath, newFullPath);
13071
13275
  let gitCommit;
@@ -13151,10 +13355,10 @@ function registerMoveNoteTools(server2, vaultPath2) {
13151
13355
  if (sanitizedTitle !== newTitle) {
13152
13356
  console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
13153
13357
  }
13154
- const fullPath = path22.join(vaultPath2, notePath);
13155
- const dir = path22.dirname(notePath);
13156
- const newPath = dir === "." ? `${sanitizedTitle}.md` : path22.join(dir, `${sanitizedTitle}.md`);
13157
- const newFullPath = path22.join(vaultPath2, newPath);
13358
+ const fullPath = path23.join(vaultPath2, notePath);
13359
+ const dir = path23.dirname(notePath);
13360
+ const newPath = dir === "." ? `${sanitizedTitle}.md` : path23.join(dir, `${sanitizedTitle}.md`);
13361
+ const newFullPath = path23.join(vaultPath2, newPath);
13158
13362
  try {
13159
13363
  await fs22.access(fullPath);
13160
13364
  } catch {
@@ -13511,7 +13715,7 @@ init_schema();
13511
13715
  // src/core/write/policy/parser.ts
13512
13716
  init_schema();
13513
13717
  import fs24 from "fs/promises";
13514
- import path23 from "path";
13718
+ import path24 from "path";
13515
13719
  import matter7 from "gray-matter";
13516
13720
  function parseYaml(content) {
13517
13721
  const parsed = matter7(`---
@@ -13560,13 +13764,13 @@ async function loadPolicyFile(filePath) {
13560
13764
  }
13561
13765
  }
13562
13766
  async function loadPolicy(vaultPath2, policyName) {
13563
- const policiesDir = path23.join(vaultPath2, ".claude", "policies");
13564
- const policyPath = path23.join(policiesDir, `${policyName}.yaml`);
13767
+ const policiesDir = path24.join(vaultPath2, ".claude", "policies");
13768
+ const policyPath = path24.join(policiesDir, `${policyName}.yaml`);
13565
13769
  try {
13566
13770
  await fs24.access(policyPath);
13567
13771
  return loadPolicyFile(policyPath);
13568
13772
  } catch {
13569
- const ymlPath = path23.join(policiesDir, `${policyName}.yml`);
13773
+ const ymlPath = path24.join(policiesDir, `${policyName}.yml`);
13570
13774
  try {
13571
13775
  await fs24.access(ymlPath);
13572
13776
  return loadPolicyFile(ymlPath);
@@ -13707,7 +13911,7 @@ init_conditions();
13707
13911
  init_schema();
13708
13912
  init_writer();
13709
13913
  import fs26 from "fs/promises";
13710
- import path25 from "path";
13914
+ import path26 from "path";
13711
13915
  init_constants();
13712
13916
  async function executeStep(step, vaultPath2, context, conditionResults) {
13713
13917
  const { execute, reason } = shouldStepExecute(step.when, conditionResults);
@@ -13781,7 +13985,7 @@ async function executeAddToSection(params, vaultPath2, context) {
13781
13985
  const preserveListNesting = params.preserveListNesting !== false;
13782
13986
  const suggestOutgoingLinks = params.suggestOutgoingLinks !== false;
13783
13987
  const maxSuggestions = Number(params.maxSuggestions) || 3;
13784
- const fullPath = path25.join(vaultPath2, notePath);
13988
+ const fullPath = path26.join(vaultPath2, notePath);
13785
13989
  try {
13786
13990
  await fs26.access(fullPath);
13787
13991
  } catch {
@@ -13821,7 +14025,7 @@ async function executeRemoveFromSection(params, vaultPath2) {
13821
14025
  const pattern = String(params.pattern || "");
13822
14026
  const mode = params.mode || "first";
13823
14027
  const useRegex = Boolean(params.useRegex);
13824
- const fullPath = path25.join(vaultPath2, notePath);
14028
+ const fullPath = path26.join(vaultPath2, notePath);
13825
14029
  try {
13826
14030
  await fs26.access(fullPath);
13827
14031
  } catch {
@@ -13852,7 +14056,7 @@ async function executeReplaceInSection(params, vaultPath2, context) {
13852
14056
  const mode = params.mode || "first";
13853
14057
  const useRegex = Boolean(params.useRegex);
13854
14058
  const skipWikilinks = Boolean(params.skipWikilinks);
13855
- const fullPath = path25.join(vaultPath2, notePath);
14059
+ const fullPath = path26.join(vaultPath2, notePath);
13856
14060
  try {
13857
14061
  await fs26.access(fullPath);
13858
14062
  } catch {
@@ -13895,7 +14099,7 @@ async function executeCreateNote(params, vaultPath2, context) {
13895
14099
  if (!validatePath(vaultPath2, notePath)) {
13896
14100
  return { success: false, message: "Invalid path: path traversal not allowed", path: notePath };
13897
14101
  }
13898
- const fullPath = path25.join(vaultPath2, notePath);
14102
+ const fullPath = path26.join(vaultPath2, notePath);
13899
14103
  try {
13900
14104
  await fs26.access(fullPath);
13901
14105
  if (!overwrite) {
@@ -13903,7 +14107,7 @@ async function executeCreateNote(params, vaultPath2, context) {
13903
14107
  }
13904
14108
  } catch {
13905
14109
  }
13906
- const dir = path25.dirname(fullPath);
14110
+ const dir = path26.dirname(fullPath);
13907
14111
  await fs26.mkdir(dir, { recursive: true });
13908
14112
  const { content: processedContent } = maybeApplyWikilinks(content, skipWikilinks, notePath);
13909
14113
  await writeVaultFile(vaultPath2, notePath, processedContent, frontmatter);
@@ -13923,7 +14127,7 @@ async function executeDeleteNote(params, vaultPath2) {
13923
14127
  if (!validatePath(vaultPath2, notePath)) {
13924
14128
  return { success: false, message: "Invalid path: path traversal not allowed", path: notePath };
13925
14129
  }
13926
- const fullPath = path25.join(vaultPath2, notePath);
14130
+ const fullPath = path26.join(vaultPath2, notePath);
13927
14131
  try {
13928
14132
  await fs26.access(fullPath);
13929
14133
  } catch {
@@ -13940,7 +14144,7 @@ async function executeToggleTask(params, vaultPath2) {
13940
14144
  const notePath = String(params.path || "");
13941
14145
  const task = String(params.task || "");
13942
14146
  const section = params.section ? String(params.section) : void 0;
13943
- const fullPath = path25.join(vaultPath2, notePath);
14147
+ const fullPath = path26.join(vaultPath2, notePath);
13944
14148
  try {
13945
14149
  await fs26.access(fullPath);
13946
14150
  } catch {
@@ -13983,7 +14187,7 @@ async function executeAddTask(params, vaultPath2, context) {
13983
14187
  const completed = Boolean(params.completed);
13984
14188
  const skipWikilinks = Boolean(params.skipWikilinks);
13985
14189
  const preserveListNesting = params.preserveListNesting !== false;
13986
- const fullPath = path25.join(vaultPath2, notePath);
14190
+ const fullPath = path26.join(vaultPath2, notePath);
13987
14191
  try {
13988
14192
  await fs26.access(fullPath);
13989
14193
  } catch {
@@ -14020,7 +14224,7 @@ async function executeAddTask(params, vaultPath2, context) {
14020
14224
  async function executeUpdateFrontmatter(params, vaultPath2) {
14021
14225
  const notePath = String(params.path || "");
14022
14226
  const updates = params.frontmatter || {};
14023
- const fullPath = path25.join(vaultPath2, notePath);
14227
+ const fullPath = path26.join(vaultPath2, notePath);
14024
14228
  try {
14025
14229
  await fs26.access(fullPath);
14026
14230
  } catch {
@@ -14042,7 +14246,7 @@ async function executeAddFrontmatterField(params, vaultPath2) {
14042
14246
  const notePath = String(params.path || "");
14043
14247
  const key = String(params.key || "");
14044
14248
  const value = params.value;
14045
- const fullPath = path25.join(vaultPath2, notePath);
14249
+ const fullPath = path26.join(vaultPath2, notePath);
14046
14250
  try {
14047
14251
  await fs26.access(fullPath);
14048
14252
  } catch {
@@ -14205,7 +14409,7 @@ async function executePolicy(policy, vaultPath2, variables, commit = false) {
14205
14409
  async function rollbackChanges(vaultPath2, originalContents, filesModified) {
14206
14410
  for (const filePath of filesModified) {
14207
14411
  const original = originalContents.get(filePath);
14208
- const fullPath = path25.join(vaultPath2, filePath);
14412
+ const fullPath = path26.join(vaultPath2, filePath);
14209
14413
  if (original === null) {
14210
14414
  try {
14211
14415
  await fs26.unlink(fullPath);
@@ -14260,9 +14464,9 @@ async function previewPolicy(policy, vaultPath2, variables) {
14260
14464
 
14261
14465
  // src/core/write/policy/storage.ts
14262
14466
  import fs27 from "fs/promises";
14263
- import path26 from "path";
14467
+ import path27 from "path";
14264
14468
  function getPoliciesDir(vaultPath2) {
14265
- return path26.join(vaultPath2, ".claude", "policies");
14469
+ return path27.join(vaultPath2, ".claude", "policies");
14266
14470
  }
14267
14471
  async function ensurePoliciesDir(vaultPath2) {
14268
14472
  const dir = getPoliciesDir(vaultPath2);
@@ -14277,7 +14481,7 @@ async function listPolicies(vaultPath2) {
14277
14481
  if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
14278
14482
  continue;
14279
14483
  }
14280
- const filePath = path26.join(dir, file);
14484
+ const filePath = path27.join(dir, file);
14281
14485
  const stat3 = await fs27.stat(filePath);
14282
14486
  const content = await fs27.readFile(filePath, "utf-8");
14283
14487
  const metadata = extractPolicyMetadata(content);
@@ -14302,7 +14506,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
14302
14506
  const dir = getPoliciesDir(vaultPath2);
14303
14507
  await ensurePoliciesDir(vaultPath2);
14304
14508
  const filename = `${policyName}.yaml`;
14305
- const filePath = path26.join(dir, filename);
14509
+ const filePath = path27.join(dir, filename);
14306
14510
  if (!overwrite) {
14307
14511
  try {
14308
14512
  await fs27.access(filePath);
@@ -14846,7 +15050,7 @@ import { z as z20 } from "zod";
14846
15050
 
14847
15051
  // src/core/write/tagRename.ts
14848
15052
  import * as fs28 from "fs/promises";
14849
- import * as path27 from "path";
15053
+ import * as path28 from "path";
14850
15054
  import matter8 from "gray-matter";
14851
15055
  import { getProtectedZones } from "@velvetmonkey/vault-core";
14852
15056
  function getNotesInFolder3(index, folder) {
@@ -14952,7 +15156,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
14952
15156
  const previews = [];
14953
15157
  let totalChanges = 0;
14954
15158
  for (const note of affectedNotes) {
14955
- const fullPath = path27.join(vaultPath2, note.path);
15159
+ const fullPath = path28.join(vaultPath2, note.path);
14956
15160
  let fileContent;
14957
15161
  try {
14958
15162
  fileContent = await fs28.readFile(fullPath, "utf-8");
@@ -15590,8 +15794,8 @@ function getNoteAccessFrequency(stateDb2, daysBack = 30) {
15590
15794
  }
15591
15795
  }
15592
15796
  }
15593
- return Array.from(noteMap.entries()).map(([path30, stats]) => ({
15594
- path: path30,
15797
+ return Array.from(noteMap.entries()).map(([path31, stats]) => ({
15798
+ path: path31,
15595
15799
  access_count: stats.access_count,
15596
15800
  last_accessed: stats.last_accessed,
15597
15801
  tools_used: Array.from(stats.tools)
@@ -15744,7 +15948,7 @@ import { z as z25 } from "zod";
15744
15948
 
15745
15949
  // src/core/read/similarity.ts
15746
15950
  import * as fs29 from "fs";
15747
- import * as path28 from "path";
15951
+ import * as path29 from "path";
15748
15952
  var STOP_WORDS = /* @__PURE__ */ new Set([
15749
15953
  "the",
15750
15954
  "be",
@@ -15881,7 +16085,7 @@ function extractKeyTerms(content, maxTerms = 15) {
15881
16085
  }
15882
16086
  function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
15883
16087
  const limit = options.limit ?? 10;
15884
- const absPath = path28.join(vaultPath2, sourcePath);
16088
+ const absPath = path29.join(vaultPath2, sourcePath);
15885
16089
  let content;
15886
16090
  try {
15887
16091
  content = fs29.readFileSync(absPath, "utf-8");
@@ -16009,7 +16213,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb) {
16009
16213
  exclude_linked: z25.boolean().optional().describe("Exclude notes already linked to/from the source note (default: true)")
16010
16214
  }
16011
16215
  },
16012
- async ({ path: path30, limit, exclude_linked }) => {
16216
+ async ({ path: path31, limit, exclude_linked }) => {
16013
16217
  const index = getIndex();
16014
16218
  const vaultPath2 = getVaultPath();
16015
16219
  const stateDb2 = getStateDb();
@@ -16018,10 +16222,10 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb) {
16018
16222
  content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
16019
16223
  };
16020
16224
  }
16021
- if (!index.notes.has(path30)) {
16225
+ if (!index.notes.has(path31)) {
16022
16226
  return {
16023
16227
  content: [{ type: "text", text: JSON.stringify({
16024
- error: `Note not found: ${path30}`,
16228
+ error: `Note not found: ${path31}`,
16025
16229
  hint: "Use the full relative path including .md extension"
16026
16230
  }, null, 2) }]
16027
16231
  };
@@ -16032,12 +16236,12 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb) {
16032
16236
  };
16033
16237
  const useHybrid = hasEmbeddingsIndex();
16034
16238
  const method = useHybrid ? "hybrid" : "bm25";
16035
- const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path30, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path30, opts);
16239
+ const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path31, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path31, opts);
16036
16240
  return {
16037
16241
  content: [{
16038
16242
  type: "text",
16039
16243
  text: JSON.stringify({
16040
- source: path30,
16244
+ source: path31,
16041
16245
  method,
16042
16246
  exclude_linked: exclude_linked ?? true,
16043
16247
  count: results.length,
@@ -16275,6 +16479,7 @@ function registerMergeTools2(server2, getStateDb) {
16275
16479
 
16276
16480
  // src/index.ts
16277
16481
  import * as fs30 from "node:fs/promises";
16482
+ import { createHash as createHash2 } from "node:crypto";
16278
16483
 
16279
16484
  // src/resources/vault.ts
16280
16485
  function registerVaultResources(server2, getIndex) {
@@ -16885,6 +17090,7 @@ async function runPostIndexWork(index) {
16885
17090
  }
16886
17091
  if (process.env.FLYWHEEL_WATCH !== "false") {
16887
17092
  const config = parseWatcherConfig();
17093
+ const lastContentHashes = /* @__PURE__ */ new Map();
16888
17094
  serverLog("watcher", `File watcher enabled (debounce: ${config.debounceMs}ms)`);
16889
17095
  const watcher = createVaultWatcher({
16890
17096
  vaultPath,
@@ -16916,8 +17122,8 @@ async function runPostIndexWork(index) {
16916
17122
  }
16917
17123
  } catch {
16918
17124
  try {
16919
- const dir = path29.dirname(event.path);
16920
- const base = path29.basename(event.path);
17125
+ const dir = path30.dirname(event.path);
17126
+ const base = path30.basename(event.path);
16921
17127
  const resolvedDir = realpathSync(dir).replace(/\\/g, "/");
16922
17128
  for (const prefix of vaultPrefixes) {
16923
17129
  if (resolvedDir.startsWith(prefix + "/") || resolvedDir === prefix) {
@@ -16932,16 +17138,52 @@ async function runPostIndexWork(index) {
16932
17138
  }
16933
17139
  }
16934
17140
  }
16935
- serverLog("watcher", `Processing ${batch.events.length} file changes`);
17141
+ const filteredEvents = [];
17142
+ for (const event of batch.events) {
17143
+ if (event.type === "delete") {
17144
+ filteredEvents.push(event);
17145
+ lastContentHashes.delete(event.path);
17146
+ continue;
17147
+ }
17148
+ try {
17149
+ const content = await fs30.readFile(path30.join(vaultPath, event.path), "utf-8");
17150
+ const hash = createHash2("md5").update(content).digest("hex");
17151
+ if (lastContentHashes.get(event.path) === hash) {
17152
+ serverLog("watcher", `Hash unchanged, skipping: ${event.path}`);
17153
+ continue;
17154
+ }
17155
+ lastContentHashes.set(event.path, hash);
17156
+ filteredEvents.push(event);
17157
+ } catch {
17158
+ filteredEvents.push(event);
17159
+ }
17160
+ }
17161
+ if (filteredEvents.length === 0) {
17162
+ serverLog("watcher", "All files unchanged (hash gate), skipping batch");
17163
+ return;
17164
+ }
17165
+ serverLog("watcher", `Processing ${filteredEvents.length} file changes`);
16936
17166
  const batchStart = Date.now();
16937
- const changedPaths = batch.events.map((e) => e.path);
17167
+ const changedPaths = filteredEvents.map((e) => e.path);
16938
17168
  const tracker = createStepTracker();
16939
17169
  try {
16940
- tracker.start("index_rebuild", { files_changed: batch.events.length, changed_paths: changedPaths });
16941
- vaultIndex = await buildVaultIndex(vaultPath);
17170
+ tracker.start("index_rebuild", { files_changed: filteredEvents.length, changed_paths: changedPaths });
17171
+ if (!vaultIndex) {
17172
+ vaultIndex = await buildVaultIndex(vaultPath);
17173
+ serverLog("watcher", `Index rebuilt (full): ${vaultIndex.notes.size} notes, ${vaultIndex.entities.size} entities`);
17174
+ } else {
17175
+ const absoluteBatch = {
17176
+ ...batch,
17177
+ events: filteredEvents.map((e) => ({
17178
+ ...e,
17179
+ path: path30.join(vaultPath, e.path)
17180
+ }))
17181
+ };
17182
+ const batchResult = await processBatch(vaultIndex, vaultPath, absoluteBatch);
17183
+ serverLog("watcher", `Incremental: ${batchResult.successful}/${batchResult.total} files in ${batchResult.durationMs}ms`);
17184
+ }
16942
17185
  setIndexState("ready");
16943
17186
  tracker.end({ note_count: vaultIndex.notes.size, entity_count: vaultIndex.entities.size, tag_count: vaultIndex.tags.size });
16944
- serverLog("watcher", `Index rebuilt: ${vaultIndex.notes.size} notes, ${vaultIndex.entities.size} entities`);
16945
17187
  const hubBefore = /* @__PURE__ */ new Map();
16946
17188
  if (stateDb) {
16947
17189
  const rows = stateDb.db.prepare("SELECT name, hub_score FROM entities").all();
@@ -16985,16 +17227,16 @@ async function runPostIndexWork(index) {
16985
17227
  serverLog("watcher", `Recency: failed: ${e}`);
16986
17228
  }
16987
17229
  if (hasEmbeddingsIndex()) {
16988
- tracker.start("note_embeddings", { files: batch.events.length });
17230
+ tracker.start("note_embeddings", { files: filteredEvents.length });
16989
17231
  let embUpdated = 0;
16990
17232
  let embRemoved = 0;
16991
- for (const event of batch.events) {
17233
+ for (const event of filteredEvents) {
16992
17234
  try {
16993
17235
  if (event.type === "delete") {
16994
17236
  removeEmbedding(event.path);
16995
17237
  embRemoved++;
16996
17238
  } else if (event.path.endsWith(".md")) {
16997
- const absPath = path29.join(vaultPath, event.path);
17239
+ const absPath = path30.join(vaultPath, event.path);
16998
17240
  await updateEmbedding(event.path, absPath);
16999
17241
  embUpdated++;
17000
17242
  }
@@ -17007,12 +17249,12 @@ async function runPostIndexWork(index) {
17007
17249
  tracker.skip("note_embeddings", "not built");
17008
17250
  }
17009
17251
  if (hasEntityEmbeddingsIndex() && stateDb) {
17010
- tracker.start("entity_embeddings", { files: batch.events.length });
17252
+ tracker.start("entity_embeddings", { files: filteredEvents.length });
17011
17253
  let entEmbUpdated = 0;
17012
17254
  const entEmbNames = [];
17013
17255
  try {
17014
17256
  const allEntities = getAllEntitiesFromDb3(stateDb);
17015
- for (const event of batch.events) {
17257
+ for (const event of filteredEvents) {
17016
17258
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
17017
17259
  const matching = allEntities.filter((e) => e.path === event.path);
17018
17260
  for (const entity of matching) {
@@ -17046,10 +17288,10 @@ async function runPostIndexWork(index) {
17046
17288
  } else {
17047
17289
  tracker.skip("index_cache", "no stateDb");
17048
17290
  }
17049
- tracker.start("task_cache", { files: batch.events.length });
17291
+ tracker.start("task_cache", { files: filteredEvents.length });
17050
17292
  let taskUpdated = 0;
17051
17293
  let taskRemoved = 0;
17052
- for (const event of batch.events) {
17294
+ for (const event of filteredEvents) {
17053
17295
  try {
17054
17296
  if (event.type === "delete") {
17055
17297
  removeTaskCacheForFile(event.path);
@@ -17063,11 +17305,11 @@ async function runPostIndexWork(index) {
17063
17305
  }
17064
17306
  tracker.end({ updated: taskUpdated, removed: taskRemoved });
17065
17307
  serverLog("watcher", `Task cache: ${taskUpdated} updated, ${taskRemoved} removed`);
17066
- tracker.start("forward_links", { files: batch.events.length });
17308
+ tracker.start("forward_links", { files: filteredEvents.length });
17067
17309
  const forwardLinkResults = [];
17068
17310
  let totalResolved = 0;
17069
17311
  let totalDead = 0;
17070
- for (const event of batch.events) {
17312
+ for (const event of filteredEvents) {
17071
17313
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
17072
17314
  try {
17073
17315
  const links = getForwardLinksForNote(vaultIndex, event.path);
@@ -17095,10 +17337,10 @@ async function runPostIndexWork(index) {
17095
17337
  links: forwardLinkResults
17096
17338
  });
17097
17339
  serverLog("watcher", `Forward links: ${totalResolved} resolved, ${totalDead} dead`);
17098
- tracker.start("wikilink_check", { files: batch.events.length });
17340
+ tracker.start("wikilink_check", { files: filteredEvents.length });
17099
17341
  const trackedLinks = [];
17100
17342
  if (stateDb) {
17101
- for (const event of batch.events) {
17343
+ for (const event of filteredEvents) {
17102
17344
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
17103
17345
  try {
17104
17346
  const apps = getTrackedApplications(stateDb, event.path);
@@ -17109,13 +17351,13 @@ async function runPostIndexWork(index) {
17109
17351
  }
17110
17352
  tracker.end({ tracked: trackedLinks });
17111
17353
  serverLog("watcher", `Wikilink check: ${trackedLinks.reduce((s, t) => s + t.entities.length, 0)} tracked links in ${trackedLinks.length} files`);
17112
- tracker.start("implicit_feedback", { files: batch.events.length });
17354
+ tracker.start("implicit_feedback", { files: filteredEvents.length });
17113
17355
  const feedbackResults = [];
17114
17356
  if (stateDb) {
17115
- for (const event of batch.events) {
17357
+ for (const event of filteredEvents) {
17116
17358
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
17117
17359
  try {
17118
- const content = await fs30.readFile(path29.join(vaultPath, event.path), "utf-8");
17360
+ const content = await fs30.readFile(path30.join(vaultPath, event.path), "utf-8");
17119
17361
  const removed = processImplicitFeedback(stateDb, event.path, content);
17120
17362
  for (const entity of removed) feedbackResults.push({ entity, file: event.path });
17121
17363
  } catch {
@@ -17132,12 +17374,12 @@ async function runPostIndexWork(index) {
17132
17374
  trigger: "watcher",
17133
17375
  duration_ms: duration,
17134
17376
  note_count: vaultIndex.notes.size,
17135
- files_changed: batch.events.length,
17377
+ files_changed: filteredEvents.length,
17136
17378
  changed_paths: changedPaths,
17137
17379
  steps: tracker.steps
17138
17380
  });
17139
17381
  }
17140
- serverLog("watcher", `Batch complete: ${batch.events.length} files, ${duration}ms, ${tracker.steps.length} steps`);
17382
+ serverLog("watcher", `Batch complete: ${filteredEvents.length} files, ${duration}ms, ${tracker.steps.length} steps`);
17141
17383
  } catch (err) {
17142
17384
  setIndexState("error");
17143
17385
  setIndexError(err instanceof Error ? err : new Error(String(err)));
@@ -17147,7 +17389,7 @@ async function runPostIndexWork(index) {
17147
17389
  trigger: "watcher",
17148
17390
  duration_ms: duration,
17149
17391
  success: false,
17150
- files_changed: batch.events.length,
17392
+ files_changed: filteredEvents.length,
17151
17393
  changed_paths: changedPaths,
17152
17394
  error: err instanceof Error ? err.message : String(err),
17153
17395
  steps: tracker.steps