@velvetmonkey/flywheel-memory 2.0.124 → 2.0.126

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 +433 -329
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -895,17 +895,17 @@ function updateSuppressionList(stateDb2, now) {
895
895
  `DELETE FROM wikilink_suppressions
896
896
  WHERE datetime(updated_at, '+' || ? || ' days') <= datetime('now')`
897
897
  ).run(SUPPRESSION_TTL_DAYS);
898
- for (const stat4 of weightedStats) {
899
- const posteriorMean = computePosteriorMean(stat4.weightedCorrect, stat4.weightedFp);
900
- const totalObs = PRIOR_ALPHA + stat4.weightedCorrect + PRIOR_BETA + stat4.weightedFp;
898
+ for (const stat5 of weightedStats) {
899
+ const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp);
900
+ const totalObs = PRIOR_ALPHA + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
901
901
  if (totalObs < SUPPRESSION_MIN_OBSERVATIONS) {
902
902
  continue;
903
903
  }
904
904
  if (posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD) {
905
- upsert.run(stat4.entity, 1 - posteriorMean);
905
+ upsert.run(stat5.entity, 1 - posteriorMean);
906
906
  updated++;
907
907
  } else {
908
- remove.run(stat4.entity);
908
+ remove.run(stat5.entity);
909
909
  }
910
910
  }
911
911
  });
@@ -974,31 +974,31 @@ function getAllFeedbackBoosts(stateDb2, folder, now) {
974
974
  if (folder !== void 0) {
975
975
  folderStatsMap = /* @__PURE__ */ new Map();
976
976
  for (const gs of globalStats) {
977
- const fs33 = getWeightedFolderStats(stateDb2, gs.entity, folder, now);
978
- if (fs33.rawTotal >= FEEDBACK_BOOST_MIN_SAMPLES) {
977
+ const fs34 = getWeightedFolderStats(stateDb2, gs.entity, folder, now);
978
+ if (fs34.rawTotal >= FEEDBACK_BOOST_MIN_SAMPLES) {
979
979
  folderStatsMap.set(gs.entity, {
980
- weightedAccuracy: fs33.weightedAccuracy,
981
- rawCount: fs33.rawTotal
980
+ weightedAccuracy: fs34.weightedAccuracy,
981
+ rawCount: fs34.rawTotal
982
982
  });
983
983
  }
984
984
  }
985
985
  }
986
986
  const boosts = /* @__PURE__ */ new Map();
987
- for (const stat4 of globalStats) {
988
- if (stat4.rawTotal < FEEDBACK_BOOST_MIN_SAMPLES) continue;
987
+ for (const stat5 of globalStats) {
988
+ if (stat5.rawTotal < FEEDBACK_BOOST_MIN_SAMPLES) continue;
989
989
  let accuracy;
990
990
  let sampleCount;
991
- const fs33 = folderStatsMap?.get(stat4.entity);
992
- if (fs33 && fs33.rawCount >= FEEDBACK_BOOST_MIN_SAMPLES) {
993
- accuracy = fs33.weightedAccuracy;
994
- sampleCount = fs33.rawCount;
991
+ const fs34 = folderStatsMap?.get(stat5.entity);
992
+ if (fs34 && fs34.rawCount >= FEEDBACK_BOOST_MIN_SAMPLES) {
993
+ accuracy = fs34.weightedAccuracy;
994
+ sampleCount = fs34.rawCount;
995
995
  } else {
996
- accuracy = stat4.weightedAccuracy;
997
- sampleCount = stat4.rawTotal;
996
+ accuracy = stat5.weightedAccuracy;
997
+ sampleCount = stat5.rawTotal;
998
998
  }
999
999
  const boost = computeBoostFromAccuracy(accuracy, sampleCount);
1000
1000
  if (boost !== 0) {
1001
- boosts.set(stat4.entity, boost);
1001
+ boosts.set(stat5.entity, boost);
1002
1002
  }
1003
1003
  }
1004
1004
  return boosts;
@@ -1006,13 +1006,13 @@ function getAllFeedbackBoosts(stateDb2, folder, now) {
1006
1006
  function getAllSuppressionPenalties(stateDb2, now) {
1007
1007
  const penalties = /* @__PURE__ */ new Map();
1008
1008
  const weightedStats = getWeightedEntityStats(stateDb2, now);
1009
- for (const stat4 of weightedStats) {
1010
- const posteriorMean = computePosteriorMean(stat4.weightedCorrect, stat4.weightedFp);
1011
- const totalObs = PRIOR_ALPHA + stat4.weightedCorrect + PRIOR_BETA + stat4.weightedFp;
1009
+ for (const stat5 of weightedStats) {
1010
+ const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp);
1011
+ const totalObs = PRIOR_ALPHA + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
1012
1012
  if (totalObs >= SUPPRESSION_MIN_OBSERVATIONS && posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD) {
1013
1013
  const penalty = Math.round(MAX_SUPPRESSION_PENALTY * (1 - posteriorMean / SUPPRESSION_POSTERIOR_THRESHOLD));
1014
1014
  if (penalty < 0) {
1015
- penalties.set(stat4.entity, penalty);
1015
+ penalties.set(stat5.entity, penalty);
1016
1016
  }
1017
1017
  }
1018
1018
  }
@@ -1554,8 +1554,8 @@ function clearLastMutationCommit() {
1554
1554
  async function checkGitLock(vaultPath2) {
1555
1555
  const lockPath = path8.join(vaultPath2, ".git/index.lock");
1556
1556
  try {
1557
- const stat4 = await fs6.stat(lockPath);
1558
- const ageMs = Date.now() - stat4.mtimeMs;
1557
+ const stat5 = await fs6.stat(lockPath);
1558
+ const ageMs = Date.now() - stat5.mtimeMs;
1559
1559
  return {
1560
1560
  locked: true,
1561
1561
  stale: ageMs > STALE_LOCK_THRESHOLD_MS,
@@ -1577,8 +1577,8 @@ async function isGitRepo(vaultPath2) {
1577
1577
  async function checkLockFile(vaultPath2) {
1578
1578
  const lockPath = path8.join(vaultPath2, ".git/index.lock");
1579
1579
  try {
1580
- const stat4 = await fs6.stat(lockPath);
1581
- const ageMs = Date.now() - stat4.mtimeMs;
1580
+ const stat5 = await fs6.stat(lockPath);
1581
+ const ageMs = Date.now() - stat5.mtimeMs;
1582
1582
  return { stale: ageMs > STALE_LOCK_THRESHOLD_MS, ageMs };
1583
1583
  } catch {
1584
1584
  return null;
@@ -3282,6 +3282,7 @@ var init_edgeWeights = __esm({
3282
3282
  // src/core/write/wikilinks.ts
3283
3283
  var wikilinks_exports = {};
3284
3284
  __export(wikilinks_exports, {
3285
+ applyProactiveSuggestions: () => applyProactiveSuggestions,
3285
3286
  checkAndRefreshIfStale: () => checkAndRefreshIfStale,
3286
3287
  checkPreflightSimilarity: () => checkPreflightSimilarity,
3287
3288
  detectAliasCollisions: () => detectAliasCollisions,
@@ -3289,6 +3290,7 @@ __export(wikilinks_exports, {
3289
3290
  getCooccurrenceIndex: () => getCooccurrenceIndex,
3290
3291
  getEntityIndex: () => getEntityIndex,
3291
3292
  getEntityIndexStats: () => getEntityIndexStats,
3293
+ getNoteContext: () => getNoteContext,
3292
3294
  getWikilinkStrictness: () => getWikilinkStrictness,
3293
3295
  getWriteStateDb: () => getWriteStateDb,
3294
3296
  initializeEntityIndex: () => initializeEntityIndex,
@@ -3317,6 +3319,8 @@ import {
3317
3319
  getEntitiesByAlias,
3318
3320
  searchEntities as searchEntitiesDb
3319
3321
  } from "@velvetmonkey/vault-core";
3322
+ import path11 from "path";
3323
+ import * as fs7 from "fs/promises";
3320
3324
  function setWriteStateDb(stateDb2) {
3321
3325
  moduleStateDb5 = stateDb2;
3322
3326
  setGitStateDb(stateDb2);
@@ -4233,6 +4237,76 @@ async function checkPreflightSimilarity(noteName) {
4233
4237
  }
4234
4238
  return result;
4235
4239
  }
4240
+ async function applyProactiveSuggestions(filePath, vaultPath2, suggestions, config) {
4241
+ const stateDb2 = getWriteStateDb();
4242
+ const candidates = suggestions.filter((s) => s.score >= config.minScore && s.confidence === "high").slice(0, config.maxPerFile);
4243
+ if (candidates.length === 0) {
4244
+ return { applied: [], skipped: [] };
4245
+ }
4246
+ const fullPath = path11.join(vaultPath2, filePath);
4247
+ try {
4248
+ const stat5 = await fs7.stat(fullPath);
4249
+ if (Date.now() - stat5.mtimeMs < 3e4) {
4250
+ return { applied: [], skipped: candidates.map((c) => c.entity) };
4251
+ }
4252
+ } catch {
4253
+ return { applied: [], skipped: candidates.map((c) => c.entity) };
4254
+ }
4255
+ let content;
4256
+ try {
4257
+ content = await fs7.readFile(fullPath, "utf-8");
4258
+ } catch {
4259
+ return { applied: [], skipped: candidates.map((c) => c.entity) };
4260
+ }
4261
+ const entityObjects = [];
4262
+ for (const candidate of candidates) {
4263
+ if (stateDb2 && isSuppressed(stateDb2, candidate.entity)) continue;
4264
+ if (stateDb2) {
4265
+ const entityObj = getEntityByName(stateDb2, candidate.entity);
4266
+ if (entityObj) {
4267
+ entityObjects.push({
4268
+ name: entityObj.name,
4269
+ path: entityObj.path,
4270
+ aliases: entityObj.aliases ?? []
4271
+ });
4272
+ continue;
4273
+ }
4274
+ }
4275
+ entityObjects.push(candidate.entity);
4276
+ }
4277
+ if (entityObjects.length === 0) {
4278
+ return { applied: [], skipped: candidates.map((c) => c.entity) };
4279
+ }
4280
+ const result = applyWikilinks(content, entityObjects, {
4281
+ firstOccurrenceOnly: true,
4282
+ caseInsensitive: true
4283
+ });
4284
+ if (result.linksAdded === 0) {
4285
+ return { applied: [], skipped: candidates.map((c) => c.entity) };
4286
+ }
4287
+ try {
4288
+ await fs7.writeFile(fullPath, result.content, "utf-8");
4289
+ } catch {
4290
+ return { applied: [], skipped: candidates.map((c) => c.entity) };
4291
+ }
4292
+ if (stateDb2) {
4293
+ trackWikilinkApplications(stateDb2, filePath, result.linkedEntities);
4294
+ try {
4295
+ const markApplied = stateDb2.db.prepare(
4296
+ `UPDATE suggestion_events SET applied = 1
4297
+ WHERE note_path = ? AND entity = ? AND applied = 0`
4298
+ );
4299
+ for (const entity of result.linkedEntities) {
4300
+ markApplied.run(filePath, entity);
4301
+ }
4302
+ } catch {
4303
+ }
4304
+ }
4305
+ return {
4306
+ applied: result.linkedEntities,
4307
+ skipped: candidates.map((c) => c.entity).filter((e) => !result.linkedEntities.includes(e))
4308
+ };
4309
+ }
4236
4310
  var moduleStateDb5, moduleConfig, ALL_IMPLICIT_PATTERNS, entityIndex, indexReady, indexError2, lastLoadedAt, cooccurrenceIndex, recencyIndex, DEFAULT_EXCLUDE_FOLDERS, SUGGESTION_PATTERN, GENERIC_WORDS, MAX_ENTITY_LENGTH, MAX_ENTITY_WORDS, ARTICLE_PATTERNS, STRICTNESS_CONFIGS, TYPE_BOOST, CROSS_FOLDER_BOOST, SEMANTIC_MIN_SIMILARITY, SEMANTIC_MAX_BOOST, CONTEXT_BOOST, MIN_SUGGESTION_SCORE, MIN_MATCH_RATIO, FULL_ALIAS_MATCH_BONUS;
4237
4311
  var init_wikilinks = __esm({
4238
4312
  "src/core/write/wikilinks.ts"() {
@@ -4478,8 +4552,8 @@ var init_constants = __esm({
4478
4552
  });
4479
4553
 
4480
4554
  // src/core/write/writer.ts
4481
- import fs18 from "fs/promises";
4482
- import path20 from "path";
4555
+ import fs19 from "fs/promises";
4556
+ import path21 from "path";
4483
4557
  import matter5 from "gray-matter";
4484
4558
  import { createHash as createHash2 } from "node:crypto";
4485
4559
  function isSensitivePath(filePath) {
@@ -4807,13 +4881,13 @@ function validatePath(vaultPath2, notePath) {
4807
4881
  if (notePath.startsWith("\\")) {
4808
4882
  return false;
4809
4883
  }
4810
- const resolvedVault = path20.resolve(vaultPath2);
4811
- const resolvedNote = path20.resolve(vaultPath2, notePath);
4884
+ const resolvedVault = path21.resolve(vaultPath2);
4885
+ const resolvedNote = path21.resolve(vaultPath2, notePath);
4812
4886
  return resolvedNote.startsWith(resolvedVault);
4813
4887
  }
4814
4888
  function sanitizeNotePath(notePath) {
4815
- const dir = path20.dirname(notePath);
4816
- let filename = path20.basename(notePath);
4889
+ const dir = path21.dirname(notePath);
4890
+ let filename = path21.basename(notePath);
4817
4891
  const ext = filename.endsWith(".md") ? ".md" : "";
4818
4892
  let stem2 = ext ? filename.slice(0, -ext.length) : filename;
4819
4893
  stem2 = stem2.replace(/\s+/g, "-");
@@ -4822,7 +4896,7 @@ function sanitizeNotePath(notePath) {
4822
4896
  stem2 = stem2.replace(/-{2,}/g, "-");
4823
4897
  stem2 = stem2.replace(/^-+|-+$/g, "");
4824
4898
  filename = stem2 + (ext || ".md");
4825
- return dir === "." ? filename : path20.join(dir, filename).replace(/\\/g, "/");
4899
+ return dir === "." ? filename : path21.join(dir, filename).replace(/\\/g, "/");
4826
4900
  }
4827
4901
  async function validatePathSecure(vaultPath2, notePath) {
4828
4902
  if (notePath.startsWith("/")) {
@@ -4849,8 +4923,8 @@ async function validatePathSecure(vaultPath2, notePath) {
4849
4923
  reason: "Path traversal not allowed"
4850
4924
  };
4851
4925
  }
4852
- const resolvedVault = path20.resolve(vaultPath2);
4853
- const resolvedNote = path20.resolve(vaultPath2, notePath);
4926
+ const resolvedVault = path21.resolve(vaultPath2);
4927
+ const resolvedNote = path21.resolve(vaultPath2, notePath);
4854
4928
  if (!resolvedNote.startsWith(resolvedVault)) {
4855
4929
  return {
4856
4930
  valid: false,
@@ -4864,18 +4938,18 @@ async function validatePathSecure(vaultPath2, notePath) {
4864
4938
  };
4865
4939
  }
4866
4940
  try {
4867
- const fullPath = path20.join(vaultPath2, notePath);
4941
+ const fullPath = path21.join(vaultPath2, notePath);
4868
4942
  try {
4869
- await fs18.access(fullPath);
4870
- const realPath = await fs18.realpath(fullPath);
4871
- const realVaultPath = await fs18.realpath(vaultPath2);
4943
+ await fs19.access(fullPath);
4944
+ const realPath = await fs19.realpath(fullPath);
4945
+ const realVaultPath = await fs19.realpath(vaultPath2);
4872
4946
  if (!realPath.startsWith(realVaultPath)) {
4873
4947
  return {
4874
4948
  valid: false,
4875
4949
  reason: "Symlink target is outside vault"
4876
4950
  };
4877
4951
  }
4878
- const relativePath = path20.relative(realVaultPath, realPath);
4952
+ const relativePath = path21.relative(realVaultPath, realPath);
4879
4953
  if (isSensitivePath(relativePath)) {
4880
4954
  return {
4881
4955
  valid: false,
@@ -4883,11 +4957,11 @@ async function validatePathSecure(vaultPath2, notePath) {
4883
4957
  };
4884
4958
  }
4885
4959
  } catch {
4886
- const parentDir = path20.dirname(fullPath);
4960
+ const parentDir = path21.dirname(fullPath);
4887
4961
  try {
4888
- await fs18.access(parentDir);
4889
- const realParentPath = await fs18.realpath(parentDir);
4890
- const realVaultPath = await fs18.realpath(vaultPath2);
4962
+ await fs19.access(parentDir);
4963
+ const realParentPath = await fs19.realpath(parentDir);
4964
+ const realVaultPath = await fs19.realpath(vaultPath2);
4891
4965
  if (!realParentPath.startsWith(realVaultPath)) {
4892
4966
  return {
4893
4967
  valid: false,
@@ -4912,10 +4986,10 @@ async function readVaultFile(vaultPath2, notePath) {
4912
4986
  if (!validatePath(vaultPath2, notePath)) {
4913
4987
  throw new Error("Invalid path: path traversal not allowed");
4914
4988
  }
4915
- const fullPath = path20.join(vaultPath2, notePath);
4916
- const [rawContent, stat4] = await Promise.all([
4917
- fs18.readFile(fullPath, "utf-8"),
4918
- fs18.stat(fullPath)
4989
+ const fullPath = path21.join(vaultPath2, notePath);
4990
+ const [rawContent, stat5] = await Promise.all([
4991
+ fs19.readFile(fullPath, "utf-8"),
4992
+ fs19.stat(fullPath)
4919
4993
  ]);
4920
4994
  const contentHash2 = computeContentHash(rawContent);
4921
4995
  const lineEnding = detectLineEnding(rawContent);
@@ -4927,7 +5001,7 @@ async function readVaultFile(vaultPath2, notePath) {
4927
5001
  frontmatter,
4928
5002
  rawContent,
4929
5003
  lineEnding,
4930
- mtimeMs: stat4.mtimeMs,
5004
+ mtimeMs: stat5.mtimeMs,
4931
5005
  contentHash: contentHash2
4932
5006
  };
4933
5007
  }
@@ -4967,9 +5041,9 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
4967
5041
  if (!validation.valid) {
4968
5042
  throw new Error(`Invalid path: ${validation.reason}`);
4969
5043
  }
4970
- const fullPath = path20.join(vaultPath2, notePath);
5044
+ const fullPath = path21.join(vaultPath2, notePath);
4971
5045
  if (expectedHash) {
4972
- const currentRaw = await fs18.readFile(fullPath, "utf-8");
5046
+ const currentRaw = await fs19.readFile(fullPath, "utf-8");
4973
5047
  const currentHash = computeContentHash(currentRaw);
4974
5048
  if (currentHash !== expectedHash) {
4975
5049
  throw new WriteConflictError(notePath);
@@ -4978,7 +5052,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
4978
5052
  let output = matter5.stringify(content, frontmatter);
4979
5053
  output = normalizeTrailingNewline(output);
4980
5054
  output = convertLineEndings(output, lineEnding);
4981
- await fs18.writeFile(fullPath, output, "utf-8");
5055
+ await fs19.writeFile(fullPath, output, "utf-8");
4982
5056
  }
4983
5057
  function removeFromSection(content, section, pattern, mode = "first", useRegex = false) {
4984
5058
  const lines = content.split("\n");
@@ -5367,8 +5441,8 @@ function createContext(variables = {}) {
5367
5441
  steps: {}
5368
5442
  };
5369
5443
  }
5370
- function resolvePath(obj, path33) {
5371
- const parts = path33.split(".");
5444
+ function resolvePath(obj, path34) {
5445
+ const parts = path34.split(".");
5372
5446
  let current = obj;
5373
5447
  for (const part of parts) {
5374
5448
  if (current === void 0 || current === null) {
@@ -5825,8 +5899,8 @@ __export(conditions_exports, {
5825
5899
  evaluateCondition: () => evaluateCondition,
5826
5900
  shouldStepExecute: () => shouldStepExecute
5827
5901
  });
5828
- import fs25 from "fs/promises";
5829
- import path26 from "path";
5902
+ import fs26 from "fs/promises";
5903
+ import path27 from "path";
5830
5904
  async function evaluateCondition(condition, vaultPath2, context) {
5831
5905
  const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
5832
5906
  const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
@@ -5879,9 +5953,9 @@ async function evaluateCondition(condition, vaultPath2, context) {
5879
5953
  }
5880
5954
  }
5881
5955
  async function evaluateFileExists(vaultPath2, notePath, expectExists) {
5882
- const fullPath = path26.join(vaultPath2, notePath);
5956
+ const fullPath = path27.join(vaultPath2, notePath);
5883
5957
  try {
5884
- await fs25.access(fullPath);
5958
+ await fs26.access(fullPath);
5885
5959
  return {
5886
5960
  met: expectExists,
5887
5961
  reason: expectExists ? `File exists: ${notePath}` : `File exists (expected not to): ${notePath}`
@@ -5894,9 +5968,9 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
5894
5968
  }
5895
5969
  }
5896
5970
  async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
5897
- const fullPath = path26.join(vaultPath2, notePath);
5971
+ const fullPath = path27.join(vaultPath2, notePath);
5898
5972
  try {
5899
- await fs25.access(fullPath);
5973
+ await fs26.access(fullPath);
5900
5974
  } catch {
5901
5975
  return {
5902
5976
  met: !expectExists,
@@ -5925,9 +5999,9 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
5925
5999
  }
5926
6000
  }
5927
6001
  async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
5928
- const fullPath = path26.join(vaultPath2, notePath);
6002
+ const fullPath = path27.join(vaultPath2, notePath);
5929
6003
  try {
5930
- await fs25.access(fullPath);
6004
+ await fs26.access(fullPath);
5931
6005
  } catch {
5932
6006
  return {
5933
6007
  met: !expectExists,
@@ -5956,9 +6030,9 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
5956
6030
  }
5957
6031
  }
5958
6032
  async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
5959
- const fullPath = path26.join(vaultPath2, notePath);
6033
+ const fullPath = path27.join(vaultPath2, notePath);
5960
6034
  try {
5961
- await fs25.access(fullPath);
6035
+ await fs26.access(fullPath);
5962
6036
  } catch {
5963
6037
  return {
5964
6038
  met: false,
@@ -6100,7 +6174,7 @@ var init_taskHelpers = __esm({
6100
6174
  });
6101
6175
 
6102
6176
  // src/index.ts
6103
- import * as path32 from "path";
6177
+ import * as path33 from "path";
6104
6178
  import { readFileSync as readFileSync5, realpathSync, existsSync as existsSync3, statSync as statSync5 } from "fs";
6105
6179
  import { fileURLToPath } from "url";
6106
6180
  import { dirname as dirname6, join as join17 } from "path";
@@ -6339,8 +6413,8 @@ function updateIndexProgress(parsed, total) {
6339
6413
  function normalizeTarget(target) {
6340
6414
  return target.toLowerCase().replace(/\.md$/, "");
6341
6415
  }
6342
- function normalizeNotePath(path33) {
6343
- return path33.toLowerCase().replace(/\.md$/, "");
6416
+ function normalizeNotePath(path34) {
6417
+ return path34.toLowerCase().replace(/\.md$/, "");
6344
6418
  }
6345
6419
  async function buildVaultIndex(vaultPath2, options = {}) {
6346
6420
  const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
@@ -6509,7 +6583,7 @@ function findSimilarEntity(index, target) {
6509
6583
  }
6510
6584
  const maxDist = normalizedLen <= 10 ? 1 : 2;
6511
6585
  let bestMatch;
6512
- for (const [entity, path33] of index.entities) {
6586
+ for (const [entity, path34] of index.entities) {
6513
6587
  const lenDiff = Math.abs(entity.length - normalizedLen);
6514
6588
  if (lenDiff > maxDist) {
6515
6589
  continue;
@@ -6517,7 +6591,7 @@ function findSimilarEntity(index, target) {
6517
6591
  const dist = levenshteinDistance(normalized, entity);
6518
6592
  if (dist > 0 && dist <= maxDist) {
6519
6593
  if (!bestMatch || dist < bestMatch.distance) {
6520
- bestMatch = { path: path33, entity, distance: dist };
6594
+ bestMatch = { path: path34, entity, distance: dist };
6521
6595
  if (dist === 1) {
6522
6596
  return bestMatch;
6523
6597
  }
@@ -7048,30 +7122,30 @@ var EventQueue = class {
7048
7122
  * Add a new event to the queue
7049
7123
  */
7050
7124
  push(type, rawPath) {
7051
- const path33 = normalizePath(rawPath);
7125
+ const path34 = normalizePath(rawPath);
7052
7126
  const now = Date.now();
7053
7127
  const event = {
7054
7128
  type,
7055
- path: path33,
7129
+ path: path34,
7056
7130
  timestamp: now
7057
7131
  };
7058
- let pending = this.pending.get(path33);
7132
+ let pending = this.pending.get(path34);
7059
7133
  if (!pending) {
7060
7134
  pending = {
7061
7135
  events: [],
7062
7136
  timer: null,
7063
7137
  lastEvent: now
7064
7138
  };
7065
- this.pending.set(path33, pending);
7139
+ this.pending.set(path34, pending);
7066
7140
  }
7067
7141
  pending.events.push(event);
7068
7142
  pending.lastEvent = now;
7069
- console.error(`[flywheel] QUEUE: pushed ${type} for ${path33}, pending=${this.pending.size}`);
7143
+ console.error(`[flywheel] QUEUE: pushed ${type} for ${path34}, pending=${this.pending.size}`);
7070
7144
  if (pending.timer) {
7071
7145
  clearTimeout(pending.timer);
7072
7146
  }
7073
7147
  pending.timer = setTimeout(() => {
7074
- this.flushPath(path33);
7148
+ this.flushPath(path34);
7075
7149
  }, this.config.debounceMs);
7076
7150
  if (this.pending.size >= this.config.batchSize) {
7077
7151
  this.flush();
@@ -7092,10 +7166,10 @@ var EventQueue = class {
7092
7166
  /**
7093
7167
  * Flush a single path's events
7094
7168
  */
7095
- flushPath(path33) {
7096
- const pending = this.pending.get(path33);
7169
+ flushPath(path34) {
7170
+ const pending = this.pending.get(path34);
7097
7171
  if (!pending || pending.events.length === 0) return;
7098
- console.error(`[flywheel] QUEUE: flushing ${path33}, events=${pending.events.length}`);
7172
+ console.error(`[flywheel] QUEUE: flushing ${path34}, events=${pending.events.length}`);
7099
7173
  if (pending.timer) {
7100
7174
  clearTimeout(pending.timer);
7101
7175
  pending.timer = null;
@@ -7104,7 +7178,7 @@ var EventQueue = class {
7104
7178
  if (coalescedType) {
7105
7179
  const coalesced = {
7106
7180
  type: coalescedType,
7107
- path: path33,
7181
+ path: path34,
7108
7182
  originalEvents: [...pending.events]
7109
7183
  };
7110
7184
  this.onBatch({
@@ -7113,7 +7187,7 @@ var EventQueue = class {
7113
7187
  timestamp: Date.now()
7114
7188
  });
7115
7189
  }
7116
- this.pending.delete(path33);
7190
+ this.pending.delete(path34);
7117
7191
  }
7118
7192
  /**
7119
7193
  * Flush all pending events
@@ -7125,7 +7199,7 @@ var EventQueue = class {
7125
7199
  }
7126
7200
  if (this.pending.size === 0) return;
7127
7201
  const events = [];
7128
- for (const [path33, pending] of this.pending) {
7202
+ for (const [path34, pending] of this.pending) {
7129
7203
  if (pending.timer) {
7130
7204
  clearTimeout(pending.timer);
7131
7205
  }
@@ -7133,7 +7207,7 @@ var EventQueue = class {
7133
7207
  if (coalescedType) {
7134
7208
  events.push({
7135
7209
  type: coalescedType,
7136
- path: path33,
7210
+ path: path34,
7137
7211
  originalEvents: [...pending.events]
7138
7212
  });
7139
7213
  }
@@ -7326,8 +7400,8 @@ async function upsertNote(index, vaultPath2, notePath) {
7326
7400
  releasedKeys = removeNoteFromIndex(index, notePath);
7327
7401
  }
7328
7402
  const fullPath = path7.join(vaultPath2, notePath);
7329
- const fs33 = await import("fs/promises");
7330
- const stats = await fs33.stat(fullPath);
7403
+ const fs34 = await import("fs/promises");
7404
+ const stats = await fs34.stat(fullPath);
7331
7405
  const vaultFile = {
7332
7406
  path: notePath,
7333
7407
  absolutePath: fullPath,
@@ -7519,31 +7593,31 @@ function createVaultWatcher(options) {
7519
7593
  usePolling: config.usePolling,
7520
7594
  interval: config.usePolling ? config.pollInterval : void 0
7521
7595
  });
7522
- watcher.on("add", (path33) => {
7523
- console.error(`[flywheel] RAW EVENT: add ${path33}`);
7524
- if (shouldWatch(path33, vaultPath2)) {
7525
- console.error(`[flywheel] ACCEPTED: add ${path33}`);
7526
- eventQueue.push("add", path33);
7596
+ watcher.on("add", (path34) => {
7597
+ console.error(`[flywheel] RAW EVENT: add ${path34}`);
7598
+ if (shouldWatch(path34, vaultPath2)) {
7599
+ console.error(`[flywheel] ACCEPTED: add ${path34}`);
7600
+ eventQueue.push("add", path34);
7527
7601
  } else {
7528
- console.error(`[flywheel] FILTERED: add ${path33}`);
7602
+ console.error(`[flywheel] FILTERED: add ${path34}`);
7529
7603
  }
7530
7604
  });
7531
- watcher.on("change", (path33) => {
7532
- console.error(`[flywheel] RAW EVENT: change ${path33}`);
7533
- if (shouldWatch(path33, vaultPath2)) {
7534
- console.error(`[flywheel] ACCEPTED: change ${path33}`);
7535
- eventQueue.push("change", path33);
7605
+ watcher.on("change", (path34) => {
7606
+ console.error(`[flywheel] RAW EVENT: change ${path34}`);
7607
+ if (shouldWatch(path34, vaultPath2)) {
7608
+ console.error(`[flywheel] ACCEPTED: change ${path34}`);
7609
+ eventQueue.push("change", path34);
7536
7610
  } else {
7537
- console.error(`[flywheel] FILTERED: change ${path33}`);
7611
+ console.error(`[flywheel] FILTERED: change ${path34}`);
7538
7612
  }
7539
7613
  });
7540
- watcher.on("unlink", (path33) => {
7541
- console.error(`[flywheel] RAW EVENT: unlink ${path33}`);
7542
- if (shouldWatch(path33, vaultPath2)) {
7543
- console.error(`[flywheel] ACCEPTED: unlink ${path33}`);
7544
- eventQueue.push("unlink", path33);
7614
+ watcher.on("unlink", (path34) => {
7615
+ console.error(`[flywheel] RAW EVENT: unlink ${path34}`);
7616
+ if (shouldWatch(path34, vaultPath2)) {
7617
+ console.error(`[flywheel] ACCEPTED: unlink ${path34}`);
7618
+ eventQueue.push("unlink", path34);
7545
7619
  } else {
7546
- console.error(`[flywheel] FILTERED: unlink ${path33}`);
7620
+ console.error(`[flywheel] FILTERED: unlink ${path34}`);
7547
7621
  }
7548
7622
  });
7549
7623
  watcher.on("ready", () => {
@@ -7722,7 +7796,7 @@ async function flushLogs() {
7722
7796
  init_vault();
7723
7797
  init_serverLog();
7724
7798
  init_vault_scope();
7725
- import * as fs7 from "fs";
7799
+ import * as fs8 from "fs";
7726
7800
  var EXCLUDED_DIRS3 = /* @__PURE__ */ new Set([
7727
7801
  ".obsidian",
7728
7802
  ".trash",
@@ -7790,11 +7864,11 @@ async function buildFTS5Index(vaultPath2) {
7790
7864
  const rows = [];
7791
7865
  for (const file of indexableFiles) {
7792
7866
  try {
7793
- const stats = fs7.statSync(file.absolutePath);
7867
+ const stats = fs8.statSync(file.absolutePath);
7794
7868
  if (stats.size > MAX_INDEX_FILE_SIZE) {
7795
7869
  continue;
7796
7870
  }
7797
- const raw = fs7.readFileSync(file.absolutePath, "utf-8");
7871
+ const raw = fs8.readFileSync(file.absolutePath, "utf-8");
7798
7872
  const { frontmatter, body } = splitFrontmatter(raw);
7799
7873
  const title = file.path.replace(/\.md$/, "").split("/").pop() || file.path;
7800
7874
  rows.push([file.path, title, frontmatter, body]);
@@ -7921,11 +7995,11 @@ function countFTS5Mentions(term) {
7921
7995
  init_embeddings();
7922
7996
 
7923
7997
  // src/core/read/taskCache.ts
7924
- import * as path12 from "path";
7998
+ import * as path13 from "path";
7925
7999
 
7926
8000
  // src/tools/read/tasks.ts
7927
- import * as fs8 from "fs";
7928
- import * as path11 from "path";
8001
+ import * as fs9 from "fs";
8002
+ import * as path12 from "path";
7929
8003
  var TASK_REGEX = /^(\s*)- \[([ xX\-])\]\s+(.+)$/;
7930
8004
  var TAG_REGEX2 = /#([a-zA-Z][a-zA-Z0-9_/-]*)/g;
7931
8005
  var DATE_REGEX = /\b(\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4})\b/;
@@ -7951,7 +8025,7 @@ function extractDueDate(text) {
7951
8025
  async function extractTasksFromNote(notePath, absolutePath) {
7952
8026
  let content;
7953
8027
  try {
7954
- content = await fs8.promises.readFile(absolutePath, "utf-8");
8028
+ content = await fs9.promises.readFile(absolutePath, "utf-8");
7955
8029
  } catch {
7956
8030
  return [];
7957
8031
  }
@@ -7994,7 +8068,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
7994
8068
  const allTasks = [];
7995
8069
  for (const note of index.notes.values()) {
7996
8070
  if (folder && !note.path.startsWith(folder)) continue;
7997
- const absolutePath = path11.join(vaultPath2, note.path);
8071
+ const absolutePath = path12.join(vaultPath2, note.path);
7998
8072
  const tasks = await extractTasksFromNote(note.path, absolutePath);
7999
8073
  allTasks.push(...tasks);
8000
8074
  }
@@ -8038,7 +8112,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
8038
8112
  async function getTasksFromNote(index, notePath, vaultPath2, excludeTags = []) {
8039
8113
  const note = index.notes.get(notePath);
8040
8114
  if (!note) return null;
8041
- const absolutePath = path11.join(vaultPath2, notePath);
8115
+ const absolutePath = path12.join(vaultPath2, notePath);
8042
8116
  let tasks = await extractTasksFromNote(notePath, absolutePath);
8043
8117
  if (excludeTags.length > 0) {
8044
8118
  tasks = tasks.filter(
@@ -8100,7 +8174,7 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
8100
8174
  }
8101
8175
  const allRows = [];
8102
8176
  for (const notePath of notePaths) {
8103
- const absolutePath = path12.join(vaultPath2, notePath);
8177
+ const absolutePath = path13.join(vaultPath2, notePath);
8104
8178
  const tasks = await extractTasksFromNote(notePath, absolutePath);
8105
8179
  for (const task of tasks) {
8106
8180
  if (excludeTags?.length && excludeTags.some((t) => task.tags.includes(t))) {
@@ -8143,7 +8217,7 @@ async function updateTaskCacheForFile(vaultPath2, relativePath) {
8143
8217
  const db4 = getDb3();
8144
8218
  if (!db4) return;
8145
8219
  db4.prepare("DELETE FROM tasks WHERE path = ?").run(relativePath);
8146
- const absolutePath = path12.join(vaultPath2, relativePath);
8220
+ const absolutePath = path13.join(vaultPath2, relativePath);
8147
8221
  const tasks = await extractTasksFromNote(relativePath, absolutePath);
8148
8222
  if (tasks.length > 0) {
8149
8223
  const insertStmt = db4.prepare(`
@@ -8285,8 +8359,8 @@ function refreshIfStale(vaultPath2, index, excludeTags) {
8285
8359
  import { openStateDb, scanVaultEntities as scanVaultEntities4, getSessionId, getAllEntitiesFromDb as getAllEntitiesFromDb3, findEntityMatches as findEntityMatches2, getProtectedZones as getProtectedZones2, rangeOverlapsProtectedZone, detectImplicitEntities as detectImplicitEntities3, loadContentHashes, saveContentHashBatch, renameContentHash } from "@velvetmonkey/vault-core";
8286
8360
 
8287
8361
  // src/tools/read/graph.ts
8288
- import * as fs9 from "fs";
8289
- import * as path13 from "path";
8362
+ import * as fs10 from "fs";
8363
+ import * as path14 from "path";
8290
8364
  import { z } from "zod";
8291
8365
 
8292
8366
  // src/core/read/constants.ts
@@ -8737,8 +8811,8 @@ function requireIndex() {
8737
8811
  // src/tools/read/graph.ts
8738
8812
  async function getContext(vaultPath2, sourcePath, line, contextLines = 1) {
8739
8813
  try {
8740
- const fullPath = path13.join(vaultPath2, sourcePath);
8741
- const content = await fs9.promises.readFile(fullPath, "utf-8");
8814
+ const fullPath = path14.join(vaultPath2, sourcePath);
8815
+ const content = await fs10.promises.readFile(fullPath, "utf-8");
8742
8816
  const allLines = content.split("\n");
8743
8817
  let fmLines = 0;
8744
8818
  if (allLines[0]?.trimEnd() === "---") {
@@ -9211,15 +9285,15 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb2 = ()
9211
9285
  const weightedStats = getWeightedEntityStats(stateDb2);
9212
9286
  const statsMap = new Map(weightedStats.map((s) => [s.entity.toLowerCase(), s]));
9213
9287
  for (const suggestion of scored.detailed) {
9214
- const stat4 = statsMap.get(suggestion.entity.toLowerCase());
9215
- if (stat4) {
9216
- const posteriorMean = computePosteriorMean(stat4.weightedCorrect, stat4.weightedFp);
9217
- const totalObs = PRIOR_ALPHA + stat4.weightedCorrect + PRIOR_BETA + stat4.weightedFp;
9288
+ const stat5 = statsMap.get(suggestion.entity.toLowerCase());
9289
+ if (stat5) {
9290
+ const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp);
9291
+ const totalObs = PRIOR_ALPHA + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
9218
9292
  suggestion.suppressionContext = {
9219
9293
  posteriorMean: Math.round(posteriorMean * 1e3) / 1e3,
9220
9294
  totalObservations: Math.round(totalObs * 10) / 10,
9221
9295
  isSuppressed: totalObs >= SUPPRESSION_MIN_OBSERVATIONS && posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD,
9222
- falsePositiveRate: Math.round(stat4.weightedFpRate * 1e3) / 1e3
9296
+ falsePositiveRate: Math.round(stat5.weightedFpRate * 1e3) / 1e3
9223
9297
  };
9224
9298
  }
9225
9299
  }
@@ -9264,14 +9338,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb2 = ()
9264
9338
  };
9265
9339
  function findSimilarEntity2(target, entities) {
9266
9340
  const targetLower = target.toLowerCase();
9267
- for (const [name, path33] of entities) {
9341
+ for (const [name, path34] of entities) {
9268
9342
  if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
9269
- return path33;
9343
+ return path34;
9270
9344
  }
9271
9345
  }
9272
- for (const [name, path33] of entities) {
9346
+ for (const [name, path34] of entities) {
9273
9347
  if (name.includes(targetLower) || targetLower.includes(name)) {
9274
- return path33;
9348
+ return path34;
9275
9349
  }
9276
9350
  }
9277
9351
  return void 0;
@@ -9493,7 +9567,7 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb2 = ()
9493
9567
  }
9494
9568
 
9495
9569
  // src/tools/read/health.ts
9496
- import * as fs10 from "fs";
9570
+ import * as fs11 from "fs";
9497
9571
  import { z as z3 } from "zod";
9498
9572
 
9499
9573
  // src/tools/read/periodic.ts
@@ -10112,7 +10186,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
10112
10186
  const indexErrorObj = getIndexError();
10113
10187
  let vaultAccessible = false;
10114
10188
  try {
10115
- fs10.accessSync(vaultPath2, fs10.constants.R_OK);
10189
+ fs11.accessSync(vaultPath2, fs11.constants.R_OK);
10116
10190
  vaultAccessible = true;
10117
10191
  } catch {
10118
10192
  vaultAccessible = false;
@@ -10354,8 +10428,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
10354
10428
  daily_counts: z3.record(z3.number())
10355
10429
  }).describe("Activity summary for the last 7 days")
10356
10430
  };
10357
- function isPeriodicNote3(path33) {
10358
- const filename = path33.split("/").pop() || "";
10431
+ function isPeriodicNote3(path34) {
10432
+ const filename = path34.split("/").pop() || "";
10359
10433
  const nameWithoutExt = filename.replace(/\.md$/, "");
10360
10434
  const patterns = [
10361
10435
  /^\d{4}-\d{2}-\d{2}$/,
@@ -10370,7 +10444,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
10370
10444
  // YYYY (yearly)
10371
10445
  ];
10372
10446
  const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
10373
- const folder = path33.split("/")[0]?.toLowerCase() || "";
10447
+ const folder = path34.split("/")[0]?.toLowerCase() || "";
10374
10448
  return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
10375
10449
  }
10376
10450
  server2.registerTool(
@@ -10519,7 +10593,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
10519
10593
  detail: `Schema version ${SCHEMA_VERSION}`
10520
10594
  });
10521
10595
  try {
10522
- fs10.accessSync(vaultPath2, fs10.constants.R_OK | fs10.constants.W_OK);
10596
+ fs11.accessSync(vaultPath2, fs11.constants.R_OK | fs11.constants.W_OK);
10523
10597
  checks.push({ name: "vault_access", status: "ok", detail: `Vault readable and writable at ${vaultPath2}` });
10524
10598
  } catch {
10525
10599
  checks.push({ name: "vault_access", status: "error", detail: `Vault not accessible at ${vaultPath2}`, fix: "Check PROJECT_PATH environment variable and directory permissions" });
@@ -10605,11 +10679,11 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
10605
10679
  try {
10606
10680
  const dbPath = stateDb2.db.name;
10607
10681
  if (dbPath && dbPath !== ":memory:") {
10608
- const dbStat = fs10.statSync(dbPath);
10682
+ const dbStat = fs11.statSync(dbPath);
10609
10683
  const dbSizeMb = Math.round(dbStat.size / 1024 / 1024 * 10) / 10;
10610
10684
  let walSizeMb = 0;
10611
10685
  try {
10612
- const walStat = fs10.statSync(dbPath + "-wal");
10686
+ const walStat = fs11.statSync(dbPath + "-wal");
10613
10687
  walSizeMb = Math.round(walStat.size / 1024 / 1024 * 10) / 10;
10614
10688
  } catch {
10615
10689
  }
@@ -10838,6 +10912,7 @@ function enrichResultCompact(result, index, stateDb2, opts) {
10838
10912
  if (preview) enriched.snippet = preview;
10839
10913
  }
10840
10914
  if (note) {
10915
+ if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
10841
10916
  enriched.backlink_count = backlinks.length;
10842
10917
  enriched.modified = note.modified.toISOString();
10843
10918
  if (note.tags.length > 0) enriched.tags = note.tags;
@@ -10968,6 +11043,7 @@ function enrichEntityCompact(entityName, stateDb2, index) {
10968
11043
  const backlinks = index.backlinks.get(normalizedPath) || [];
10969
11044
  enriched.backlink_count = backlinks.length;
10970
11045
  if (note) {
11046
+ if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
10971
11047
  if (note.tags.length > 0) enriched.tags = note.tags;
10972
11048
  if (note.outlinks.length > 0) {
10973
11049
  enriched.outlink_names = getOutlinkNames(note.outlinks, entityPath, index, stateDb2, COMPACT_OUTLINK_NAMES);
@@ -10984,6 +11060,7 @@ function enrichNoteCompact(notePath, stateDb2, index) {
10984
11060
  if (!note) return enriched;
10985
11061
  const normalizedPath = notePath.toLowerCase().replace(/\.md$/, "");
10986
11062
  const backlinks = index.backlinks.get(normalizedPath) || [];
11063
+ if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
10987
11064
  if (note.tags.length > 0) enriched.tags = note.tags;
10988
11065
  enriched.backlink_count = backlinks.length;
10989
11066
  enriched.modified = note.modified.toISOString();
@@ -11064,13 +11141,13 @@ function multiHopBackfill(primaryResults, index, stateDb2, config = {}) {
11064
11141
  candidates.sort((a, b) => b.score - a.score);
11065
11142
  return candidates.slice(0, cfg.maxBackfill).map((c) => c.result);
11066
11143
  }
11067
- function scoreCandidate(path33, index, stateDb2) {
11068
- const note = index.notes.get(path33);
11144
+ function scoreCandidate(path34, index, stateDb2) {
11145
+ const note = index.notes.get(path34);
11069
11146
  const decay = recencyDecay(note?.modified);
11070
11147
  let hubScore = 1;
11071
11148
  if (stateDb2) {
11072
11149
  try {
11073
- const title = note?.title ?? path33.replace(/\.md$/, "").split("/").pop() ?? "";
11150
+ const title = note?.title ?? path34.replace(/\.md$/, "").split("/").pop() ?? "";
11074
11151
  const entity = getEntityByName3(stateDb2, title);
11075
11152
  if (entity) hubScore = entity.hubScore ?? 1;
11076
11153
  } catch {
@@ -11448,8 +11525,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
11448
11525
  }
11449
11526
 
11450
11527
  // src/tools/read/system.ts
11451
- import * as fs11 from "fs";
11452
- import * as path14 from "path";
11528
+ import * as fs12 from "fs";
11529
+ import * as path15 from "path";
11453
11530
  import { z as z5 } from "zod";
11454
11531
  import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
11455
11532
 
@@ -11762,8 +11839,8 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
11762
11839
  continue;
11763
11840
  }
11764
11841
  try {
11765
- const fullPath = path14.join(vaultPath2, note.path);
11766
- const content = await fs11.promises.readFile(fullPath, "utf-8");
11842
+ const fullPath = path15.join(vaultPath2, note.path);
11843
+ const content = await fs12.promises.readFile(fullPath, "utf-8");
11767
11844
  const lines = content.split("\n");
11768
11845
  for (let i = 0; i < lines.length; i++) {
11769
11846
  const line = lines[i];
@@ -12022,8 +12099,8 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
12022
12099
  import { z as z6 } from "zod";
12023
12100
 
12024
12101
  // src/tools/read/structure.ts
12025
- import * as fs12 from "fs";
12026
- import * as path15 from "path";
12102
+ import * as fs13 from "fs";
12103
+ import * as path16 from "path";
12027
12104
  var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
12028
12105
  function extractHeadings2(content) {
12029
12106
  const lines = content.split("\n");
@@ -12077,10 +12154,10 @@ function buildSections(headings, totalLines) {
12077
12154
  async function getNoteStructure(index, notePath, vaultPath2) {
12078
12155
  const note = index.notes.get(notePath);
12079
12156
  if (!note) return null;
12080
- const absolutePath = path15.join(vaultPath2, notePath);
12157
+ const absolutePath = path16.join(vaultPath2, notePath);
12081
12158
  let content;
12082
12159
  try {
12083
- content = await fs12.promises.readFile(absolutePath, "utf-8");
12160
+ content = await fs13.promises.readFile(absolutePath, "utf-8");
12084
12161
  } catch {
12085
12162
  return null;
12086
12163
  }
@@ -12100,10 +12177,10 @@ async function getNoteStructure(index, notePath, vaultPath2) {
12100
12177
  async function getSectionContent(index, notePath, headingText, vaultPath2, includeSubheadings = true) {
12101
12178
  const note = index.notes.get(notePath);
12102
12179
  if (!note) return null;
12103
- const absolutePath = path15.join(vaultPath2, notePath);
12180
+ const absolutePath = path16.join(vaultPath2, notePath);
12104
12181
  let content;
12105
12182
  try {
12106
- content = await fs12.promises.readFile(absolutePath, "utf-8");
12183
+ content = await fs13.promises.readFile(absolutePath, "utf-8");
12107
12184
  } catch {
12108
12185
  return null;
12109
12186
  }
@@ -12142,10 +12219,10 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
12142
12219
  const results = [];
12143
12220
  for (const note of index.notes.values()) {
12144
12221
  if (folder && !note.path.startsWith(folder)) continue;
12145
- const absolutePath = path15.join(vaultPath2, note.path);
12222
+ const absolutePath = path16.join(vaultPath2, note.path);
12146
12223
  let content;
12147
12224
  try {
12148
- content = await fs12.promises.readFile(absolutePath, "utf-8");
12225
+ content = await fs13.promises.readFile(absolutePath, "utf-8");
12149
12226
  } catch {
12150
12227
  continue;
12151
12228
  }
@@ -12177,30 +12254,30 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
12177
12254
  include_content: z6.boolean().default(true).describe("Include the text content under each top-level section. Set false to get structure only.")
12178
12255
  }
12179
12256
  },
12180
- async ({ path: path33, include_content }) => {
12257
+ async ({ path: path34, include_content }) => {
12181
12258
  const index = getIndex();
12182
12259
  const vaultPath2 = getVaultPath();
12183
- const result = await getNoteStructure(index, path33, vaultPath2);
12260
+ const result = await getNoteStructure(index, path34, vaultPath2);
12184
12261
  if (!result) {
12185
12262
  return {
12186
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path33 }, null, 2) }]
12263
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path34 }, null, 2) }]
12187
12264
  };
12188
12265
  }
12189
12266
  if (include_content) {
12190
12267
  for (const section of result.sections) {
12191
- const sectionResult = await getSectionContent(index, path33, section.heading.text, vaultPath2, true);
12268
+ const sectionResult = await getSectionContent(index, path34, section.heading.text, vaultPath2, true);
12192
12269
  if (sectionResult) {
12193
12270
  section.content = sectionResult.content;
12194
12271
  }
12195
12272
  }
12196
12273
  }
12197
- const note = index.notes.get(path33);
12274
+ const note = index.notes.get(path34);
12198
12275
  const enriched = { ...result };
12199
12276
  if (note) {
12200
12277
  enriched.frontmatter = note.frontmatter;
12201
12278
  enriched.tags = note.tags;
12202
12279
  enriched.aliases = note.aliases;
12203
- const normalizedPath = path33.toLowerCase().replace(/\.md$/, "");
12280
+ const normalizedPath = path34.toLowerCase().replace(/\.md$/, "");
12204
12281
  const backlinks = index.backlinks.get(normalizedPath) || [];
12205
12282
  enriched.backlink_count = backlinks.length;
12206
12283
  enriched.outlink_count = note.outlinks.length;
@@ -12233,15 +12310,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
12233
12310
  include_subheadings: z6.boolean().default(true).describe("Include content under subheadings")
12234
12311
  }
12235
12312
  },
12236
- async ({ path: path33, heading, include_subheadings }) => {
12313
+ async ({ path: path34, heading, include_subheadings }) => {
12237
12314
  const index = getIndex();
12238
12315
  const vaultPath2 = getVaultPath();
12239
- const result = await getSectionContent(index, path33, heading, vaultPath2, include_subheadings);
12316
+ const result = await getSectionContent(index, path34, heading, vaultPath2, include_subheadings);
12240
12317
  if (!result) {
12241
12318
  return {
12242
12319
  content: [{ type: "text", text: JSON.stringify({
12243
12320
  error: "Section not found",
12244
- path: path33,
12321
+ path: path34,
12245
12322
  heading
12246
12323
  }, null, 2) }]
12247
12324
  };
@@ -12295,16 +12372,16 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
12295
12372
  offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
12296
12373
  }
12297
12374
  },
12298
- async ({ path: path33, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
12375
+ async ({ path: path34, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
12299
12376
  const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
12300
12377
  const index = getIndex();
12301
12378
  const vaultPath2 = getVaultPath();
12302
12379
  const config = getConfig2();
12303
- if (path33) {
12304
- const result2 = await getTasksFromNote(index, path33, vaultPath2, config.exclude_task_tags || []);
12380
+ if (path34) {
12381
+ const result2 = await getTasksFromNote(index, path34, vaultPath2, config.exclude_task_tags || []);
12305
12382
  if (!result2) {
12306
12383
  return {
12307
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path33 }, null, 2) }]
12384
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path34 }, null, 2) }]
12308
12385
  };
12309
12386
  }
12310
12387
  let filtered = result2;
@@ -12314,7 +12391,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
12314
12391
  const paged2 = filtered.slice(offset, offset + limit);
12315
12392
  return {
12316
12393
  content: [{ type: "text", text: JSON.stringify({
12317
- path: path33,
12394
+ path: path34,
12318
12395
  total_count: filtered.length,
12319
12396
  returned_count: paged2.length,
12320
12397
  open: result2.filter((t) => t.status === "open").length,
@@ -12469,8 +12546,8 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
12469
12546
 
12470
12547
  // src/tools/read/migrations.ts
12471
12548
  import { z as z7 } from "zod";
12472
- import * as fs13 from "fs/promises";
12473
- import * as path16 from "path";
12549
+ import * as fs14 from "fs/promises";
12550
+ import * as path17 from "path";
12474
12551
  import matter2 from "gray-matter";
12475
12552
  function getNotesInFolder(index, folder) {
12476
12553
  const notes = [];
@@ -12483,17 +12560,17 @@ function getNotesInFolder(index, folder) {
12483
12560
  return notes;
12484
12561
  }
12485
12562
  async function readFileContent(notePath, vaultPath2) {
12486
- const fullPath = path16.join(vaultPath2, notePath);
12563
+ const fullPath = path17.join(vaultPath2, notePath);
12487
12564
  try {
12488
- return await fs13.readFile(fullPath, "utf-8");
12565
+ return await fs14.readFile(fullPath, "utf-8");
12489
12566
  } catch {
12490
12567
  return null;
12491
12568
  }
12492
12569
  }
12493
12570
  async function writeFileContent(notePath, vaultPath2, content) {
12494
- const fullPath = path16.join(vaultPath2, notePath);
12571
+ const fullPath = path17.join(vaultPath2, notePath);
12495
12572
  try {
12496
- await fs13.writeFile(fullPath, content, "utf-8");
12573
+ await fs14.writeFile(fullPath, content, "utf-8");
12497
12574
  return true;
12498
12575
  } catch {
12499
12576
  return false;
@@ -12671,8 +12748,8 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
12671
12748
  }
12672
12749
 
12673
12750
  // src/tools/read/graphAnalysis.ts
12674
- import fs14 from "node:fs";
12675
- import path17 from "node:path";
12751
+ import fs15 from "node:fs";
12752
+ import path18 from "node:path";
12676
12753
  import { z as z8 } from "zod";
12677
12754
 
12678
12755
  // src/tools/read/schema.ts
@@ -13385,7 +13462,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb2
13385
13462
  const scored = allNotes.map((note) => {
13386
13463
  let wordCount = 0;
13387
13464
  try {
13388
- const content = fs14.readFileSync(path17.join(vaultPath2, note.path), "utf-8");
13465
+ const content = fs15.readFileSync(path18.join(vaultPath2, note.path), "utf-8");
13389
13466
  const body = content.replace(/^---[\s\S]*?---\n?/, "");
13390
13467
  wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
13391
13468
  } catch {
@@ -14015,15 +14092,15 @@ function registerSemanticAnalysisTools(server2, getIndex, getVaultPath) {
14015
14092
  import { z as z11 } from "zod";
14016
14093
 
14017
14094
  // src/tools/read/bidirectional.ts
14018
- import * as fs15 from "fs/promises";
14019
- import * as path18 from "path";
14095
+ import * as fs16 from "fs/promises";
14096
+ import * as path19 from "path";
14020
14097
  import matter3 from "gray-matter";
14021
14098
  var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
14022
14099
  var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
14023
14100
  async function readFileContent2(notePath, vaultPath2) {
14024
- const fullPath = path18.join(vaultPath2, notePath);
14101
+ const fullPath = path19.join(vaultPath2, notePath);
14025
14102
  try {
14026
- return await fs15.readFile(fullPath, "utf-8");
14103
+ return await fs16.readFile(fullPath, "utf-8");
14027
14104
  } catch {
14028
14105
  return null;
14029
14106
  }
@@ -14199,21 +14276,21 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
14199
14276
  }
14200
14277
 
14201
14278
  // src/tools/read/computed.ts
14202
- import * as fs16 from "fs/promises";
14203
- import * as path19 from "path";
14279
+ import * as fs17 from "fs/promises";
14280
+ import * as path20 from "path";
14204
14281
  import matter4 from "gray-matter";
14205
14282
  async function readFileContent3(notePath, vaultPath2) {
14206
- const fullPath = path19.join(vaultPath2, notePath);
14283
+ const fullPath = path20.join(vaultPath2, notePath);
14207
14284
  try {
14208
- return await fs16.readFile(fullPath, "utf-8");
14285
+ return await fs17.readFile(fullPath, "utf-8");
14209
14286
  } catch {
14210
14287
  return null;
14211
14288
  }
14212
14289
  }
14213
14290
  async function getFileStats(notePath, vaultPath2) {
14214
- const fullPath = path19.join(vaultPath2, notePath);
14291
+ const fullPath = path20.join(vaultPath2, notePath);
14215
14292
  try {
14216
- const stats = await fs16.stat(fullPath);
14293
+ const stats = await fs17.stat(fullPath);
14217
14294
  return {
14218
14295
  modified: stats.mtime,
14219
14296
  created: stats.birthtime
@@ -14343,7 +14420,7 @@ async function computeFrontmatter(index, notePath, vaultPath2, fields) {
14343
14420
 
14344
14421
  // src/tools/read/noteIntelligence.ts
14345
14422
  init_embeddings();
14346
- import fs17 from "node:fs";
14423
+ import fs18 from "node:fs";
14347
14424
  import nodePath from "node:path";
14348
14425
  function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfig2) {
14349
14426
  server2.registerTool(
@@ -14403,7 +14480,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
14403
14480
  }
14404
14481
  let noteContent;
14405
14482
  try {
14406
- noteContent = fs17.readFileSync(nodePath.join(vaultPath2, notePath), "utf-8");
14483
+ noteContent = fs18.readFileSync(nodePath.join(vaultPath2, notePath), "utf-8");
14407
14484
  } catch {
14408
14485
  return {
14409
14486
  content: [{ type: "text", text: JSON.stringify({
@@ -14482,8 +14559,8 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
14482
14559
  // src/tools/write/mutations.ts
14483
14560
  init_writer();
14484
14561
  import { z as z12 } from "zod";
14485
- import fs20 from "fs/promises";
14486
- import path22 from "path";
14562
+ import fs21 from "fs/promises";
14563
+ import path23 from "path";
14487
14564
 
14488
14565
  // src/core/write/validator.ts
14489
14566
  var TIMESTAMP_PATTERN = /^\*\*\d{2}:\d{2}\*\*/;
@@ -14701,8 +14778,8 @@ init_constants();
14701
14778
  init_writer();
14702
14779
  init_wikilinks();
14703
14780
  init_wikilinkFeedback();
14704
- import fs19 from "fs/promises";
14705
- import path21 from "path";
14781
+ import fs20 from "fs/promises";
14782
+ import path22 from "path";
14706
14783
  function formatMcpResult(result) {
14707
14784
  if (result.tokensEstimate === void 0 || result.tokensEstimate === 0) {
14708
14785
  result.tokensEstimate = estimateTokens(result);
@@ -14750,8 +14827,8 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
14750
14827
  }
14751
14828
  async function getPolicyHint(vaultPath2) {
14752
14829
  try {
14753
- const policiesDir = path21.join(vaultPath2, ".claude", "policies");
14754
- const files = await fs19.readdir(policiesDir);
14830
+ const policiesDir = path22.join(vaultPath2, ".claude", "policies");
14831
+ const files = await fs20.readdir(policiesDir);
14755
14832
  const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
14756
14833
  if (yamlFiles.length > 0) {
14757
14834
  const names = yamlFiles.map((f) => f.replace(/\.ya?ml$/, "")).join(", ");
@@ -14762,9 +14839,9 @@ async function getPolicyHint(vaultPath2) {
14762
14839
  return "";
14763
14840
  }
14764
14841
  async function ensureFileExists(vaultPath2, notePath) {
14765
- const fullPath = path21.join(vaultPath2, notePath);
14842
+ const fullPath = path22.join(vaultPath2, notePath);
14766
14843
  try {
14767
- await fs19.access(fullPath);
14844
+ await fs20.access(fullPath);
14768
14845
  return null;
14769
14846
  } catch {
14770
14847
  const hint = await getPolicyHint(vaultPath2);
@@ -14940,17 +15017,17 @@ async function executeCreateNote(options) {
14940
15017
  if (!pathCheck.valid) {
14941
15018
  return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
14942
15019
  }
14943
- const fullPath = path21.join(vaultPath2, notePath);
15020
+ const fullPath = path22.join(vaultPath2, notePath);
14944
15021
  let fileExists = false;
14945
15022
  try {
14946
- await fs19.access(fullPath);
15023
+ await fs20.access(fullPath);
14947
15024
  fileExists = true;
14948
15025
  } catch {
14949
15026
  }
14950
15027
  if (fileExists && !overwrite) {
14951
15028
  return { success: false, result: errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`), filesWritten: [] };
14952
15029
  }
14953
- await fs19.mkdir(path21.dirname(fullPath), { recursive: true });
15030
+ await fs20.mkdir(path22.dirname(fullPath), { recursive: true });
14954
15031
  const { maybeApplyWikilinks: maybeApplyWikilinks2 } = await Promise.resolve().then(() => (init_wikilinks(), wikilinks_exports));
14955
15032
  const { content: processedContent } = maybeApplyWikilinks2(content, skipWikilinks ?? false, notePath);
14956
15033
  let finalFrontmatter = frontmatter;
@@ -14984,13 +15061,13 @@ async function executeDeleteNote(options) {
14984
15061
  if (!pathCheck.valid) {
14985
15062
  return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
14986
15063
  }
14987
- const fullPath = path21.join(vaultPath2, notePath);
15064
+ const fullPath = path22.join(vaultPath2, notePath);
14988
15065
  try {
14989
- await fs19.access(fullPath);
15066
+ await fs20.access(fullPath);
14990
15067
  } catch {
14991
15068
  return { success: false, result: errorResult(notePath, `File not found: ${notePath}`), filesWritten: [] };
14992
15069
  }
14993
- await fs19.unlink(fullPath);
15070
+ await fs20.unlink(fullPath);
14994
15071
  const result = successResult(notePath, `Deleted note: ${notePath}`, {});
14995
15072
  return { success: true, result, filesWritten: [notePath] };
14996
15073
  } catch (error) {
@@ -15008,10 +15085,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
15008
15085
  if (!validation.valid) {
15009
15086
  throw new Error(`Path blocked: ${validation.reason}`);
15010
15087
  }
15011
- const fullPath = path22.join(vaultPath2, notePath);
15012
- await fs20.mkdir(path22.dirname(fullPath), { recursive: true });
15088
+ const fullPath = path23.join(vaultPath2, notePath);
15089
+ await fs21.mkdir(path23.dirname(fullPath), { recursive: true });
15013
15090
  const templates = config.templates || {};
15014
- const filename = path22.basename(notePath, ".md").toLowerCase();
15091
+ const filename = path23.basename(notePath, ".md").toLowerCase();
15015
15092
  let templatePath;
15016
15093
  const dailyPattern = /^\d{4}-\d{2}-\d{2}/;
15017
15094
  const weeklyPattern = /^\d{4}-W\d{2}/;
@@ -15032,10 +15109,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
15032
15109
  let templateContent;
15033
15110
  if (templatePath) {
15034
15111
  try {
15035
- const absTemplatePath = path22.join(vaultPath2, templatePath);
15036
- templateContent = await fs20.readFile(absTemplatePath, "utf-8");
15112
+ const absTemplatePath = path23.join(vaultPath2, templatePath);
15113
+ templateContent = await fs21.readFile(absTemplatePath, "utf-8");
15037
15114
  } catch {
15038
- const title = path22.basename(notePath, ".md");
15115
+ const title = path23.basename(notePath, ".md");
15039
15116
  templateContent = `---
15040
15117
  ---
15041
15118
 
@@ -15044,7 +15121,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
15044
15121
  templatePath = void 0;
15045
15122
  }
15046
15123
  } else {
15047
- const title = path22.basename(notePath, ".md");
15124
+ const title = path23.basename(notePath, ".md");
15048
15125
  templateContent = `---
15049
15126
  ---
15050
15127
 
@@ -15053,7 +15130,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
15053
15130
  }
15054
15131
  const now = /* @__PURE__ */ new Date();
15055
15132
  const dateStr = now.toISOString().split("T")[0];
15056
- templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path22.basename(notePath, ".md"));
15133
+ templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path23.basename(notePath, ".md"));
15057
15134
  const matter9 = (await import("gray-matter")).default;
15058
15135
  const parsed = matter9(templateContent);
15059
15136
  if (!parsed.data.date) {
@@ -15094,9 +15171,9 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
15094
15171
  let noteCreated = false;
15095
15172
  let templateUsed;
15096
15173
  if (create_if_missing && !dry_run) {
15097
- const fullPath = path22.join(vaultPath2, notePath);
15174
+ const fullPath = path23.join(vaultPath2, notePath);
15098
15175
  try {
15099
- await fs20.access(fullPath);
15176
+ await fs21.access(fullPath);
15100
15177
  } catch {
15101
15178
  const config = getConfig2();
15102
15179
  const result = await createNoteFromTemplate(vaultPath2, notePath, config);
@@ -15105,9 +15182,9 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
15105
15182
  }
15106
15183
  }
15107
15184
  if (create_if_missing && dry_run) {
15108
- const fullPath = path22.join(vaultPath2, notePath);
15185
+ const fullPath = path23.join(vaultPath2, notePath);
15109
15186
  try {
15110
- await fs20.access(fullPath);
15187
+ await fs21.access(fullPath);
15111
15188
  } catch {
15112
15189
  return {
15113
15190
  content: [{
@@ -15592,8 +15669,8 @@ Example: vault_update_frontmatter({ path: "projects/alpha.md", frontmatter: { st
15592
15669
  init_writer();
15593
15670
  init_wikilinks();
15594
15671
  import { z as z15 } from "zod";
15595
- import fs21 from "fs/promises";
15596
- import path23 from "path";
15672
+ import fs22 from "fs/promises";
15673
+ import path24 from "path";
15597
15674
  function registerNoteTools(server2, getVaultPath, getIndex) {
15598
15675
  server2.tool(
15599
15676
  "vault_create_note",
@@ -15619,23 +15696,23 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
15619
15696
  if (!validatePath(vaultPath2, notePath)) {
15620
15697
  return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
15621
15698
  }
15622
- const fullPath = path23.join(vaultPath2, notePath);
15699
+ const fullPath = path24.join(vaultPath2, notePath);
15623
15700
  const existsCheck = await ensureFileExists(vaultPath2, notePath);
15624
15701
  if (existsCheck === null && !overwrite) {
15625
15702
  return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
15626
15703
  }
15627
- const dir = path23.dirname(fullPath);
15628
- await fs21.mkdir(dir, { recursive: true });
15704
+ const dir = path24.dirname(fullPath);
15705
+ await fs22.mkdir(dir, { recursive: true });
15629
15706
  let effectiveContent = content;
15630
15707
  let effectiveFrontmatter = frontmatter;
15631
15708
  if (template) {
15632
- const templatePath = path23.join(vaultPath2, template);
15709
+ const templatePath = path24.join(vaultPath2, template);
15633
15710
  try {
15634
- const raw = await fs21.readFile(templatePath, "utf-8");
15711
+ const raw = await fs22.readFile(templatePath, "utf-8");
15635
15712
  const matter9 = (await import("gray-matter")).default;
15636
15713
  const parsed = matter9(raw);
15637
15714
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
15638
- const title = path23.basename(notePath, ".md");
15715
+ const title = path24.basename(notePath, ".md");
15639
15716
  let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
15640
15717
  if (content) {
15641
15718
  templateContent = templateContent.trimEnd() + "\n\n" + content;
@@ -15654,7 +15731,7 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
15654
15731
  effectiveFrontmatter.created = now.toISOString();
15655
15732
  }
15656
15733
  const warnings = [];
15657
- const noteName = path23.basename(notePath, ".md");
15734
+ const noteName = path24.basename(notePath, ".md");
15658
15735
  const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
15659
15736
  const preflight = await checkPreflightSimilarity(noteName);
15660
15737
  if (preflight.existingEntity) {
@@ -15795,8 +15872,8 @@ ${sources}`;
15795
15872
  }
15796
15873
  return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
15797
15874
  }
15798
- const fullPath = path23.join(vaultPath2, notePath);
15799
- await fs21.unlink(fullPath);
15875
+ const fullPath = path24.join(vaultPath2, notePath);
15876
+ await fs22.unlink(fullPath);
15800
15877
  const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
15801
15878
  const message = backlinkWarning ? `Deleted note: ${notePath}
15802
15879
 
@@ -15816,8 +15893,8 @@ init_writer();
15816
15893
  init_git();
15817
15894
  init_wikilinks();
15818
15895
  import { z as z16 } from "zod";
15819
- import fs22 from "fs/promises";
15820
- import path24 from "path";
15896
+ import fs23 from "fs/promises";
15897
+ import path25 from "path";
15821
15898
  import matter6 from "gray-matter";
15822
15899
  function escapeRegex(str) {
15823
15900
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -15836,16 +15913,16 @@ function extractWikilinks2(content) {
15836
15913
  return wikilinks;
15837
15914
  }
15838
15915
  function getTitleFromPath(filePath) {
15839
- return path24.basename(filePath, ".md");
15916
+ return path25.basename(filePath, ".md");
15840
15917
  }
15841
15918
  async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
15842
15919
  const results = [];
15843
15920
  const allTargets = [targetTitle, ...targetAliases].map((t) => t.toLowerCase());
15844
15921
  async function scanDir(dir) {
15845
15922
  const files = [];
15846
- const entries = await fs22.readdir(dir, { withFileTypes: true });
15923
+ const entries = await fs23.readdir(dir, { withFileTypes: true });
15847
15924
  for (const entry of entries) {
15848
- const fullPath = path24.join(dir, entry.name);
15925
+ const fullPath = path25.join(dir, entry.name);
15849
15926
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
15850
15927
  files.push(...await scanDir(fullPath));
15851
15928
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -15856,8 +15933,8 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
15856
15933
  }
15857
15934
  const allFiles = await scanDir(vaultPath2);
15858
15935
  for (const filePath of allFiles) {
15859
- const relativePath = path24.relative(vaultPath2, filePath);
15860
- const content = await fs22.readFile(filePath, "utf-8");
15936
+ const relativePath = path25.relative(vaultPath2, filePath);
15937
+ const content = await fs23.readFile(filePath, "utf-8");
15861
15938
  const wikilinks = extractWikilinks2(content);
15862
15939
  const matchingLinks = [];
15863
15940
  for (const link of wikilinks) {
@@ -15943,10 +16020,10 @@ function registerMoveNoteTools(server2, getVaultPath) {
15943
16020
  };
15944
16021
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
15945
16022
  }
15946
- const oldFullPath = path24.join(vaultPath2, oldPath);
15947
- const newFullPath = path24.join(vaultPath2, newPath);
16023
+ const oldFullPath = path25.join(vaultPath2, oldPath);
16024
+ const newFullPath = path25.join(vaultPath2, newPath);
15948
16025
  try {
15949
- await fs22.access(oldFullPath);
16026
+ await fs23.access(oldFullPath);
15950
16027
  } catch {
15951
16028
  const result2 = {
15952
16029
  success: false,
@@ -15956,7 +16033,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
15956
16033
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
15957
16034
  }
15958
16035
  try {
15959
- await fs22.access(newFullPath);
16036
+ await fs23.access(newFullPath);
15960
16037
  const result2 = {
15961
16038
  success: false,
15962
16039
  message: `Destination already exists: ${newPath}`,
@@ -15965,7 +16042,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
15965
16042
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
15966
16043
  } catch {
15967
16044
  }
15968
- const sourceContent = await fs22.readFile(oldFullPath, "utf-8");
16045
+ const sourceContent = await fs23.readFile(oldFullPath, "utf-8");
15969
16046
  const parsed = matter6(sourceContent);
15970
16047
  const aliases = extractAliases2(parsed.data);
15971
16048
  const oldTitle = getTitleFromPath(oldPath);
@@ -16025,9 +16102,9 @@ function registerMoveNoteTools(server2, getVaultPath) {
16025
16102
  };
16026
16103
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
16027
16104
  }
16028
- const destDir = path24.dirname(newFullPath);
16029
- await fs22.mkdir(destDir, { recursive: true });
16030
- await fs22.rename(oldFullPath, newFullPath);
16105
+ const destDir = path25.dirname(newFullPath);
16106
+ await fs23.mkdir(destDir, { recursive: true });
16107
+ await fs23.rename(oldFullPath, newFullPath);
16031
16108
  let gitCommit;
16032
16109
  let undoAvailable;
16033
16110
  let staleLockDetected;
@@ -16101,12 +16178,12 @@ function registerMoveNoteTools(server2, getVaultPath) {
16101
16178
  if (sanitizedTitle !== newTitle) {
16102
16179
  console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
16103
16180
  }
16104
- const fullPath = path24.join(vaultPath2, notePath);
16105
- const dir = path24.dirname(notePath);
16106
- const newPath = dir === "." ? `${sanitizedTitle}.md` : path24.join(dir, `${sanitizedTitle}.md`);
16107
- const newFullPath = path24.join(vaultPath2, newPath);
16181
+ const fullPath = path25.join(vaultPath2, notePath);
16182
+ const dir = path25.dirname(notePath);
16183
+ const newPath = dir === "." ? `${sanitizedTitle}.md` : path25.join(dir, `${sanitizedTitle}.md`);
16184
+ const newFullPath = path25.join(vaultPath2, newPath);
16108
16185
  try {
16109
- await fs22.access(fullPath);
16186
+ await fs23.access(fullPath);
16110
16187
  } catch {
16111
16188
  const result2 = {
16112
16189
  success: false,
@@ -16117,7 +16194,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16117
16194
  }
16118
16195
  if (fullPath !== newFullPath) {
16119
16196
  try {
16120
- await fs22.access(newFullPath);
16197
+ await fs23.access(newFullPath);
16121
16198
  const result2 = {
16122
16199
  success: false,
16123
16200
  message: `A note with this title already exists: ${newPath}`,
@@ -16127,7 +16204,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16127
16204
  } catch {
16128
16205
  }
16129
16206
  }
16130
- const sourceContent = await fs22.readFile(fullPath, "utf-8");
16207
+ const sourceContent = await fs23.readFile(fullPath, "utf-8");
16131
16208
  const parsed = matter6(sourceContent);
16132
16209
  const aliases = extractAliases2(parsed.data);
16133
16210
  const oldTitle = getTitleFromPath(notePath);
@@ -16187,7 +16264,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16187
16264
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
16188
16265
  }
16189
16266
  if (fullPath !== newFullPath) {
16190
- await fs22.rename(fullPath, newFullPath);
16267
+ await fs23.rename(fullPath, newFullPath);
16191
16268
  }
16192
16269
  let gitCommit;
16193
16270
  let undoAvailable;
@@ -16235,7 +16312,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16235
16312
  init_writer();
16236
16313
  init_wikilinks();
16237
16314
  import { z as z17 } from "zod";
16238
- import fs23 from "fs/promises";
16315
+ import fs24 from "fs/promises";
16239
16316
  function registerMergeTools(server2, getVaultPath) {
16240
16317
  server2.tool(
16241
16318
  "merge_entities",
@@ -16362,7 +16439,7 @@ ${trimmedSource}`;
16362
16439
  }
16363
16440
  await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter, "LF", targetContentHash);
16364
16441
  const fullSourcePath = `${vaultPath2}/${source_path}`;
16365
- await fs23.unlink(fullSourcePath);
16442
+ await fs24.unlink(fullSourcePath);
16366
16443
  initializeEntityIndex(vaultPath2).catch((err) => {
16367
16444
  console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
16368
16445
  });
@@ -16634,8 +16711,8 @@ init_schema();
16634
16711
 
16635
16712
  // src/core/write/policy/parser.ts
16636
16713
  init_schema();
16637
- import fs24 from "fs/promises";
16638
- import path25 from "path";
16714
+ import fs25 from "fs/promises";
16715
+ import path26 from "path";
16639
16716
  import matter7 from "gray-matter";
16640
16717
  function parseYaml(content) {
16641
16718
  const parsed = matter7(`---
@@ -16660,7 +16737,7 @@ function parsePolicyString(yamlContent) {
16660
16737
  }
16661
16738
  async function loadPolicyFile(filePath) {
16662
16739
  try {
16663
- const content = await fs24.readFile(filePath, "utf-8");
16740
+ const content = await fs25.readFile(filePath, "utf-8");
16664
16741
  return parsePolicyString(content);
16665
16742
  } catch (error) {
16666
16743
  if (error.code === "ENOENT") {
@@ -16684,15 +16761,15 @@ async function loadPolicyFile(filePath) {
16684
16761
  }
16685
16762
  }
16686
16763
  async function loadPolicy(vaultPath2, policyName) {
16687
- const policiesDir = path25.join(vaultPath2, ".claude", "policies");
16688
- const policyPath = path25.join(policiesDir, `${policyName}.yaml`);
16764
+ const policiesDir = path26.join(vaultPath2, ".claude", "policies");
16765
+ const policyPath = path26.join(policiesDir, `${policyName}.yaml`);
16689
16766
  try {
16690
- await fs24.access(policyPath);
16767
+ await fs25.access(policyPath);
16691
16768
  return loadPolicyFile(policyPath);
16692
16769
  } catch {
16693
- const ymlPath = path25.join(policiesDir, `${policyName}.yml`);
16770
+ const ymlPath = path26.join(policiesDir, `${policyName}.yml`);
16694
16771
  try {
16695
- await fs24.access(ymlPath);
16772
+ await fs25.access(ymlPath);
16696
16773
  return loadPolicyFile(ymlPath);
16697
16774
  } catch {
16698
16775
  return {
@@ -16832,8 +16909,8 @@ init_schema();
16832
16909
  init_writer();
16833
16910
  init_git();
16834
16911
  init_wikilinks();
16835
- import fs26 from "fs/promises";
16836
- import path27 from "path";
16912
+ import fs27 from "fs/promises";
16913
+ import path28 from "path";
16837
16914
  init_constants();
16838
16915
  async function executeStep(step, vaultPath2, context, conditionResults, searchFn) {
16839
16916
  const { execute, reason } = shouldStepExecute(step.when, conditionResults);
@@ -17041,9 +17118,9 @@ async function executeToggleTask(params, vaultPath2) {
17041
17118
  const notePath = String(params.path || "");
17042
17119
  const task = String(params.task || "");
17043
17120
  const section = params.section ? String(params.section) : void 0;
17044
- const fullPath = path27.join(vaultPath2, notePath);
17121
+ const fullPath = path28.join(vaultPath2, notePath);
17045
17122
  try {
17046
- await fs26.access(fullPath);
17123
+ await fs27.access(fullPath);
17047
17124
  } catch {
17048
17125
  return { success: false, message: `File not found: ${notePath}`, path: notePath };
17049
17126
  }
@@ -17324,15 +17401,15 @@ async function rollbackChanges(vaultPath2, originalContents, filesModified) {
17324
17401
  const pathCheck = await validatePathSecure(vaultPath2, filePath);
17325
17402
  if (!pathCheck.valid) continue;
17326
17403
  const original = originalContents.get(filePath);
17327
- const fullPath = path27.join(vaultPath2, filePath);
17404
+ const fullPath = path28.join(vaultPath2, filePath);
17328
17405
  if (original === null) {
17329
17406
  try {
17330
- await fs26.unlink(fullPath);
17407
+ await fs27.unlink(fullPath);
17331
17408
  } catch {
17332
17409
  }
17333
17410
  } else if (original !== void 0) {
17334
17411
  try {
17335
- await fs26.writeFile(fullPath, original);
17412
+ await fs27.writeFile(fullPath, original);
17336
17413
  } catch {
17337
17414
  }
17338
17415
  }
@@ -17378,33 +17455,33 @@ async function previewPolicy(policy, vaultPath2, variables) {
17378
17455
  }
17379
17456
 
17380
17457
  // src/core/write/policy/storage.ts
17381
- import fs27 from "fs/promises";
17382
- import path28 from "path";
17458
+ import fs28 from "fs/promises";
17459
+ import path29 from "path";
17383
17460
  function getPoliciesDir(vaultPath2) {
17384
- return path28.join(vaultPath2, ".claude", "policies");
17461
+ return path29.join(vaultPath2, ".claude", "policies");
17385
17462
  }
17386
17463
  async function ensurePoliciesDir(vaultPath2) {
17387
17464
  const dir = getPoliciesDir(vaultPath2);
17388
- await fs27.mkdir(dir, { recursive: true });
17465
+ await fs28.mkdir(dir, { recursive: true });
17389
17466
  }
17390
17467
  async function listPolicies(vaultPath2) {
17391
17468
  const dir = getPoliciesDir(vaultPath2);
17392
17469
  const policies = [];
17393
17470
  try {
17394
- const files = await fs27.readdir(dir);
17471
+ const files = await fs28.readdir(dir);
17395
17472
  for (const file of files) {
17396
17473
  if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
17397
17474
  continue;
17398
17475
  }
17399
- const filePath = path28.join(dir, file);
17400
- const stat4 = await fs27.stat(filePath);
17401
- const content = await fs27.readFile(filePath, "utf-8");
17476
+ const filePath = path29.join(dir, file);
17477
+ const stat5 = await fs28.stat(filePath);
17478
+ const content = await fs28.readFile(filePath, "utf-8");
17402
17479
  const metadata = extractPolicyMetadata(content);
17403
17480
  policies.push({
17404
17481
  name: metadata.name || file.replace(/\.ya?ml$/, ""),
17405
17482
  description: metadata.description || "No description",
17406
17483
  path: file,
17407
- lastModified: stat4.mtime,
17484
+ lastModified: stat5.mtime,
17408
17485
  version: metadata.version || "1.0",
17409
17486
  requiredVariables: metadata.variables || []
17410
17487
  });
@@ -17421,10 +17498,10 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
17421
17498
  const dir = getPoliciesDir(vaultPath2);
17422
17499
  await ensurePoliciesDir(vaultPath2);
17423
17500
  const filename = `${policyName}.yaml`;
17424
- const filePath = path28.join(dir, filename);
17501
+ const filePath = path29.join(dir, filename);
17425
17502
  if (!overwrite) {
17426
17503
  try {
17427
- await fs27.access(filePath);
17504
+ await fs28.access(filePath);
17428
17505
  return {
17429
17506
  success: false,
17430
17507
  path: filename,
@@ -17441,7 +17518,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
17441
17518
  message: `Invalid policy: ${validation.errors.map((e) => e.message).join("; ")}`
17442
17519
  };
17443
17520
  }
17444
- await fs27.writeFile(filePath, content, "utf-8");
17521
+ await fs28.writeFile(filePath, content, "utf-8");
17445
17522
  return {
17446
17523
  success: true,
17447
17524
  path: filename,
@@ -17966,8 +18043,8 @@ function registerPolicyTools(server2, getVaultPath, getSearchFn) {
17966
18043
  import { z as z21 } from "zod";
17967
18044
 
17968
18045
  // src/core/write/tagRename.ts
17969
- import * as fs28 from "fs/promises";
17970
- import * as path29 from "path";
18046
+ import * as fs29 from "fs/promises";
18047
+ import * as path30 from "path";
17971
18048
  import matter8 from "gray-matter";
17972
18049
  import { getProtectedZones } from "@velvetmonkey/vault-core";
17973
18050
  function getNotesInFolder3(index, folder) {
@@ -18073,10 +18150,10 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
18073
18150
  const previews = [];
18074
18151
  let totalChanges = 0;
18075
18152
  for (const note of affectedNotes) {
18076
- const fullPath = path29.join(vaultPath2, note.path);
18153
+ const fullPath = path30.join(vaultPath2, note.path);
18077
18154
  let fileContent;
18078
18155
  try {
18079
- fileContent = await fs28.readFile(fullPath, "utf-8");
18156
+ fileContent = await fs29.readFile(fullPath, "utf-8");
18080
18157
  } catch {
18081
18158
  continue;
18082
18159
  }
@@ -18149,7 +18226,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
18149
18226
  previews.push(preview);
18150
18227
  if (!dryRun) {
18151
18228
  const newContent = matter8.stringify(updatedContent, fm);
18152
- await fs28.writeFile(fullPath, newContent, "utf-8");
18229
+ await fs29.writeFile(fullPath, newContent, "utf-8");
18153
18230
  }
18154
18231
  }
18155
18232
  }
@@ -19084,7 +19161,7 @@ function selectByMmr(candidates, limit, lambda = 0.7) {
19084
19161
  // src/core/read/snippets.ts
19085
19162
  init_embeddings();
19086
19163
  init_stemmer();
19087
- import * as fs29 from "fs";
19164
+ import * as fs30 from "fs";
19088
19165
  function stripFrontmatter(content) {
19089
19166
  const match = content.match(/^---[\s\S]*?---\n([\s\S]*)$/);
19090
19167
  return match ? match[1] : content;
@@ -19130,7 +19207,7 @@ async function extractBestSnippets(filePath, queryEmbedding, queryTokens, option
19130
19207
  const maxChunkChars = options?.maxChunkChars ?? 500;
19131
19208
  let content;
19132
19209
  try {
19133
- content = fs29.readFileSync(filePath, "utf-8");
19210
+ content = fs30.readFileSync(filePath, "utf-8");
19134
19211
  } catch {
19135
19212
  return [];
19136
19213
  }
@@ -19449,6 +19526,14 @@ function registerRecallTools(server2, getStateDb2, getVaultPath, getIndex) {
19449
19526
  const notes = results.filter((r) => r.type === "note");
19450
19527
  const memories = results.filter((r) => r.type === "memory");
19451
19528
  const index = getIndex?.() ?? null;
19529
+ const enrichedNotes = notes.map((n) => ({
19530
+ path: n.id,
19531
+ snippet: n.content,
19532
+ ...enrichNoteCompact(n.id, stateDb2, index)
19533
+ }));
19534
+ const hopResults = index ? multiHopBackfill(enrichedNotes, index, stateDb2, {
19535
+ maxBackfill: Math.max(5, (args.max_results ?? 10) - notes.length)
19536
+ }) : [];
19452
19537
  return {
19453
19538
  content: [{
19454
19539
  type: "text",
@@ -19458,20 +19543,12 @@ function registerRecallTools(server2, getStateDb2, getVaultPath, getIndex) {
19458
19543
  entities: entities.map((e) => ({
19459
19544
  name: e.id,
19460
19545
  description: e.content,
19461
- score: Math.round(e.score * 10) / 10,
19462
- breakdown: e.breakdown,
19463
19546
  ...enrichEntityCompact(e.id, stateDb2, index)
19464
19547
  })),
19465
- notes: notes.map((n) => ({
19466
- path: n.id,
19467
- snippet: n.content,
19468
- score: Math.round(n.score * 10) / 10,
19469
- ...enrichNoteCompact(n.id, stateDb2, index)
19470
- })),
19548
+ notes: [...enrichedNotes, ...hopResults],
19471
19549
  memories: memories.map((m) => ({
19472
19550
  key: m.id,
19473
- value: m.content,
19474
- score: Math.round(m.score * 10) / 10
19551
+ value: m.content
19475
19552
  }))
19476
19553
  }, null, 2)
19477
19554
  }]
@@ -19565,8 +19642,8 @@ function getNoteAccessFrequency(stateDb2, daysBack = 30) {
19565
19642
  }
19566
19643
  }
19567
19644
  }
19568
- return Array.from(noteMap.entries()).map(([path33, stats]) => ({
19569
- path: path33,
19645
+ return Array.from(noteMap.entries()).map(([path34, stats]) => ({
19646
+ path: path34,
19570
19647
  access_count: stats.access_count,
19571
19648
  last_accessed: stats.last_accessed,
19572
19649
  tools_used: Array.from(stats.tools)
@@ -19905,8 +19982,8 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb2) {
19905
19982
  init_wikilinks();
19906
19983
  init_wikilinkFeedback();
19907
19984
  import { z as z28 } from "zod";
19908
- import * as fs30 from "fs/promises";
19909
- import * as path30 from "path";
19985
+ import * as fs31 from "fs/promises";
19986
+ import * as path31 from "path";
19910
19987
  import { scanVaultEntities as scanVaultEntities3, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
19911
19988
  init_embeddings();
19912
19989
  function hasSkipWikilinks(content) {
@@ -19919,16 +19996,16 @@ function hasSkipWikilinks(content) {
19919
19996
  async function collectMarkdownFiles(dirPath, basePath, excludeFolders) {
19920
19997
  const results = [];
19921
19998
  try {
19922
- const entries = await fs30.readdir(dirPath, { withFileTypes: true });
19999
+ const entries = await fs31.readdir(dirPath, { withFileTypes: true });
19923
20000
  for (const entry of entries) {
19924
20001
  if (entry.name.startsWith(".")) continue;
19925
- const fullPath = path30.join(dirPath, entry.name);
20002
+ const fullPath = path31.join(dirPath, entry.name);
19926
20003
  if (entry.isDirectory()) {
19927
20004
  if (excludeFolders.some((f) => entry.name.toLowerCase() === f.toLowerCase())) continue;
19928
20005
  const sub = await collectMarkdownFiles(fullPath, basePath, excludeFolders);
19929
20006
  results.push(...sub);
19930
20007
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
19931
- results.push(path30.relative(basePath, fullPath));
20008
+ results.push(path31.relative(basePath, fullPath));
19932
20009
  }
19933
20010
  }
19934
20011
  } catch {
@@ -19958,7 +20035,7 @@ var EXCLUDE_FOLDERS = [
19958
20035
  ];
19959
20036
  function buildStatusReport(stateDb2, vaultPath2) {
19960
20037
  const recommendations = [];
19961
- const dbPath = path30.join(vaultPath2, ".flywheel", "state.db");
20038
+ const dbPath = path31.join(vaultPath2, ".flywheel", "state.db");
19962
20039
  const statedbExists = stateDb2 !== null;
19963
20040
  if (!statedbExists) {
19964
20041
  recommendations.push("StateDb not initialized \u2014 server needs restart");
@@ -20084,10 +20161,10 @@ async function executeRun(stateDb2, vaultPath2) {
20084
20161
  const allFiles = await collectMarkdownFiles(vaultPath2, vaultPath2, EXCLUDE_FOLDERS);
20085
20162
  let eligible = 0;
20086
20163
  for (const relativePath of allFiles) {
20087
- const fullPath = path30.join(vaultPath2, relativePath);
20164
+ const fullPath = path31.join(vaultPath2, relativePath);
20088
20165
  let content;
20089
20166
  try {
20090
- content = await fs30.readFile(fullPath, "utf-8");
20167
+ content = await fs31.readFile(fullPath, "utf-8");
20091
20168
  } catch {
20092
20169
  continue;
20093
20170
  }
@@ -20142,10 +20219,10 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
20142
20219
  const eligible = [];
20143
20220
  let notesSkipped = 0;
20144
20221
  for (const relativePath of allFiles) {
20145
- const fullPath = path30.join(vaultPath2, relativePath);
20222
+ const fullPath = path31.join(vaultPath2, relativePath);
20146
20223
  let content;
20147
20224
  try {
20148
- content = await fs30.readFile(fullPath, "utf-8");
20225
+ content = await fs31.readFile(fullPath, "utf-8");
20149
20226
  } catch {
20150
20227
  continue;
20151
20228
  }
@@ -20172,8 +20249,8 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
20172
20249
  match_count: result.linksAdded
20173
20250
  });
20174
20251
  if (!dryRun) {
20175
- const fullPath = path30.join(vaultPath2, relativePath);
20176
- await fs30.writeFile(fullPath, result.content, "utf-8");
20252
+ const fullPath = path31.join(vaultPath2, relativePath);
20253
+ await fs31.writeFile(fullPath, result.content, "utf-8");
20177
20254
  notesModified++;
20178
20255
  if (stateDb2) {
20179
20256
  trackWikilinkApplications(stateDb2, relativePath, entities);
@@ -20563,8 +20640,8 @@ import { z as z31 } from "zod";
20563
20640
 
20564
20641
  // src/core/read/similarity.ts
20565
20642
  init_embeddings();
20566
- import * as fs31 from "fs";
20567
- import * as path31 from "path";
20643
+ import * as fs32 from "fs";
20644
+ import * as path32 from "path";
20568
20645
  var STOP_WORDS = /* @__PURE__ */ new Set([
20569
20646
  "the",
20570
20647
  "be",
@@ -20701,10 +20778,10 @@ function extractKeyTerms(content, maxTerms = 15) {
20701
20778
  }
20702
20779
  function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
20703
20780
  const limit = options.limit ?? 10;
20704
- const absPath = path31.join(vaultPath2, sourcePath);
20781
+ const absPath = path32.join(vaultPath2, sourcePath);
20705
20782
  let content;
20706
20783
  try {
20707
- content = fs31.readFileSync(absPath, "utf-8");
20784
+ content = fs32.readFileSync(absPath, "utf-8");
20708
20785
  } catch {
20709
20786
  return [];
20710
20787
  }
@@ -20843,7 +20920,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb2) {
20843
20920
  diversity: z31.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
20844
20921
  }
20845
20922
  },
20846
- async ({ path: path33, limit, diversity }) => {
20923
+ async ({ path: path34, limit, diversity }) => {
20847
20924
  const index = getIndex();
20848
20925
  const vaultPath2 = getVaultPath();
20849
20926
  const stateDb2 = getStateDb2();
@@ -20852,10 +20929,10 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb2) {
20852
20929
  content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
20853
20930
  };
20854
20931
  }
20855
- if (!index.notes.has(path33)) {
20932
+ if (!index.notes.has(path34)) {
20856
20933
  return {
20857
20934
  content: [{ type: "text", text: JSON.stringify({
20858
- error: `Note not found: ${path33}`,
20935
+ error: `Note not found: ${path34}`,
20859
20936
  hint: "Use the full relative path including .md extension"
20860
20937
  }, null, 2) }]
20861
20938
  };
@@ -20867,12 +20944,12 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb2) {
20867
20944
  };
20868
20945
  const useHybrid = hasEmbeddingsIndex();
20869
20946
  const method = useHybrid ? "hybrid" : "bm25";
20870
- const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path33, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path33, opts);
20947
+ const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path34, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path34, opts);
20871
20948
  return {
20872
20949
  content: [{
20873
20950
  type: "text",
20874
20951
  text: JSON.stringify({
20875
- source: path33,
20952
+ source: path34,
20876
20953
  method,
20877
20954
  count: results.length,
20878
20955
  similar: results
@@ -21747,7 +21824,7 @@ init_cooccurrence();
21747
21824
  init_retrievalCooccurrence();
21748
21825
  init_corrections();
21749
21826
  init_edgeWeights();
21750
- import * as fs32 from "node:fs/promises";
21827
+ import * as fs33 from "node:fs/promises";
21751
21828
  import { createHash as createHash3 } from "node:crypto";
21752
21829
 
21753
21830
  // src/resources/vault.ts
@@ -22293,7 +22370,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
22293
22370
  let totalBytes = 0;
22294
22371
  for (const p of notePaths) {
22295
22372
  try {
22296
- totalBytes += statSync5(path32.join(vp, p)).size;
22373
+ totalBytes += statSync5(path33.join(vp, p)).size;
22297
22374
  } catch {
22298
22375
  }
22299
22376
  }
@@ -22792,22 +22869,22 @@ async function buildStartupCatchupBatch(vaultPath2, sinceMs) {
22792
22869
  async function scanDir(dir) {
22793
22870
  let entries;
22794
22871
  try {
22795
- entries = await fs32.readdir(dir, { withFileTypes: true });
22872
+ entries = await fs33.readdir(dir, { withFileTypes: true });
22796
22873
  } catch {
22797
22874
  return;
22798
22875
  }
22799
22876
  for (const entry of entries) {
22800
- const fullPath = path32.join(dir, entry.name);
22877
+ const fullPath = path33.join(dir, entry.name);
22801
22878
  if (entry.isDirectory()) {
22802
22879
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
22803
22880
  await scanDir(fullPath);
22804
22881
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
22805
22882
  try {
22806
- const stat4 = await fs32.stat(fullPath);
22807
- if (stat4.mtimeMs > sinceMs) {
22883
+ const stat5 = await fs33.stat(fullPath);
22884
+ if (stat5.mtimeMs > sinceMs) {
22808
22885
  events.push({
22809
22886
  type: "upsert",
22810
- path: path32.relative(vaultPath2, fullPath),
22887
+ path: path33.relative(vaultPath2, fullPath),
22811
22888
  originalEvents: []
22812
22889
  });
22813
22890
  }
@@ -22997,8 +23074,8 @@ async function runPostIndexWork(index) {
22997
23074
  }
22998
23075
  } catch {
22999
23076
  try {
23000
- const dir = path32.dirname(rawPath);
23001
- const base = path32.basename(rawPath);
23077
+ const dir = path33.dirname(rawPath);
23078
+ const base = path33.basename(rawPath);
23002
23079
  const resolvedDir = realpathSync(dir).replace(/\\/g, "/");
23003
23080
  for (const prefix of vaultPrefixes) {
23004
23081
  if (resolvedDir.startsWith(prefix + "/") || resolvedDir === prefix) {
@@ -23030,7 +23107,7 @@ async function runPostIndexWork(index) {
23030
23107
  continue;
23031
23108
  }
23032
23109
  try {
23033
- const content = await fs32.readFile(path32.join(vaultPath, event.path), "utf-8");
23110
+ const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
23034
23111
  const hash = createHash3("sha256").update(content).digest("hex").slice(0, 16);
23035
23112
  if (lastContentHashes.get(event.path) === hash) {
23036
23113
  serverLog("watcher", `Hash unchanged, skipping: ${event.path}`);
@@ -23111,7 +23188,7 @@ async function runPostIndexWork(index) {
23111
23188
  ...batch,
23112
23189
  events: filteredEvents.map((e) => ({
23113
23190
  ...e,
23114
- path: path32.join(vaultPath, e.path)
23191
+ path: path33.join(vaultPath, e.path)
23115
23192
  }))
23116
23193
  };
23117
23194
  const batchResult = await processBatch(vaultIndex, vaultPath, absoluteBatch);
@@ -23262,7 +23339,7 @@ async function runPostIndexWork(index) {
23262
23339
  removeEmbedding(event.path);
23263
23340
  embRemoved++;
23264
23341
  } else if (event.path.endsWith(".md")) {
23265
- const absPath = path32.join(vaultPath, event.path);
23342
+ const absPath = path33.join(vaultPath, event.path);
23266
23343
  await updateEmbedding(event.path, absPath);
23267
23344
  embUpdated++;
23268
23345
  }
@@ -23482,7 +23559,7 @@ async function runPostIndexWork(index) {
23482
23559
  for (const event of filteredEvents) {
23483
23560
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
23484
23561
  try {
23485
- const content = await fs32.readFile(path32.join(vaultPath, event.path), "utf-8");
23562
+ const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
23486
23563
  const zones = getProtectedZones2(content);
23487
23564
  const linked = new Set(
23488
23565
  (forwardLinkResults.find((r) => r.file === event.path)?.resolved ?? []).map((n) => n.toLowerCase())
@@ -23523,7 +23600,7 @@ async function runPostIndexWork(index) {
23523
23600
  for (const event of filteredEvents) {
23524
23601
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
23525
23602
  try {
23526
- const content = await fs32.readFile(path32.join(vaultPath, event.path), "utf-8");
23603
+ const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
23527
23604
  const removed = processImplicitFeedback(stateDb, event.path, content);
23528
23605
  for (const entity of removed) feedbackResults.push({ entity, file: event.path });
23529
23606
  } catch {
@@ -23603,7 +23680,7 @@ async function runPostIndexWork(index) {
23603
23680
  for (const event of filteredEvents) {
23604
23681
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
23605
23682
  try {
23606
- const content = await fs32.readFile(path32.join(vaultPath, event.path), "utf-8");
23683
+ const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
23607
23684
  const zones = getProtectedZones2(content);
23608
23685
  const linkedSet = new Set(
23609
23686
  (forwardLinkResults.find((r) => r.file === event.path)?.resolved ?? []).concat(forwardLinkResults.find((r) => r.file === event.path)?.dead ?? []).map((n) => n.toLowerCase())
@@ -23636,7 +23713,7 @@ async function runPostIndexWork(index) {
23636
23713
  for (const event of filteredEvents) {
23637
23714
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
23638
23715
  try {
23639
- const content = await fs32.readFile(path32.join(vaultPath, event.path), "utf-8");
23716
+ const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
23640
23717
  const result = await suggestRelatedLinks(content, {
23641
23718
  maxSuggestions: 5,
23642
23719
  strictness: "balanced",
@@ -23660,6 +23737,33 @@ async function runPostIndexWork(index) {
23660
23737
  if (suggestionResults.length > 0) {
23661
23738
  serverLog("watcher", `Suggestion scoring: ${suggestionResults.length} files scored`);
23662
23739
  }
23740
+ if (flywheelConfig?.proactive_linking !== false && suggestionResults.length > 0) {
23741
+ tracker.start("proactive_linking", { files: suggestionResults.length });
23742
+ try {
23743
+ const proactiveResults = [];
23744
+ for (const { file, top } of suggestionResults) {
23745
+ if (getNoteContext(file) === "daily") continue;
23746
+ try {
23747
+ const result = await applyProactiveSuggestions(file, vaultPath, top, {
23748
+ minScore: flywheelConfig?.proactive_min_score ?? 20,
23749
+ maxPerFile: flywheelConfig?.proactive_max_per_file ?? 3
23750
+ });
23751
+ if (result.applied.length > 0) {
23752
+ proactiveResults.push({ file, applied: result.applied });
23753
+ }
23754
+ } catch {
23755
+ }
23756
+ }
23757
+ const totalApplied = proactiveResults.reduce((s, r) => s + r.applied.length, 0);
23758
+ tracker.end({ files_modified: proactiveResults.length, total_applied: totalApplied, results: proactiveResults });
23759
+ if (totalApplied > 0) {
23760
+ serverLog("watcher", `Proactive linking: ${totalApplied} links in ${proactiveResults.length} files`);
23761
+ }
23762
+ } catch (e) {
23763
+ tracker.end({ error: String(e) });
23764
+ serverLog("watcher", `Proactive linking failed: ${e}`, "error");
23765
+ }
23766
+ }
23663
23767
  tracker.start("tag_scan", { files: filteredEvents.length });
23664
23768
  try {
23665
23769
  const tagDiffs = [];