@velvetmonkey/flywheel-memory 2.5.9 → 2.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
- }) : x)(function(x) {
7
- if (typeof require !== "undefined") return require.apply(this, arguments);
8
- throw Error('Dynamic require of "' + x + '" is not supported');
9
- });
10
4
  var __esm = (fn, res) => function __init() {
11
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
6
  };
@@ -283,6 +277,8 @@ var init_vault_scope = __esm({
283
277
  import * as crypto from "crypto";
284
278
  import * as fs3 from "fs";
285
279
  import * as path2 from "path";
280
+ import { Worker } from "node:worker_threads";
281
+ import { fileURLToPath } from "node:url";
286
282
  function getModelConfig() {
287
283
  const envModel = process.env.EMBEDDING_MODEL?.trim();
288
284
  if (!envModel) return MODEL_REGISTRY[DEFAULT_MODEL];
@@ -334,92 +330,115 @@ function clearEmbeddingsForRebuild() {
334
330
  function setEmbeddingsDatabase(database) {
335
331
  db = database;
336
332
  }
337
- function clearModelCache(modelId) {
338
- try {
339
- const candidates = [];
340
- try {
341
- const transformersDir = path2.dirname(__require.resolve("@huggingface/transformers/package.json"));
342
- candidates.push(path2.join(transformersDir, ".cache", ...modelId.split("/")));
343
- } catch {
344
- }
345
- const home = process.env.HOME || process.env.USERPROFILE || "";
346
- if (home) {
347
- const npxDir = path2.join(home, ".npm", "_npx");
348
- if (fs3.existsSync(npxDir)) {
349
- for (const hash of fs3.readdirSync(npxDir)) {
350
- const candidate = path2.join(npxDir, hash, "node_modules", "@huggingface", "transformers", ".cache", ...modelId.split("/"));
351
- if (fs3.existsSync(candidate)) candidates.push(candidate);
352
- }
353
- }
354
- }
355
- for (const cacheDir of candidates) {
356
- if (fs3.existsSync(cacheDir)) {
357
- fs3.rmSync(cacheDir, { recursive: true, force: true });
358
- console.error(`[Semantic] Deleted corrupted model cache: ${cacheDir}`);
359
- }
360
- }
361
- } catch (e) {
362
- console.error(`[Semantic] Could not clear model cache: ${e instanceof Error ? e.message : e}`);
363
- }
333
+ function resolveWorkerPath() {
334
+ const thisFile = typeof __filename !== "undefined" ? __filename : fileURLToPath(import.meta.url);
335
+ const thisDir = path2.dirname(thisFile);
336
+ const workerPath = path2.join(thisDir, "embedding-worker.js");
337
+ if (fs3.existsSync(workerPath)) return workerPath;
338
+ const devPath = path2.resolve(thisDir, "..", "..", "..", "dist", "embedding-worker.js");
339
+ if (fs3.existsSync(devPath)) return devPath;
340
+ throw new Error(
341
+ `Embedding worker not found at ${workerPath}. Run 'npm run build' to generate it.`
342
+ );
364
343
  }
365
344
  async function initEmbeddings() {
366
- if (pipeline) return;
367
- if (initPromise) return initPromise;
368
- initPromise = (async () => {
369
- const MAX_RETRIES = 3;
370
- const RETRY_DELAYS = [2e3, 5e3, 1e4];
371
- let cacheCleared = false;
372
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
373
- try {
374
- const transformers = await Function("specifier", "return import(specifier)")("@huggingface/transformers");
375
- console.error(`[Semantic] Loading model ${activeModelConfig.id} (~23MB, cached after first download)...`);
376
- pipeline = await transformers.pipeline("feature-extraction", activeModelConfig.id, {
377
- dtype: "fp32"
378
- });
379
- console.error(`[Semantic] Model loaded successfully`);
380
- if (activeModelConfig.dims === 0) {
381
- const probe = await pipeline("test", { pooling: "mean", normalize: true });
382
- activeModelConfig.dims = probe.data.length;
383
- console.error(`[Semantic] Probed model ${activeModelConfig.id}: ${activeModelConfig.dims} dims`);
384
- }
385
- return;
386
- } catch (err) {
387
- if (err instanceof Error && (err.message.includes("Cannot find package") || err.message.includes("MODULE_NOT_FOUND") || err.message.includes("Cannot find module") || err.message.includes("ERR_MODULE_NOT_FOUND"))) {
388
- initPromise = null;
389
- throw new Error(
390
- "Semantic search requires @huggingface/transformers. Install it with: npm install @huggingface/transformers"
391
- );
345
+ if (workerReady && worker) return;
346
+ if (workerInitPromise) return workerInitPromise;
347
+ workerInitPromise = new Promise((resolve3, reject) => {
348
+ try {
349
+ const workerPath = resolveWorkerPath();
350
+ console.error(`[Semantic] Spawning embedding worker: ${workerPath}`);
351
+ worker = new Worker(workerPath);
352
+ worker.on("message", (msg) => {
353
+ switch (msg.type) {
354
+ case "ready":
355
+ workerReady = true;
356
+ workerDims = msg.dims;
357
+ if (activeModelConfig.dims === 0) {
358
+ activeModelConfig.dims = msg.dims;
359
+ console.error(`[Semantic] Probed model ${activeModelConfig.id}: ${msg.dims} dims`);
360
+ }
361
+ console.error(`[Semantic] Worker ready (model: ${activeModelConfig.id}, dims: ${msg.dims})`);
362
+ resolve3();
363
+ break;
364
+ case "result": {
365
+ const pending = pendingEmbeds.get(msg.id);
366
+ if (pending) {
367
+ pendingEmbeds.delete(msg.id);
368
+ pending.resolve(new Float32Array(msg.embedding));
369
+ }
370
+ break;
371
+ }
372
+ case "error": {
373
+ if (msg.fatal) {
374
+ console.error(`[Semantic] Worker fatal error: ${msg.message}`);
375
+ console.error(`[Semantic] Semantic search disabled. Keyword search (BM25) remains available.`);
376
+ terminateWorker();
377
+ workerInitPromise = null;
378
+ reject(new Error(msg.message));
379
+ } else if (msg.id != null) {
380
+ const pending = pendingEmbeds.get(msg.id);
381
+ if (pending) {
382
+ pendingEmbeds.delete(msg.id);
383
+ pending.reject(new Error(msg.message));
384
+ }
385
+ }
386
+ break;
387
+ }
392
388
  }
393
- const errMsg = err instanceof Error ? err.message : String(err);
394
- if (!cacheCleared && (errMsg.includes("Protobuf parsing failed") || errMsg.includes("onnx"))) {
395
- console.error(`[Semantic] Corrupted model cache detected: ${errMsg}`);
396
- clearModelCache(activeModelConfig.id);
397
- cacheCleared = true;
398
- pipeline = null;
399
- continue;
389
+ });
390
+ worker.on("error", (err) => {
391
+ console.error(`[Semantic] Worker error: ${err.message}`);
392
+ handleWorkerCrash();
393
+ if (!workerReady) {
394
+ workerInitPromise = null;
395
+ reject(err);
400
396
  }
401
- if (attempt < MAX_RETRIES) {
402
- const delay = RETRY_DELAYS[attempt - 1];
403
- console.error(`[Semantic] Model load failed (attempt ${attempt}/${MAX_RETRIES}): ${errMsg}`);
404
- console.error(`[Semantic] Retrying in ${delay / 1e3}s...`);
405
- await new Promise((resolve2) => setTimeout(resolve2, delay));
406
- pipeline = null;
407
- } else {
408
- console.error(`[Semantic] Model load failed after ${MAX_RETRIES} attempts: ${err instanceof Error ? err.message : err}`);
409
- console.error(`[Semantic] Semantic search disabled. Keyword search (BM25) remains available.`);
410
- initPromise = null;
411
- throw err;
397
+ });
398
+ worker.on("exit", (code) => {
399
+ if (code !== 0 && workerReady) {
400
+ console.error(`[Semantic] Worker exited with code ${code}`);
401
+ handleWorkerCrash();
412
402
  }
413
- }
403
+ });
404
+ worker.postMessage({ type: "init", modelId: activeModelConfig.id });
405
+ } catch (err) {
406
+ workerInitPromise = null;
407
+ reject(err);
414
408
  }
415
- })();
416
- return initPromise;
409
+ });
410
+ return workerInitPromise;
411
+ }
412
+ function handleWorkerCrash() {
413
+ for (const [id, pending] of pendingEmbeds) {
414
+ pending.reject(new Error("Embedding worker crashed"));
415
+ pendingEmbeds.delete(id);
416
+ }
417
+ worker = null;
418
+ workerReady = false;
419
+ workerInitPromise = null;
420
+ }
421
+ function terminateWorker() {
422
+ if (worker) {
423
+ try {
424
+ worker.postMessage({ type: "shutdown" });
425
+ } catch {
426
+ }
427
+ worker = null;
428
+ }
429
+ workerReady = false;
430
+ workerInitPromise = null;
417
431
  }
418
432
  async function embedText(text) {
419
433
  await initEmbeddings();
420
- const truncated = text.slice(0, 2e3);
421
- const result = await pipeline(truncated, { pooling: "mean", normalize: true });
422
- return new Float32Array(result.data);
434
+ if (!worker) {
435
+ throw new Error("Embedding worker not available");
436
+ }
437
+ const id = ++embedRequestId;
438
+ return new Promise((resolve3, reject) => {
439
+ pendingEmbeds.set(id, { resolve: resolve3, reject });
440
+ worker.postMessage({ type: "embed", id, text });
441
+ });
423
442
  }
424
443
  async function embedTextCached(text) {
425
444
  const existing = embeddingCache.get(text);
@@ -1116,7 +1135,7 @@ function getEntityEmbeddingsCount() {
1116
1135
  return 0;
1117
1136
  }
1118
1137
  }
1119
- var MODEL_REGISTRY, DEFAULT_MODEL, activeModelConfig, MAX_FILE_SIZE2, db, pipeline, initPromise, embeddingsBuilding, embeddingCache, EMBEDDING_CACHE_MAX, entityEmbeddingsMap, inferredCategoriesMap, EMBEDDING_TEXT_VERSION;
1138
+ var MODEL_REGISTRY, DEFAULT_MODEL, activeModelConfig, MAX_FILE_SIZE2, db, embeddingsBuilding, worker, workerReady, workerDims, workerInitPromise, embedRequestId, pendingEmbeds, embeddingCache, EMBEDDING_CACHE_MAX, entityEmbeddingsMap, inferredCategoriesMap, EMBEDDING_TEXT_VERSION;
1120
1139
  var init_embeddings = __esm({
1121
1140
  "src/core/read/embeddings.ts"() {
1122
1141
  "use strict";
@@ -1133,9 +1152,13 @@ var init_embeddings = __esm({
1133
1152
  activeModelConfig = getModelConfig();
1134
1153
  MAX_FILE_SIZE2 = 5 * 1024 * 1024;
1135
1154
  db = null;
1136
- pipeline = null;
1137
- initPromise = null;
1138
1155
  embeddingsBuilding = false;
1156
+ worker = null;
1157
+ workerReady = false;
1158
+ workerDims = 0;
1159
+ workerInitPromise = null;
1160
+ embedRequestId = 0;
1161
+ pendingEmbeds = /* @__PURE__ */ new Map();
1139
1162
  embeddingCache = /* @__PURE__ */ new Map();
1140
1163
  EMBEDDING_CACHE_MAX = 500;
1141
1164
  entityEmbeddingsMap = /* @__PURE__ */ new Map();
@@ -1255,9 +1278,12 @@ function saveRecencyToStateDb(index, explicitStateDb) {
1255
1278
  }
1256
1279
  console.error(`[Flywheel] saveRecencyToStateDb: Saving ${index.lastMentioned.size} entries...`);
1257
1280
  try {
1258
- for (const [entityNameLower, timestamp] of index.lastMentioned) {
1259
- recordEntityMention(stateDb2, entityNameLower, new Date(timestamp));
1260
- }
1281
+ const runInTransaction = stateDb2.db.transaction(() => {
1282
+ for (const [entityNameLower, timestamp] of index.lastMentioned) {
1283
+ recordEntityMention(stateDb2, entityNameLower, new Date(timestamp));
1284
+ }
1285
+ });
1286
+ runInTransaction();
1261
1287
  const count = stateDb2.db.prepare("SELECT COUNT(*) as cnt FROM recency").get();
1262
1288
  console.error(`[Flywheel] Saved recency: ${index.lastMentioned.size} entries \u2192 ${count.cnt} rows in table`);
1263
1289
  } catch (e) {
@@ -1930,18 +1956,18 @@ function updateSuppressionList(stateDb2, now) {
1930
1956
  `DELETE FROM wikilink_suppressions
1931
1957
  WHERE datetime(updated_at, '+' || ? || ' days') <= datetime('now')`
1932
1958
  ).run(SUPPRESSION_TTL_DAYS);
1933
- for (const stat5 of weightedStats) {
1934
- const effectiveAlpha = getEffectiveAlpha(stat5.entity);
1935
- const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp, effectiveAlpha);
1936
- const totalObs = effectiveAlpha + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
1959
+ for (const stat4 of weightedStats) {
1960
+ const effectiveAlpha = getEffectiveAlpha(stat4.entity);
1961
+ const posteriorMean = computePosteriorMean(stat4.weightedCorrect, stat4.weightedFp, effectiveAlpha);
1962
+ const totalObs = effectiveAlpha + stat4.weightedCorrect + PRIOR_BETA + stat4.weightedFp;
1937
1963
  if (totalObs < SUPPRESSION_MIN_OBSERVATIONS) {
1938
1964
  continue;
1939
1965
  }
1940
1966
  if (posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD) {
1941
- upsert.run(stat5.entity, 1 - posteriorMean);
1967
+ upsert.run(stat4.entity, 1 - posteriorMean);
1942
1968
  updated++;
1943
1969
  } else {
1944
- remove.run(stat5.entity);
1970
+ remove.run(stat4.entity);
1945
1971
  }
1946
1972
  }
1947
1973
  });
@@ -2011,31 +2037,31 @@ function getAllFeedbackBoosts(stateDb2, folder, now) {
2011
2037
  if (folder !== void 0) {
2012
2038
  folderStatsMap = /* @__PURE__ */ new Map();
2013
2039
  for (const gs of globalStats) {
2014
- const fs35 = getWeightedFolderStats(stateDb2, gs.entity, folder, now);
2015
- if (fs35.rawTotal >= FEEDBACK_BOOST_MIN_SAMPLES) {
2040
+ const fs36 = getWeightedFolderStats(stateDb2, gs.entity, folder, now);
2041
+ if (fs36.rawTotal >= FEEDBACK_BOOST_MIN_SAMPLES) {
2016
2042
  folderStatsMap.set(gs.entity, {
2017
- weightedAccuracy: fs35.weightedAccuracy,
2018
- rawCount: fs35.rawTotal
2043
+ weightedAccuracy: fs36.weightedAccuracy,
2044
+ rawCount: fs36.rawTotal
2019
2045
  });
2020
2046
  }
2021
2047
  }
2022
2048
  }
2023
2049
  const boosts = /* @__PURE__ */ new Map();
2024
- for (const stat5 of globalStats) {
2025
- if (stat5.rawTotal < FEEDBACK_BOOST_MIN_SAMPLES) continue;
2050
+ for (const stat4 of globalStats) {
2051
+ if (stat4.rawTotal < FEEDBACK_BOOST_MIN_SAMPLES) continue;
2026
2052
  let accuracy;
2027
2053
  let sampleCount;
2028
- const fs35 = folderStatsMap?.get(stat5.entity);
2029
- if (fs35 && fs35.rawCount >= FEEDBACK_BOOST_MIN_SAMPLES) {
2030
- accuracy = fs35.weightedAccuracy;
2031
- sampleCount = fs35.rawCount;
2054
+ const fs36 = folderStatsMap?.get(stat4.entity);
2055
+ if (fs36 && fs36.rawCount >= FEEDBACK_BOOST_MIN_SAMPLES) {
2056
+ accuracy = fs36.weightedAccuracy;
2057
+ sampleCount = fs36.rawCount;
2032
2058
  } else {
2033
- accuracy = stat5.weightedAccuracy;
2034
- sampleCount = stat5.rawTotal;
2059
+ accuracy = stat4.weightedAccuracy;
2060
+ sampleCount = stat4.rawTotal;
2035
2061
  }
2036
2062
  const boost = computeBoostFromAccuracy(accuracy, sampleCount);
2037
2063
  if (boost !== 0) {
2038
- boosts.set(stat5.entity, boost);
2064
+ boosts.set(stat4.entity, boost);
2039
2065
  }
2040
2066
  }
2041
2067
  return boosts;
@@ -2043,18 +2069,18 @@ function getAllFeedbackBoosts(stateDb2, folder, now) {
2043
2069
  function getAllSuppressionPenalties(stateDb2, now) {
2044
2070
  const penalties = /* @__PURE__ */ new Map();
2045
2071
  const weightedStats = getWeightedEntityStats(stateDb2, now);
2046
- for (const stat5 of weightedStats) {
2047
- const effectiveAlpha = getEffectiveAlpha(stat5.entity);
2048
- const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp, effectiveAlpha);
2049
- const totalObs = effectiveAlpha + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
2072
+ for (const stat4 of weightedStats) {
2073
+ const effectiveAlpha = getEffectiveAlpha(stat4.entity);
2074
+ const posteriorMean = computePosteriorMean(stat4.weightedCorrect, stat4.weightedFp, effectiveAlpha);
2075
+ const totalObs = effectiveAlpha + stat4.weightedCorrect + PRIOR_BETA + stat4.weightedFp;
2050
2076
  if (totalObs >= SUPPRESSION_MIN_OBSERVATIONS) {
2051
2077
  if (posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD) {
2052
2078
  const penalty = Math.round(MAX_SUPPRESSION_PENALTY * (1 - posteriorMean / SUPPRESSION_POSTERIOR_THRESHOLD));
2053
2079
  if (penalty < 0) {
2054
- penalties.set(stat5.entity, penalty);
2080
+ penalties.set(stat4.entity, penalty);
2055
2081
  }
2056
2082
  } else if (posteriorMean < SOFT_PENALTY_THRESHOLD) {
2057
- penalties.set(stat5.entity, SOFT_PENALTY);
2083
+ penalties.set(stat4.entity, SOFT_PENALTY);
2058
2084
  }
2059
2085
  }
2060
2086
  }
@@ -2632,8 +2658,8 @@ function clearLastMutationCommit() {
2632
2658
  async function checkGitLock(vaultPath2) {
2633
2659
  const lockPath = path10.join(vaultPath2, ".git/index.lock");
2634
2660
  try {
2635
- const stat5 = await fs6.stat(lockPath);
2636
- const ageMs = Date.now() - stat5.mtimeMs;
2661
+ const stat4 = await fs6.stat(lockPath);
2662
+ const ageMs = Date.now() - stat4.mtimeMs;
2637
2663
  return {
2638
2664
  locked: true,
2639
2665
  stale: ageMs > STALE_LOCK_THRESHOLD_MS,
@@ -2655,8 +2681,8 @@ async function isGitRepo(vaultPath2) {
2655
2681
  async function checkLockFile(vaultPath2) {
2656
2682
  const lockPath = path10.join(vaultPath2, ".git/index.lock");
2657
2683
  try {
2658
- const stat5 = await fs6.stat(lockPath);
2659
- const ageMs = Date.now() - stat5.mtimeMs;
2684
+ const stat4 = await fs6.stat(lockPath);
2685
+ const ageMs = Date.now() - stat4.mtimeMs;
2660
2686
  return { stale: ageMs > STALE_LOCK_THRESHOLD_MS, ageMs };
2661
2687
  } catch {
2662
2688
  return null;
@@ -2670,12 +2696,12 @@ function isLockContentionError(error) {
2670
2696
  return false;
2671
2697
  }
2672
2698
  function sleep(ms) {
2673
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2699
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2674
2700
  }
2675
- function calculateDelay(attempt, config) {
2676
- let delay = config.baseDelayMs * Math.pow(2, attempt);
2677
- delay = Math.min(delay, config.maxDelayMs);
2678
- if (config.jitter) {
2701
+ function calculateDelay(attempt, config2) {
2702
+ let delay = config2.baseDelayMs * Math.pow(2, attempt);
2703
+ delay = Math.min(delay, config2.maxDelayMs);
2704
+ if (config2.jitter) {
2679
2705
  delay = delay + Math.random() * delay * 0.5;
2680
2706
  }
2681
2707
  return Math.round(delay);
@@ -3569,8 +3595,8 @@ function setWriteStateDb(stateDb2) {
3569
3595
  function getWriteStateDb() {
3570
3596
  return getActiveScopeOrNull()?.stateDb ?? moduleStateDb5;
3571
3597
  }
3572
- function setWikilinkConfig(config) {
3573
- moduleConfig = config;
3598
+ function setWikilinkConfig(config2) {
3599
+ moduleConfig = config2;
3574
3600
  }
3575
3601
  function getConfig() {
3576
3602
  const scope = getActiveScopeOrNull();
@@ -3938,9 +3964,9 @@ function isCommonWordFalsePositive(entityName, rawContent, category) {
3938
3964
  if (!IMPLICIT_EXCLUDE_WORDS.has(lowerName) && !COMMON_ENGLISH_WORDS.has(lowerName)) return false;
3939
3965
  return !rawContent.includes(entityName);
3940
3966
  }
3941
- function capScoreWithoutContentRelevance(score, contentRelevance, config) {
3942
- if (contentRelevance < config.contentRelevanceFloor) {
3943
- return Math.min(score, config.noRelevanceCap);
3967
+ function capScoreWithoutContentRelevance(score, contentRelevance, config2) {
3968
+ if (contentRelevance < config2.contentRelevanceFloor) {
3969
+ return Math.min(score, config2.noRelevanceCap);
3944
3970
  }
3945
3971
  return score;
3946
3972
  }
@@ -3988,7 +4014,7 @@ function getAdaptiveMinScore(contentLength, baseScore) {
3988
4014
  }
3989
4015
  return baseScore;
3990
4016
  }
3991
- function scoreNameAgainstContent(name, contentTokens, contentStems, config, coocIndex, disableExact, disableStem) {
4017
+ function scoreNameAgainstContent(name, contentTokens, contentStems, config2, coocIndex, disableExact, disableStem) {
3992
4018
  const nameTokens = tokenize(name);
3993
4019
  if (nameTokens.length === 0) {
3994
4020
  return { exactScore: 0, stemScore: 0, lexicalScore: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0, nameTokens: [], unmatchedTokenIndices: [] };
@@ -4004,11 +4030,11 @@ function scoreNameAgainstContent(name, contentTokens, contentStems, config, cooc
4004
4030
  const nameStem = nameStems[i];
4005
4031
  const idfWeight = coocIndex ? tokenIdf(token, coocIndex) : 1;
4006
4032
  if (!disableExact && contentTokens.has(token)) {
4007
- exactScore += config.exactMatchBonus * idfWeight;
4033
+ exactScore += config2.exactMatchBonus * idfWeight;
4008
4034
  matchedWords++;
4009
4035
  exactMatches++;
4010
4036
  } else if (!disableStem && contentStems.has(nameStem)) {
4011
- stemScore += config.stemMatchBonus * idfWeight;
4037
+ stemScore += config2.stemMatchBonus * idfWeight;
4012
4038
  matchedWords++;
4013
4039
  } else {
4014
4040
  unmatchedTokenIndices.push(i);
@@ -4017,7 +4043,7 @@ function scoreNameAgainstContent(name, contentTokens, contentStems, config, cooc
4017
4043
  const lexicalScore = Math.round((exactScore + stemScore) * 10) / 10;
4018
4044
  return { exactScore, stemScore, lexicalScore, matchedWords, exactMatches, totalTokens: nameTokens.length, nameTokens, unmatchedTokenIndices };
4019
4045
  }
4020
- function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms, config, disabled, coocIndex, tokenFuzzyCache) {
4046
+ function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms, config2, disabled, coocIndex, tokenFuzzyCache) {
4021
4047
  const zero = { contentMatch: 0, fuzzyMatch: 0, totalLexical: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0 };
4022
4048
  const entityName = getEntityName2(entity);
4023
4049
  const aliases = getEntityAliases(entity);
@@ -4026,10 +4052,10 @@ function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
4026
4052
  const disableFuzzy = disabled.has("fuzzy_match");
4027
4053
  const cache = tokenFuzzyCache ?? /* @__PURE__ */ new Map();
4028
4054
  const idfFn = (token) => coocIndex ? tokenIdf(token, coocIndex) : 1;
4029
- const nameResult = scoreNameAgainstContent(entityName, contentTokens, contentStems, config, coocIndex, disableExact, disableStem);
4055
+ const nameResult = scoreNameAgainstContent(entityName, contentTokens, contentStems, config2, coocIndex, disableExact, disableStem);
4030
4056
  let bestAliasResult = { exactScore: 0, stemScore: 0, lexicalScore: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0, nameTokens: [], unmatchedTokenIndices: [] };
4031
4057
  for (const alias of aliases) {
4032
- const aliasResult = scoreNameAgainstContent(alias, contentTokens, contentStems, config, coocIndex, disableExact, disableStem);
4058
+ const aliasResult = scoreNameAgainstContent(alias, contentTokens, contentStems, config2, coocIndex, disableExact, disableStem);
4033
4059
  if (aliasResult.lexicalScore > bestAliasResult.lexicalScore) {
4034
4060
  bestAliasResult = aliasResult;
4035
4061
  }
@@ -4056,7 +4082,7 @@ function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
4056
4082
  contentTokens,
4057
4083
  collapsedContentTerms,
4058
4084
  fuzzyTargetName,
4059
- config.fuzzyMatchBonus,
4085
+ config2.fuzzyMatchBonus,
4060
4086
  idfFn,
4061
4087
  cache
4062
4088
  );
@@ -4070,11 +4096,11 @@ function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
4070
4096
  }
4071
4097
  if (totalTokens > 1) {
4072
4098
  const matchRatio = matchedWords / totalTokens;
4073
- if (matchRatio < config.minMatchRatio) {
4099
+ if (matchRatio < config2.minMatchRatio) {
4074
4100
  return zero;
4075
4101
  }
4076
4102
  }
4077
- if (config.requireMultipleMatches && totalTokens === 1) {
4103
+ if (config2.requireMultipleMatches && totalTokens === 1) {
4078
4104
  if (exactMatches === 0 && fuzzyMatchedWords === 0) {
4079
4105
  return zero;
4080
4106
  }
@@ -4105,8 +4131,8 @@ async function suggestRelatedLinks(content, options = {}) {
4105
4131
  disabledLayers = []
4106
4132
  } = options;
4107
4133
  const disabled = new Set(disabledLayers);
4108
- const config = STRICTNESS_CONFIGS[strictness];
4109
- const adaptiveMinScore = getAdaptiveMinScore(content.length, config.minSuggestionScore);
4134
+ const config2 = STRICTNESS_CONFIGS[strictness];
4135
+ const adaptiveMinScore = getAdaptiveMinScore(content.length, config2.minSuggestionScore);
4110
4136
  const noteContext = notePath ? getNoteContext(notePath) : "general";
4111
4137
  const contextBoosts = CONTEXT_BOOST[noteContext];
4112
4138
  const emptyResult = { suggestions: [], suffix: "" };
@@ -4128,7 +4154,7 @@ async function suggestRelatedLinks(content, options = {}) {
4128
4154
  const contentTokens = /* @__PURE__ */ new Set();
4129
4155
  const contentStems = /* @__PURE__ */ new Set();
4130
4156
  for (const token of rawTokens) {
4131
- if (token.length >= config.minWordLength && !STOPWORDS_EN2.has(token)) {
4157
+ if (token.length >= config2.minWordLength && !STOPWORDS_EN2.has(token)) {
4132
4158
  contentTokens.add(token);
4133
4159
  contentStems.add(stem(token));
4134
4160
  }
@@ -4136,7 +4162,7 @@ async function suggestRelatedLinks(content, options = {}) {
4136
4162
  if (contentTokens.size === 0) {
4137
4163
  return emptyResult;
4138
4164
  }
4139
- const orderedContentTokens = [...rawTokens].filter((token) => token.length >= config.minWordLength && !STOPWORDS_EN2.has(token)).map(normalizeFuzzyTerm).filter((token) => token.length > 0);
4165
+ const orderedContentTokens = [...rawTokens].filter((token) => token.length >= config2.minWordLength && !STOPWORDS_EN2.has(token)).map(normalizeFuzzyTerm).filter((token) => token.length > 0);
4140
4166
  const collapsedContentTerms = disabled.has("fuzzy_match") ? /* @__PURE__ */ new Set() : buildCollapsedContentTerms(orderedContentTokens);
4141
4167
  const tokenFuzzyCache = /* @__PURE__ */ new Map();
4142
4168
  const linkedEntities = excludeLinked ? extractLinkedEntities(content) : /* @__PURE__ */ new Set();
@@ -4167,7 +4193,7 @@ async function suggestRelatedLinks(content, options = {}) {
4167
4193
  const paths = correctedPairs.get(entityName.toLowerCase());
4168
4194
  if (paths.has(notePath)) continue;
4169
4195
  }
4170
- const entityScore = disabled.has("exact_match") && disabled.has("stem_match") && disabled.has("fuzzy_match") ? { contentMatch: 0, fuzzyMatch: 0, totalLexical: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0 } : scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms, config, disabled, cooccurrenceIndex, tokenFuzzyCache);
4196
+ const entityScore = disabled.has("exact_match") && disabled.has("stem_match") && disabled.has("fuzzy_match") ? { contentMatch: 0, fuzzyMatch: 0, totalLexical: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0 } : scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms, config2, disabled, cooccurrenceIndex, tokenFuzzyCache);
4171
4197
  const contentScore = entityScore.contentMatch;
4172
4198
  const fuzzyMatchScore = entityScore.fuzzyMatch;
4173
4199
  const hasLexicalEvidence = entityScore.totalLexical > 0;
@@ -4204,7 +4230,7 @@ async function suggestRelatedLinks(content, options = {}) {
4204
4230
  }
4205
4231
  const layerSuppressionPenalty = disabled.has("feedback") ? 0 : suppressionPenalties.get(entityName) ?? 0;
4206
4232
  score += layerSuppressionPenalty;
4207
- score = capScoreWithoutContentRelevance(score, contentScore + fuzzyMatchScore, config);
4233
+ score = capScoreWithoutContentRelevance(score, contentScore + fuzzyMatchScore, config2);
4208
4234
  if (hasLexicalEvidence && score >= adaptiveMinScore) {
4209
4235
  scoredEntities.push({
4210
4236
  name: entityName,
@@ -4271,13 +4297,13 @@ async function suggestRelatedLinks(content, options = {}) {
4271
4297
  existing.score += boost;
4272
4298
  existing.breakdown.cooccurrenceBoost += boost;
4273
4299
  const existingContentRelevance = existing.breakdown.contentMatch + existing.breakdown.fuzzyMatch + (existing.breakdown.semanticBoost ?? 0);
4274
- existing.score = capScoreWithoutContentRelevance(existing.score, existingContentRelevance, config);
4300
+ existing.score = capScoreWithoutContentRelevance(existing.score, existingContentRelevance, config2);
4275
4301
  } else {
4276
4302
  const entityTokens = tokenize(entityName);
4277
4303
  const hasContentOverlap = entityTokens.some(
4278
4304
  (token) => contentTokens.has(token) || contentStems.has(stem(token))
4279
4305
  );
4280
- const strongCooccurrence = boost >= config.minCooccurrenceGate;
4306
+ const strongCooccurrence = boost >= config2.minCooccurrenceGate;
4281
4307
  if (!hasContentOverlap && !strongCooccurrence) {
4282
4308
  continue;
4283
4309
  }
@@ -4297,7 +4323,7 @@ async function suggestRelatedLinks(content, options = {}) {
4297
4323
  const suppPenalty = disabled.has("feedback") ? 0 : suppressionPenalties.get(entityName) ?? 0;
4298
4324
  let totalBoost = boost + typeBoost + contextBoost + recencyBoostVal + crossFolderBoost + hubBoost + feedbackAdj + edgeWeightBoost + prospectBoost + suppPenalty;
4299
4325
  const coocContentRelevance = hasContentOverlap ? 5 : 0;
4300
- totalBoost = capScoreWithoutContentRelevance(totalBoost, coocContentRelevance, config);
4326
+ totalBoost = capScoreWithoutContentRelevance(totalBoost, coocContentRelevance, config2);
4301
4327
  const effectiveMinScore = !hasContentOverlap ? Math.max(adaptiveMinScore, 7) : adaptiveMinScore;
4302
4328
  if (totalBoost >= effectiveMinScore) {
4303
4329
  scoredEntities.push({
@@ -4393,11 +4419,11 @@ async function suggestRelatedLinks(content, options = {}) {
4393
4419
  }
4394
4420
  for (const entry of scoredEntities) {
4395
4421
  const contentRelevance = entry.breakdown.contentMatch + entry.breakdown.fuzzyMatch + (entry.breakdown.semanticBoost ?? 0);
4396
- entry.score = capScoreWithoutContentRelevance(entry.score, contentRelevance, config);
4422
+ entry.score = capScoreWithoutContentRelevance(entry.score, contentRelevance, config2);
4397
4423
  }
4398
4424
  const relevantEntities = scoredEntities.filter((e) => {
4399
4425
  if (!entitiesWithAnyScoringPath.has(e.name)) return false;
4400
- if (config.minContentMatch > 0 && e.breakdown.contentMatch < config.minContentMatch) return false;
4426
+ if (config2.minContentMatch > 0 && e.breakdown.contentMatch < config2.minContentMatch) return false;
4401
4427
  return true;
4402
4428
  });
4403
4429
  if (relevantEntities.length === 0) {
@@ -4664,16 +4690,16 @@ async function checkPreflightSimilarity(noteName) {
4664
4690
  }
4665
4691
  return result;
4666
4692
  }
4667
- async function applyProactiveSuggestions(filePath, vaultPath2, suggestions, config) {
4693
+ async function applyProactiveSuggestions(filePath, vaultPath2, suggestions, config2) {
4668
4694
  const stateDb2 = getWriteStateDb();
4669
- const candidates = suggestions.filter((s) => s.score >= config.minScore && s.confidence === "high").slice(0, config.maxPerFile);
4695
+ const candidates = suggestions.filter((s) => s.score >= config2.minScore && s.confidence === "high").slice(0, config2.maxPerFile);
4670
4696
  if (candidates.length === 0) {
4671
4697
  return { applied: [], skipped: [] };
4672
4698
  }
4673
4699
  const fullPath = path11.join(vaultPath2, filePath);
4674
4700
  try {
4675
- const stat5 = await fs7.stat(fullPath);
4676
- if (Date.now() - stat5.mtimeMs < 3e4) {
4701
+ const stat4 = await fs7.stat(fullPath);
4702
+ if (Date.now() - stat4.mtimeMs < 3e4) {
4677
4703
  return { applied: [], skipped: candidates.map((c) => c.entity) };
4678
4704
  }
4679
4705
  } catch {
@@ -4973,7 +4999,7 @@ function enqueueProactiveSuggestions(stateDb2, entries) {
4973
4999
  }
4974
5000
  return enqueued;
4975
5001
  }
4976
- async function drainProactiveQueue(stateDb2, vaultPath2, config, applyFn) {
5002
+ async function drainProactiveQueue(stateDb2, vaultPath2, config2, applyFn) {
4977
5003
  const result = {
4978
5004
  applied: [],
4979
5005
  expired: 0,
@@ -5017,7 +5043,7 @@ async function drainProactiveQueue(stateDb2, vaultPath2, config, applyFn) {
5017
5043
  continue;
5018
5044
  }
5019
5045
  const todayCount = countTodayApplied.get(filePath, todayStr).cnt;
5020
- if (todayCount >= config.maxPerDay) {
5046
+ if (todayCount >= config2.maxPerDay) {
5021
5047
  result.skippedDailyCap += suggestions.length;
5022
5048
  for (const s of suggestions) {
5023
5049
  try {
@@ -5029,10 +5055,10 @@ async function drainProactiveQueue(stateDb2, vaultPath2, config, applyFn) {
5029
5055
  }
5030
5056
  continue;
5031
5057
  }
5032
- const remaining = config.maxPerDay - todayCount;
5058
+ const remaining = config2.maxPerDay - todayCount;
5033
5059
  const capped = suggestions.slice(0, remaining);
5034
5060
  try {
5035
- const applyResult = await applyFn(filePath, vaultPath2, capped, config);
5061
+ const applyResult = await applyFn(filePath, vaultPath2, capped, config2);
5036
5062
  if (applyResult.applied.length > 0) {
5037
5063
  result.applied.push({ file: filePath, entities: applyResult.applied });
5038
5064
  const appliedAt = Date.now();
@@ -5081,8 +5107,8 @@ var init_tool_embeddings_generated = __esm({
5081
5107
  model: "Xenova/all-MiniLM-L6-v2",
5082
5108
  dims: 384,
5083
5109
  version: 1,
5084
- generatedAt: "2026-04-03T22:38:15.295Z",
5085
- sourceHash: "1fb6c9a93b42b358",
5110
+ generatedAt: "2026-04-06T22:11:22.074Z",
5111
+ sourceHash: "55904c4f8b00ff85",
5086
5112
  tools: [
5087
5113
  { name: "absorb_as_alias", category: "corrections", tier: 2, descriptionHash: "27554e8b6b3bb1a4", embedding: [-0.087313, -0.015109, 0.022541, 8661e-6, -0.024621, -0.027376, -9959e-6, -4143e-6, 0.020362, -0.025815, 0.03722, -0.017272, 0.010364, -0.082973, 0.063234, 0.105669, 0.030314, 0.109193, -0.024833, -0.022116, 0.020565, 0.067968, -0.013485, -4453e-6, 0.023703, -0.012531, -0.110704, 0.046425, 0.037009, -0.100492, 0.057064, 0.106952, -0.064645, 0.032072, 0.011054, 0.113279, 6113e-6, 2155e-6, 0.052647, -0.037832, 0.032187, -6735e-6, -0.027158, -0.033756, -0.071285, -0.025865, -0.040482, 0.02512, -0.037086, 4166e-6, -0.012929, -0.07895, -0.101903, 0.085107, -0.014636, 0.091582, -0.052795, 0.05756, -0.057044, -2231e-6, -0.013445, 2289e-6, -77e-5, -0.049304, -0.013433, -0.01156, 0.039263, 0.055036, -228e-5, -0.021411, 0.025402, -0.015392, -0.016927, -0.01263, 0.016506, 5128e-6, -0.045499, 0.041929, -0.098371, -0.102299, -0.05505, -0.021133, -0.011038, -0.045412, 0.051449, 0.043985, -0.026773, -0.061248, -0.013004, 0.020089, 0.020349, -0.049656, 0.14248, -0.023849, 1326e-6, 0.038924, 0.015142, -0.027207, 0.137747, 0.086333, -0.051942, 0.125201, -0.015227, 0.035816, -0.016593, -3143e-6, 0.016584, 0.057709, 3425e-6, -0.086785, 0.077055, 0.012249, 0.054973, -0.041238, 0.036758, -0.018482, 0.017911, -0.025632, 0.070721, -0.025979, 0.090299, 0.013505, -0.015338, 0.035118, -0.062938, -55e-6, -0.031485, 0, 0.048271, 0.118939, -7432e-6, -0.027482, -0.017351, -2898e-6, -0.036961, -3044e-6, -0.032485, -0.039268, 8085e-6, -0.01109, 0.025195, 273e-6, -0.081679, -0.043759, -1642e-6, 0.133163, 0.054599, 0.052329, -0.014879, 0.066345, -0.063461, 0.046176, -6885e-6, 0.050879, -0.032148, -0.03098, -0.01647, 2323e-6, -0.012937, 0.020916, 0.05669, 0.050599, -0.012922, 0.029857, -0.023119, -0.056459, -0.052397, -0.063056, 0.032488, 0.063419, 5355e-6, -0.081225, -2636e-6, -0.051448, -0.01502, 0.046703, 0.158323, -0.016006, 0.054453, -5225e-6, 0.055346, 3194e-6, -0.055126, -0.047608, -0.027439, -6931e-6, 0.015054, 0.070964, 0.016329, -7436e-6, -0.087056, 0.0326, -8886e-6, 0.069308, 1744e-6, -0.031456, 0.05816, -0.024318, -0.084045, 0.034875, -0.052924, 0.060689, -0.020979, -0.137039, -0.069438, 4228e-6, 0.103073, 0.026546, -0.043743, 0.015869, -1255e-6, -0.020976, -0.036293, 0.019493, 0.019183, -0.167767, -0.047504, -0.043037, -0.05305, -0.011494, 0.020628, -0.029303, 0.066295, -0, 0.06668, 6677e-6, -0.027402, 0.074721, -0.071961, 0.022582, 0.074355, 0.042852, -0.025373, 0.034536, 0.017667, -0.010542, -4169e-6, -0.083182, -2746e-6, -0.053237, 0.041962, -0.041424, -0.037738, 0.075779, 7597e-6, -0.035105, 0.089325, 0.059282, 0.087981, 0.014721, -6421e-6, -0.050171, 0.022545, -0.066806, 0.053691, -0.046373, -0.066624, -0.05594, -2923e-6, -0.072017, -0.035938, -0.031965, -0.078192, -0.01764, 0.047027, 0.05296, -0.039368, 0.060137, -0.014895, 2928e-6, -0.053409, 0.059899, -0.071938, -0.084993, 0.014731, -9255e-6, -0.012538, -0.057773, -0.01937, 0.011484, 0.118908, -4683e-6, -0.101681, -0.030016, 0.04375, 0.078497, -0.024205, -4214e-6, 0.041916, -0.032365, -0.03231, -8687e-6, -0.054573, 9854e-6, 3192e-6, -0.041575, 0.030278, -0.063586, 0.050707, -0.033235, 0.028844, -0.078475, -0.046236, 0.019996, -0.103771, -0.031058, 0.036113, 0.053975, -0.013333, -0.037514, -0.05237, 0.069865, -0.022002, 0.043238, -0.058168, -0.01832, 0.065564, 0.052553, -0.016286, -0, -0.060219, 0.015225, -0.044145, 0.023352, -0.052523, -0.013584, -0.073037, -0.033518, -0.01456, -7575e-6, 2766e-6, -0.030461, -0.045765, -0.042223, -0.032423, -0.03025, 0.020423, 0.066116, -0.085401, -0.038306, -0.12109, 0.040213, 131e-6, -0.04345, 0.070947, 0.014882, 7721e-6, 0.088877, 0.047515, -0.074231, 0.048281, -648e-6, 0.016151, 0.047549, -1148e-6, 0.028044, 0.088863, 0.014517, 0.022091, 0.123896, 0.05443, 0.032042, -0.05105, 0.069732, 0.057286, -0.060648, 108e-5, -0.050034, 0.03852, -0.017144, 0.024196, -0.032345, -0.031836, -0.030237, -0.01748, -1953e-6, 0.045463, 0.059662, 0.020283, -603e-5, 0.145025, -0.031073, 0.069219, 0.012386] },
5088
5114
  { name: "brief", category: "memory", tier: 1, descriptionHash: "cd54c8b5f8962c0c", embedding: [-0.03413, 0.038967, -0.058868, 5065e-6, 0.013581, -9201e-6, 0.12418, 0.084974, 0.0466, -0.027719, -0.03003, 2981e-6, -0.019524, 6126e-6, 0.121445, 0.030564, 0.113974, -0.019305, 0.010584, 0.022772, 0.040912, 0.049654, 0.012009, 0.014554, -317e-6, 0.010126, -0.084735, -1667e-6, 0.083471, -7537e-6, 0.013307, 0.106008, 0.040402, 0.065893, -0.017905, 0.099291, 0.017319, 0.015036, -6372e-6, -0.066617, -0.060547, -0.011005, -0.057781, 0.076661, 9708e-6, -0.045502, -0.044302, -9454e-6, -0.045664, 9896e-6, -0.053175, 0.012171, -0.021067, 0.071767, -8367e-6, 0.128232, -5307e-6, 0.069086, -0.038624, 0.028583, -0.068574, -0.100884, -0.052176, 0.044373, -0.012476, 0.041925, 0.032421, 9466e-6, 0.055237, -0.135804, -0.045271, -0.032277, -0.034088, 0.044972, -0.037657, 0.01615, 0.023087, -0.047546, 5856e-6, -0.118672, -0.013503, 0.027683, 0.021988, 623e-5, -9077e-6, -0.021581, 0.013314, -0.037775, -9507e-6, 0.037748, 85e-5, -0.052845, -7436e-6, 0.03521, 1699e-6, 0.028288, -0.035951, -0.049538, 3357e-6, 0.065678, 0.059998, 0.099599, 0.012784, 0.041595, -0.076292, -0.037029, -0.037229, 0.013993, -0.035633, 4893e-6, -0.057584, 0.047254, -0.010266, -0.037457, 0.077024, -0.074891, -0.038335, -0.034745, 0.026768, 0.120412, 0.06315, -0.034399, -0.048602, -0.025597, -0.086307, -0.035078, 0.03381, 0, 0.074016, -5576e-6, -0.035261, 0.135218, 8885e-6, 0.058899, -0.020769, -0.018972, -0.036681, -0.017513, 0.022473, 0.099641, -0.01237, 0.02122, 0.020966, -0.011241, -0.106938, 0.129492, 0.066344, -0.036365, -0.033113, -6609e-6, -6963e-6, -335e-5, 0.113098, 0.012043, 0.013074, -0.05108, -0.02582, 0.038526, -0.04915, -0.044315, -0.060091, -8148e-6, 0.053718, 0.02143, -5476e-6, -0.021335, -0.010914, -0.106699, -0.040708, 0.013624, 0.031717, -0.058864, -0.091039, -0.10624, -0.040266, 0.024783, 0.036104, 0.013351, 5674e-6, 0.013377, -0.014192, 0.012503, 6394e-6, -0.02841, -4971e-6, -0.022927, -2823e-6, 0.087578, 0.041583, 7853e-6, -0.083155, 0.025704, 0.033491, 0.011361, -0.04942, -0.044508, 0.0311, -0.046498, -0.027239, 3602e-6, 0.040659, 0.050427, -0.025982, 0.014929, 0.015421, 0.031525, -0.062248, 0.03429, 0.052807, -0.044957, -0.023206, 0.055626, -0.038784, 0.022958, 0.043242, -0.128127, -0.054922, 3364e-6, -0.076564, -0.037719, 0.03462, 0.055063, -0.070653, -0, 0.06233, -0.030029, -0.032604, 0.014582, 0.045522, 0.028487, 0.060517, -2969e-6, -0.114903, 0.015776, -0.035656, 5943e-6, 0.029417, 1415e-6, 0.010575, 6686e-6, 0.033255, -0.076697, -0.018056, 0.07942, 0.059409, -0.03671, -0.127963, 0.026159, 0.014287, -0.024613, -0.05576, -0.032465, -0.023479, -0.030419, 9631e-6, -9214e-6, -0.016757, 0.013094, -0.050212, -0.035049, 0.049743, -0.065495, -0.075293, -9875e-6, 0.129848, 0.020889, -0.079259, -0.026347, -0.02803, 2228e-6, -0.065164, 0.043506, -0.033239, -295e-6, -6674e-6, -0.011676, 0.014787, -0.070129, -0.065786, -0.041317, -3707e-6, -0.025125, -0.027828, -0.086907, 0.07926, 7317e-6, -0.053674, -0.034213, 0.054837, -0.062371, 0.036652, 0.070297, -0.090998, 0.015399, 0.052273, -0.051188, 0.039306, -0.066122, 0.040192, 0.02005, -0.022352, -0.17157, -0.039433, -7233e-6, -0.039524, 15e-4, -0.027335, 0.059864, 0.062908, 0.051305, -0.020745, 0.089525, -0.011786, -0.020093, -0.070926, -0.055612, -0.052107, 0.110824, -385e-5, -0, -0.070805, -0.035939, 0.03615, 0.036301, 0.044026, 2509e-6, -5092e-6, 0.068686, 0.037356, 0.02202, 0.025483, -0.032118, -0.107202, -0.04086, 0.048949, 0.038155, -5906e-6, 0.013435, -0.031491, -0.055225, 0.067576, 0.035719, -0.022875, 0.016182, 0.043324, 0.011605, 0.061764, 0.143727, 0.050779, -0.052731, -0.015323, 0.062416, -0.018611, -0.07632, -0.047169, 0.100932, 0.033838, -0.021418, 0.046654, 0.014842, 0.036839, -0.024439, 0.035967, 0.067279, -5978e-6, 0.097739, -0.058634, 0.020037, 1243e-6, -0.081814, -0.06543, 8175e-6, 5117e-6, 0.080321, 57e-6, 0.063212, 0.053892, -0.021493, 0.052762, 0.018396, 0.067694, 0.088151, -0.031927, -0.019349] },
@@ -5107,7 +5133,6 @@ var init_tool_embeddings_generated = __esm({
5107
5133
  { name: "get_note_structure", category: "read", tier: 1, descriptionHash: "d46b747577458fa3", embedding: [-0.034053, 0.058568, -0.013073, 0.051956, 1878e-6, -3484e-6, -0.021384, 0.029459, 0.01866, -0.048506, 4879e-6, -0.02044, 0.02816, -0.035958, 0.080657, 0.031094, 0.034978, 0.025658, 0.023314, -0.026633, 0.088011, 0.055726, 0.045466, -0.019004, -0.045637, 0.051535, -0.119519, -817e-5, 0.051132, -8649e-6, 0.044961, 0.056027, 0.074836, 0.074553, 475e-5, 0.110606, 1981e-6, -0.035258, 0.012263, -0.027338, -0.030202, 0.040594, -0.03311, 0.025563, 0.016389, -7492e-6, -0.057405, -1909e-6, -0.045447, 0.022976, -0.076963, -0.012285, -0.068218, 0.041681, 0.041577, 0.117179, 0.019101, -0.019711, -0.089762, -0.050572, 0.028838, -0.050339, -0.029523, -0.061273, -0.030107, 0.02912, 0.019308, -5468e-6, 0.049428, -0.14302, 0.095878, 9145e-6, 0.020457, 0.041474, 9214e-6, 4388e-6, -4607e-6, -0.014015, -0.047316, -0.11267, -0.098773, 0.044953, 8675e-6, 0.041169, 0.025942, -0.011891, 0.026525, -0.064685, -6678e-6, 0.061513, 0.096213, -0.123228, -0.065633, -0.019303, 8386e-6, 0.072118, 0.01913, -0.061346, 0.102101, 5084e-6, 0.055616, 0.078131, 0.079695, 0.018583, -0.035607, 0.0278, -0.045358, 0.060122, -0.048107, -0.049176, -0.011027, 0.064188, 0.053857, -0.01995, 6125e-6, -0.108854, -7305e-6, -0.03411, 0.045967, 0.111935, 0.112075, 0.01486, -0.022984, 0.015949, -0.102044, -0.012269, -0.075949, 0, 0.054186, 0.018959, 0.0149, 0.017011, -0.049196, 0.023439, 0.023984, 0.05285, -0.06726, -0.020796, -4855e-6, -7865e-6, -0.024857, 0.043349, -0.02489, -1547e-6, -0.045001, 0.06978, 0.019432, 0.03429, 0.011628, 7296e-6, 0.018451, -0.048771, 0.057362, 0.011788, -0.064353, -0.059757, -0.106931, -8745e-6, -0.036667, -0.079833, 0.024444, -0.047112, 0.016105, 0.06526, -0.040127, -4395e-6, -0.046705, -0.106663, 7097e-6, 3845e-6, 0.063465, -0.057979, -0.069587, 773e-6, -0.11854, 0.03829, 0.025318, 0.012997, 0.071065, 0.019042, -653e-5, 0.015978, 0.011882, 0.021631, 0.056059, 0.037697, -0.025745, 0.029873, 0.069977, -1341e-6, -0.017607, 0.032514, -0.040447, 0.023605, -0.032664, -0.0646, 0.069692, 8649e-6, -7163e-6, 0.047986, -7216e-6, 0.023747, -0.016205, -0.082133, -0.048612, -0.094448, 0.069621, -7387e-6, -0.038751, 0.01059, 0.040886, 0.077897, -0.032101, 0.034213, 0.053679, -0.182541, -0.026943, -0.061374, -0.044323, -0.04285, -0.047162, -0.073464, -8375e-6, -0, 0.045809, -0.051449, 0.014081, -0.057152, -0.024508, 0.044098, -0.031783, -2289e-6, -9969e-6, 0.082517, 0.02157, 0.014477, -0.029174, -0.04392, 0.011175, 0.048167, 9909e-6, -0.051525, 0.047285, 0.080488, -0.050922, -0.01143, -0.040418, 0.062125, 0.028396, -8963e-6, -0.051493, -0.048817, 0.027338, -0.066401, 0.017883, -0.053035, -0.053539, -0.036246, -0.057684, -0.034075, -414e-5, -0.073031, -0.040457, 0.086656, 0.068735, 0.107286, 4776e-6, -4959e-6, -0.047611, -0.028712, -0.024964, 0.089701, -0.045587, -0.043428, 0.053476, -0.030139, 3572e-6, -1346e-6, -0.018707, 0.049658, 5663e-6, 1265e-6, -0.078038, -0.049664, -0.022299, 0.065011, -0.11443, 0.034629, 3051e-6, -0.084661, 0.014807, -0.076934, -0.094602, -0.014275, -0.038493, 7669e-6, 0.05943, -0.032143, 0.065115, 0.079638, -0.061122, -0.014708, -0.043751, -0.029774, -0.015585, -0.015531, 0.043522, 0.07072, 0.040783, -0.031524, 9546e-6, 0.02525, -0.023371, -0.010383, -2826e-6, -0.059428, 9702e-6, 0.06052, 0.040492, -0, -0.11971, -0.057381, -0.063901, -0.019077, -5356e-6, -1707e-6, 0.040946, 0.026112, -0.042693, 5638e-6, 0.034895, -0.043335, -0.135079, -0.044002, -0.016958, 2444e-6, 0.091011, 0.011747, -0.040649, -0.039531, -0.015739, 0.053451, -0.049184, -1028e-6, 0.066912, 0.023815, 1227e-6, 0.099626, 0.038918, 0.016415, 0.082349, 0.038295, -0.012661, 5537e-6, 0.054001, 0.03846, 0.067522, -0.012906, -4587e-6, 0.111114, 0.082928, 0.054718, -0.064304, 0.059634, 0.073812, -0.026092, -0.040453, 0.016418, 0.038886, -0.059243, -0.028257, -0.024486, -0.051302, 7722e-6, -0.095123, 0.021788, 0.070279, 0.054412, 0.042015, -0.062366, 0.102527, 0.025488, 0.072279, 0.017399] },
5108
5134
  { name: "get_section_content", category: "read", tier: 1, descriptionHash: "3143db09c864e123", embedding: [-0.028641, 0.085473, -76e-6, 0.05119, 0.031131, 1587e-6, -0.018978, 0.057946, -0.01217, -0.034409, -567e-6, 8665e-6, 0.021315, -0.022961, 0.091059, -0.030405, 0.046316, 0.042966, 0.021119, -1504e-6, 0.053647, 0.101898, 8477e-6, 0.03071, -0.054229, 0.035659, -0.15261, 0.053486, -4781e-6, -0.043234, 0.043718, -0.012788, 0.018779, 0.011845, 8181e-6, 0.036182, 0.0978, -0.036718, 0.072551, 0.017413, -0.027154, 0.029073, -0.060769, 0.023862, 0.027807, -0.022091, -0.063303, -5347e-6, 7137e-6, -0.021798, -0.034714, -0.023498, -0.02925, 0.092221, 0.010238, 0.109993, 534e-6, -0.025775, -0.050855, 6795e-6, -0.064772, -0.069964, -4102e-6, -0.073128, -0.035828, 0.016207, -0.03324, 0.016634, -0.045777, -0.027336, 0.027186, -0.025611, 0.038306, 0.052737, 9481e-6, -0.025895, -0.050778, -0.043424, -0.049807, -0.119121, -0.084864, 0.066539, -0.016666, 0.049291, -0.018263, 5135e-6, -2983e-6, -0.012152, -0.070296, 0.027834, 0.053321, -0.082358, -0.014167, 0.030441, -0.064228, 0.044649, -144e-5, -0.109388, 0.060139, 0.015573, -8612e-6, 0.030612, 0.060786, -0.01802, -0.067729, -0.013319, -8888e-6, 0.028932, -0.045929, -0.042227, 0.010539, 0.033105, -0.015885, -4513e-6, 116e-6, -0.066676, 0.048411, -0.029626, 0.032187, 0.082548, 0.114786, -0.01838, -8558e-6, 0.065853, -0.074634, -0.039559, 0.031106, 0, 6209e-6, -8677e-6, 3492e-6, 0.05194, -0.032895, 0.03922, -0.031535, -898e-5, -0.025381, -0.014504, 0.013425, -0.047406, -7584e-6, 0.06063, -0.029333, -0.011628, 5507e-6, 0.057618, -0.025885, 0.023597, -0.031007, 2971e-6, 0.012595, -0.060759, 0.050706, 0.052725, -0.065455, 734e-6, -0.113944, -0.011068, -908e-5, -0.03675, 0.037886, -0.118776, 254e-5, 0.051742, 3988e-6, -2943e-6, -0.073215, -0.046089, -0.014969, -6248e-6, 0.06288, -0.03653, -0.076457, 0.040562, -0.053199, 0.046905, -665e-6, -0.03219, 0.052905, 0.018131, 0.047858, -0.034036, 0.052139, -0.031547, 3647e-6, 0.053643, -4297e-6, 7731e-6, 0.124256, 0.012433, -0.04248, 0.029914, -0.059878, 0.025681, -1956e-6, -0.046563, 167e-6, -2874e-6, -0.067715, 0.032702, -0.010757, 0.048113, 729e-6, -0.046407, -0.069157, -0.070698, 0.116455, -0.038527, -454e-5, 0.038641, 0.064124, 8841e-6, 8073e-6, 0.029897, 0.065531, -0.099762, -0.027115, -0.081608, -0.128888, -0.097702, 0.03308, -0.087019, 0.013349, -0, 0.072201, -0.014661, -0.048643, -0.060926, -0.077314, 0.059431, 0.011764, 0.035201, 0.016747, 0.085562, 0.018052, 9969e-6, -0.039091, -0.012121, 0.065632, -906e-5, -0.072452, -0.069153, 0.059193, 0.019054, -0.085421, -0.077256, -0.018214, 0.012881, 0.020715, 7903e-6, -0.055617, -0.029759, 0.015393, -0.088237, -8017e-6, -0.045987, -1644e-6, -8979e-6, -0.075813, -0.031571, -0.061005, -0.027525, -339e-5, 0.060508, 0.110903, 0.101027, 0.077668, -0.034899, -0.072861, 0.028758, 0.028136, 0.095881, -0.049, 0.016251, -0.045377, -0.04172, -4138e-6, 0.031363, -0.016481, 0.051365, -0.02863, 0.038089, -0.095247, -0.068709, -0.017717, 0.067932, -0.094599, 0.061901, 0.042007, -0.020073, 0.031228, -0.09808, -0.041746, -821e-6, -0.024428, -0.04163, 0.071431, -0.059914, 0.06119, 0.112077, -0.08753, 0.020743, -0.103866, 0.039082, -4396e-6, -0.02896, -0.056667, 0.066637, 0.090426, -0.014902, -0.050542, -0.016232, 0.015545, -0.017757, 7608e-6, -0.03554, 0.078521, 0.078539, -0.030191, -0, -0.017856, -0.069979, -0.012196, -0.062197, -0.054607, 0.061452, -0.028817, -0.059814, -0.033044, 0.023097, 5961e-6, -0.069378, -0.095493, -0.084676, -0.036096, 0.047724, 0.036811, 0.060726, -0.012988, -0.051958, -0.059858, 0.063799, -0.010038, -838e-5, 0.046756, 0.077427, -2525e-6, 0.093047, 0.057901, -0.037286, 0.114778, 0.032906, 0.026368, 0.016743, 0.085209, 0.042384, 0.038353, 0.051377, 0.079111, 0.114371, 0.081498, -557e-5, -0.036095, 0.027621, 0.078159, 2603e-6, -0.046535, 0.032286, 0.069105, -0.031222, -0.071619, -0.015424, -0.045878, 0.032742, -0.087811, 0.022644, 0.071666, 0.020854, -0.028094, -0.044219, 0.070801, 0.031554, 0.06052, 0.063509] },
5109
5135
  { name: "get_strong_connections", category: "graph", tier: 2, descriptionHash: "6e2af7b6029af542", embedding: [-0.037771, -0.027994, -0.068021, 0.023556, -0.090681, -0.051207, -0.041261, 0.017216, -0.06359, -0.055238, 806e-5, 0.02676, 8123e-6, 1787e-6, 6061e-6, 0.135593, 0.059854, 0.028837, -0.030354, -0.060799, 0.0423, -0.063322, -0.018217, -4433e-6, 0.047159, -0.055318, -0.083385, 0.060145, 0.073422, -0.064649, 356e-6, 5751e-6, -0.139108, 7324e-6, -0.042962, 0.037446, 0.053805, -0.037605, 372e-5, 0.011429, 0.026703, 0.020876, 0.03223, -0.03014, -0.040705, 0.016356, -0.101406, 0.074424, -0.047714, -0.037129, -0.088537, 0.012154, -0.057325, 0.045473, 0.084099, 0.054132, -5249e-6, -5968e-6, -0.055892, 0.068053, 0.054631, -0.04568, -0.069245, -0.020054, -0.04224, 5718e-6, -2155e-6, 0.114993, 7884e-6, 0.018219, 0.031962, -0.072873, -0.097566, 0.019057, 0.063863, 0.052558, 0.011555, -1668e-6, -0.067272, -0.120076, -0.06419, 0.01966, -0.028191, 0.045533, 0.068489, 814e-6, -0.012517, -0.081064, 0.038081, 0.052053, 0.036193, 0.040413, -0.052446, 0.026766, 0.028761, 0.060208, -0.025242, -0.087489, -0.04182, 0.056964, -0.017134, 0.016552, 5959e-6, -0.045877, 0.043168, 0.072253, -0.031332, 0.029309, 0.046305, -7642e-6, -0.022719, 0.034447, 0.022557, 3578e-6, -0.041477, -0.024104, -0.015113, 0.027006, 0.080433, 0.090614, 0.04795, 0.015571, -5917e-6, -0.030432, -0.036741, -0.040443, -4554e-6, 0, 0.059822, 0.0145, 0.051421, 0.015885, -0.022594, 0.037805, -0.078622, 0.014572, -0.061651, 4526e-6, -0.13038, 0.075147, -0.014407, 0.027354, 0.071657, -0.043857, 0.050021, 0.026305, 0.031753, -0.01056, 0.021751, -0.040921, -7686e-6, -0.02379, 0.114929, -0.034895, -9839e-6, 0.010689, -0.035789, 0.031663, -9746e-6, -0.039082, 0.017535, -0.013062, -0.015549, 0.02697, -0.067409, -0.018802, -0.025118, -0.024353, -0.022472, -0.04769, -0.022257, -0.021798, -0.036261, 0.023876, -0.072232, 0.017299, -0.036421, 9764e-6, -0.019015, -0.018019, 0.028643, 0.059689, 0.011619, -0.061575, -0.011098, 0.135227, 0.01339, 0.095882, 0.028497, -0.015913, -0.021305, 0.012422, 0.033127, 0.02329, -0.098138, -0.039889, 0.061132, 0.025942, -0.053474, 0.080174, 0.010111, -0.012041, -0.034366, -0.098504, -0.092239, -0.066017, 0.091857, 0.02822, -0.139999, 2266e-6, -2271e-6, 0.055157, -0.04945, 0.013613, 0.046267, -0.121393, -0.048062, -4372e-6, -0.082383, 3035e-6, 0.034201, -0.018272, 0.038456, -0, -2843e-6, 0.069342, 0.103103, 0.010287, 0.029563, -0.022873, 0.027775, -0.043618, 0.010446, 0.138289, 0.03564, 0.031568, 0.039643, -0.057704, 0.14409, -0.071936, -0.010229, -0.07411, 0.01354, 0.058899, -4457e-6, 0.05122, -0.064179, -1085e-6, 0.077797, -0.021331, 8365e-6, -0.110735, -0.012208, -2982e-6, 554e-6, 5155e-6, -0.081401, 7275e-6, -0.044218, 0.097863, -0.014108, 8492e-6, 0.040481, -0.026258, 0.065645, 0.06016, -0.0312, -0.026998, -0.014959, 0.047424, -0.05033, -0.019567, -0.101543, -0.033671, 0.016911, 0.041148, 0.018528, 0.058475, 0.011338, -1783e-6, -6998e-6, 0.051356, -0.057736, -0.025262, -0.010656, -0.061711, -0.043316, 0.076918, 0.016766, -0.030118, -1567e-6, -0.063203, -0.012617, 0.019781, 7747e-6, 0.083593, 0.064443, -259e-6, 0.030238, 0.019059, -0.058083, 9456e-6, -0.061151, 0.029125, -0.071687, 0.053585, 0.056561, 5823e-6, 0.023318, -0.020783, 0.025751, 0.035499, 0.068327, 0.019582, -0.015069, -0.02526, -0.041987, 3336e-6, -0.050373, -0, -0.076057, -0.060965, 8849e-6, -2043e-6, 0.015217, 0.080132, 0.037776, 0.092546, -0.037172, 0.115836, 0.026506, 6788e-6, -0.073694, 0.019286, -974e-5, -9829e-6, 0.046523, -0.018646, -0.041308, -0.059466, -0.096567, 192e-6, 0.025832, 0.129585, -0.01088, -0.06024, -0.019829, 0.028988, -0.011028, 0.017712, 0.022421, -0.043442, 0.01067, 0.013991, -0.031013, 0.152245, -0.121124, 0.078891, -0.028629, 0.117615, 0.026723, 0.03564, 0.032972, 0.05445, 0.111144, 0.037447, 0.02066, 0.018956, -8914e-6, -0.070642, -0.052423, -0.041498, -0.044652, -0.101854, -0.057583, -0.01058, -0.01979, -0.013734, -0.021211, -6494e-6, 2554e-6, 0.021907, 304e-5, -2321e-6] },
5110
- { name: "get_weighted_links", category: "graph", tier: 2, descriptionHash: "b15908c4739b5d3a", embedding: [-0.090975, -0.014501, -0.021864, 0.065818, -0.030286, -0.047781, -0.026542, 0.021761, -0.038294, -0.032738, -0.015163, -0.012753, 0.031894, 0.01047, 0.029989, 0.120474, 0.098771, 0.067441, -0.040694, -0.062644, 0.035177, -0.050144, -0.034699, -1753e-6, 0.070949, -0.10291, -0.13736, 0.074743, 0.089653, -0.05479, 0.020764, 0.020775, -0.10811, -0.010933, -0.097484, 0.018926, 0.046231, -0.062474, 0.022908, -0.021497, 3353e-6, 6996e-6, -1637e-6, -0.020593, -0.031638, 1062e-6, -0.07396, 0.049341, -0.096428, 0.032815, -0.058664, 0.01456, -0.032546, 0.070596, 0.048763, 0.05182, 0.023216, -0.039782, -0.049001, 0.037621, 0.029424, -0.047569, -0.074779, 0.011681, -0.012067, 3717e-6, -0.030751, 0.089352, 298e-6, -0.018922, 0.056772, -0.092662, -0.063705, 0.022077, 0.065903, 0.02897, -0.024127, -0.055941, -0.030059, -0.065825, -0.072135, 0.032097, -0.023324, 0.015092, 0.061272, -7666e-6, 0.012571, -0.051188, -0.010824, 0.079681, 0.013229, 0.065022, -0.012118, 0.063461, 0.017044, 0.059285, -0.04809, -0.079425, -0.016637, 0.076686, -5778e-6, 0.035252, 9646e-6, -0.021157, 0.022145, 6747e-6, 879e-6, 0.113067, 0.037145, -1402e-6, -0.036647, 0.037682, 0.04401, -6466e-6, -0.03851, -0.012131, 0.029901, 0.024441, 0.068556, 0.054587, 0.075806, 7062e-6, -0.016095, -0.076931, -0.022381, -0.035324, -0.02266, 0, 0.100173, -0.015921, 0.029145, -0.022108, -0.011857, 0.042613, -0.070996, -0.032842, -0.072239, -0.05262, -0.10894, 0.133753, 0.013103, 0.038695, 0.044727, -0.033728, 0.038247, 0.068295, 0.07883, -0.052155, 0.044765, -0.014218, 215e-6, 0.028915, 0.082827, -531e-6, -9761e-6, -0.019842, -0.06516, 0.047081, 4414e-6, -0.070453, -1103e-6, -0.052589, -0.018823, 0.012986, -0.037209, -0.010124, -0.011257, -0.071029, -0.016809, -0.037493, 331e-5, -0.014289, -0.078064, 7253e-6, -0.053571, 0.019083, -0.040748, 0.015793, 0.015338, -0.046805, 0.029152, 0.030541, -9975e-6, -0.024135, -0.011031, 0.086744, 0.013653, 0.032038, 0.037415, -0.013344, -0.040472, -3412e-6, -0.012778, 0.010936, -0.054764, -0.062977, 0.022929, 9276e-6, -2083e-6, 0.092949, 0.020496, 0.021255, -0.02846, -0.078688, -0.102784, -0.090458, 0.071563, 0.039953, -0.09925, -0.02562, 9363e-6, 0.026493, -0.04102, 0.024758, 0.024951, -0.134036, -0.039121, 8251e-6, -0.067827, -0.043155, -0.021372, -0.020859, 0.015264, -0, -0.029416, 0.080464, 0.060345, 0.010391, 0.02143, -6675e-6, 0.010375, -0.068239, -0.016398, 0.122661, 0.0456, 4852e-6, -0.02345, -0.025092, 0.133585, -0.079084, -0.025699, -0.044697, -0.05656, 0.03485, 0.040477, 0.051672, -0.082537, 0.09383, 0.095729, -0.021573, 0.015405, -0.051452, 81e-6, -0.022034, 4398e-6, -0.010902, -0.051642, 1343e-6, -0.02102, 0.073711, 0.024084, -0.039672, -0.012054, 3372e-6, 0.048955, 0.019228, -0.032866, -0.014474, -6002e-6, 0.021877, -0.070833, 6029e-6, -0.087879, -0.056169, 0.020016, 0.017023, 0.029297, 0.061129, -0.017747, -8161e-6, 0.017038, 0.056091, -0.092429, -0.026094, 0.016189, -0.020079, -0.0584, 0.066234, -1429e-6, -0.038221, 0.030045, -0.06633, -0.054358, 0.064258, 79e-6, 0.03817, 0.099264, -0.040309, 0.058427, -454e-6, -0.044228, 0.052338, -0.081565, -0.017557, -0.074933, 2475e-6, 0.127554, 7727e-6, 0.053678, -0.045102, 2107e-6, 0.037336, 0.040023, 4466e-6, -0.019027, -0.047474, -0.077896, 0.022258, -0.032852, -0, -0.07619, -0.01597, 0.030851, 0.057193, -0.021068, 0.108372, 0.055887, 0.076764, -0.017763, 0.10255, 0.034333, -0.039576, -0.061609, -0.02854, 0.014108, 0.016585, 0.030504, -4742e-6, -0.036712, -0.090062, -0.096437, -0.017134, 0.021224, 0.094701, 8389e-6, -0.012675, -0.020066, 0.035665, 0.050386, 0.028501, 0.038512, 238e-5, 295e-6, -0.039607, -0.024497, 0.142554, -0.075649, 0.028408, -0.039409, 0.119073, 0.046154, 0.060929, 0.039705, 0.05497, 0.116029, 0.047151, -0.059783, 0.068407, 0.039543, -0.086565, -0.01137, -0.08773, -0.061231, -0.037986, -0.052677, 0.015221, 0.022846, -7088e-6, 6404e-6, 0.010344, 0.112113, -0.020958, -6841e-6, 0.020835] },
5111
5136
  { name: "graph_analysis", category: "graph", tier: 2, descriptionHash: "21da4313470c8acf", embedding: [0.014982, 0.048667, -0.05496, 0.012859, -7511e-6, -0.080556, -0.080775, 0.010218, -6301e-6, 0.017034, 0.013734, -7477e-6, 0.060422, 0.076067, 1336e-6, 0.08975, -0.041889, 0.014917, 0.079108, -0.074126, 5897e-6, -0.056713, -0.010258, -0.042079, 0.062049, 8864e-6, -0.111993, 0.040664, 0.054887, -0.027314, -8775e-6, 0.049098, 0.016304, 0.015778, 2669e-6, 0.087052, 0.05313, 0.015003, 0.026028, -6206e-6, -0.033553, 0.01072, 0.017311, 0.035093, 0.012491, -0.034683, -0.153859, -2641e-6, -0.065432, 0.010869, -0.072863, 0.042287, -0.036151, 0.035371, 0.062681, 0.078202, 7565e-6, -0.027103, 8832e-6, -0.015145, 0.129478, -96e-5, -0.084373, 0.018726, 0.030673, 0.08512, 0.05594, 0.068198, 0.03111, 0.02847, 0.01319, -0.068583, -0.042534, 0.046815, 0.062125, 0.099523, -0.025556, 0.033946, -0.021172, -0.149886, -0.024221, -4303e-6, 2622e-6, 0.022859, -0.022048, 0.025957, -0.055911, -0.076048, 0.014287, 0.066012, 0.010988, 0.058647, -0.010238, -7619e-6, 0.046626, -6447e-6, -8203e-6, -0.041375, 3573e-6, 0.050071, 0.012537, -0.024124, 0.031998, -0.029242, -759e-5, 0.034055, -3655e-6, 0.019935, -0.020079, 3175e-6, -7819e-6, 0.024804, 0.023123, 8955e-6, 0.03206, -0.039055, -0.0511, -0.024222, 0.058775, 0.084928, 0.054938, -8055e-6, 0.067706, -0.057881, -0.017355, -0.017372, -0.071099, 0, 0.039272, -3494e-6, -0.049648, -0.017106, 0.026142, -7155e-6, -0.124206, -0.022045, -0.034048, 0.042819, -0.047638, 0.098044, -0.014169, 0.016236, 0.093998, 0.029911, 0.079728, 0.053739, -0.017549, -0.064304, 0.03097, -0.036439, 0.033522, 0.035343, 0.134186, 0.017999, 0.01137, -0.044759, -0.058738, 0.024135, -0.037312, 0.019298, 0.013909, 6484e-6, 0.038982, -0.014468, -6381e-6, -0.021227, -0.053476, -0.071868, -0.010886, -0.058535, 0.014366, -0.038675, -0.033031, 4711e-6, -0.013062, -0.031503, 0.01143, 0.02046, -0.024102, -6962e-6, -0.036209, 0.079632, -0.042118, -1449e-6, 0.013005, -1418e-6, 0.017152, 0.065533, 0.020589, -0.013602, -1018e-6, -0.029669, -0.035822, -0.026891, -0.12942, -0.02858, 0.076096, 0.044514, -0.054821, 0.082988, -6901e-6, 0.049194, -0.057347, -0.099937, -0.089271, -0.013113, -0.025257, -6699e-6, -0.083914, -0.079291, 0.014989, -0.010643, -1926e-6, -0.02845, 0.051667, -0.068924, -0.042244, -0.067379, -0.078613, -0.020605, -0.01071, 0.020394, 0.02361, -0, 5196e-6, 0.059459, 0.066098, 0.016319, 0.029665, -0.038921, 0.019445, -0.108754, 9862e-6, 0.099327, 0.011067, 0.019257, 7469e-6, -0.026524, 0.067341, -0.024194, -0.047803, -0.138884, -0.035914, 0.064449, 0.020511, -0.012535, -0.074021, 0.012927, 0.078131, 0.01318, -0.021434, -0.092091, 0.039101, 9615e-6, -0.034145, 0.013148, 0.011523, -0.010175, 0.019436, 0.023398, 0.101393, -0.080156, -0.03636, -0.066278, 0.05756, 0.068323, -0.095833, -0.029751, 0.031757, 0.075222, 66e-6, 0.073814, -0.151532, -0.067838, 0.023268, 0.060245, 0.072305, -0.038108, -0.024749, -9942e-6, 0.04971, 0.073733, -0.072968, 0.032388, 0.028846, -0.057119, -0.032219, 6511e-6, 1294e-6, -0.080719, 474e-5, -0.045133, -0.157085, 0.069242, 0.064041, 0.026467, -0.022417, -0.032153, 0.04462, -0.040503, -0.041883, -4582e-6, -5381e-6, -0.014891, -0.042522, -7419e-6, 0.060908, 0.053358, 0.013997, -697e-5, -8638e-6, 0.039554, 8026e-6, 0.027568, -0.042951, -0.046245, -0.118002, 0.030924, 0.032844, -0, -0.073586, -0.03406, -0.02731, -0.040197, 0.035654, -43e-4, 0.068621, 0.104104, -0.019369, 0.063457, 0.068685, -0.018536, -0.142823, -6219e-6, -0.040994, -0.039883, -504e-6, -0.011205, -0.037401, 0.014862, -0.0493, -1524e-6, -0.024527, 0.053348, 0.020769, -0.076086, -0.048086, 0.035365, 0.017626, -0.012496, 0.031508, 0.0174, 0.080662, -0.032491, -0.019471, 0.142096, 0.052662, 0.066275, -7575e-6, 0.089928, -0.020622, 0.022103, -0.021359, 5322e-6, 0.037768, 0.031636, -9026e-6, 0.091438, 0.049468, -0.069661, -0.018383, -0.064816, -0.094888, -0.050419, 0.015122, 0.029533, 0.01494, -0.021185, 0.085005, 0.033213, 0.107679, -0.02922, -1768e-6, -0.116227] },
5112
5137
  { name: "init_semantic", category: "search", tier: 1, descriptionHash: "43c65824a56583c8", embedding: [-0.036645, -0.048423, -0.02987, 0.017334, 918e-6, 0.017996, -0.055598, -0.046157, -0.060235, -0.03943, 0.0203, -0.068196, 0.06205, 4746e-6, 0.021267, 0.1163, 0.082818, 7109e-6, 7912e-6, -0.031837, 0.039406, 0.039051, 0.041681, -0.024567, -0.030271, -4424e-6, -0.073251, -0.01792, 0.06297, -0.021106, 0.068028, 0.119778, -8886e-6, 0.124277, -0.015507, 0.047549, -0.043916, -0.03781, 798e-6, -0.068685, -0.042466, 0.022345, -0.045979, 0.02311, 0.025188, 0.011397, -0.107553, -0.055501, -0.024378, 0.018865, -0.112677, -2974e-6, -0.063417, 0.01448, 0.06073, 0.052563, -0.029852, 0.026697, -0.026249, -0.095955, 0.011018, -0.064869, 0.03559, -0.013392, -0.025816, -6166e-6, 0.03396, -3985e-6, 0.074746, -0.051948, 0.04735, 5699e-6, -0.047815, 5083e-6, -6656e-6, 0.058819, 0.017751, 9618e-6, -0.034329, -0.142559, -0.092611, -0.011707, 0.054788, 0.026713, 0.046238, -0.058496, 0.053149, -0.064674, -7911e-6, 0.025146, 0.090716, -0.095411, -0.025432, -4719e-6, 0.044307, -9021e-6, 0.03782, 5209e-6, 0.090933, 0.010226, -0.031091, 0.059074, 0.050608, 0.02373, -0.066836, -6565e-6, -0.019631, -0.010734, 0.051661, -0.114177, -143e-5, 0.012429, 0.075123, -0.028144, 146e-5, -0.024961, 0.018168, -0.07083, 0.118612, 0.069403, 0.025245, 0.058002, -0.02929, -0.028592, -0.108462, 0.041775, -0.053198, 0, 0.081256, -6094e-6, -7254e-6, 0.056054, -0.015568, 0.037045, 0.028584, 0.106462, -0.131533, -0.058554, -0.077175, 0.046473, -0.028723, 0.082435, 0.02169, -0.05884, -0.066908, 7516e-6, -0.024083, -0.016896, 0.043299, 0.025071, -0.052559, -0.036259, 0.018016, 0.014868, 0.059475, -0.10813, -734e-6, 0.013911, -0.069934, -0.023654, -0.046397, 0.028024, 7712e-6, 0.013454, -0.034854, 0.039348, -0.084795, -0.118392, -0.010387, 0.027472, -0.013331, -0.125895, -0.041034, 0.016784, -0.037666, 0.010413, 0.062172, -0.028341, 0.054028, 0.056302, -0.026385, 0.017281, 0.080924, 0.022605, 0.028539, 0.040226, 0.033684, 4577e-6, 0.026979, -0.011067, 659e-6, 0.085469, -0.020598, 0.049251, 0.041391, -0.010572, 0.097357, 0.043209, 0.037071, 0.025847, 0.012736, -0.0629, -0.024554, -0.093576, -0.038353, -0.11029, -0.041252, 0.017235, -0.059938, -0.013057, 6945e-6, -0.02508, 1215e-6, -0.023817, 0.030486, -0.14928, 0.033516, -0.032182, 778e-5, -0.058548, -0.028677, -5839e-6, -0.018994, -0, 0.014996, -0.058951, 0.048915, 0.047534, -0.053714, -0.025611, 0.039118, 0.045065, -0.011793, 0.065329, 0.03063, -0.033068, 0.025043, -0.032686, -0.038037, 0.056003, -0.043733, -0.019769, 0.02776, 0.120434, -0.029215, 0.041728, -0.074871, 0.110722, 0.047525, 0.081546, 0.028591, -0.030607, -0.023528, -0.103818, 0.02561, -0.069368, -0.103536, -0.041664, -0.088307, 0.018, 31e-4, -0.02202, -0.099577, 0.04134, 8269e-6, 0.092631, -0.093147, 0.024291, 0.01071, -0.017115, -0.090242, 0.029033, 0.041009, -0.067994, 0.027573, -9146e-6, 0.032512, -0.033344, 1196e-6, -0.081688, 0.027215, 0.053545, -0.134254, 0.033339, 0.043814, -0.014692, 0.044891, 0.015723, -6774e-6, -0.021276, -0.036136, 0.043279, -0.04103, -3137e-6, -0.025336, 0.027017, 0.031852, 0.037979, 0.056888, 0.016986, 0.015888, -0.046773, -8154e-6, -0.053904, -0.066498, -0.012196, 0.056062, 0.091648, -0.044858, -73e-4, 0.01175, 0.061546, -0.017644, 0.02004, -66e-5, -0.018841, -0.018539, 0.079675, 0.022509, -0, -0.064331, -0.026576, -0.042327, 0.028332, -5853e-6, -0.024744, 0.047413, 0.051537, -0.056245, 4566e-6, 0.036773, -0.029659, -0.079398, 0.052442, 8523e-6, 0.020647, 9384e-6, -3593e-6, -0.014809, -0.013876, -328e-6, 0.079194, 0.010541, 0.048098, 0.018196, -6731e-6, 0.03661, 0.05604, 0.119197, 0.027253, -0.041915, 0.046821, -0.053771, -0.02169, 0.046453, 0.08273, 0.053455, 3144e-6, -0.037192, 0.043435, 0.073059, -0.034059, -0.036581, -0.041038, 0.088468, 0.015768, -0.094215, -0.021958, 0.035929, 0.011529, -0.025067, -0.013653, -0.04267, 0.0741, 4846e-6, 0.039491, -1457e-6, 7501e-6, 0.104915, -0.062235, 0.049405, -0.074123, 0.092452, 0.022295] },
5113
5138
  { name: "list_entities", category: "graph", tier: 2, descriptionHash: "151c9f9cc7ae7935", embedding: [9869e-6, 0.039221, -0.017985, 0.062127, 0.017846, 0.011479, -0.053497, 0.012181, -0.027631, 0.022112, 0.024476, -0.067664, 0.036259, -6546e-6, 0.060455, 0.083451, 0.04418, 0.055013, 0.05941, -0.150598, 0.090187, 0.018138, -0.029135, -0.021231, -5622e-6, -0.045522, -0.093901, 4966e-6, 0.019375, -0.045165, -0.046949, 0.08159, -0.05141, 0.107208, -0.062976, 0.015833, 0.072251, -0.034373, 7219e-6, -0.034998, 1076e-6, 0.013932, -0.015443, -0.025458, -0.026742, 0.020433, -0.110288, -0.017173, -0.059967, -572e-6, -0.07416, 0.038236, -0.052883, 0.05788, 0.068984, 0.076051, -0.018585, -0.029614, -0.060105, 8377e-6, 1809e-6, -0.016518, -9934e-6, 6265e-6, -0.053017, -9911e-6, 8147e-6, -404e-6, 0.043481, -0.098265, 0.080826, -0.038752, -0.055578, -0.021447, 0.046618, 5738e-6, 0.018936, 0.018056, -6225e-6, -0.09769, -0.11041, 0.089765, 0.041564, -4204e-6, -0.012703, -0.044349, -0.016736, -0.082801, -0.048237, 0.048849, -4514e-6, -0.014904, 0.091902, 0.011808, 0.06668, 0.059157, 0.067194, -0.081303, 0.044341, 0.016443, -0.039689, 0.02914, -0.032206, 0.021104, -0.073646, -0.013962, -0.014455, 0.040568, -0.022705, -8866e-6, -0.011802, 0.118062, -0.016973, -0.045874, 0.011322, -0.048643, -3256e-6, -0.0199, 0.063331, 0.037032, 0.042789, 0.03297, 6748e-6, -0.037267, -0.029454, 0.055477, -0.031838, 0, 0.132252, 0.032721, 0.040518, -0.029734, -0.088739, 0.028325, -0.027406, -8194e-6, -0.035274, 0.026574, -0.10169, 0.132857, -0.01054, 0.085067, 0.072249, -0.018404, 0.029723, 0.124009, 0.050307, -0.044323, 4088e-6, 0.034195, 8592e-6, 0.023349, 0.03524, 0.073328, -0.042406, -0.062921, -0.048544, 0.043889, -9885e-6, -0.046737, 4034e-6, 2817e-6, 0.023997, 0.066605, -0.041633, 0.023126, -0.028927, -0.076544, 0.027614, -0.025601, 0.022261, -0.039308, -0.08337, -102e-6, -0.039863, -0.058725, 0.041901, 0.062422, -261e-5, 155e-5, -0.02527, 0.047808, 0.016203, 0.020861, 0.072298, 7431e-6, -0.025393, 0.050597, 128e-6, -0.018836, -0.014498, 0.010012, -5951e-6, 0.029245, -0.029082, -0.042208, 0.06618, 0.011464, 0.046211, 0.100401, 0.096154, 101e-5, 893e-6, -0.065115, -0.074135, -0.146964, -0.034718, 0.025901, -0.050778, -0.039288, -0.060746, -0.017685, 0.029436, -0.042277, 3545e-6, -0.076755, 4314e-6, -0.032518, -0.07518, -2035e-6, 9925e-6, 121e-5, -0.031225, -0, 0.03046, -0.020084, 0.084786, -0.047479, 396e-6, -0.090925, 0.045119, 1007e-6, -0.090049, 0.087244, 1199e-6, -0.0213, -2816e-6, -0.034068, 0.046173, -0.019643, -0.052543, -0.061895, 4877e-6, 0.098381, -0.0366, -0.036437, -0.091761, 0.115674, 0.077214, -4719e-6, -0.036058, -0.074632, 0.040946, -0.080891, 0.076584, -0.071783, -0.030318, -0.018569, -6304e-6, -0.028519, -0.011312, -0.085765, -0.081931, -0.028463, -0.041507, 0.045099, -0.070759, 0.031346, 2321e-6, -0.014253, -9167e-6, 0.071664, -0.017914, -0.044684, -7361e-6, 0.033823, 0.064097, 0.034082, 0.031671, 9526e-6, -0.046141, 0.071486, -0.06172, -2236e-6, 0.030522, -3738e-6, -0.048973, 0.098027, 495e-5, -0.080678, 2526e-6, -0.049155, -0.064288, -0.01438, 0.01443, -0.017578, 9537e-6, -0.047826, 0.056887, -0.052009, -0.024801, 0.023291, -0.024461, 0.012786, -0.023644, -0.037693, 0.047088, 0.032393, 0.046701, -0.061738, 0.028013, 0.08225, 4238e-6, 0.039802, -0.028564, -0.050046, -0.115669, 0.074233, 0.050805, -0, -0.082516, 0.015736, -5736e-6, -0.033366, -0.028511, 9821e-6, 0.052369, 0.086908, -0.066551, 0.051093, 0.045218, -7851e-6, -0.121843, 4426e-6, 0.103404, -0.028686, 0.068992, 0.028774, 0.033047, 0.072594, -0.096302, -0.014749, -0.046137, -0.042746, -0.016003, -545e-5, -3868e-6, 0.047832, 0.046029, 0.038886, 0.025325, 0.070839, 0.015144, 8753e-6, 0.015014, 0.035623, 5691e-6, 0.010876, 4773e-6, 0.078394, 0.092455, 0.055167, -9887e-6, 0.067516, 0.051329, 0.031391, -0.01022, 0.017734, 0.064036, -0.063985, -0.124364, -0.058518, -0.089096, -0.042014, -0.023448, 0.04869, 0.013319, -0.032993, 0.058465, -0.016633, 0.130929, -0.055697, 7264e-6, -0.018472] },
@@ -5158,28 +5183,207 @@ var init_tool_embeddings_generated = __esm({
5158
5183
  }
5159
5184
  });
5160
5185
 
5161
- // src/core/write/constants.ts
5162
- function estimateTokens(content) {
5163
- const str = typeof content === "string" ? content : JSON.stringify(content);
5164
- return Math.ceil(str.length / 4);
5165
- }
5166
- var HEADING_REGEX3;
5167
- var init_constants2 = __esm({
5168
- "src/core/write/constants.ts"() {
5169
- "use strict";
5170
- HEADING_REGEX3 = /^(#{1,6})\s+(.+)$/;
5171
- }
5172
- });
5173
-
5174
- // src/core/write/writer.ts
5186
+ // src/core/write/path-security.ts
5175
5187
  import fs20 from "fs/promises";
5176
5188
  import path22 from "path";
5177
- import matter5 from "gray-matter";
5178
- import { createHash as createHash2 } from "node:crypto";
5179
5189
  function isSensitivePath(filePath) {
5180
5190
  const normalizedPath = filePath.replace(/\\/g, "/");
5181
5191
  return SENSITIVE_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath));
5182
5192
  }
5193
+ function isWithinDirectory(child, parent, allowEqual = false) {
5194
+ const rel = path22.relative(path22.resolve(parent), path22.resolve(child));
5195
+ if (rel === "") return allowEqual;
5196
+ const firstSeg = rel.split(path22.sep)[0];
5197
+ return firstSeg !== ".." && !path22.isAbsolute(rel);
5198
+ }
5199
+ function validatePath(vaultPath2, notePath) {
5200
+ if (notePath.startsWith("/")) {
5201
+ return false;
5202
+ }
5203
+ if (process.platform === "win32" && /^[a-zA-Z]:/.test(notePath)) {
5204
+ return false;
5205
+ }
5206
+ if (notePath.startsWith("\\")) {
5207
+ return false;
5208
+ }
5209
+ return isWithinDirectory(path22.resolve(vaultPath2, notePath), vaultPath2);
5210
+ }
5211
+ function sanitizeNotePath(notePath) {
5212
+ const dir = path22.dirname(notePath);
5213
+ let filename = path22.basename(notePath);
5214
+ const ext = filename.endsWith(".md") ? ".md" : "";
5215
+ let stem2 = ext ? filename.slice(0, -ext.length) : filename;
5216
+ stem2 = stem2.replace(/\s+/g, "-");
5217
+ stem2 = stem2.replace(/[?*<>|":]/g, "");
5218
+ stem2 = stem2.toLowerCase();
5219
+ stem2 = stem2.replace(/-{2,}/g, "-");
5220
+ stem2 = stem2.replace(/^-+|-+$/g, "");
5221
+ filename = stem2 + (ext || ".md");
5222
+ return dir === "." ? filename : path22.join(dir, filename).replace(/\\/g, "/");
5223
+ }
5224
+ async function validatePathSecure(vaultPath2, notePath) {
5225
+ if (notePath.startsWith("/")) {
5226
+ return {
5227
+ valid: false,
5228
+ reason: "Absolute paths not allowed"
5229
+ };
5230
+ }
5231
+ if (process.platform === "win32" && /^[a-zA-Z]:/.test(notePath)) {
5232
+ return {
5233
+ valid: false,
5234
+ reason: "Absolute paths not allowed"
5235
+ };
5236
+ }
5237
+ if (notePath.startsWith("\\")) {
5238
+ return {
5239
+ valid: false,
5240
+ reason: "Absolute paths not allowed"
5241
+ };
5242
+ }
5243
+ const firstSeg = path22.normalize(notePath).split(path22.sep).filter(Boolean)[0];
5244
+ if (firstSeg === "..") {
5245
+ return {
5246
+ valid: false,
5247
+ reason: "Path traversal not allowed"
5248
+ };
5249
+ }
5250
+ if (!isWithinDirectory(path22.resolve(vaultPath2, notePath), vaultPath2)) {
5251
+ return {
5252
+ valid: false,
5253
+ reason: "Path traversal not allowed"
5254
+ };
5255
+ }
5256
+ if (isSensitivePath(notePath)) {
5257
+ return {
5258
+ valid: false,
5259
+ reason: "Cannot write to sensitive file (credentials, keys, secrets)"
5260
+ };
5261
+ }
5262
+ try {
5263
+ const fullPath = path22.join(vaultPath2, notePath);
5264
+ try {
5265
+ await fs20.access(fullPath);
5266
+ const realPath = await fs20.realpath(fullPath);
5267
+ const realVaultPath = await fs20.realpath(vaultPath2);
5268
+ if (!isWithinDirectory(realPath, realVaultPath)) {
5269
+ return {
5270
+ valid: false,
5271
+ reason: "Symlink target is outside vault"
5272
+ };
5273
+ }
5274
+ const relativePath = path22.relative(realVaultPath, realPath);
5275
+ if (isSensitivePath(relativePath)) {
5276
+ return {
5277
+ valid: false,
5278
+ reason: "Symlink target is a sensitive file"
5279
+ };
5280
+ }
5281
+ } catch {
5282
+ const parentDir = path22.dirname(fullPath);
5283
+ try {
5284
+ await fs20.access(parentDir);
5285
+ const realParentPath = await fs20.realpath(parentDir);
5286
+ const realVaultPath = await fs20.realpath(vaultPath2);
5287
+ if (!isWithinDirectory(realParentPath, realVaultPath, true)) {
5288
+ return {
5289
+ valid: false,
5290
+ reason: "Parent directory symlink target is outside vault"
5291
+ };
5292
+ }
5293
+ } catch {
5294
+ }
5295
+ }
5296
+ } catch (error) {
5297
+ return {
5298
+ valid: false,
5299
+ reason: `Path validation error: ${error.message}`
5300
+ };
5301
+ }
5302
+ return { valid: true };
5303
+ }
5304
+ var SENSITIVE_PATH_PATTERNS;
5305
+ var init_path_security = __esm({
5306
+ "src/core/write/path-security.ts"() {
5307
+ "use strict";
5308
+ SENSITIVE_PATH_PATTERNS = [
5309
+ // Environment files (including backups, variations, and Windows ADS)
5310
+ /\.env($|\..*|~|\.swp|\.swo|:)/i,
5311
+ // .env, .env.local, .env~, .env.swp, .env:$DATA (ADS), etc.
5312
+ // Git credentials and config
5313
+ /\.git\/config$/i,
5314
+ // Git config (may contain tokens)
5315
+ /\.git\/credentials$/i,
5316
+ // Git credentials
5317
+ // SSL/TLS certificates and private keys (including backups)
5318
+ /\.pem($|\.bak|\.backup|\.old|\.orig|~)$/i,
5319
+ // SSL/TLS certificates + backups
5320
+ /\.key($|\.bak|\.backup|\.old|\.orig|~)$/i,
5321
+ // Private keys + backups
5322
+ /\.p12($|\.bak|\.backup|\.old|\.orig|~)$/i,
5323
+ // PKCS#12 certificates + backups
5324
+ /\.pfx($|\.bak|\.backup|\.old|\.orig|~)$/i,
5325
+ // Windows certificate format + backups
5326
+ /\.jks($|\.bak|\.backup|\.old|\.orig|~)$/i,
5327
+ // Java keystore + backups
5328
+ /\.crt($|\.bak|\.backup|\.old|\.orig|~)$/i,
5329
+ // Certificate files + backups
5330
+ // SSH keys
5331
+ /id_rsa/i,
5332
+ // SSH private key
5333
+ /id_ed25519/i,
5334
+ // SSH private key (ed25519)
5335
+ /id_ecdsa/i,
5336
+ // SSH private key (ecdsa)
5337
+ /id_dsa/i,
5338
+ // SSH private key (dsa)
5339
+ /\.ssh\/config$/i,
5340
+ // SSH config
5341
+ /authorized_keys$/i,
5342
+ // SSH authorized keys
5343
+ /known_hosts$/i,
5344
+ // SSH known hosts
5345
+ // Generic credentials/secrets files (including backups)
5346
+ /credentials\.json($|\.bak|\.backup|\.old|\.orig|~)$/i,
5347
+ // Cloud credentials + backups
5348
+ /secrets\.json($|\.bak|\.backup|\.old|\.orig|~)$/i,
5349
+ // Secrets files + backups
5350
+ /secrets\.ya?ml($|\.bak|\.backup|\.old|\.orig|~)$/i,
5351
+ // Secrets YAML + backups
5352
+ // Package manager auth
5353
+ /\.npmrc$/i,
5354
+ // npm config (may contain tokens)
5355
+ /\.netrc$/i,
5356
+ // Netrc (HTTP auth credentials)
5357
+ /\.yarnrc$/i,
5358
+ // Yarn config
5359
+ // Cloud provider credentials
5360
+ /\.aws\/credentials$/i,
5361
+ // AWS credentials
5362
+ /\.aws\/config$/i,
5363
+ // AWS config
5364
+ /gcloud\/credentials\.json/i,
5365
+ // Google Cloud credentials
5366
+ /\.azure\/credentials$/i,
5367
+ // Azure credentials
5368
+ /\.docker\/config\.json$/i,
5369
+ // Docker registry auth
5370
+ /\.kube\/config$/i,
5371
+ // Kubernetes config
5372
+ // System password files
5373
+ /\.htpasswd$/i,
5374
+ // Apache password file
5375
+ /shadow$/,
5376
+ // Unix shadow password file
5377
+ /passwd$/,
5378
+ // Unix password file
5379
+ // Hidden credential files (starting with dot)
5380
+ /^\.(credentials|secrets|tokens)$/i
5381
+ // .credentials, .secrets, .tokens
5382
+ ];
5383
+ }
5384
+ });
5385
+
5386
+ // src/core/write/regex-safety.ts
5183
5387
  function checkRegexSafety(pattern) {
5184
5388
  if (pattern.length > MAX_REGEX_LENGTH) {
5185
5389
  return `Regex pattern too long (${pattern.length} chars, max ${MAX_REGEX_LENGTH})`;
@@ -5216,21 +5420,62 @@ function safeRegexReplace(input, pattern, replacement, useRegex, global = false)
5216
5420
  const regex = createSafeRegex(pattern, global ? "g" : void 0);
5217
5421
  return input.replace(regex, replacement);
5218
5422
  }
5219
- function detectLineEnding(content) {
5220
- const crlfCount = (content.match(/\r\n/g) || []).length;
5221
- const lfCount = (content.match(/(?<!\r)\n/g) || []).length;
5222
- return crlfCount > lfCount ? "CRLF" : "LF";
5223
- }
5224
- function normalizeLineEndings(content) {
5225
- return content.replace(/\r\n/g, "\n");
5226
- }
5227
- function convertLineEndings(content, style) {
5228
- const normalized = content.replace(/\r\n/g, "\n");
5423
+ var REDOS_PATTERNS, MAX_REGEX_LENGTH;
5424
+ var init_regex_safety = __esm({
5425
+ "src/core/write/regex-safety.ts"() {
5426
+ "use strict";
5427
+ REDOS_PATTERNS = [
5428
+ // Nested quantifiers: (a+)+, (a*)+, (a+)*, (a*)*, etc.
5429
+ /(\([^)]*[+*][^)]*\))[+*]/,
5430
+ // Quantifiers followed by optional same-type quantifiers
5431
+ /[+*]\??\s*[+*]/,
5432
+ // Overlapping character classes with quantifiers followed by similar
5433
+ /\[[^\]]*\][+*].*\[[^\]]*\][+*]/,
5434
+ // Multiple adjacent capturing groups with quantifiers
5435
+ /(\([^)]+[+*]\)){2,}/,
5436
+ // Extremely long alternation groups
5437
+ /\([^)]{100,}\)/
5438
+ ];
5439
+ MAX_REGEX_LENGTH = 500;
5440
+ }
5441
+ });
5442
+
5443
+ // src/core/write/line-endings.ts
5444
+ function detectLineEnding(content) {
5445
+ const crlfCount = (content.match(/\r\n/g) || []).length;
5446
+ const lfCount = (content.match(/(?<!\r)\n/g) || []).length;
5447
+ return crlfCount > lfCount ? "CRLF" : "LF";
5448
+ }
5449
+ function normalizeLineEndings(content) {
5450
+ return content.replace(/\r\n/g, "\n");
5451
+ }
5452
+ function convertLineEndings(content, style) {
5453
+ const normalized = content.replace(/\r\n/g, "\n");
5229
5454
  return style === "CRLF" ? normalized.replace(/\n/g, "\r\n") : normalized;
5230
5455
  }
5231
5456
  function normalizeTrailingNewline(content) {
5232
5457
  return content.replace(/[\r\n\s]+$/, "") + "\n";
5233
5458
  }
5459
+ var init_line_endings = __esm({
5460
+ "src/core/write/line-endings.ts"() {
5461
+ "use strict";
5462
+ }
5463
+ });
5464
+
5465
+ // src/core/write/constants.ts
5466
+ function estimateTokens(content) {
5467
+ const str = typeof content === "string" ? content : JSON.stringify(content);
5468
+ return Math.ceil(str.length / 4);
5469
+ }
5470
+ var HEADING_REGEX3;
5471
+ var init_constants2 = __esm({
5472
+ "src/core/write/constants.ts"() {
5473
+ "use strict";
5474
+ HEADING_REGEX3 = /^(#{1,6})\s+(.+)$/;
5475
+ }
5476
+ });
5477
+
5478
+ // src/core/write/markdown-structure.ts
5234
5479
  function isEmptyPlaceholder(line) {
5235
5480
  const trimmed = line.trim();
5236
5481
  return EMPTY_PLACEHOLDER_PATTERNS.some((p) => p.test(trimmed));
@@ -5536,189 +5781,27 @@ function insertInSection(content, section, newContent, position, options) {
5536
5781
  }
5537
5782
  return lines.join("\n");
5538
5783
  }
5539
- function validatePath(vaultPath2, notePath) {
5540
- if (notePath.startsWith("/")) {
5541
- return false;
5542
- }
5543
- if (process.platform === "win32" && /^[a-zA-Z]:/.test(notePath)) {
5544
- return false;
5545
- }
5546
- if (notePath.startsWith("\\")) {
5547
- return false;
5548
- }
5549
- const resolvedVault = path22.resolve(vaultPath2);
5550
- const resolvedNote = path22.resolve(vaultPath2, notePath);
5551
- return resolvedNote.startsWith(resolvedVault);
5552
- }
5553
- function sanitizeNotePath(notePath) {
5554
- const dir = path22.dirname(notePath);
5555
- let filename = path22.basename(notePath);
5556
- const ext = filename.endsWith(".md") ? ".md" : "";
5557
- let stem2 = ext ? filename.slice(0, -ext.length) : filename;
5558
- stem2 = stem2.replace(/\s+/g, "-");
5559
- stem2 = stem2.replace(/[?*<>|":]/g, "");
5560
- stem2 = stem2.toLowerCase();
5561
- stem2 = stem2.replace(/-{2,}/g, "-");
5562
- stem2 = stem2.replace(/^-+|-+$/g, "");
5563
- filename = stem2 + (ext || ".md");
5564
- return dir === "." ? filename : path22.join(dir, filename).replace(/\\/g, "/");
5565
- }
5566
- async function validatePathSecure(vaultPath2, notePath) {
5567
- if (notePath.startsWith("/")) {
5568
- return {
5569
- valid: false,
5570
- reason: "Absolute paths not allowed"
5571
- };
5572
- }
5573
- if (process.platform === "win32" && /^[a-zA-Z]:/.test(notePath)) {
5574
- return {
5575
- valid: false,
5576
- reason: "Absolute paths not allowed"
5577
- };
5578
- }
5579
- if (notePath.startsWith("\\")) {
5580
- return {
5581
- valid: false,
5582
- reason: "Absolute paths not allowed"
5583
- };
5584
- }
5585
- if (notePath.startsWith("..")) {
5586
- return {
5587
- valid: false,
5588
- reason: "Path traversal not allowed"
5589
- };
5590
- }
5591
- const resolvedVault = path22.resolve(vaultPath2);
5592
- const resolvedNote = path22.resolve(vaultPath2, notePath);
5593
- if (!resolvedNote.startsWith(resolvedVault)) {
5594
- return {
5595
- valid: false,
5596
- reason: "Path traversal not allowed"
5597
- };
5598
- }
5599
- if (isSensitivePath(notePath)) {
5600
- return {
5601
- valid: false,
5602
- reason: "Cannot write to sensitive file (credentials, keys, secrets)"
5603
- };
5604
- }
5605
- try {
5606
- const fullPath = path22.join(vaultPath2, notePath);
5607
- try {
5608
- await fs20.access(fullPath);
5609
- const realPath = await fs20.realpath(fullPath);
5610
- const realVaultPath = await fs20.realpath(vaultPath2);
5611
- if (!realPath.startsWith(realVaultPath)) {
5612
- return {
5613
- valid: false,
5614
- reason: "Symlink target is outside vault"
5615
- };
5616
- }
5617
- const relativePath = path22.relative(realVaultPath, realPath);
5618
- if (isSensitivePath(relativePath)) {
5619
- return {
5620
- valid: false,
5621
- reason: "Symlink target is a sensitive file"
5622
- };
5623
- }
5624
- } catch {
5625
- const parentDir = path22.dirname(fullPath);
5626
- try {
5627
- await fs20.access(parentDir);
5628
- const realParentPath = await fs20.realpath(parentDir);
5629
- const realVaultPath = await fs20.realpath(vaultPath2);
5630
- if (!realParentPath.startsWith(realVaultPath)) {
5631
- return {
5632
- valid: false,
5633
- reason: "Parent directory symlink target is outside vault"
5634
- };
5635
- }
5636
- } catch {
5637
- }
5638
- }
5639
- } catch (error) {
5640
- return {
5641
- valid: false,
5642
- reason: `Path validation error: ${error.message}`
5643
- };
5644
- }
5645
- return { valid: true };
5646
- }
5647
- function computeContentHash(rawContent) {
5648
- return createHash2("sha256").update(rawContent).digest("hex").slice(0, 16);
5649
- }
5650
- async function readVaultFile(vaultPath2, notePath) {
5651
- if (!validatePath(vaultPath2, notePath)) {
5652
- throw new Error("Invalid path: path traversal not allowed");
5653
- }
5654
- const fullPath = path22.join(vaultPath2, notePath);
5655
- const [rawContent, stat5] = await Promise.all([
5656
- fs20.readFile(fullPath, "utf-8"),
5657
- fs20.stat(fullPath)
5658
- ]);
5659
- const contentHash2 = computeContentHash(rawContent);
5660
- const lineEnding = detectLineEnding(rawContent);
5661
- const normalizedContent = normalizeLineEndings(rawContent);
5662
- const parsed = matter5(normalizedContent);
5663
- const frontmatter = deepCloneFrontmatter(parsed.data);
5664
- return {
5665
- content: parsed.content,
5666
- frontmatter,
5667
- rawContent,
5668
- lineEnding,
5669
- mtimeMs: stat5.mtimeMs,
5670
- contentHash: contentHash2
5671
- };
5672
- }
5673
- function deepCloneFrontmatter(obj) {
5674
- if (obj === null || typeof obj !== "object") {
5675
- return obj;
5676
- }
5677
- if (obj instanceof Date) {
5678
- return new Date(obj.getTime());
5679
- }
5680
- if (Array.isArray(obj)) {
5681
- return obj.map((item) => {
5682
- if (item instanceof Date) {
5683
- return new Date(item.getTime());
5684
- }
5685
- if (item !== null && typeof item === "object") {
5686
- return deepCloneFrontmatter(item);
5687
- }
5688
- return item;
5689
- });
5690
- }
5691
- const cloned = {};
5692
- for (const key of Object.keys(obj)) {
5693
- const value = obj[key];
5694
- if (value instanceof Date) {
5695
- cloned[key] = new Date(value.getTime());
5696
- } else if (value !== null && typeof value === "object") {
5697
- cloned[key] = deepCloneFrontmatter(value);
5698
- } else {
5699
- cloned[key] = value;
5700
- }
5701
- }
5702
- return cloned;
5703
- }
5704
- async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEnding = "LF", expectedHash) {
5705
- const validation = await validatePathSecure(vaultPath2, notePath);
5706
- if (!validation.valid) {
5707
- throw new Error(`Invalid path: ${validation.reason}`);
5708
- }
5709
- const fullPath = path22.join(vaultPath2, notePath);
5710
- if (expectedHash) {
5711
- const currentRaw = await fs20.readFile(fullPath, "utf-8");
5712
- const currentHash = computeContentHash(currentRaw);
5713
- if (currentHash !== expectedHash) {
5714
- throw new WriteConflictError(notePath);
5715
- }
5784
+ var EMPTY_PLACEHOLDER_PATTERNS;
5785
+ var init_markdown_structure = __esm({
5786
+ "src/core/write/markdown-structure.ts"() {
5787
+ "use strict";
5788
+ init_constants2();
5789
+ EMPTY_PLACEHOLDER_PATTERNS = [
5790
+ /^\d+\.\s*$/,
5791
+ // "1. " or "2. " (numbered list placeholder)
5792
+ /^-\s*$/,
5793
+ // "- " (bullet placeholder)
5794
+ /^-\s*\[\s*\]\s*$/,
5795
+ // "- [ ] " (empty task placeholder)
5796
+ /^-\s*\[x\]\s*$/i,
5797
+ // "- [x] " (completed task placeholder)
5798
+ /^\*\s*$/
5799
+ // "* " (asterisk bullet placeholder)
5800
+ ];
5716
5801
  }
5717
- let output = matter5.stringify(content, frontmatter);
5718
- output = normalizeTrailingNewline(output);
5719
- output = convertLineEndings(output, lineEnding);
5720
- await fs20.writeFile(fullPath, output, "utf-8");
5721
- }
5802
+ });
5803
+
5804
+ // src/core/write/content-mutation.ts
5722
5805
  function removeFromSection(content, section, pattern, mode = "first", useRegex = false) {
5723
5806
  const lines = content.split("\n");
5724
5807
  const removedLines = [];
@@ -5956,119 +6039,12 @@ function injectMutationMetadata(frontmatter, scoping) {
5956
6039
  }
5957
6040
  return frontmatter;
5958
6041
  }
5959
- var SENSITIVE_PATH_PATTERNS, REDOS_PATTERNS, MAX_REGEX_LENGTH, EMPTY_PLACEHOLDER_PATTERNS, WriteConflictError, DiagnosticError;
5960
- var init_writer = __esm({
5961
- "src/core/write/writer.ts"() {
6042
+ var DiagnosticError;
6043
+ var init_content_mutation = __esm({
6044
+ "src/core/write/content-mutation.ts"() {
5962
6045
  "use strict";
5963
- init_constants2();
6046
+ init_regex_safety();
5964
6047
  init_levenshtein();
5965
- SENSITIVE_PATH_PATTERNS = [
5966
- // Environment files (including backups, variations, and Windows ADS)
5967
- /\.env($|\..*|~|\.swp|\.swo|:)/i,
5968
- // .env, .env.local, .env~, .env.swp, .env:$DATA (ADS), etc.
5969
- // Git credentials and config
5970
- /\.git\/config$/i,
5971
- // Git config (may contain tokens)
5972
- /\.git\/credentials$/i,
5973
- // Git credentials
5974
- // SSL/TLS certificates and private keys (including backups)
5975
- /\.pem($|\.bak|\.backup|\.old|\.orig|~)$/i,
5976
- // SSL/TLS certificates + backups
5977
- /\.key($|\.bak|\.backup|\.old|\.orig|~)$/i,
5978
- // Private keys + backups
5979
- /\.p12($|\.bak|\.backup|\.old|\.orig|~)$/i,
5980
- // PKCS#12 certificates + backups
5981
- /\.pfx($|\.bak|\.backup|\.old|\.orig|~)$/i,
5982
- // Windows certificate format + backups
5983
- /\.jks($|\.bak|\.backup|\.old|\.orig|~)$/i,
5984
- // Java keystore + backups
5985
- /\.crt($|\.bak|\.backup|\.old|\.orig|~)$/i,
5986
- // Certificate files + backups
5987
- // SSH keys
5988
- /id_rsa/i,
5989
- // SSH private key
5990
- /id_ed25519/i,
5991
- // SSH private key (ed25519)
5992
- /id_ecdsa/i,
5993
- // SSH private key (ecdsa)
5994
- /id_dsa/i,
5995
- // SSH private key (dsa)
5996
- /\.ssh\/config$/i,
5997
- // SSH config
5998
- /authorized_keys$/i,
5999
- // SSH authorized keys
6000
- /known_hosts$/i,
6001
- // SSH known hosts
6002
- // Generic credentials/secrets files (including backups)
6003
- /credentials\.json($|\.bak|\.backup|\.old|\.orig|~)$/i,
6004
- // Cloud credentials + backups
6005
- /secrets\.json($|\.bak|\.backup|\.old|\.orig|~)$/i,
6006
- // Secrets files + backups
6007
- /secrets\.ya?ml($|\.bak|\.backup|\.old|\.orig|~)$/i,
6008
- // Secrets YAML + backups
6009
- // Package manager auth
6010
- /\.npmrc$/i,
6011
- // npm config (may contain tokens)
6012
- /\.netrc$/i,
6013
- // Netrc (HTTP auth credentials)
6014
- /\.yarnrc$/i,
6015
- // Yarn config
6016
- // Cloud provider credentials
6017
- /\.aws\/credentials$/i,
6018
- // AWS credentials
6019
- /\.aws\/config$/i,
6020
- // AWS config
6021
- /gcloud\/credentials\.json/i,
6022
- // Google Cloud credentials
6023
- /\.azure\/credentials$/i,
6024
- // Azure credentials
6025
- /\.docker\/config\.json$/i,
6026
- // Docker registry auth
6027
- /\.kube\/config$/i,
6028
- // Kubernetes config
6029
- // System password files
6030
- /\.htpasswd$/i,
6031
- // Apache password file
6032
- /shadow$/,
6033
- // Unix shadow password file
6034
- /passwd$/,
6035
- // Unix password file
6036
- // Hidden credential files (starting with dot)
6037
- /^\.(credentials|secrets|tokens)$/i
6038
- // .credentials, .secrets, .tokens
6039
- ];
6040
- REDOS_PATTERNS = [
6041
- // Nested quantifiers: (a+)+, (a*)+, (a+)*, (a*)*, etc.
6042
- /(\([^)]*[+*][^)]*\))[+*]/,
6043
- // Quantifiers followed by optional same-type quantifiers
6044
- /[+*]\??\s*[+*]/,
6045
- // Overlapping character classes with quantifiers followed by similar
6046
- /\[[^\]]*\][+*].*\[[^\]]*\][+*]/,
6047
- // Multiple adjacent capturing groups with quantifiers
6048
- /(\([^)]+[+*]\)){2,}/,
6049
- // Extremely long alternation groups
6050
- /\([^)]{100,}\)/
6051
- ];
6052
- MAX_REGEX_LENGTH = 500;
6053
- EMPTY_PLACEHOLDER_PATTERNS = [
6054
- /^\d+\.\s*$/,
6055
- // "1. " or "2. " (numbered list placeholder)
6056
- /^-\s*$/,
6057
- // "- " (bullet placeholder)
6058
- /^-\s*\[\s*\]\s*$/,
6059
- // "- [ ] " (empty task placeholder)
6060
- /^-\s*\[x\]\s*$/i,
6061
- // "- [x] " (completed task placeholder)
6062
- /^\*\s*$/
6063
- // "* " (asterisk bullet placeholder)
6064
- ];
6065
- WriteConflictError = class extends Error {
6066
- constructor(notePath) {
6067
- super(`Write conflict on ${notePath}: file was modified externally since it was read. Re-read and retry.`);
6068
- this.notePath = notePath;
6069
- this.name = "WriteConflictError";
6070
- }
6071
- };
6072
6048
  DiagnosticError = class extends Error {
6073
6049
  diagnostic;
6074
6050
  constructor(message, diagnostic) {
@@ -6080,6 +6056,116 @@ var init_writer = __esm({
6080
6056
  }
6081
6057
  });
6082
6058
 
6059
+ // src/core/write/file-io.ts
6060
+ import fs21 from "fs/promises";
6061
+ import path23 from "path";
6062
+ import matter5 from "gray-matter";
6063
+ import { createHash as createHash2 } from "node:crypto";
6064
+ function computeContentHash(rawContent) {
6065
+ return createHash2("sha256").update(rawContent).digest("hex").slice(0, 16);
6066
+ }
6067
+ async function readVaultFile(vaultPath2, notePath) {
6068
+ const validation = await validatePathSecure(vaultPath2, notePath);
6069
+ if (!validation.valid) {
6070
+ throw new Error(`Invalid path: ${validation.reason}`);
6071
+ }
6072
+ const fullPath = path23.join(vaultPath2, notePath);
6073
+ const [rawContent, stat4] = await Promise.all([
6074
+ fs21.readFile(fullPath, "utf-8"),
6075
+ fs21.stat(fullPath)
6076
+ ]);
6077
+ const contentHash2 = computeContentHash(rawContent);
6078
+ const lineEnding = detectLineEnding(rawContent);
6079
+ const normalizedContent = normalizeLineEndings(rawContent);
6080
+ const parsed = matter5(normalizedContent);
6081
+ const frontmatter = deepCloneFrontmatter(parsed.data);
6082
+ return {
6083
+ content: parsed.content,
6084
+ frontmatter,
6085
+ rawContent,
6086
+ lineEnding,
6087
+ mtimeMs: stat4.mtimeMs,
6088
+ contentHash: contentHash2
6089
+ };
6090
+ }
6091
+ function deepCloneFrontmatter(obj) {
6092
+ if (obj === null || typeof obj !== "object") {
6093
+ return obj;
6094
+ }
6095
+ if (obj instanceof Date) {
6096
+ return new Date(obj.getTime());
6097
+ }
6098
+ if (Array.isArray(obj)) {
6099
+ return obj.map((item) => {
6100
+ if (item instanceof Date) {
6101
+ return new Date(item.getTime());
6102
+ }
6103
+ if (item !== null && typeof item === "object") {
6104
+ return deepCloneFrontmatter(item);
6105
+ }
6106
+ return item;
6107
+ });
6108
+ }
6109
+ const cloned = {};
6110
+ for (const key of Object.keys(obj)) {
6111
+ const value = obj[key];
6112
+ if (value instanceof Date) {
6113
+ cloned[key] = new Date(value.getTime());
6114
+ } else if (value !== null && typeof value === "object") {
6115
+ cloned[key] = deepCloneFrontmatter(value);
6116
+ } else {
6117
+ cloned[key] = value;
6118
+ }
6119
+ }
6120
+ return cloned;
6121
+ }
6122
+ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEnding = "LF", expectedHash) {
6123
+ const validation = await validatePathSecure(vaultPath2, notePath);
6124
+ if (!validation.valid) {
6125
+ throw new Error(`Invalid path: ${validation.reason}`);
6126
+ }
6127
+ const fullPath = path23.join(vaultPath2, notePath);
6128
+ if (expectedHash) {
6129
+ const currentRaw = await fs21.readFile(fullPath, "utf-8");
6130
+ const currentHash = computeContentHash(currentRaw);
6131
+ if (currentHash !== expectedHash) {
6132
+ throw new WriteConflictError(notePath);
6133
+ }
6134
+ }
6135
+ let output = matter5.stringify(content, frontmatter);
6136
+ output = normalizeTrailingNewline(output);
6137
+ output = convertLineEndings(output, lineEnding);
6138
+ await fs21.writeFile(fullPath, output, "utf-8");
6139
+ }
6140
+ var WriteConflictError;
6141
+ var init_file_io = __esm({
6142
+ "src/core/write/file-io.ts"() {
6143
+ "use strict";
6144
+ init_path_security();
6145
+ init_line_endings();
6146
+ WriteConflictError = class extends Error {
6147
+ constructor(notePath) {
6148
+ super(`Write conflict on ${notePath}: file was modified externally since it was read. Re-read and retry.`);
6149
+ this.notePath = notePath;
6150
+ this.name = "WriteConflictError";
6151
+ }
6152
+ };
6153
+ }
6154
+ });
6155
+
6156
+ // src/core/write/writer.ts
6157
+ var init_writer = __esm({
6158
+ "src/core/write/writer.ts"() {
6159
+ "use strict";
6160
+ init_path_security();
6161
+ init_regex_safety();
6162
+ init_line_endings();
6163
+ init_markdown_structure();
6164
+ init_content_mutation();
6165
+ init_file_io();
6166
+ }
6167
+ });
6168
+
6083
6169
  // src/core/write/policy/template.ts
6084
6170
  function formatDate2(date) {
6085
6171
  const year = date.getFullYear();
@@ -6106,8 +6192,8 @@ function createContext(variables = {}) {
6106
6192
  steps: {}
6107
6193
  };
6108
6194
  }
6109
- function resolvePath(obj, path39) {
6110
- const parts = path39.split(".");
6195
+ function resolvePath(obj, path40) {
6196
+ const parts = path40.split(".");
6111
6197
  let current = obj;
6112
6198
  for (const part of parts) {
6113
6199
  if (current === void 0 || current === null) {
@@ -6564,8 +6650,8 @@ __export(conditions_exports, {
6564
6650
  evaluateCondition: () => evaluateCondition,
6565
6651
  shouldStepExecute: () => shouldStepExecute
6566
6652
  });
6567
- import fs28 from "fs/promises";
6568
- import path30 from "path";
6653
+ import fs29 from "fs/promises";
6654
+ import path31 from "path";
6569
6655
  async function evaluateCondition(condition, vaultPath2, context) {
6570
6656
  const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
6571
6657
  const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
@@ -6618,9 +6704,9 @@ async function evaluateCondition(condition, vaultPath2, context) {
6618
6704
  }
6619
6705
  }
6620
6706
  async function evaluateFileExists(vaultPath2, notePath, expectExists) {
6621
- const fullPath = path30.join(vaultPath2, notePath);
6707
+ const fullPath = path31.join(vaultPath2, notePath);
6622
6708
  try {
6623
- await fs28.access(fullPath);
6709
+ await fs29.access(fullPath);
6624
6710
  return {
6625
6711
  met: expectExists,
6626
6712
  reason: expectExists ? `File exists: ${notePath}` : `File exists (expected not to): ${notePath}`
@@ -6633,9 +6719,9 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
6633
6719
  }
6634
6720
  }
6635
6721
  async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
6636
- const fullPath = path30.join(vaultPath2, notePath);
6722
+ const fullPath = path31.join(vaultPath2, notePath);
6637
6723
  try {
6638
- await fs28.access(fullPath);
6724
+ await fs29.access(fullPath);
6639
6725
  } catch {
6640
6726
  return {
6641
6727
  met: !expectExists,
@@ -6664,9 +6750,9 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
6664
6750
  }
6665
6751
  }
6666
6752
  async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
6667
- const fullPath = path30.join(vaultPath2, notePath);
6753
+ const fullPath = path31.join(vaultPath2, notePath);
6668
6754
  try {
6669
- await fs28.access(fullPath);
6755
+ await fs29.access(fullPath);
6670
6756
  } catch {
6671
6757
  return {
6672
6758
  met: !expectExists,
@@ -6695,9 +6781,9 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
6695
6781
  }
6696
6782
  }
6697
6783
  async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
6698
- const fullPath = path30.join(vaultPath2, notePath);
6784
+ const fullPath = path31.join(vaultPath2, notePath);
6699
6785
  try {
6700
- await fs28.access(fullPath);
6786
+ await fs29.access(fullPath);
6701
6787
  } catch {
6702
6788
  return {
6703
6789
  met: false,
@@ -6839,9 +6925,9 @@ var init_taskHelpers = __esm({
6839
6925
  });
6840
6926
 
6841
6927
  // src/index.ts
6842
- import * as path38 from "path";
6928
+ import * as path39 from "path";
6843
6929
  import { readFileSync as readFileSync6, realpathSync, existsSync as existsSync3 } from "fs";
6844
- import { fileURLToPath as fileURLToPath2 } from "url";
6930
+ import { fileURLToPath as fileURLToPath3 } from "url";
6845
6931
  import { dirname as dirname7, join as join20 } from "path";
6846
6932
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6847
6933
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -7079,8 +7165,8 @@ function updateIndexProgress(parsed, total) {
7079
7165
  function normalizeTarget(target) {
7080
7166
  return target.toLowerCase().replace(/\.md$/, "");
7081
7167
  }
7082
- function normalizeNotePath(path39) {
7083
- return path39.toLowerCase().replace(/\.md$/, "");
7168
+ function normalizeNotePath(path40) {
7169
+ return path40.toLowerCase().replace(/\.md$/, "");
7084
7170
  }
7085
7171
  async function buildVaultIndex(vaultPath2, options = {}) {
7086
7172
  const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
@@ -7128,6 +7214,7 @@ async function buildVaultIndexInternal(vaultPath2, startTime, onProgress) {
7128
7214
  console.error(`Parsed ${parsedCount}/${files.length} files (${elapsed}s)`);
7129
7215
  onProgress?.(parsedCount, files.length);
7130
7216
  }
7217
+ await new Promise((resolve3) => setImmediate(resolve3));
7131
7218
  }
7132
7219
  if (parseErrors.length > 0) {
7133
7220
  const msg = `Failed to parse ${parseErrors.length} file(s):`;
@@ -7253,7 +7340,7 @@ function findSimilarEntity(index, target) {
7253
7340
  }
7254
7341
  const maxDist = normalizedLen <= 10 ? 1 : 2;
7255
7342
  let bestMatch;
7256
- for (const [entity, path39] of index.entities) {
7343
+ for (const [entity, path40] of index.entities) {
7257
7344
  const lenDiff = Math.abs(entity.length - normalizedLen);
7258
7345
  if (lenDiff > maxDist) {
7259
7346
  continue;
@@ -7261,7 +7348,7 @@ function findSimilarEntity(index, target) {
7261
7348
  const dist = levenshteinDistance(normalized, entity);
7262
7349
  if (dist > 0 && dist <= maxDist) {
7263
7350
  if (!bestMatch || dist < bestMatch.distance) {
7264
- bestMatch = { path: path39, entity, distance: dist };
7351
+ bestMatch = { path: path40, entity, distance: dist };
7265
7352
  if (dist === 1) {
7266
7353
  return bestMatch;
7267
7354
  }
@@ -7375,21 +7462,21 @@ var DEFAULT_CONFIG = {
7375
7462
  implicit_detection: true,
7376
7463
  adaptive_strictness: true
7377
7464
  };
7378
- function migrateExcludeConfig(config) {
7465
+ function migrateExcludeConfig(config2) {
7379
7466
  const oldTags = [
7380
- ...config.exclude_task_tags ?? [],
7381
- ...config.exclude_analysis_tags ?? []
7467
+ ...config2.exclude_task_tags ?? [],
7468
+ ...config2.exclude_analysis_tags ?? []
7382
7469
  ];
7383
- const oldEntities = config.exclude_entities ?? [];
7384
- if (oldTags.length === 0 && oldEntities.length === 0) return config;
7470
+ const oldEntities = config2.exclude_entities ?? [];
7471
+ if (oldTags.length === 0 && oldEntities.length === 0) return config2;
7385
7472
  const normalizedTags = oldTags.map((t) => t.startsWith("#") ? t : `#${t}`);
7386
7473
  const merged = /* @__PURE__ */ new Set([
7387
- ...config.exclude ?? [],
7474
+ ...config2.exclude ?? [],
7388
7475
  ...normalizedTags,
7389
7476
  ...oldEntities
7390
7477
  ]);
7391
7478
  return {
7392
- ...config,
7479
+ ...config2,
7393
7480
  exclude: Array.from(merged),
7394
7481
  // Clear deprecated fields
7395
7482
  exclude_task_tags: void 0,
@@ -7488,11 +7575,11 @@ function inferConfig(index, vaultPath2) {
7488
7575
  }
7489
7576
  return inferred;
7490
7577
  }
7491
- function getExcludeTags(config) {
7492
- return (config.exclude ?? []).filter((e) => e.startsWith("#")).map((e) => e.slice(1));
7578
+ function getExcludeTags(config2) {
7579
+ return (config2.exclude ?? []).filter((e) => e.startsWith("#")).map((e) => e.slice(1));
7493
7580
  }
7494
- function getExcludeEntities(config) {
7495
- return (config.exclude ?? []).filter((e) => !e.startsWith("#"));
7581
+ function getExcludeEntities(config2) {
7582
+ return (config2.exclude ?? []).filter((e) => !e.startsWith("#"));
7496
7583
  }
7497
7584
  var TEMPLATE_PATTERNS = {
7498
7585
  daily: /^daily[\s._-]*(note|template)?\.md$/i,
@@ -7685,8 +7772,8 @@ function normalizePath(filePath) {
7685
7772
  function getRelativePath(vaultPath2, filePath) {
7686
7773
  const normalizedVault = normalizePath(vaultPath2);
7687
7774
  const normalizedFile = normalizePath(filePath);
7688
- const relative3 = path5.posix.relative(normalizedVault, normalizedFile);
7689
- return relative3;
7775
+ const relative2 = path5.posix.relative(normalizedVault, normalizedFile);
7776
+ return relative2;
7690
7777
  }
7691
7778
  function shouldWatch(filePath, vaultPath2) {
7692
7779
  const normalized = normalizePath(filePath);
@@ -7812,38 +7899,38 @@ var EventQueue = class {
7812
7899
  config;
7813
7900
  flushTimer = null;
7814
7901
  onBatch;
7815
- constructor(config, onBatch) {
7816
- this.config = config;
7902
+ constructor(config2, onBatch) {
7903
+ this.config = config2;
7817
7904
  this.onBatch = onBatch;
7818
7905
  }
7819
7906
  /**
7820
7907
  * Add a new event to the queue
7821
7908
  */
7822
7909
  push(type, rawPath) {
7823
- const path39 = normalizePath(rawPath);
7910
+ const path40 = normalizePath(rawPath);
7824
7911
  const now = Date.now();
7825
7912
  const event = {
7826
7913
  type,
7827
- path: path39,
7914
+ path: path40,
7828
7915
  timestamp: now
7829
7916
  };
7830
- let pending = this.pending.get(path39);
7917
+ let pending = this.pending.get(path40);
7831
7918
  if (!pending) {
7832
7919
  pending = {
7833
7920
  events: [],
7834
7921
  timer: null,
7835
7922
  lastEvent: now
7836
7923
  };
7837
- this.pending.set(path39, pending);
7924
+ this.pending.set(path40, pending);
7838
7925
  }
7839
7926
  pending.events.push(event);
7840
7927
  pending.lastEvent = now;
7841
- console.error(`[flywheel] QUEUE: pushed ${type} for ${path39}, pending=${this.pending.size}`);
7928
+ console.error(`[flywheel] QUEUE: pushed ${type} for ${path40}, pending=${this.pending.size}`);
7842
7929
  if (pending.timer) {
7843
7930
  clearTimeout(pending.timer);
7844
7931
  }
7845
7932
  pending.timer = setTimeout(() => {
7846
- this.flushPath(path39);
7933
+ this.flushPath(path40);
7847
7934
  }, this.config.debounceMs);
7848
7935
  if (this.pending.size >= this.config.batchSize) {
7849
7936
  this.flush();
@@ -7864,10 +7951,10 @@ var EventQueue = class {
7864
7951
  /**
7865
7952
  * Flush a single path's events
7866
7953
  */
7867
- flushPath(path39) {
7868
- const pending = this.pending.get(path39);
7954
+ flushPath(path40) {
7955
+ const pending = this.pending.get(path40);
7869
7956
  if (!pending || pending.events.length === 0) return;
7870
- console.error(`[flywheel] QUEUE: flushing ${path39}, events=${pending.events.length}`);
7957
+ console.error(`[flywheel] QUEUE: flushing ${path40}, events=${pending.events.length}`);
7871
7958
  if (pending.timer) {
7872
7959
  clearTimeout(pending.timer);
7873
7960
  pending.timer = null;
@@ -7876,7 +7963,7 @@ var EventQueue = class {
7876
7963
  if (coalescedType) {
7877
7964
  const coalesced = {
7878
7965
  type: coalescedType,
7879
- path: path39,
7966
+ path: path40,
7880
7967
  originalEvents: [...pending.events]
7881
7968
  };
7882
7969
  this.onBatch({
@@ -7885,7 +7972,7 @@ var EventQueue = class {
7885
7972
  timestamp: Date.now()
7886
7973
  });
7887
7974
  }
7888
- this.pending.delete(path39);
7975
+ this.pending.delete(path40);
7889
7976
  }
7890
7977
  /**
7891
7978
  * Flush all pending events
@@ -7897,7 +7984,7 @@ var EventQueue = class {
7897
7984
  }
7898
7985
  if (this.pending.size === 0) return;
7899
7986
  const events = [];
7900
- for (const [path39, pending] of this.pending) {
7987
+ for (const [path40, pending] of this.pending) {
7901
7988
  if (pending.timer) {
7902
7989
  clearTimeout(pending.timer);
7903
7990
  }
@@ -7905,7 +7992,7 @@ var EventQueue = class {
7905
7992
  if (coalescedType) {
7906
7993
  events.push({
7907
7994
  type: coalescedType,
7908
- path: path39,
7995
+ path: path40,
7909
7996
  originalEvents: [...pending.events]
7910
7997
  });
7911
7998
  }
@@ -8099,8 +8186,8 @@ async function upsertNote(index, vaultPath2, notePath) {
8099
8186
  releasedKeys = removeNoteFromIndex(index, notePath);
8100
8187
  }
8101
8188
  const fullPath = path7.join(vaultPath2, notePath);
8102
- const fs35 = await import("fs/promises");
8103
- const stats = await fs35.stat(fullPath);
8189
+ const fs36 = await import("fs/promises");
8190
+ const stats = await fs36.stat(fullPath);
8104
8191
  const vaultFile = {
8105
8192
  path: notePath,
8106
8193
  absolutePath: fullPath,
@@ -8205,7 +8292,7 @@ async function processBatch(index, vaultPath2, batch, options = {}) {
8205
8292
  }
8206
8293
  onProgress?.(processed, total);
8207
8294
  if (processed % YIELD_INTERVAL === 0 && processed < total) {
8208
- await new Promise((resolve2) => setImmediate(resolve2));
8295
+ await new Promise((resolve3) => setImmediate(resolve3));
8209
8296
  }
8210
8297
  }
8211
8298
  const durationMs = Date.now() - startTime;
@@ -8226,7 +8313,7 @@ init_serverLog();
8226
8313
  // src/core/read/watch/index.ts
8227
8314
  function createVaultWatcher(options) {
8228
8315
  const { vaultPath: vaultPath2, onBatch, onStateChange, onError } = options;
8229
- const config = {
8316
+ const config2 = {
8230
8317
  ...DEFAULT_WATCHER_CONFIG,
8231
8318
  ...parseWatcherConfig(),
8232
8319
  ...options.config
@@ -8271,7 +8358,7 @@ function createVaultWatcher(options) {
8271
8358
  }
8272
8359
  }
8273
8360
  };
8274
- const eventQueue = new EventQueue(config, processBatch2);
8361
+ const eventQueue = new EventQueue(config2, processBatch2);
8275
8362
  const instance = {
8276
8363
  get status() {
8277
8364
  return getStatus();
@@ -8284,8 +8371,8 @@ function createVaultWatcher(options) {
8284
8371
  console.error("[flywheel] Watcher already started");
8285
8372
  return;
8286
8373
  }
8287
- console.error(`[flywheel] Starting file watcher (debounce: ${config.debounceMs}ms, flush: ${config.flushMs}ms)`);
8288
- console.error(`[flywheel] Chokidar options: usePolling=${config.usePolling}, interval=${config.pollInterval}, vaultPath=${vaultPath2}`);
8374
+ console.error(`[flywheel] Starting file watcher (debounce: ${config2.debounceMs}ms, flush: ${config2.flushMs}ms)`);
8375
+ console.error(`[flywheel] Chokidar options: usePolling=${config2.usePolling}, interval=${config2.pollInterval}, vaultPath=${vaultPath2}`);
8289
8376
  watcher = chokidar.watch(vaultPath2, {
8290
8377
  ignored: createIgnoreFunction(vaultPath2),
8291
8378
  persistent: true,
@@ -8294,34 +8381,34 @@ function createVaultWatcher(options) {
8294
8381
  stabilityThreshold: 300,
8295
8382
  pollInterval: 100
8296
8383
  },
8297
- usePolling: config.usePolling,
8298
- interval: config.usePolling ? config.pollInterval : void 0
8384
+ usePolling: config2.usePolling,
8385
+ interval: config2.usePolling ? config2.pollInterval : void 0
8299
8386
  });
8300
- watcher.on("add", (path39) => {
8301
- console.error(`[flywheel] RAW EVENT: add ${path39}`);
8302
- if (shouldWatch(path39, vaultPath2)) {
8303
- console.error(`[flywheel] ACCEPTED: add ${path39}`);
8304
- eventQueue.push("add", path39);
8387
+ watcher.on("add", (path40) => {
8388
+ console.error(`[flywheel] RAW EVENT: add ${path40}`);
8389
+ if (shouldWatch(path40, vaultPath2)) {
8390
+ console.error(`[flywheel] ACCEPTED: add ${path40}`);
8391
+ eventQueue.push("add", path40);
8305
8392
  } else {
8306
- console.error(`[flywheel] FILTERED: add ${path39}`);
8393
+ console.error(`[flywheel] FILTERED: add ${path40}`);
8307
8394
  }
8308
8395
  });
8309
- watcher.on("change", (path39) => {
8310
- console.error(`[flywheel] RAW EVENT: change ${path39}`);
8311
- if (shouldWatch(path39, vaultPath2)) {
8312
- console.error(`[flywheel] ACCEPTED: change ${path39}`);
8313
- eventQueue.push("change", path39);
8396
+ watcher.on("change", (path40) => {
8397
+ console.error(`[flywheel] RAW EVENT: change ${path40}`);
8398
+ if (shouldWatch(path40, vaultPath2)) {
8399
+ console.error(`[flywheel] ACCEPTED: change ${path40}`);
8400
+ eventQueue.push("change", path40);
8314
8401
  } else {
8315
- console.error(`[flywheel] FILTERED: change ${path39}`);
8402
+ console.error(`[flywheel] FILTERED: change ${path40}`);
8316
8403
  }
8317
8404
  });
8318
- watcher.on("unlink", (path39) => {
8319
- console.error(`[flywheel] RAW EVENT: unlink ${path39}`);
8320
- if (shouldWatch(path39, vaultPath2)) {
8321
- console.error(`[flywheel] ACCEPTED: unlink ${path39}`);
8322
- eventQueue.push("unlink", path39);
8405
+ watcher.on("unlink", (path40) => {
8406
+ console.error(`[flywheel] RAW EVENT: unlink ${path40}`);
8407
+ if (shouldWatch(path40, vaultPath2)) {
8408
+ console.error(`[flywheel] ACCEPTED: unlink ${path40}`);
8409
+ eventQueue.push("unlink", path40);
8323
8410
  } else {
8324
- console.error(`[flywheel] FILTERED: unlink ${path39}`);
8411
+ console.error(`[flywheel] FILTERED: unlink ${path40}`);
8325
8412
  }
8326
8413
  });
8327
8414
  watcher.on("ready", () => {
@@ -9479,6 +9566,104 @@ function refreshIfStale(vaultPath2, index, excludeTags) {
9479
9566
  init_wikilinkFeedback();
9480
9567
  init_corrections();
9481
9568
  init_edgeWeights();
9569
+ var DeferredStepScheduler = class {
9570
+ timers = /* @__PURE__ */ new Map();
9571
+ executor = null;
9572
+ /** Set the executor context (called once during watcher setup) */
9573
+ setExecutor(exec) {
9574
+ this.executor = exec;
9575
+ }
9576
+ /** Schedule a deferred step to run after delayMs. Cancels any existing timer for this step. */
9577
+ schedule(step, delayMs) {
9578
+ this.cancel(step);
9579
+ const timer2 = setTimeout(() => {
9580
+ this.timers.delete(step);
9581
+ this.executeStep(step);
9582
+ }, delayMs);
9583
+ timer2.unref();
9584
+ this.timers.set(step, timer2);
9585
+ serverLog("deferred", `Scheduled ${step} in ${Math.round(delayMs / 1e3)}s`);
9586
+ }
9587
+ /** Cancel a pending deferred step */
9588
+ cancel(step) {
9589
+ const existing = this.timers.get(step);
9590
+ if (existing) {
9591
+ clearTimeout(existing);
9592
+ this.timers.delete(step);
9593
+ }
9594
+ }
9595
+ /** Cancel all pending deferred steps (called on shutdown) */
9596
+ cancelAll() {
9597
+ for (const timer2 of this.timers.values()) clearTimeout(timer2);
9598
+ this.timers.clear();
9599
+ }
9600
+ /** Check if any steps are pending */
9601
+ get pendingCount() {
9602
+ return this.timers.size;
9603
+ }
9604
+ async executeStep(step) {
9605
+ const exec = this.executor;
9606
+ if (!exec) return;
9607
+ if (exec.ctx.pipelineActivity.busy) {
9608
+ serverLog("deferred", `Skipping ${step}: pipeline busy`);
9609
+ return;
9610
+ }
9611
+ const start = Date.now();
9612
+ try {
9613
+ switch (step) {
9614
+ case "entity_scan": {
9615
+ await exec.updateEntitiesInStateDb(exec.vp, exec.sd);
9616
+ exec.ctx.lastEntityScanAt = Date.now();
9617
+ if (exec.sd) {
9618
+ await exportHubScores(exec.getVaultIndex(), exec.sd);
9619
+ exec.ctx.lastHubScoreRebuildAt = Date.now();
9620
+ }
9621
+ break;
9622
+ }
9623
+ case "hub_scores": {
9624
+ await exportHubScores(exec.getVaultIndex(), exec.sd);
9625
+ exec.ctx.lastHubScoreRebuildAt = Date.now();
9626
+ break;
9627
+ }
9628
+ case "recency": {
9629
+ const entities = exec.sd ? getAllEntitiesFromDb(exec.sd) : [];
9630
+ const entityInput = entities.map((e) => ({ name: e.name, path: e.path, aliases: e.aliases }));
9631
+ const recencyIndex2 = await buildRecencyIndex(exec.vp, entityInput);
9632
+ saveRecencyToStateDb(recencyIndex2, exec.sd ?? void 0);
9633
+ break;
9634
+ }
9635
+ case "cooccurrence": {
9636
+ const entities = exec.sd ? getAllEntitiesFromDb(exec.sd) : [];
9637
+ const entityNames = entities.map((e) => e.name);
9638
+ const cooccurrenceIdx = await mineCooccurrences(exec.vp, entityNames);
9639
+ setCooccurrenceIndex(cooccurrenceIdx);
9640
+ exec.ctx.lastCooccurrenceRebuildAt = Date.now();
9641
+ exec.ctx.cooccurrenceIndex = cooccurrenceIdx;
9642
+ if (exec.sd) saveCooccurrenceToStateDb(exec.sd, cooccurrenceIdx);
9643
+ break;
9644
+ }
9645
+ case "edge_weights": {
9646
+ if (exec.sd) {
9647
+ recomputeEdgeWeights(exec.sd);
9648
+ exec.ctx.lastEdgeWeightRebuildAt = Date.now();
9649
+ }
9650
+ break;
9651
+ }
9652
+ }
9653
+ const duration = Date.now() - start;
9654
+ serverLog("deferred", `Completed ${step} in ${duration}ms`);
9655
+ if (exec.sd) {
9656
+ recordIndexEvent(exec.sd, {
9657
+ trigger: "deferred",
9658
+ duration_ms: duration,
9659
+ note_count: exec.getVaultIndex().notes.size
9660
+ });
9661
+ }
9662
+ } catch (err) {
9663
+ serverLog("deferred", `Failed ${step}: ${err instanceof Error ? err.message : err}`, "error");
9664
+ }
9665
+ }
9666
+ };
9482
9667
  var PIPELINE_TOTAL_STEPS = 22;
9483
9668
  function createEmptyPipelineActivity() {
9484
9669
  return {
@@ -9706,6 +9891,7 @@ var PipelineRunner = class {
9706
9891
  tracker.skip("entity_scan", `cache valid (${Math.round(entityScanAgeMs / 1e3)}s old)`);
9707
9892
  this.entitiesBefore = p.sd ? getAllEntitiesFromDb(p.sd) : [];
9708
9893
  this.entitiesAfter = this.entitiesBefore;
9894
+ p.deferredScheduler?.schedule("entity_scan", 5 * 60 * 1e3 - entityScanAgeMs);
9709
9895
  serverLog("watcher", `Entity scan: throttled (${Math.round(entityScanAgeMs / 1e3)}s old)`);
9710
9896
  return;
9711
9897
  }
@@ -9751,6 +9937,7 @@ var PipelineRunner = class {
9751
9937
  const { p } = this;
9752
9938
  const hubAgeMs = p.ctx.lastHubScoreRebuildAt > 0 ? Date.now() - p.ctx.lastHubScoreRebuildAt : Infinity;
9753
9939
  if (hubAgeMs < 5 * 60 * 1e3) {
9940
+ p.deferredScheduler?.schedule("hub_scores", 5 * 60 * 1e3 - hubAgeMs);
9754
9941
  serverLog("watcher", `Hub scores: throttled (${Math.round(hubAgeMs / 1e3)}s old)`);
9755
9942
  return { skipped: true, age_ms: hubAgeMs };
9756
9943
  }
@@ -9780,6 +9967,7 @@ var PipelineRunner = class {
9780
9967
  serverLog("watcher", `Recency: rebuilt ${recencyIndex2.lastMentioned.size} entities`);
9781
9968
  return { rebuilt: true, entities: recencyIndex2.lastMentioned.size };
9782
9969
  }
9970
+ p.deferredScheduler?.schedule("recency", 60 * 60 * 1e3 - cacheAgeMs);
9783
9971
  serverLog("watcher", `Recency: cache valid (${Math.round(cacheAgeMs / 1e3)}s old)`);
9784
9972
  return { rebuilt: false, cached_age_ms: cacheAgeMs };
9785
9973
  }
@@ -9799,6 +9987,7 @@ var PipelineRunner = class {
9799
9987
  serverLog("watcher", `Co-occurrence: rebuilt ${cooccurrenceIdx._metadata.total_associations} associations`);
9800
9988
  return { rebuilt: true, associations: cooccurrenceIdx._metadata.total_associations };
9801
9989
  }
9990
+ p.deferredScheduler?.schedule("cooccurrence", 60 * 60 * 1e3 - cooccurrenceAgeMs);
9802
9991
  serverLog("watcher", `Co-occurrence: cache valid (${Math.round(cooccurrenceAgeMs / 1e3)}s old)`);
9803
9992
  return { rebuilt: false, age_ms: cooccurrenceAgeMs };
9804
9993
  }
@@ -9821,6 +10010,7 @@ var PipelineRunner = class {
9821
10010
  top_changes: result.top_changes
9822
10011
  };
9823
10012
  }
10013
+ p.deferredScheduler?.schedule("edge_weights", 60 * 60 * 1e3 - edgeWeightAgeMs);
9824
10014
  serverLog("watcher", `Edge weights: cache valid (${Math.round(edgeWeightAgeMs / 1e3)}s old)`);
9825
10015
  return { rebuilt: false, age_ms: edgeWeightAgeMs };
9826
10016
  }
@@ -10810,7 +11000,7 @@ function getToolSelectionReport(stateDb2, daysBack = 7) {
10810
11000
  }
10811
11001
 
10812
11002
  // src/index.ts
10813
- import { openStateDb, scanVaultEntities as scanVaultEntities4, getAllEntitiesFromDb as getAllEntitiesFromDb5, loadContentHashes, saveContentHashBatch, renameContentHash, checkDbIntegrity as checkDbIntegrity2, safeBackupAsync as safeBackupAsync2, preserveCorruptedDb, deleteStateDbFiles, attemptSalvage } from "@velvetmonkey/vault-core";
11003
+ import { openStateDb, scanVaultEntities as scanVaultEntities5, getAllEntitiesFromDb as getAllEntitiesFromDb6, loadContentHashes, saveContentHashBatch, renameContentHash, checkDbIntegrity as checkDbIntegrity2, safeBackupAsync as safeBackupAsync2, preserveCorruptedDb, deleteStateDbFiles, attemptSalvage } from "@velvetmonkey/vault-core";
10814
11004
 
10815
11005
  // src/core/write/memory.ts
10816
11006
  init_wikilinkFeedback();
@@ -11229,37 +11419,413 @@ function getSweepResults() {
11229
11419
  return cachedResults;
11230
11420
  }
11231
11421
 
11232
- // src/core/shared/metrics.ts
11233
- init_wikilinkFeedback();
11234
- var ALL_METRICS = [
11235
- "note_count",
11236
- "link_count",
11237
- "orphan_count",
11238
- "tag_count",
11239
- "entity_count",
11240
- "avg_links_per_note",
11241
- "link_density",
11242
- "connected_ratio",
11243
- "wikilink_accuracy",
11244
- "wikilink_feedback_volume",
11245
- "wikilink_suppressed_count"
11246
- ];
11247
- function computeMetrics(index, stateDb2) {
11422
+ // src/core/read/watch/maintenance.ts
11423
+ init_serverLog();
11424
+ import { getAllEntitiesFromDb as getAllEntitiesFromDb2 } from "@velvetmonkey/vault-core";
11425
+ init_recency();
11426
+ init_cooccurrence();
11427
+ init_wikilinks();
11428
+ init_edgeWeights();
11429
+
11430
+ // src/core/shared/graphSnapshots.ts
11431
+ function computeGraphMetrics(index) {
11248
11432
  const noteCount = index.notes.size;
11249
- let linkCount = 0;
11250
- for (const note of index.notes.values()) {
11251
- linkCount += note.outlinks.length;
11433
+ if (noteCount === 0) {
11434
+ return {
11435
+ avg_degree: 0,
11436
+ max_degree: 0,
11437
+ cluster_count: 0,
11438
+ largest_cluster_size: 0,
11439
+ hub_scores_top10: []
11440
+ };
11252
11441
  }
11253
- const connectedNotes = /* @__PURE__ */ new Set();
11442
+ const degreeMap = /* @__PURE__ */ new Map();
11443
+ const adjacency = /* @__PURE__ */ new Map();
11254
11444
  for (const [notePath, note] of index.notes) {
11255
- if (note.outlinks.length > 0) {
11256
- connectedNotes.add(notePath);
11257
- }
11258
- }
11259
- for (const [target, backlinks] of index.backlinks) {
11260
- for (const bl of backlinks) {
11261
- connectedNotes.add(bl.source);
11262
- }
11445
+ if (!adjacency.has(notePath)) adjacency.set(notePath, /* @__PURE__ */ new Set());
11446
+ let degree = note.outlinks.length;
11447
+ for (const link of note.outlinks) {
11448
+ const targetLower = link.target.toLowerCase();
11449
+ const resolvedPath = index.entities.get(targetLower);
11450
+ if (resolvedPath && index.notes.has(resolvedPath)) {
11451
+ adjacency.get(notePath).add(resolvedPath);
11452
+ if (!adjacency.has(resolvedPath)) adjacency.set(resolvedPath, /* @__PURE__ */ new Set());
11453
+ adjacency.get(resolvedPath).add(notePath);
11454
+ }
11455
+ }
11456
+ degreeMap.set(notePath, degree);
11457
+ }
11458
+ for (const [target, backlinks] of index.backlinks) {
11459
+ const targetLower = target.toLowerCase();
11460
+ const resolvedPath = index.entities.get(targetLower);
11461
+ if (resolvedPath && degreeMap.has(resolvedPath)) {
11462
+ degreeMap.set(resolvedPath, degreeMap.get(resolvedPath) + backlinks.length);
11463
+ }
11464
+ }
11465
+ let totalDegree = 0;
11466
+ let maxDegree = 0;
11467
+ let maxDegreeNote = "";
11468
+ for (const [notePath, degree] of degreeMap) {
11469
+ totalDegree += degree;
11470
+ if (degree > maxDegree) {
11471
+ maxDegree = degree;
11472
+ maxDegreeNote = notePath;
11473
+ }
11474
+ }
11475
+ const avgDegree = noteCount > 0 ? Math.round(totalDegree / noteCount * 100) / 100 : 0;
11476
+ const visited = /* @__PURE__ */ new Set();
11477
+ const clusters = [];
11478
+ for (const notePath of index.notes.keys()) {
11479
+ if (visited.has(notePath)) continue;
11480
+ const queue = [notePath];
11481
+ visited.add(notePath);
11482
+ let clusterSize = 0;
11483
+ while (queue.length > 0) {
11484
+ const current = queue.shift();
11485
+ clusterSize++;
11486
+ const neighbors = adjacency.get(current);
11487
+ if (neighbors) {
11488
+ for (const neighbor of neighbors) {
11489
+ if (!visited.has(neighbor)) {
11490
+ visited.add(neighbor);
11491
+ queue.push(neighbor);
11492
+ }
11493
+ }
11494
+ }
11495
+ }
11496
+ clusters.push(clusterSize);
11497
+ }
11498
+ const clusterCount = clusters.length;
11499
+ const largestClusterSize = clusters.length > 0 ? Math.max(...clusters) : 0;
11500
+ const sorted = Array.from(degreeMap.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10);
11501
+ const hubScoresTop10 = sorted.map(([notePath, degree]) => {
11502
+ const note = index.notes.get(notePath);
11503
+ return {
11504
+ entity: note?.title ?? notePath,
11505
+ degree
11506
+ };
11507
+ });
11508
+ return {
11509
+ avg_degree: avgDegree,
11510
+ max_degree: maxDegree,
11511
+ cluster_count: clusterCount,
11512
+ largest_cluster_size: largestClusterSize,
11513
+ hub_scores_top10: hubScoresTop10
11514
+ };
11515
+ }
11516
+ function recordGraphSnapshot(stateDb2, metrics) {
11517
+ const timestamp = Date.now();
11518
+ const insert = stateDb2.db.prepare(
11519
+ "INSERT INTO graph_snapshots (timestamp, metric, value, details) VALUES (?, ?, ?, ?)"
11520
+ );
11521
+ const transaction = stateDb2.db.transaction(() => {
11522
+ insert.run(timestamp, "avg_degree", metrics.avg_degree, null);
11523
+ insert.run(timestamp, "max_degree", metrics.max_degree, null);
11524
+ insert.run(timestamp, "cluster_count", metrics.cluster_count, null);
11525
+ insert.run(timestamp, "largest_cluster_size", metrics.largest_cluster_size, null);
11526
+ insert.run(
11527
+ timestamp,
11528
+ "hub_scores_top10",
11529
+ metrics.hub_scores_top10.length,
11530
+ JSON.stringify(metrics.hub_scores_top10)
11531
+ );
11532
+ });
11533
+ transaction();
11534
+ }
11535
+ function getEmergingHubs(stateDb2, daysBack = 30) {
11536
+ const cutoff = Date.now() - daysBack * 24 * 60 * 60 * 1e3;
11537
+ const latestRow = stateDb2.db.prepare(
11538
+ `SELECT details FROM graph_snapshots
11539
+ WHERE metric = 'hub_scores_top10'
11540
+ ORDER BY timestamp DESC LIMIT 1`
11541
+ ).get();
11542
+ const previousRow = stateDb2.db.prepare(
11543
+ `SELECT details FROM graph_snapshots
11544
+ WHERE metric = 'hub_scores_top10' AND timestamp >= ?
11545
+ ORDER BY timestamp ASC LIMIT 1`
11546
+ ).get(cutoff);
11547
+ if (!latestRow?.details) return [];
11548
+ const currentHubs = JSON.parse(latestRow.details);
11549
+ const previousHubs = previousRow?.details ? JSON.parse(previousRow.details) : [];
11550
+ const previousMap = /* @__PURE__ */ new Map();
11551
+ for (const hub of previousHubs) {
11552
+ previousMap.set(hub.entity, hub.degree);
11553
+ }
11554
+ const emerging = currentHubs.map((hub) => {
11555
+ const prevDegree = previousMap.get(hub.entity) ?? 0;
11556
+ return {
11557
+ entity: hub.entity,
11558
+ current_degree: hub.degree,
11559
+ previous_degree: prevDegree,
11560
+ growth: hub.degree - prevDegree
11561
+ };
11562
+ });
11563
+ emerging.sort((a, b) => b.growth - a.growth);
11564
+ return emerging;
11565
+ }
11566
+ function compareGraphSnapshots(stateDb2, timestampBefore, timestampAfter) {
11567
+ const SCALAR_METRICS = ["avg_degree", "max_degree", "cluster_count", "largest_cluster_size"];
11568
+ function getSnapshotAt(ts) {
11569
+ const row = stateDb2.db.prepare(
11570
+ `SELECT DISTINCT timestamp FROM graph_snapshots WHERE timestamp <= ? ORDER BY timestamp DESC LIMIT 1`
11571
+ ).get(ts);
11572
+ if (!row) return null;
11573
+ const rows = stateDb2.db.prepare(
11574
+ `SELECT metric, value, details FROM graph_snapshots WHERE timestamp = ?`
11575
+ ).all(row.timestamp);
11576
+ return rows;
11577
+ }
11578
+ const beforeRows = getSnapshotAt(timestampBefore) ?? [];
11579
+ const afterRows = getSnapshotAt(timestampAfter) ?? [];
11580
+ const beforeMap = /* @__PURE__ */ new Map();
11581
+ const afterMap = /* @__PURE__ */ new Map();
11582
+ for (const r of beforeRows) beforeMap.set(r.metric, { value: r.value, details: r.details });
11583
+ for (const r of afterRows) afterMap.set(r.metric, { value: r.value, details: r.details });
11584
+ const metricChanges = SCALAR_METRICS.map((metric) => {
11585
+ const before = beforeMap.get(metric)?.value ?? 0;
11586
+ const after = afterMap.get(metric)?.value ?? 0;
11587
+ const delta = after - before;
11588
+ const deltaPercent = before !== 0 ? Math.round(delta / before * 1e4) / 100 : delta !== 0 ? 100 : 0;
11589
+ return { metric, before, after, delta, deltaPercent };
11590
+ });
11591
+ const beforeHubs = beforeMap.get("hub_scores_top10")?.details ? JSON.parse(beforeMap.get("hub_scores_top10").details) : [];
11592
+ const afterHubs = afterMap.get("hub_scores_top10")?.details ? JSON.parse(afterMap.get("hub_scores_top10").details) : [];
11593
+ const beforeHubMap = /* @__PURE__ */ new Map();
11594
+ for (const h of beforeHubs) beforeHubMap.set(h.entity, h.degree);
11595
+ const afterHubMap = /* @__PURE__ */ new Map();
11596
+ for (const h of afterHubs) afterHubMap.set(h.entity, h.degree);
11597
+ const allHubEntities = /* @__PURE__ */ new Set([...beforeHubMap.keys(), ...afterHubMap.keys()]);
11598
+ const hubScoreChanges = [];
11599
+ for (const entity of allHubEntities) {
11600
+ const before = beforeHubMap.get(entity) ?? 0;
11601
+ const after = afterHubMap.get(entity) ?? 0;
11602
+ if (before !== after) {
11603
+ hubScoreChanges.push({ entity, before, after, delta: after - before });
11604
+ }
11605
+ }
11606
+ hubScoreChanges.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
11607
+ return { metricChanges, hubScoreChanges };
11608
+ }
11609
+ function purgeOldSnapshots(stateDb2, retentionDays = 90) {
11610
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
11611
+ const result = stateDb2.db.prepare(
11612
+ "DELETE FROM graph_snapshots WHERE timestamp < ?"
11613
+ ).run(cutoff);
11614
+ return result.changes;
11615
+ }
11616
+
11617
+ // src/core/read/watch/maintenance.ts
11618
+ var DEFAULT_INTERVAL_MS = 2 * 60 * 60 * 1e3;
11619
+ var MIN_INTERVAL_MS = 10 * 60 * 1e3;
11620
+ var JITTER_FACTOR = 0.15;
11621
+ var RECENT_REBUILD_THRESHOLD_MS = 60 * 60 * 1e3;
11622
+ var IDLE_THRESHOLD_MS = 30 * 1e3;
11623
+ var STEP_TTLS = {
11624
+ entity_scan: 5 * 60 * 1e3,
11625
+ // 5 minutes
11626
+ hub_scores: 5 * 60 * 1e3,
11627
+ // 5 minutes
11628
+ recency: 60 * 60 * 1e3,
11629
+ // 1 hour
11630
+ cooccurrence: 60 * 60 * 1e3,
11631
+ // 1 hour
11632
+ edge_weights: 60 * 60 * 1e3,
11633
+ // 1 hour
11634
+ config_inference: 2 * 60 * 60 * 1e3
11635
+ // 2 hours (only runs during maintenance)
11636
+ };
11637
+ var timer = null;
11638
+ var config = null;
11639
+ var lastConfigInferenceAt = 0;
11640
+ function addJitter(interval) {
11641
+ const jitter = interval * JITTER_FACTOR * (2 * Math.random() - 1);
11642
+ return Math.max(MIN_INTERVAL_MS, interval + jitter);
11643
+ }
11644
+ function startMaintenanceTimer(cfg, intervalMs) {
11645
+ config = cfg;
11646
+ const baseInterval = Math.max(intervalMs ?? DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS);
11647
+ scheduleNext(baseInterval);
11648
+ serverLog("maintenance", `Timer started (interval ~${Math.round(baseInterval / 6e4)}min)`);
11649
+ }
11650
+ function stopMaintenanceTimer() {
11651
+ if (timer) {
11652
+ clearTimeout(timer);
11653
+ timer = null;
11654
+ }
11655
+ config = null;
11656
+ }
11657
+ function scheduleNext(baseInterval) {
11658
+ timer = setTimeout(() => {
11659
+ runMaintenance(baseInterval);
11660
+ }, addJitter(baseInterval));
11661
+ timer.unref();
11662
+ }
11663
+ async function runMaintenance(baseInterval) {
11664
+ const cfg = config;
11665
+ if (!cfg) return;
11666
+ const { ctx, sd } = cfg;
11667
+ if (ctx.pipelineActivity.busy) {
11668
+ serverLog("maintenance", "Skipped: pipeline busy");
11669
+ scheduleNext(baseInterval);
11670
+ return;
11671
+ }
11672
+ const lastFullRebuild = cfg.getLastFullRebuildAt();
11673
+ if (lastFullRebuild > 0 && Date.now() - lastFullRebuild < RECENT_REBUILD_THRESHOLD_MS) {
11674
+ serverLog("maintenance", `Skipped: full rebuild ${Math.round((Date.now() - lastFullRebuild) / 6e4)}min ago`);
11675
+ scheduleNext(baseInterval);
11676
+ return;
11677
+ }
11678
+ const lastRequest = cfg.getLastMcpRequestAt();
11679
+ if (lastRequest > 0 && Date.now() - lastRequest < IDLE_THRESHOLD_MS) {
11680
+ serverLog("maintenance", "Skipped: server not idle, retrying in 1min");
11681
+ timer = setTimeout(() => runMaintenance(baseInterval), 60 * 1e3);
11682
+ timer.unref();
11683
+ return;
11684
+ }
11685
+ const start = Date.now();
11686
+ const stepsRun = [];
11687
+ const tracker = createStepTracker();
11688
+ try {
11689
+ const now = Date.now();
11690
+ const entityAge = ctx.lastEntityScanAt > 0 ? now - ctx.lastEntityScanAt : Infinity;
11691
+ if (entityAge >= STEP_TTLS.entity_scan) {
11692
+ tracker.start("entity_scan", {});
11693
+ await cfg.updateEntitiesInStateDb(cfg.vp, sd);
11694
+ ctx.lastEntityScanAt = Date.now();
11695
+ const entities = sd ? getAllEntitiesFromDb2(sd) : [];
11696
+ tracker.end({ entity_count: entities.length });
11697
+ stepsRun.push("entity_scan");
11698
+ }
11699
+ const hubAge = ctx.lastHubScoreRebuildAt > 0 ? now - ctx.lastHubScoreRebuildAt : Infinity;
11700
+ if (hubAge >= STEP_TTLS.hub_scores) {
11701
+ tracker.start("hub_scores", {});
11702
+ const updated = await exportHubScores(cfg.getVaultIndex(), sd);
11703
+ ctx.lastHubScoreRebuildAt = Date.now();
11704
+ tracker.end({ updated: updated ?? 0 });
11705
+ stepsRun.push("hub_scores");
11706
+ }
11707
+ const cachedRecency = loadRecencyFromStateDb(sd ?? void 0);
11708
+ const recencyAge = cachedRecency ? now - (cachedRecency.lastUpdated ?? 0) : Infinity;
11709
+ if (recencyAge >= STEP_TTLS.recency) {
11710
+ tracker.start("recency", {});
11711
+ const entities = sd ? getAllEntitiesFromDb2(sd) : [];
11712
+ const entityInput = entities.map((e) => ({ name: e.name, path: e.path, aliases: e.aliases }));
11713
+ const recencyIndex2 = await buildRecencyIndex(cfg.vp, entityInput);
11714
+ saveRecencyToStateDb(recencyIndex2, sd ?? void 0);
11715
+ tracker.end({ entities: recencyIndex2.lastMentioned.size });
11716
+ stepsRun.push("recency");
11717
+ }
11718
+ const cooccurrenceAge = ctx.lastCooccurrenceRebuildAt > 0 ? now - ctx.lastCooccurrenceRebuildAt : Infinity;
11719
+ if (cooccurrenceAge >= STEP_TTLS.cooccurrence) {
11720
+ tracker.start("cooccurrence", {});
11721
+ const entities = sd ? getAllEntitiesFromDb2(sd) : [];
11722
+ const entityNames = entities.map((e) => e.name);
11723
+ const cooccurrenceIdx = await mineCooccurrences(cfg.vp, entityNames);
11724
+ setCooccurrenceIndex(cooccurrenceIdx);
11725
+ ctx.lastCooccurrenceRebuildAt = Date.now();
11726
+ ctx.cooccurrenceIndex = cooccurrenceIdx;
11727
+ if (sd) saveCooccurrenceToStateDb(sd, cooccurrenceIdx);
11728
+ tracker.end({ associations: cooccurrenceIdx._metadata.total_associations });
11729
+ stepsRun.push("cooccurrence");
11730
+ }
11731
+ const edgeWeightAge = ctx.lastEdgeWeightRebuildAt > 0 ? now - ctx.lastEdgeWeightRebuildAt : Infinity;
11732
+ if (sd && edgeWeightAge >= STEP_TTLS.edge_weights) {
11733
+ tracker.start("edge_weights", {});
11734
+ const result = recomputeEdgeWeights(sd);
11735
+ ctx.lastEdgeWeightRebuildAt = Date.now();
11736
+ tracker.end({ edges: result.edges_updated });
11737
+ stepsRun.push("edge_weights");
11738
+ }
11739
+ const configAge = lastConfigInferenceAt > 0 ? now - lastConfigInferenceAt : Infinity;
11740
+ if (sd && configAge >= STEP_TTLS.config_inference) {
11741
+ tracker.start("config_inference", {});
11742
+ const existing = loadConfig(sd);
11743
+ const inferred = inferConfig(cfg.getVaultIndex(), cfg.vp);
11744
+ saveConfig(sd, inferred, existing);
11745
+ cfg.updateFlywheelConfig(loadConfig(sd));
11746
+ lastConfigInferenceAt = Date.now();
11747
+ tracker.end({ inferred: true });
11748
+ stepsRun.push("config_inference");
11749
+ }
11750
+ if (sd && stepsRun.length > 0) {
11751
+ try {
11752
+ tracker.start("graph_snapshot", {});
11753
+ const graphMetrics = computeGraphMetrics(cfg.getVaultIndex());
11754
+ recordGraphSnapshot(sd, graphMetrics);
11755
+ tracker.end({ recorded: true });
11756
+ stepsRun.push("graph_snapshot");
11757
+ } catch (err) {
11758
+ tracker.end({ error: String(err) });
11759
+ }
11760
+ }
11761
+ if (sd && stepsRun.length > 0) {
11762
+ try {
11763
+ saveVaultIndexToCache(sd, cfg.getVaultIndex());
11764
+ ctx.lastIndexCacheSaveAt = Date.now();
11765
+ } catch {
11766
+ }
11767
+ }
11768
+ const duration = Date.now() - start;
11769
+ if (stepsRun.length > 0) {
11770
+ serverLog("maintenance", `Completed ${stepsRun.length} steps in ${duration}ms: ${stepsRun.join(", ")}`);
11771
+ if (sd) {
11772
+ recordIndexEvent(sd, {
11773
+ trigger: "maintenance",
11774
+ duration_ms: duration,
11775
+ note_count: cfg.getVaultIndex().notes.size,
11776
+ steps: tracker.steps
11777
+ });
11778
+ }
11779
+ } else {
11780
+ serverLog("maintenance", `All steps fresh, nothing to do (${duration}ms)`);
11781
+ }
11782
+ } catch (err) {
11783
+ const duration = Date.now() - start;
11784
+ serverLog("maintenance", `Failed after ${duration}ms: ${err instanceof Error ? err.message : err}`, "error");
11785
+ if (sd) {
11786
+ recordIndexEvent(sd, {
11787
+ trigger: "maintenance",
11788
+ duration_ms: duration,
11789
+ success: false,
11790
+ error: err instanceof Error ? err.message : String(err),
11791
+ steps: tracker.steps
11792
+ });
11793
+ }
11794
+ }
11795
+ scheduleNext(baseInterval);
11796
+ }
11797
+
11798
+ // src/core/shared/metrics.ts
11799
+ init_wikilinkFeedback();
11800
+ var ALL_METRICS = [
11801
+ "note_count",
11802
+ "link_count",
11803
+ "orphan_count",
11804
+ "tag_count",
11805
+ "entity_count",
11806
+ "avg_links_per_note",
11807
+ "link_density",
11808
+ "connected_ratio",
11809
+ "wikilink_accuracy",
11810
+ "wikilink_feedback_volume",
11811
+ "wikilink_suppressed_count"
11812
+ ];
11813
+ function computeMetrics(index, stateDb2) {
11814
+ const noteCount = index.notes.size;
11815
+ let linkCount = 0;
11816
+ for (const note of index.notes.values()) {
11817
+ linkCount += note.outlinks.length;
11818
+ }
11819
+ const connectedNotes = /* @__PURE__ */ new Set();
11820
+ for (const [notePath, note] of index.notes) {
11821
+ if (note.outlinks.length > 0) {
11822
+ connectedNotes.add(notePath);
11823
+ }
11824
+ }
11825
+ for (const [target, backlinks] of index.backlinks) {
11826
+ for (const bl of backlinks) {
11827
+ connectedNotes.add(bl.source);
11828
+ }
11263
11829
  const targetPath = index.entities.get(target);
11264
11830
  if (targetPath && index.notes.has(targetPath)) {
11265
11831
  connectedNotes.add(targetPath);
@@ -11508,8 +12074,8 @@ function getNoteAccessFrequency(stateDb2, daysBack = 30) {
11508
12074
  }
11509
12075
  }
11510
12076
  }
11511
- return Array.from(noteMap.entries()).map(([path39, stats]) => ({
11512
- path: path39,
12077
+ return Array.from(noteMap.entries()).map(([path40, stats]) => ({
12078
+ path: path40,
11513
12079
  access_count: stats.access_count,
11514
12080
  last_accessed: stats.last_accessed,
11515
12081
  tools_used: Array.from(stats.tools)
@@ -11569,237 +12135,50 @@ function getSessionHistory(stateDb2, sessionId) {
11569
12135
  function getSessionDetail(stateDb2, sessionId, options = {}) {
11570
12136
  const { include_children = true, limit = 50 } = options;
11571
12137
  const rows = include_children ? stateDb2.db.prepare(`
11572
- SELECT * FROM tool_invocations
11573
- WHERE session_id = ? OR session_id LIKE ?
11574
- ORDER BY timestamp
11575
- LIMIT ?
11576
- `).all(sessionId, `${sessionId}.%`, limit) : stateDb2.db.prepare(`
11577
- SELECT * FROM tool_invocations
11578
- WHERE session_id = ?
11579
- ORDER BY timestamp
11580
- LIMIT ?
11581
- `).all(sessionId, limit);
11582
- if (rows.length === 0) return null;
11583
- const tools = /* @__PURE__ */ new Set();
11584
- const notes = /* @__PURE__ */ new Set();
11585
- for (const row of rows) {
11586
- tools.add(row.tool_name);
11587
- if (row.note_paths) {
11588
- try {
11589
- for (const p of JSON.parse(row.note_paths)) notes.add(p);
11590
- } catch {
11591
- }
11592
- }
11593
- }
11594
- return {
11595
- summary: {
11596
- session_id: sessionId,
11597
- started_at: rows[0].timestamp,
11598
- last_activity: rows[rows.length - 1].timestamp,
11599
- tool_count: rows.length,
11600
- unique_tools: Array.from(tools),
11601
- notes_accessed: Array.from(notes)
11602
- },
11603
- invocations: rows.map(rowToInvocation)
11604
- };
11605
- }
11606
- function getRecentInvocations(stateDb2, limit = 20) {
11607
- const rows = stateDb2.db.prepare(
11608
- "SELECT * FROM tool_invocations ORDER BY timestamp DESC LIMIT ?"
11609
- ).all(limit);
11610
- return rows.map(rowToInvocation);
11611
- }
11612
- function purgeOldInvocations(stateDb2, retentionDays = 90) {
11613
- const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
11614
- const result = stateDb2.db.prepare(
11615
- "DELETE FROM tool_invocations WHERE timestamp < ?"
11616
- ).run(cutoff);
11617
- return result.changes;
11618
- }
11619
-
11620
- // src/core/shared/graphSnapshots.ts
11621
- function computeGraphMetrics(index) {
11622
- const noteCount = index.notes.size;
11623
- if (noteCount === 0) {
11624
- return {
11625
- avg_degree: 0,
11626
- max_degree: 0,
11627
- cluster_count: 0,
11628
- largest_cluster_size: 0,
11629
- hub_scores_top10: []
11630
- };
11631
- }
11632
- const degreeMap = /* @__PURE__ */ new Map();
11633
- const adjacency = /* @__PURE__ */ new Map();
11634
- for (const [notePath, note] of index.notes) {
11635
- if (!adjacency.has(notePath)) adjacency.set(notePath, /* @__PURE__ */ new Set());
11636
- let degree = note.outlinks.length;
11637
- for (const link of note.outlinks) {
11638
- const targetLower = link.target.toLowerCase();
11639
- const resolvedPath = index.entities.get(targetLower);
11640
- if (resolvedPath && index.notes.has(resolvedPath)) {
11641
- adjacency.get(notePath).add(resolvedPath);
11642
- if (!adjacency.has(resolvedPath)) adjacency.set(resolvedPath, /* @__PURE__ */ new Set());
11643
- adjacency.get(resolvedPath).add(notePath);
11644
- }
11645
- }
11646
- degreeMap.set(notePath, degree);
11647
- }
11648
- for (const [target, backlinks] of index.backlinks) {
11649
- const targetLower = target.toLowerCase();
11650
- const resolvedPath = index.entities.get(targetLower);
11651
- if (resolvedPath && degreeMap.has(resolvedPath)) {
11652
- degreeMap.set(resolvedPath, degreeMap.get(resolvedPath) + backlinks.length);
11653
- }
11654
- }
11655
- let totalDegree = 0;
11656
- let maxDegree = 0;
11657
- let maxDegreeNote = "";
11658
- for (const [notePath, degree] of degreeMap) {
11659
- totalDegree += degree;
11660
- if (degree > maxDegree) {
11661
- maxDegree = degree;
11662
- maxDegreeNote = notePath;
11663
- }
11664
- }
11665
- const avgDegree = noteCount > 0 ? Math.round(totalDegree / noteCount * 100) / 100 : 0;
11666
- const visited = /* @__PURE__ */ new Set();
11667
- const clusters = [];
11668
- for (const notePath of index.notes.keys()) {
11669
- if (visited.has(notePath)) continue;
11670
- const queue = [notePath];
11671
- visited.add(notePath);
11672
- let clusterSize = 0;
11673
- while (queue.length > 0) {
11674
- const current = queue.shift();
11675
- clusterSize++;
11676
- const neighbors = adjacency.get(current);
11677
- if (neighbors) {
11678
- for (const neighbor of neighbors) {
11679
- if (!visited.has(neighbor)) {
11680
- visited.add(neighbor);
11681
- queue.push(neighbor);
11682
- }
11683
- }
12138
+ SELECT * FROM tool_invocations
12139
+ WHERE session_id = ? OR session_id LIKE ?
12140
+ ORDER BY timestamp
12141
+ LIMIT ?
12142
+ `).all(sessionId, `${sessionId}.%`, limit) : stateDb2.db.prepare(`
12143
+ SELECT * FROM tool_invocations
12144
+ WHERE session_id = ?
12145
+ ORDER BY timestamp
12146
+ LIMIT ?
12147
+ `).all(sessionId, limit);
12148
+ if (rows.length === 0) return null;
12149
+ const tools = /* @__PURE__ */ new Set();
12150
+ const notes = /* @__PURE__ */ new Set();
12151
+ for (const row of rows) {
12152
+ tools.add(row.tool_name);
12153
+ if (row.note_paths) {
12154
+ try {
12155
+ for (const p of JSON.parse(row.note_paths)) notes.add(p);
12156
+ } catch {
11684
12157
  }
11685
12158
  }
11686
- clusters.push(clusterSize);
11687
12159
  }
11688
- const clusterCount = clusters.length;
11689
- const largestClusterSize = clusters.length > 0 ? Math.max(...clusters) : 0;
11690
- const sorted = Array.from(degreeMap.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10);
11691
- const hubScoresTop10 = sorted.map(([notePath, degree]) => {
11692
- const note = index.notes.get(notePath);
11693
- return {
11694
- entity: note?.title ?? notePath,
11695
- degree
11696
- };
11697
- });
11698
12160
  return {
11699
- avg_degree: avgDegree,
11700
- max_degree: maxDegree,
11701
- cluster_count: clusterCount,
11702
- largest_cluster_size: largestClusterSize,
11703
- hub_scores_top10: hubScoresTop10
12161
+ summary: {
12162
+ session_id: sessionId,
12163
+ started_at: rows[0].timestamp,
12164
+ last_activity: rows[rows.length - 1].timestamp,
12165
+ tool_count: rows.length,
12166
+ unique_tools: Array.from(tools),
12167
+ notes_accessed: Array.from(notes)
12168
+ },
12169
+ invocations: rows.map(rowToInvocation)
11704
12170
  };
11705
12171
  }
11706
- function recordGraphSnapshot(stateDb2, metrics) {
11707
- const timestamp = Date.now();
11708
- const insert = stateDb2.db.prepare(
11709
- "INSERT INTO graph_snapshots (timestamp, metric, value, details) VALUES (?, ?, ?, ?)"
11710
- );
11711
- const transaction = stateDb2.db.transaction(() => {
11712
- insert.run(timestamp, "avg_degree", metrics.avg_degree, null);
11713
- insert.run(timestamp, "max_degree", metrics.max_degree, null);
11714
- insert.run(timestamp, "cluster_count", metrics.cluster_count, null);
11715
- insert.run(timestamp, "largest_cluster_size", metrics.largest_cluster_size, null);
11716
- insert.run(
11717
- timestamp,
11718
- "hub_scores_top10",
11719
- metrics.hub_scores_top10.length,
11720
- JSON.stringify(metrics.hub_scores_top10)
11721
- );
11722
- });
11723
- transaction();
11724
- }
11725
- function getEmergingHubs(stateDb2, daysBack = 30) {
11726
- const cutoff = Date.now() - daysBack * 24 * 60 * 60 * 1e3;
11727
- const latestRow = stateDb2.db.prepare(
11728
- `SELECT details FROM graph_snapshots
11729
- WHERE metric = 'hub_scores_top10'
11730
- ORDER BY timestamp DESC LIMIT 1`
11731
- ).get();
11732
- const previousRow = stateDb2.db.prepare(
11733
- `SELECT details FROM graph_snapshots
11734
- WHERE metric = 'hub_scores_top10' AND timestamp >= ?
11735
- ORDER BY timestamp ASC LIMIT 1`
11736
- ).get(cutoff);
11737
- if (!latestRow?.details) return [];
11738
- const currentHubs = JSON.parse(latestRow.details);
11739
- const previousHubs = previousRow?.details ? JSON.parse(previousRow.details) : [];
11740
- const previousMap = /* @__PURE__ */ new Map();
11741
- for (const hub of previousHubs) {
11742
- previousMap.set(hub.entity, hub.degree);
11743
- }
11744
- const emerging = currentHubs.map((hub) => {
11745
- const prevDegree = previousMap.get(hub.entity) ?? 0;
11746
- return {
11747
- entity: hub.entity,
11748
- current_degree: hub.degree,
11749
- previous_degree: prevDegree,
11750
- growth: hub.degree - prevDegree
11751
- };
11752
- });
11753
- emerging.sort((a, b) => b.growth - a.growth);
11754
- return emerging;
11755
- }
11756
- function compareGraphSnapshots(stateDb2, timestampBefore, timestampAfter) {
11757
- const SCALAR_METRICS = ["avg_degree", "max_degree", "cluster_count", "largest_cluster_size"];
11758
- function getSnapshotAt(ts) {
11759
- const row = stateDb2.db.prepare(
11760
- `SELECT DISTINCT timestamp FROM graph_snapshots WHERE timestamp <= ? ORDER BY timestamp DESC LIMIT 1`
11761
- ).get(ts);
11762
- if (!row) return null;
11763
- const rows = stateDb2.db.prepare(
11764
- `SELECT metric, value, details FROM graph_snapshots WHERE timestamp = ?`
11765
- ).all(row.timestamp);
11766
- return rows;
11767
- }
11768
- const beforeRows = getSnapshotAt(timestampBefore) ?? [];
11769
- const afterRows = getSnapshotAt(timestampAfter) ?? [];
11770
- const beforeMap = /* @__PURE__ */ new Map();
11771
- const afterMap = /* @__PURE__ */ new Map();
11772
- for (const r of beforeRows) beforeMap.set(r.metric, { value: r.value, details: r.details });
11773
- for (const r of afterRows) afterMap.set(r.metric, { value: r.value, details: r.details });
11774
- const metricChanges = SCALAR_METRICS.map((metric) => {
11775
- const before = beforeMap.get(metric)?.value ?? 0;
11776
- const after = afterMap.get(metric)?.value ?? 0;
11777
- const delta = after - before;
11778
- const deltaPercent = before !== 0 ? Math.round(delta / before * 1e4) / 100 : delta !== 0 ? 100 : 0;
11779
- return { metric, before, after, delta, deltaPercent };
11780
- });
11781
- const beforeHubs = beforeMap.get("hub_scores_top10")?.details ? JSON.parse(beforeMap.get("hub_scores_top10").details) : [];
11782
- const afterHubs = afterMap.get("hub_scores_top10")?.details ? JSON.parse(afterMap.get("hub_scores_top10").details) : [];
11783
- const beforeHubMap = /* @__PURE__ */ new Map();
11784
- for (const h of beforeHubs) beforeHubMap.set(h.entity, h.degree);
11785
- const afterHubMap = /* @__PURE__ */ new Map();
11786
- for (const h of afterHubs) afterHubMap.set(h.entity, h.degree);
11787
- const allHubEntities = /* @__PURE__ */ new Set([...beforeHubMap.keys(), ...afterHubMap.keys()]);
11788
- const hubScoreChanges = [];
11789
- for (const entity of allHubEntities) {
11790
- const before = beforeHubMap.get(entity) ?? 0;
11791
- const after = afterHubMap.get(entity) ?? 0;
11792
- if (before !== after) {
11793
- hubScoreChanges.push({ entity, before, after, delta: after - before });
11794
- }
11795
- }
11796
- hubScoreChanges.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
11797
- return { metricChanges, hubScoreChanges };
12172
+ function getRecentInvocations(stateDb2, limit = 20) {
12173
+ const rows = stateDb2.db.prepare(
12174
+ "SELECT * FROM tool_invocations ORDER BY timestamp DESC LIMIT ?"
12175
+ ).all(limit);
12176
+ return rows.map(rowToInvocation);
11798
12177
  }
11799
- function purgeOldSnapshots(stateDb2, retentionDays = 90) {
12178
+ function purgeOldInvocations(stateDb2, retentionDays = 90) {
11800
12179
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
11801
12180
  const result = stateDb2.db.prepare(
11802
- "DELETE FROM graph_snapshots WHERE timestamp < ?"
12181
+ "DELETE FROM tool_invocations WHERE timestamp < ?"
11803
12182
  ).run(cutoff);
11804
12183
  return result.changes;
11805
12184
  }
@@ -11811,7 +12190,7 @@ init_recency();
11811
12190
  init_prospects();
11812
12191
  init_cooccurrence();
11813
12192
  init_retrievalCooccurrence();
11814
- import * as fs34 from "node:fs/promises";
12193
+ import * as fs35 from "node:fs/promises";
11815
12194
  import { createHash as createHash4 } from "node:crypto";
11816
12195
 
11817
12196
  // src/vault-registry.ts
@@ -12042,7 +12421,6 @@ var TOOL_CATEGORY = {
12042
12421
  get_common_neighbors: "graph",
12043
12422
  get_backlinks: "graph",
12044
12423
  get_forward_links: "graph",
12045
- get_weighted_links: "graph",
12046
12424
  get_strong_connections: "graph",
12047
12425
  // schema (7 tools) -- schema intelligence + migrations
12048
12426
  vault_schema: "schema",
@@ -12130,7 +12508,6 @@ var TOOL_TIER = {
12130
12508
  get_common_neighbors: 2,
12131
12509
  get_backlinks: 2,
12132
12510
  get_forward_links: 2,
12133
- get_weighted_links: 2,
12134
12511
  get_strong_connections: 2,
12135
12512
  suggest_wikilinks: 2,
12136
12513
  validate_links: 2,
@@ -12385,10 +12762,10 @@ Use "flywheel_config" to inspect runtime configuration and set "tool_tier_overri
12385
12762
  }
12386
12763
 
12387
12764
  // src/tool-registry.ts
12388
- import * as path37 from "path";
12765
+ import * as path38 from "path";
12389
12766
  import { dirname as dirname5, join as join18 } from "path";
12390
12767
  import { statSync as statSync6, readFileSync as readFileSync5 } from "fs";
12391
- import { fileURLToPath } from "url";
12768
+ import { fileURLToPath as fileURLToPath2 } from "url";
12392
12769
  import { z as z39 } from "zod";
12393
12770
  import { CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
12394
12771
  import { getSessionId } from "@velvetmonkey/vault-core";
@@ -12684,8 +13061,8 @@ var DEFAULT_CONFIG2 = {
12684
13061
  maxOutlinksPerHop: 10,
12685
13062
  maxBackfill: 10
12686
13063
  };
12687
- function multiHopBackfill(primaryResults, index, stateDb2, config = {}) {
12688
- const cfg = { ...DEFAULT_CONFIG2, ...config };
13064
+ function multiHopBackfill(primaryResults, index, stateDb2, config2 = {}) {
13065
+ const cfg = { ...DEFAULT_CONFIG2, ...config2 };
12689
13066
  const seen = new Set(primaryResults.map((r) => r.path).filter(Boolean));
12690
13067
  const candidates = [];
12691
13068
  const hop1Results = [];
@@ -12737,13 +13114,13 @@ function multiHopBackfill(primaryResults, index, stateDb2, config = {}) {
12737
13114
  candidates.sort((a, b) => b.score - a.score);
12738
13115
  return candidates.slice(0, cfg.maxBackfill).map((c) => c.result);
12739
13116
  }
12740
- function scoreCandidate(path39, index, stateDb2) {
12741
- const note = index.notes.get(path39);
13117
+ function scoreCandidate(path40, index, stateDb2) {
13118
+ const note = index.notes.get(path40);
12742
13119
  const decay = recencyDecay(note?.modified);
12743
13120
  let hubScore = 1;
12744
13121
  if (stateDb2) {
12745
13122
  try {
12746
- const title = note?.title ?? path39.replace(/\.md$/, "").split("/").pop() ?? "";
13123
+ const title = note?.title ?? path40.replace(/\.md$/, "").split("/").pop() ?? "";
12747
13124
  const entity = getEntityByName3(stateDb2, title);
12748
13125
  if (entity) hubScore = entity.hubScore ?? 1;
12749
13126
  } catch {
@@ -13203,11 +13580,11 @@ function applyEntityBridging(results, stateDb2, maxBridgesPerResult = 5) {
13203
13580
  const linkMap = /* @__PURE__ */ new Map();
13204
13581
  try {
13205
13582
  const paths = results.map((r) => r.path).filter(Boolean);
13206
- for (const path39 of paths) {
13583
+ for (const path40 of paths) {
13207
13584
  const rows = stateDb2.db.prepare(
13208
13585
  "SELECT target FROM note_links WHERE note_path = ?"
13209
- ).all(path39);
13210
- linkMap.set(path39, new Set(rows.map((r) => r.target)));
13586
+ ).all(path40);
13587
+ linkMap.set(path40, new Set(rows.map((r) => r.target)));
13211
13588
  }
13212
13589
  } catch {
13213
13590
  return;
@@ -14347,50 +14724,6 @@ function registerGraphTools(server2, getIndex, getVaultPath, getStateDb4) {
14347
14724
  };
14348
14725
  }
14349
14726
  );
14350
- server2.tool(
14351
- "get_weighted_links",
14352
- "Use when ranking outgoing links from a note by relationship strength. Produces weighted link entries reflecting edge survival, co-session access, and source activity. Returns ranked outgoing links with weight scores. Does not include incoming links \u2014 use get_strong_connections for bidirectional.",
14353
- {
14354
- path: z2.string().describe('Path to the note (e.g., "daily/2026-02-24.md")'),
14355
- min_weight: z2.number().default(1).describe("Minimum weight threshold (default 1.0)"),
14356
- limit: z2.number().default(20).describe("Maximum number of results to return")
14357
- },
14358
- async ({ path: notePath, min_weight, limit: requestedLimit }) => {
14359
- const stateDb2 = getStateDb4?.();
14360
- if (!stateDb2) {
14361
- return { content: [{ type: "text", text: JSON.stringify({ error: "StateDb not initialized" }) }] };
14362
- }
14363
- const limit = Math.min(requestedLimit ?? 20, MAX_LIMIT);
14364
- const now = Date.now();
14365
- const rows = stateDb2.db.prepare(`
14366
- SELECT target, weight, weight_updated_at
14367
- FROM note_links
14368
- WHERE note_path = ?
14369
- ORDER BY weight DESC
14370
- `).all(notePath);
14371
- const results = rows.map((row) => {
14372
- const daysSinceUpdated = row.weight_updated_at ? (now - row.weight_updated_at) / (1e3 * 60 * 60 * 24) : 0;
14373
- const decayFactor = Math.max(0.1, 1 - daysSinceUpdated / 180);
14374
- const effectiveWeight = Math.round(row.weight * decayFactor * 1e3) / 1e3;
14375
- return {
14376
- target: row.target,
14377
- weight: row.weight,
14378
- weight_effective: effectiveWeight,
14379
- last_updated: row.weight_updated_at
14380
- };
14381
- }).filter((r) => r.weight_effective >= min_weight).slice(0, limit);
14382
- return {
14383
- content: [{
14384
- type: "text",
14385
- text: JSON.stringify({
14386
- note: notePath,
14387
- count: results.length,
14388
- links: results
14389
- }, null, 2)
14390
- }]
14391
- };
14392
- }
14393
- );
14394
14727
  server2.tool(
14395
14728
  "get_strong_connections",
14396
14729
  "Use when finding the most important relationships for a note in both directions. Produces bidirectional connections ranked by combined edge weight. Returns both incoming and outgoing links sorted by strength. Does not compute path distances \u2014 use get_link_path for shortest paths.",
@@ -14729,16 +15062,16 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb4 = ()
14729
15062
  const weightedStats = getWeightedEntityStats(stateDb3);
14730
15063
  const statsMap = new Map(weightedStats.map((s) => [s.entity.toLowerCase(), s]));
14731
15064
  for (const suggestion of scored.detailed) {
14732
- const stat5 = statsMap.get(suggestion.entity.toLowerCase());
14733
- if (stat5) {
15065
+ const stat4 = statsMap.get(suggestion.entity.toLowerCase());
15066
+ if (stat4) {
14734
15067
  const effectiveAlpha = isAiConfigEntity(suggestion.entity) ? AI_CONFIG_PRIOR_ALPHA : PRIOR_ALPHA;
14735
- const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp, effectiveAlpha);
14736
- const totalObs = effectiveAlpha + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
15068
+ const posteriorMean = computePosteriorMean(stat4.weightedCorrect, stat4.weightedFp, effectiveAlpha);
15069
+ const totalObs = effectiveAlpha + stat4.weightedCorrect + PRIOR_BETA + stat4.weightedFp;
14737
15070
  suggestion.suppressionContext = {
14738
15071
  posteriorMean: Math.round(posteriorMean * 1e3) / 1e3,
14739
15072
  totalObservations: Math.round(totalObs * 10) / 10,
14740
15073
  isSuppressed: totalObs >= SUPPRESSION_MIN_OBSERVATIONS && posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD,
14741
- falsePositiveRate: Math.round(stat5.weightedFpRate * 1e3) / 1e3
15074
+ falsePositiveRate: Math.round(stat4.weightedFpRate * 1e3) / 1e3
14742
15075
  };
14743
15076
  }
14744
15077
  }
@@ -14783,14 +15116,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb4 = ()
14783
15116
  };
14784
15117
  function findSimilarEntity2(target, entities) {
14785
15118
  const targetLower = target.toLowerCase();
14786
- for (const [name, path39] of entities) {
15119
+ for (const [name, path40] of entities) {
14787
15120
  if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
14788
- return path39;
15121
+ return path40;
14789
15122
  }
14790
15123
  }
14791
- for (const [name, path39] of entities) {
15124
+ for (const [name, path40] of entities) {
14792
15125
  if (name.includes(targetLower) || targetLower.includes(name)) {
14793
- return path39;
15126
+ return path40;
14794
15127
  }
14795
15128
  }
14796
15129
  return void 0;
@@ -15501,7 +15834,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15501
15834
  }
15502
15835
  const indexBuilt = indexState2 === "ready" && index !== void 0 && index.notes !== void 0;
15503
15836
  let lastIndexActivityAt;
15504
- let lastFullRebuildAt;
15837
+ let lastFullRebuildAt2;
15505
15838
  let lastWatcherBatchAt;
15506
15839
  let lastBuild;
15507
15840
  let lastManual;
@@ -15511,13 +15844,13 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15511
15844
  if (lastAny) lastIndexActivityAt = lastAny.timestamp;
15512
15845
  lastBuild = getLastEventByTrigger(stateDb2, "startup_build") ?? void 0;
15513
15846
  lastManual = getLastEventByTrigger(stateDb2, "manual_refresh") ?? void 0;
15514
- lastFullRebuildAt = Math.max(lastBuild?.timestamp ?? 0, lastManual?.timestamp ?? 0) || void 0;
15847
+ lastFullRebuildAt2 = Math.max(lastBuild?.timestamp ?? 0, lastManual?.timestamp ?? 0) || void 0;
15515
15848
  const lastWatcher = getLastEventByTrigger(stateDb2, "watcher");
15516
15849
  if (lastWatcher) lastWatcherBatchAt = lastWatcher.timestamp;
15517
15850
  } catch {
15518
15851
  }
15519
15852
  }
15520
- const freshnessTimestamp = lastFullRebuildAt ?? (indexBuilt && index.builtAt ? index.builtAt.getTime() : void 0);
15853
+ const freshnessTimestamp = lastFullRebuildAt2 ?? (indexBuilt && index.builtAt ? index.builtAt.getTime() : void 0);
15521
15854
  const indexAge = freshnessTimestamp ? Math.floor((Date.now() - freshnessTimestamp) / 1e3) : -1;
15522
15855
  const indexStale = indexBuilt && indexAge > STALE_THRESHOLD_SECONDS;
15523
15856
  if (indexState2 === "building") {
@@ -15564,8 +15897,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15564
15897
  }
15565
15898
  let configInfo;
15566
15899
  if (isFull) {
15567
- const config = getConfig2();
15568
- configInfo = Object.keys(config).length > 0 ? config : void 0;
15900
+ const config2 = getConfig2();
15901
+ configInfo = Object.keys(config2).length > 0 ? config2 : void 0;
15569
15902
  }
15570
15903
  let lastRebuild;
15571
15904
  if (stateDb2) {
@@ -15694,15 +16027,15 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15694
16027
  watcher_pending: getWatcherStatus2()?.pendingEvents,
15695
16028
  last_index_activity_at: lastIndexActivityAt,
15696
16029
  last_index_activity_ago_seconds: lastIndexActivityAt ? Math.floor((Date.now() - lastIndexActivityAt) / 1e3) : void 0,
15697
- last_full_rebuild_at: lastFullRebuildAt,
16030
+ last_full_rebuild_at: lastFullRebuildAt2,
15698
16031
  last_watcher_batch_at: lastWatcherBatchAt,
15699
16032
  pipeline_activity: pipelineActivity,
15700
16033
  dead_link_count: isFull ? deadLinkCount : void 0,
15701
16034
  top_dead_link_targets: isFull ? topDeadLinkTargets : void 0,
15702
16035
  sweep: isFull ? getSweepResults() ?? void 0 : void 0,
15703
16036
  proactive_linking: isFull && stateDb2 ? (() => {
15704
- const config = getConfig2();
15705
- const enabled = config.proactive_linking !== false;
16037
+ const config2 = getConfig2();
16038
+ const enabled = config2.proactive_linking !== false;
15706
16039
  const queuePending = stateDb2.db.prepare(
15707
16040
  `SELECT COUNT(*) as cnt FROM proactive_queue WHERE status = 'pending'`
15708
16041
  ).get();
@@ -15810,8 +16143,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15810
16143
  daily_counts: z4.record(z4.number())
15811
16144
  }).describe("Activity summary for the last 7 days")
15812
16145
  };
15813
- function isPeriodicNote3(path39) {
15814
- const filename = path39.split("/").pop() || "";
16146
+ function isPeriodicNote3(path40) {
16147
+ const filename = path40.split("/").pop() || "";
15815
16148
  const nameWithoutExt = filename.replace(/\.md$/, "");
15816
16149
  const patterns = [
15817
16150
  /^\d{4}-\d{2}-\d{2}$/,
@@ -15826,7 +16159,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15826
16159
  // YYYY (yearly)
15827
16160
  ];
15828
16161
  const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
15829
- const folder = path39.split("/")[0]?.toLowerCase() || "";
16162
+ const folder = path40.split("/")[0]?.toLowerCase() || "";
15830
16163
  return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
15831
16164
  }
15832
16165
  async function runVaultStats() {
@@ -16348,7 +16681,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
16348
16681
 
16349
16682
  // src/tools/read/system.ts
16350
16683
  import { z as z5 } from "zod";
16351
- import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2, getAllEntitiesFromDb as getAllEntitiesFromDb2 } from "@velvetmonkey/vault-core";
16684
+ import { scanVaultEntities as scanVaultEntities3, getEntityIndexFromDb as getEntityIndexFromDb2, getAllEntitiesFromDb as getAllEntitiesFromDb3 } from "@velvetmonkey/vault-core";
16352
16685
 
16353
16686
  // src/core/read/aliasSuggestions.ts
16354
16687
  import { STOPWORDS_EN as STOPWORDS_EN3 } from "@velvetmonkey/vault-core";
@@ -16465,11 +16798,11 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
16465
16798
  if (stateDb2) {
16466
16799
  tracker.start("entity_sync", {});
16467
16800
  try {
16468
- const config = loadConfig(stateDb2);
16469
- const excludeFolders = config.exclude_entity_folders?.length ? config.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
16470
- const entityIndex2 = await scanVaultEntities2(vaultPath2, {
16801
+ const config2 = loadConfig(stateDb2);
16802
+ const excludeFolders = config2.exclude_entity_folders?.length ? config2.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
16803
+ const entityIndex2 = await scanVaultEntities3(vaultPath2, {
16471
16804
  excludeFolders,
16472
- customCategories: config.custom_categories
16805
+ customCategories: config2.custom_categories
16473
16806
  });
16474
16807
  stateDb2.replaceAllEntities(entityIndex2);
16475
16808
  tracker.end({ entities: entityIndex2._metadata.total_entities });
@@ -16591,7 +16924,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
16591
16924
  if (stateDb2) {
16592
16925
  tracker.start("recency", {});
16593
16926
  try {
16594
- const entities = getAllEntitiesFromDb2(stateDb2).map((e) => ({
16927
+ const entities = getAllEntitiesFromDb3(stateDb2).map((e) => ({
16595
16928
  name: e.name,
16596
16929
  path: e.path,
16597
16930
  aliases: e.aliases
@@ -16610,7 +16943,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
16610
16943
  if (stateDb2) {
16611
16944
  tracker.start("cooccurrence", {});
16612
16945
  try {
16613
- const entityNames = getAllEntitiesFromDb2(stateDb2).map((e) => e.name);
16946
+ const entityNames = getAllEntitiesFromDb3(stateDb2).map((e) => e.name);
16614
16947
  const cooccurrenceIdx = await mineCooccurrences(vaultPath2, entityNames);
16615
16948
  setCooccurrenceIndex(cooccurrenceIdx);
16616
16949
  saveCooccurrenceToStateDb(stateDb2, cooccurrenceIdx);
@@ -16655,7 +16988,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
16655
16988
  if (stateDb2 && hasEntityEmbeddingsIndex()) {
16656
16989
  tracker.start("entity_embeddings", {});
16657
16990
  try {
16658
- const entities = getAllEntitiesFromDb2(stateDb2);
16991
+ const entities = getAllEntitiesFromDb3(stateDb2);
16659
16992
  if (entities.length > 0) {
16660
16993
  const entityMap = new Map(entities.map((e) => [e.name, {
16661
16994
  name: e.name,
@@ -16933,13 +17266,13 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
16933
17266
  max_content_chars: z6.number().default(2e4).describe("Max total chars of section content to include. Sections are truncated at paragraph boundaries.")
16934
17267
  }
16935
17268
  },
16936
- async ({ path: path39, include_content, max_content_chars }) => {
17269
+ async ({ path: path40, include_content, max_content_chars }) => {
16937
17270
  const index = getIndex();
16938
17271
  const vaultPath2 = getVaultPath();
16939
- const result = await getNoteStructure(index, path39, vaultPath2);
17272
+ const result = await getNoteStructure(index, path40, vaultPath2);
16940
17273
  if (!result) {
16941
17274
  return {
16942
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path39 }, null, 2) }]
17275
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path40 }, null, 2) }]
16943
17276
  };
16944
17277
  }
16945
17278
  let totalChars = 0;
@@ -16950,7 +17283,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
16950
17283
  truncated = true;
16951
17284
  break;
16952
17285
  }
16953
- const sectionResult = await getSectionContent(index, path39, section.heading.text, vaultPath2, true);
17286
+ const sectionResult = await getSectionContent(index, path40, section.heading.text, vaultPath2, true);
16954
17287
  if (sectionResult) {
16955
17288
  let content = sectionResult.content;
16956
17289
  const remaining = max_content_chars - totalChars;
@@ -16965,13 +17298,13 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
16965
17298
  }
16966
17299
  }
16967
17300
  }
16968
- const note = index.notes.get(path39);
17301
+ const note = index.notes.get(path40);
16969
17302
  const enriched = { ...result };
16970
17303
  if (note) {
16971
17304
  enriched.frontmatter = note.frontmatter;
16972
17305
  enriched.tags = note.tags;
16973
17306
  enriched.aliases = note.aliases;
16974
- const normalizedPath = path39.toLowerCase().replace(/\.md$/, "");
17307
+ const normalizedPath = path40.toLowerCase().replace(/\.md$/, "");
16975
17308
  const backlinks = index.backlinks.get(normalizedPath) || [];
16976
17309
  enriched.backlink_count = backlinks.length;
16977
17310
  enriched.outlink_count = note.outlinks.length;
@@ -17009,15 +17342,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17009
17342
  max_content_chars: z6.number().default(1e4).describe("Max chars of section content. Truncated at paragraph boundaries.")
17010
17343
  }
17011
17344
  },
17012
- async ({ path: path39, heading, include_subheadings, max_content_chars }) => {
17345
+ async ({ path: path40, heading, include_subheadings, max_content_chars }) => {
17013
17346
  const index = getIndex();
17014
17347
  const vaultPath2 = getVaultPath();
17015
- const result = await getSectionContent(index, path39, heading, vaultPath2, include_subheadings);
17348
+ const result = await getSectionContent(index, path40, heading, vaultPath2, include_subheadings);
17016
17349
  if (!result) {
17017
17350
  return {
17018
17351
  content: [{ type: "text", text: JSON.stringify({
17019
17352
  error: "Section not found",
17020
- path: path39,
17353
+ path: path40,
17021
17354
  heading
17022
17355
  }, null, 2) }]
17023
17356
  };
@@ -17078,16 +17411,16 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17078
17411
  offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
17079
17412
  }
17080
17413
  },
17081
- async ({ path: path39, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
17414
+ async ({ path: path40, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
17082
17415
  const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
17083
17416
  const index = getIndex();
17084
17417
  const vaultPath2 = getVaultPath();
17085
- const config = getConfig2();
17086
- if (path39) {
17087
- const result2 = await getTasksFromNote(index, path39, vaultPath2, getExcludeTags(config));
17418
+ const config2 = getConfig2();
17419
+ if (path40) {
17420
+ const result2 = await getTasksFromNote(index, path40, vaultPath2, getExcludeTags(config2));
17088
17421
  if (!result2) {
17089
17422
  return {
17090
- content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path39 }, null, 2) }]
17423
+ content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path40 }, null, 2) }]
17091
17424
  };
17092
17425
  }
17093
17426
  let filtered = result2;
@@ -17097,7 +17430,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17097
17430
  const paged2 = filtered.slice(offset, offset + limit);
17098
17431
  return {
17099
17432
  content: [{ type: "text", text: JSON.stringify({
17100
- path: path39,
17433
+ path: path40,
17101
17434
  total_count: filtered.length,
17102
17435
  returned_count: paged2.length,
17103
17436
  open: result2.filter((t) => t.status === "open").length,
@@ -17107,12 +17440,12 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17107
17440
  };
17108
17441
  }
17109
17442
  if (isTaskCacheReady()) {
17110
- refreshIfStale(vaultPath2, index, getExcludeTags(config));
17443
+ refreshIfStale(vaultPath2, index, getExcludeTags(config2));
17111
17444
  if (has_due_date) {
17112
17445
  const result3 = queryTasksFromCache({
17113
17446
  status,
17114
17447
  folder,
17115
- excludeTags: getExcludeTags(config),
17448
+ excludeTags: getExcludeTags(config2),
17116
17449
  has_due_date: true,
17117
17450
  limit,
17118
17451
  offset
@@ -17129,7 +17462,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17129
17462
  status,
17130
17463
  folder,
17131
17464
  tag,
17132
- excludeTags: getExcludeTags(config),
17465
+ excludeTags: getExcludeTags(config2),
17133
17466
  limit,
17134
17467
  offset
17135
17468
  });
@@ -17148,7 +17481,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17148
17481
  const allResults = await getTasksWithDueDates(index, vaultPath2, {
17149
17482
  status,
17150
17483
  folder,
17151
- excludeTags: getExcludeTags(config)
17484
+ excludeTags: getExcludeTags(config2)
17152
17485
  });
17153
17486
  const paged2 = allResults.slice(offset, offset + limit);
17154
17487
  return {
@@ -17164,7 +17497,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17164
17497
  folder,
17165
17498
  tag,
17166
17499
  limit: limit + offset,
17167
- excludeTags: getExcludeTags(config)
17500
+ excludeTags: getExcludeTags(config2)
17168
17501
  });
17169
17502
  const paged = result.tasks.slice(offset, offset + limit);
17170
17503
  return {
@@ -17831,10 +18164,10 @@ function isTemplatePath(notePath) {
17831
18164
  const folder = notePath.split("/")[0]?.toLowerCase() || "";
17832
18165
  return folder === "templates" || folder === "template";
17833
18166
  }
17834
- function getExcludedPaths(index, config) {
18167
+ function getExcludedPaths(index, config2) {
17835
18168
  const excluded = /* @__PURE__ */ new Set();
17836
- const excludeTags = new Set(getExcludeTags(config).map((t) => t.toLowerCase()));
17837
- const excludeEntities = new Set(getExcludeEntities(config).map((e) => e.toLowerCase()));
18169
+ const excludeTags = new Set(getExcludeTags(config2).map((t) => t.toLowerCase()));
18170
+ const excludeEntities = new Set(getExcludeEntities(config2).map((e) => e.toLowerCase()));
17838
18171
  if (excludeTags.size === 0 && excludeEntities.size === 0) return excluded;
17839
18172
  for (const note of index.notes.values()) {
17840
18173
  if (excludeTags.size > 0) {
@@ -17881,8 +18214,8 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb4
17881
18214
  requireIndex();
17882
18215
  const limit = Math.min(requestedLimit ?? 50, MAX_LIMIT);
17883
18216
  const index = getIndex();
17884
- const config = getConfig2?.() ?? {};
17885
- const excludedPaths = getExcludedPaths(index, config);
18217
+ const config2 = getConfig2?.() ?? {};
18218
+ const excludedPaths = getExcludedPaths(index, config2);
17886
18219
  switch (analysis) {
17887
18220
  case "orphans": {
17888
18221
  const allOrphans = findOrphanNotes(index, folder).filter((o) => !isPeriodicNote(o.path) && !excludedPaths.has(o.path));
@@ -18050,7 +18383,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb4
18050
18383
  return !note || !excludedPaths.has(note.path);
18051
18384
  });
18052
18385
  }
18053
- const excludeEntities = new Set(getExcludeEntities(config).map((e) => e.toLowerCase()));
18386
+ const excludeEntities = new Set(getExcludeEntities(config2).map((e) => e.toLowerCase()));
18054
18387
  if (excludeEntities.size > 0) {
18055
18388
  hubs = hubs.filter((hub) => !excludeEntities.has(hub.entity.toLowerCase()));
18056
18389
  }
@@ -19079,8 +19412,8 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
19079
19412
  // src/tools/write/mutations.ts
19080
19413
  init_writer();
19081
19414
  import { z as z12 } from "zod";
19082
- import fs23 from "fs/promises";
19083
- import path25 from "path";
19415
+ import fs24 from "fs/promises";
19416
+ import path26 from "path";
19084
19417
 
19085
19418
  // src/core/write/validator.ts
19086
19419
  var TIMESTAMP_PATTERN = /^\*\*\d{2}:\d{2}\*\*/;
@@ -19298,63 +19631,63 @@ init_constants2();
19298
19631
  init_writer();
19299
19632
  init_wikilinks();
19300
19633
  init_wikilinkFeedback();
19301
- import fs22 from "fs/promises";
19302
- import path24 from "path";
19634
+ import fs23 from "fs/promises";
19635
+ import path25 from "path";
19303
19636
 
19304
19637
  // src/core/write/policy/policyPaths.ts
19305
- import fs21 from "fs/promises";
19306
- import path23 from "path";
19638
+ import fs22 from "fs/promises";
19639
+ import path24 from "path";
19307
19640
  function getPoliciesDir(vaultPath2) {
19308
- return path23.join(vaultPath2, ".flywheel", "policies");
19641
+ return path24.join(vaultPath2, ".flywheel", "policies");
19309
19642
  }
19310
19643
  function getLegacyPoliciesDir(vaultPath2) {
19311
- return path23.join(vaultPath2, ".claude", "policies");
19644
+ return path24.join(vaultPath2, ".claude", "policies");
19312
19645
  }
19313
19646
  async function ensurePoliciesDir(vaultPath2) {
19314
19647
  const dir = getPoliciesDir(vaultPath2);
19315
- await fs21.mkdir(dir, { recursive: true });
19648
+ await fs22.mkdir(dir, { recursive: true });
19316
19649
  }
19317
19650
  async function migratePoliciesIfNeeded(vaultPath2) {
19318
19651
  const legacyDir = getLegacyPoliciesDir(vaultPath2);
19319
19652
  let files;
19320
19653
  try {
19321
- files = await fs21.readdir(legacyDir);
19654
+ files = await fs22.readdir(legacyDir);
19322
19655
  } catch {
19323
19656
  return;
19324
19657
  }
19325
19658
  const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
19326
19659
  if (yamlFiles.length === 0) {
19327
19660
  await tryRmdir(legacyDir);
19328
- await tryRmdir(path23.join(vaultPath2, ".claude"));
19661
+ await tryRmdir(path24.join(vaultPath2, ".claude"));
19329
19662
  return;
19330
19663
  }
19331
19664
  await ensurePoliciesDir(vaultPath2);
19332
19665
  const destDir = getPoliciesDir(vaultPath2);
19333
19666
  for (const file of yamlFiles) {
19334
- const src = path23.join(legacyDir, file);
19335
- const dest = path23.join(destDir, file);
19667
+ const src = path24.join(legacyDir, file);
19668
+ const dest = path24.join(destDir, file);
19336
19669
  try {
19337
- await fs21.access(dest);
19670
+ await fs22.access(dest);
19338
19671
  } catch {
19339
- await fs21.copyFile(src, dest);
19672
+ await fs22.copyFile(src, dest);
19340
19673
  }
19341
- await fs21.unlink(src);
19674
+ await fs22.unlink(src);
19342
19675
  }
19343
19676
  await tryRmdir(legacyDir);
19344
- await tryRmdir(path23.join(vaultPath2, ".claude"));
19677
+ await tryRmdir(path24.join(vaultPath2, ".claude"));
19345
19678
  }
19346
19679
  async function tryRmdir(dir) {
19347
19680
  try {
19348
- const remaining = await fs21.readdir(dir);
19681
+ const remaining = await fs22.readdir(dir);
19349
19682
  if (remaining.length === 0) {
19350
- await fs21.rmdir(dir);
19683
+ await fs22.rmdir(dir);
19351
19684
  }
19352
19685
  } catch {
19353
19686
  }
19354
19687
  }
19355
19688
  var migrationCache = /* @__PURE__ */ new Map();
19356
19689
  async function ensureMigrated(vaultPath2) {
19357
- const key = path23.resolve(vaultPath2);
19690
+ const key = path24.resolve(vaultPath2);
19358
19691
  if (!migrationCache.has(key)) {
19359
19692
  migrationCache.set(key, migratePoliciesIfNeeded(vaultPath2));
19360
19693
  }
@@ -19410,7 +19743,7 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
19410
19743
  async function getPolicyHint(vaultPath2) {
19411
19744
  try {
19412
19745
  const policiesDir = getPoliciesDir(vaultPath2);
19413
- const files = await fs22.readdir(policiesDir);
19746
+ const files = await fs23.readdir(policiesDir);
19414
19747
  const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
19415
19748
  if (yamlFiles.length > 0) {
19416
19749
  const names = yamlFiles.map((f) => f.replace(/\.ya?ml$/, "")).join(", ");
@@ -19421,9 +19754,9 @@ async function getPolicyHint(vaultPath2) {
19421
19754
  return "";
19422
19755
  }
19423
19756
  async function ensureFileExists(vaultPath2, notePath) {
19424
- const fullPath = path24.join(vaultPath2, notePath);
19757
+ const fullPath = path25.join(vaultPath2, notePath);
19425
19758
  try {
19426
- await fs22.access(fullPath);
19759
+ await fs23.access(fullPath);
19427
19760
  return null;
19428
19761
  } catch {
19429
19762
  const hint = await getPolicyHint(vaultPath2);
@@ -19626,17 +19959,17 @@ async function executeCreateNote(options) {
19626
19959
  if (!pathCheck.valid) {
19627
19960
  return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
19628
19961
  }
19629
- const fullPath = path24.join(vaultPath2, notePath);
19962
+ const fullPath = path25.join(vaultPath2, notePath);
19630
19963
  let fileExists = false;
19631
19964
  try {
19632
- await fs22.access(fullPath);
19965
+ await fs23.access(fullPath);
19633
19966
  fileExists = true;
19634
19967
  } catch {
19635
19968
  }
19636
19969
  if (fileExists && !overwrite) {
19637
19970
  return { success: false, result: errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`), filesWritten: [] };
19638
19971
  }
19639
- await fs22.mkdir(path24.dirname(fullPath), { recursive: true });
19972
+ await fs23.mkdir(path25.dirname(fullPath), { recursive: true });
19640
19973
  const { maybeApplyWikilinks: maybeApplyWikilinks2 } = await Promise.resolve().then(() => (init_wikilinks(), wikilinks_exports));
19641
19974
  const { content: processedContent } = maybeApplyWikilinks2(content, skipWikilinks ?? false, notePath);
19642
19975
  let finalFrontmatter = frontmatter;
@@ -19670,13 +20003,13 @@ async function executeDeleteNote(options) {
19670
20003
  if (!pathCheck.valid) {
19671
20004
  return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
19672
20005
  }
19673
- const fullPath = path24.join(vaultPath2, notePath);
20006
+ const fullPath = path25.join(vaultPath2, notePath);
19674
20007
  try {
19675
- await fs22.access(fullPath);
20008
+ await fs23.access(fullPath);
19676
20009
  } catch {
19677
20010
  return { success: false, result: errorResult(notePath, `File not found: ${notePath}`), filesWritten: [] };
19678
20011
  }
19679
- await fs22.unlink(fullPath);
20012
+ await fs23.unlink(fullPath);
19680
20013
  const result = successResult(notePath, `Deleted note: ${notePath}`, {});
19681
20014
  return { success: true, result, filesWritten: [notePath] };
19682
20015
  } catch (error) {
@@ -19689,15 +20022,15 @@ async function executeDeleteNote(options) {
19689
20022
  }
19690
20023
 
19691
20024
  // src/tools/write/mutations.ts
19692
- async function createNoteFromTemplate(vaultPath2, notePath, config) {
20025
+ async function createNoteFromTemplate(vaultPath2, notePath, config2) {
19693
20026
  const validation = await validatePathSecure(vaultPath2, notePath);
19694
20027
  if (!validation.valid) {
19695
20028
  throw new Error(`Path blocked: ${validation.reason}`);
19696
20029
  }
19697
- const fullPath = path25.join(vaultPath2, notePath);
19698
- await fs23.mkdir(path25.dirname(fullPath), { recursive: true });
19699
- const templates = config.templates || {};
19700
- const filename = path25.basename(notePath, ".md").toLowerCase();
20030
+ const fullPath = path26.join(vaultPath2, notePath);
20031
+ await fs24.mkdir(path26.dirname(fullPath), { recursive: true });
20032
+ const templates = config2.templates || {};
20033
+ const filename = path26.basename(notePath, ".md").toLowerCase();
19701
20034
  let templatePath;
19702
20035
  let periodicType;
19703
20036
  const dailyPattern = /^\d{4}-\d{2}-\d{2}/;
@@ -19730,7 +20063,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
19730
20063
  ];
19731
20064
  for (const candidate of candidates) {
19732
20065
  try {
19733
- await fs23.access(path25.join(vaultPath2, candidate));
20066
+ await fs24.access(path26.join(vaultPath2, candidate));
19734
20067
  templatePath = candidate;
19735
20068
  console.error(`[Flywheel] Template not in config but found at ${candidate} \u2014 using it`);
19736
20069
  break;
@@ -19741,11 +20074,11 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
19741
20074
  let templateContent;
19742
20075
  if (templatePath) {
19743
20076
  try {
19744
- const absTemplatePath = path25.join(vaultPath2, templatePath);
19745
- templateContent = await fs23.readFile(absTemplatePath, "utf-8");
20077
+ const absTemplatePath = path26.join(vaultPath2, templatePath);
20078
+ templateContent = await fs24.readFile(absTemplatePath, "utf-8");
19746
20079
  } catch {
19747
20080
  console.error(`[Flywheel] Template at ${templatePath} not readable, using minimal fallback`);
19748
- const title = path25.basename(notePath, ".md");
20081
+ const title = path26.basename(notePath, ".md");
19749
20082
  templateContent = `---
19750
20083
  ---
19751
20084
 
@@ -19757,7 +20090,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
19757
20090
  if (periodicType) {
19758
20091
  console.error(`[Flywheel] No ${periodicType} template found in config or vault \u2014 using minimal fallback`);
19759
20092
  }
19760
- const title = path25.basename(notePath, ".md");
20093
+ const title = path26.basename(notePath, ".md");
19761
20094
  templateContent = `---
19762
20095
  ---
19763
20096
 
@@ -19766,7 +20099,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
19766
20099
  }
19767
20100
  const now = /* @__PURE__ */ new Date();
19768
20101
  const dateStr = now.toISOString().split("T")[0];
19769
- templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path25.basename(notePath, ".md"));
20102
+ templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path26.basename(notePath, ".md"));
19770
20103
  const matter9 = (await import("gray-matter")).default;
19771
20104
  const parsed = matter9(templateContent);
19772
20105
  if (!parsed.data.date) {
@@ -19805,20 +20138,20 @@ function registerMutationTools(server2, getVaultPath, getConfig2 = () => ({})) {
19805
20138
  let noteCreated = false;
19806
20139
  let templateUsed;
19807
20140
  if (create_if_missing && !dry_run) {
19808
- const fullPath = path25.join(vaultPath2, notePath);
20141
+ const fullPath = path26.join(vaultPath2, notePath);
19809
20142
  try {
19810
- await fs23.access(fullPath);
20143
+ await fs24.access(fullPath);
19811
20144
  } catch {
19812
- const config = getConfig2();
19813
- const result = await createNoteFromTemplate(vaultPath2, notePath, config);
20145
+ const config2 = getConfig2();
20146
+ const result = await createNoteFromTemplate(vaultPath2, notePath, config2);
19814
20147
  noteCreated = result.created;
19815
20148
  templateUsed = result.templateUsed;
19816
20149
  }
19817
20150
  }
19818
20151
  if (create_if_missing && dry_run) {
19819
- const fullPath = path25.join(vaultPath2, notePath);
20152
+ const fullPath = path26.join(vaultPath2, notePath);
19820
20153
  try {
19821
- await fs23.access(fullPath);
20154
+ await fs24.access(fullPath);
19822
20155
  } catch {
19823
20156
  return {
19824
20157
  content: [{
@@ -20302,8 +20635,8 @@ function registerFrontmatterTools(server2, getVaultPath) {
20302
20635
  init_writer();
20303
20636
  init_wikilinks();
20304
20637
  import { z as z15 } from "zod";
20305
- import fs24 from "fs/promises";
20306
- import path26 from "path";
20638
+ import fs25 from "fs/promises";
20639
+ import path27 from "path";
20307
20640
  function registerNoteTools(server2, getVaultPath, getIndex) {
20308
20641
  server2.tool(
20309
20642
  "vault_create_note",
@@ -20329,23 +20662,23 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
20329
20662
  if (!validatePath(vaultPath2, notePath)) {
20330
20663
  return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
20331
20664
  }
20332
- const fullPath = path26.join(vaultPath2, notePath);
20665
+ const fullPath = path27.join(vaultPath2, notePath);
20333
20666
  const existsCheck = await ensureFileExists(vaultPath2, notePath);
20334
20667
  if (existsCheck === null && !overwrite) {
20335
20668
  return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
20336
20669
  }
20337
- const dir = path26.dirname(fullPath);
20338
- await fs24.mkdir(dir, { recursive: true });
20670
+ const dir = path27.dirname(fullPath);
20671
+ await fs25.mkdir(dir, { recursive: true });
20339
20672
  let effectiveContent = content;
20340
20673
  let effectiveFrontmatter = frontmatter;
20341
20674
  if (template) {
20342
- const templatePath = path26.join(vaultPath2, template);
20675
+ const templatePath = path27.join(vaultPath2, template);
20343
20676
  try {
20344
- const raw = await fs24.readFile(templatePath, "utf-8");
20677
+ const raw = await fs25.readFile(templatePath, "utf-8");
20345
20678
  const matter9 = (await import("gray-matter")).default;
20346
20679
  const parsed = matter9(raw);
20347
20680
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
20348
- const title = path26.basename(notePath, ".md");
20681
+ const title = path27.basename(notePath, ".md");
20349
20682
  let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
20350
20683
  if (content) {
20351
20684
  templateContent = templateContent.trimEnd() + "\n\n" + content;
@@ -20364,7 +20697,7 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
20364
20697
  effectiveFrontmatter.created = now.toISOString();
20365
20698
  }
20366
20699
  const warnings = [];
20367
- const noteName = path26.basename(notePath, ".md");
20700
+ const noteName = path27.basename(notePath, ".md");
20368
20701
  const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
20369
20702
  const preflight = await checkPreflightSimilarity(noteName);
20370
20703
  if (preflight.existingEntity) {
@@ -20505,8 +20838,8 @@ ${sources}`;
20505
20838
  }
20506
20839
  return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
20507
20840
  }
20508
- const fullPath = path26.join(vaultPath2, notePath);
20509
- await fs24.unlink(fullPath);
20841
+ const fullPath = path27.join(vaultPath2, notePath);
20842
+ await fs25.unlink(fullPath);
20510
20843
  const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
20511
20844
  const message = backlinkWarning ? `Deleted note: ${notePath}
20512
20845
 
@@ -20526,8 +20859,8 @@ init_writer();
20526
20859
  init_git();
20527
20860
  init_wikilinks();
20528
20861
  import { z as z16 } from "zod";
20529
- import fs25 from "fs/promises";
20530
- import path27 from "path";
20862
+ import fs26 from "fs/promises";
20863
+ import path28 from "path";
20531
20864
  import matter6 from "gray-matter";
20532
20865
  function escapeRegex(str) {
20533
20866
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -20546,16 +20879,16 @@ function extractWikilinks2(content) {
20546
20879
  return wikilinks;
20547
20880
  }
20548
20881
  function getTitleFromPath(filePath) {
20549
- return path27.basename(filePath, ".md");
20882
+ return path28.basename(filePath, ".md");
20550
20883
  }
20551
20884
  async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
20552
20885
  const results = [];
20553
20886
  const allTargets = [targetTitle, ...targetAliases].map((t) => t.toLowerCase());
20554
20887
  async function scanDir(dir) {
20555
20888
  const files = [];
20556
- const entries = await fs25.readdir(dir, { withFileTypes: true });
20889
+ const entries = await fs26.readdir(dir, { withFileTypes: true });
20557
20890
  for (const entry of entries) {
20558
- const fullPath = path27.join(dir, entry.name);
20891
+ const fullPath = path28.join(dir, entry.name);
20559
20892
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
20560
20893
  files.push(...await scanDir(fullPath));
20561
20894
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -20566,8 +20899,8 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
20566
20899
  }
20567
20900
  const allFiles = await scanDir(vaultPath2);
20568
20901
  for (const filePath of allFiles) {
20569
- const relativePath = path27.relative(vaultPath2, filePath);
20570
- const content = await fs25.readFile(filePath, "utf-8");
20902
+ const relativePath = path28.relative(vaultPath2, filePath);
20903
+ const content = await fs26.readFile(filePath, "utf-8");
20571
20904
  const wikilinks = extractWikilinks2(content);
20572
20905
  const matchingLinks = [];
20573
20906
  for (const link of wikilinks) {
@@ -20653,10 +20986,10 @@ function registerMoveNoteTools(server2, getVaultPath) {
20653
20986
  };
20654
20987
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
20655
20988
  }
20656
- const oldFullPath = path27.join(vaultPath2, oldPath);
20657
- const newFullPath = path27.join(vaultPath2, newPath);
20989
+ const oldFullPath = path28.join(vaultPath2, oldPath);
20990
+ const newFullPath = path28.join(vaultPath2, newPath);
20658
20991
  try {
20659
- await fs25.access(oldFullPath);
20992
+ await fs26.access(oldFullPath);
20660
20993
  } catch {
20661
20994
  const result2 = {
20662
20995
  success: false,
@@ -20666,7 +20999,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
20666
20999
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
20667
21000
  }
20668
21001
  try {
20669
- await fs25.access(newFullPath);
21002
+ await fs26.access(newFullPath);
20670
21003
  const result2 = {
20671
21004
  success: false,
20672
21005
  message: `Destination already exists: ${newPath}`,
@@ -20675,7 +21008,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
20675
21008
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
20676
21009
  } catch {
20677
21010
  }
20678
- const sourceContent = await fs25.readFile(oldFullPath, "utf-8");
21011
+ const sourceContent = await fs26.readFile(oldFullPath, "utf-8");
20679
21012
  const parsed = matter6(sourceContent);
20680
21013
  const aliases = extractAliases2(parsed.data);
20681
21014
  const oldTitle = getTitleFromPath(oldPath);
@@ -20735,9 +21068,9 @@ function registerMoveNoteTools(server2, getVaultPath) {
20735
21068
  };
20736
21069
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
20737
21070
  }
20738
- const destDir = path27.dirname(newFullPath);
20739
- await fs25.mkdir(destDir, { recursive: true });
20740
- await fs25.rename(oldFullPath, newFullPath);
21071
+ const destDir = path28.dirname(newFullPath);
21072
+ await fs26.mkdir(destDir, { recursive: true });
21073
+ await fs26.rename(oldFullPath, newFullPath);
20741
21074
  let gitCommit;
20742
21075
  let undoAvailable;
20743
21076
  let staleLockDetected;
@@ -20811,12 +21144,12 @@ function registerMoveNoteTools(server2, getVaultPath) {
20811
21144
  if (sanitizedTitle !== newTitle) {
20812
21145
  console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
20813
21146
  }
20814
- const fullPath = path27.join(vaultPath2, notePath);
20815
- const dir = path27.dirname(notePath);
20816
- const newPath = dir === "." ? `${sanitizedTitle}.md` : path27.join(dir, `${sanitizedTitle}.md`);
20817
- const newFullPath = path27.join(vaultPath2, newPath);
21147
+ const fullPath = path28.join(vaultPath2, notePath);
21148
+ const dir = path28.dirname(notePath);
21149
+ const newPath = dir === "." ? `${sanitizedTitle}.md` : path28.join(dir, `${sanitizedTitle}.md`);
21150
+ const newFullPath = path28.join(vaultPath2, newPath);
20818
21151
  try {
20819
- await fs25.access(fullPath);
21152
+ await fs26.access(fullPath);
20820
21153
  } catch {
20821
21154
  const result2 = {
20822
21155
  success: false,
@@ -20827,7 +21160,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
20827
21160
  }
20828
21161
  if (fullPath !== newFullPath) {
20829
21162
  try {
20830
- await fs25.access(newFullPath);
21163
+ await fs26.access(newFullPath);
20831
21164
  const result2 = {
20832
21165
  success: false,
20833
21166
  message: `A note with this title already exists: ${newPath}`,
@@ -20837,7 +21170,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
20837
21170
  } catch {
20838
21171
  }
20839
21172
  }
20840
- const sourceContent = await fs25.readFile(fullPath, "utf-8");
21173
+ const sourceContent = await fs26.readFile(fullPath, "utf-8");
20841
21174
  const parsed = matter6(sourceContent);
20842
21175
  const aliases = extractAliases2(parsed.data);
20843
21176
  const oldTitle = getTitleFromPath(notePath);
@@ -20897,7 +21230,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
20897
21230
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
20898
21231
  }
20899
21232
  if (fullPath !== newFullPath) {
20900
- await fs25.rename(fullPath, newFullPath);
21233
+ await fs26.rename(fullPath, newFullPath);
20901
21234
  }
20902
21235
  let gitCommit;
20903
21236
  let undoAvailable;
@@ -20945,8 +21278,8 @@ function registerMoveNoteTools(server2, getVaultPath) {
20945
21278
  init_writer();
20946
21279
  init_wikilinks();
20947
21280
  import { z as z17 } from "zod";
20948
- import fs26 from "fs/promises";
20949
- import path28 from "path";
21281
+ import fs27 from "fs/promises";
21282
+ import path29 from "path";
20950
21283
  function registerMergeTools(server2, getVaultPath) {
20951
21284
  server2.tool(
20952
21285
  "merge_entities",
@@ -21073,7 +21406,7 @@ ${trimmedSource}`;
21073
21406
  }
21074
21407
  await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter, "LF", targetContentHash);
21075
21408
  const fullSourcePath = `${vaultPath2}/${source_path}`;
21076
- await fs26.unlink(fullSourcePath);
21409
+ await fs27.unlink(fullSourcePath);
21077
21410
  initializeEntityIndex(vaultPath2).catch((err) => {
21078
21411
  console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
21079
21412
  });
@@ -21188,7 +21521,7 @@ ${trimmedSource}`;
21188
21521
  }
21189
21522
  let sourceDeleted = false;
21190
21523
  if (sourceNoteFile) {
21191
- await fs26.unlink(`${vaultPath2}/${sourceNoteFile}`);
21524
+ await fs27.unlink(`${vaultPath2}/${sourceNoteFile}`);
21192
21525
  sourceDeleted = true;
21193
21526
  }
21194
21527
  initializeEntityIndex(vaultPath2).catch((err) => {
@@ -21237,21 +21570,21 @@ async function findSourceNote(vaultPath2, sourceName, excludePath) {
21237
21570
  async function scanDir(dir) {
21238
21571
  let entries;
21239
21572
  try {
21240
- entries = await fs26.readdir(dir, { withFileTypes: true });
21573
+ entries = await fs27.readdir(dir, { withFileTypes: true });
21241
21574
  } catch {
21242
21575
  return null;
21243
21576
  }
21244
21577
  for (const entry of entries) {
21245
21578
  if (entry.name.startsWith(".")) continue;
21246
- const fullPath = path28.join(dir, entry.name);
21579
+ const fullPath = path29.join(dir, entry.name);
21247
21580
  if (entry.isDirectory()) {
21248
21581
  const found = await scanDir(fullPath);
21249
21582
  if (found) return found;
21250
21583
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
21251
- const basename5 = path28.basename(entry.name, ".md");
21584
+ const basename5 = path29.basename(entry.name, ".md");
21252
21585
  if (basename5.toLowerCase() === targetLower) {
21253
- const relative3 = path28.relative(vaultPath2, fullPath).replace(/\\/g, "/");
21254
- if (relative3 !== excludePath) return relative3;
21586
+ const relative2 = path29.relative(vaultPath2, fullPath).replace(/\\/g, "/");
21587
+ if (relative2 !== excludePath) return relative2;
21255
21588
  }
21256
21589
  }
21257
21590
  }
@@ -21371,7 +21704,7 @@ Message: ${undoResult.undoneCommit.message}` : void 0
21371
21704
  }
21372
21705
 
21373
21706
  // src/tools/write/policy.ts
21374
- import * as path33 from "path";
21707
+ import * as path34 from "path";
21375
21708
  import { z as z20 } from "zod";
21376
21709
 
21377
21710
  // src/core/write/policy/index.ts
@@ -21380,8 +21713,8 @@ init_schema();
21380
21713
 
21381
21714
  // src/core/write/policy/parser.ts
21382
21715
  init_schema();
21383
- import fs27 from "fs/promises";
21384
- import path29 from "path";
21716
+ import fs28 from "fs/promises";
21717
+ import path30 from "path";
21385
21718
  import matter7 from "gray-matter";
21386
21719
  function parseYaml(content) {
21387
21720
  const parsed = matter7(`---
@@ -21406,7 +21739,7 @@ function parsePolicyString(yamlContent) {
21406
21739
  }
21407
21740
  async function loadPolicyFile(filePath) {
21408
21741
  try {
21409
- const content = await fs27.readFile(filePath, "utf-8");
21742
+ const content = await fs28.readFile(filePath, "utf-8");
21410
21743
  return parsePolicyString(content);
21411
21744
  } catch (error) {
21412
21745
  if (error.code === "ENOENT") {
@@ -21432,14 +21765,14 @@ async function loadPolicyFile(filePath) {
21432
21765
  async function loadPolicy(vaultPath2, policyName) {
21433
21766
  await ensureMigrated(vaultPath2);
21434
21767
  const policiesDir = getPoliciesDir(vaultPath2);
21435
- const policyPath = path29.join(policiesDir, `${policyName}.yaml`);
21768
+ const policyPath = path30.join(policiesDir, `${policyName}.yaml`);
21436
21769
  try {
21437
- await fs27.access(policyPath);
21770
+ await fs28.access(policyPath);
21438
21771
  return loadPolicyFile(policyPath);
21439
21772
  } catch {
21440
- const ymlPath = path29.join(policiesDir, `${policyName}.yml`);
21773
+ const ymlPath = path30.join(policiesDir, `${policyName}.yml`);
21441
21774
  try {
21442
- await fs27.access(ymlPath);
21775
+ await fs28.access(ymlPath);
21443
21776
  return loadPolicyFile(ymlPath);
21444
21777
  } catch {
21445
21778
  return {
@@ -21579,8 +21912,8 @@ init_schema();
21579
21912
  init_writer();
21580
21913
  init_git();
21581
21914
  init_wikilinks();
21582
- import fs29 from "fs/promises";
21583
- import path31 from "path";
21915
+ import fs30 from "fs/promises";
21916
+ import path32 from "path";
21584
21917
  init_constants2();
21585
21918
  async function executeStep(step, vaultPath2, context, conditionResults, searchFn) {
21586
21919
  const { execute, reason } = shouldStepExecute(step.when, conditionResults);
@@ -21771,12 +22104,12 @@ async function executeCreateNote2(params, vaultPath2, context) {
21771
22104
  let frontmatter = params.frontmatter || {};
21772
22105
  if (params.template) {
21773
22106
  try {
21774
- const templatePath = path31.join(vaultPath2, String(params.template));
21775
- const raw = await fs29.readFile(templatePath, "utf-8");
22107
+ const templatePath = path32.join(vaultPath2, String(params.template));
22108
+ const raw = await fs30.readFile(templatePath, "utf-8");
21776
22109
  const matter9 = (await import("gray-matter")).default;
21777
22110
  const parsed = matter9(raw);
21778
22111
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
21779
- const title = path31.basename(notePath, ".md");
22112
+ const title = path32.basename(notePath, ".md");
21780
22113
  let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
21781
22114
  if (content) {
21782
22115
  templateContent = templateContent.trimEnd() + "\n\n" + content;
@@ -21809,9 +22142,9 @@ async function executeToggleTask(params, vaultPath2) {
21809
22142
  const notePath = String(params.path || "");
21810
22143
  const task = String(params.task || "");
21811
22144
  const section = params.section ? String(params.section) : void 0;
21812
- const fullPath = path31.join(vaultPath2, notePath);
22145
+ const fullPath = path32.join(vaultPath2, notePath);
21813
22146
  try {
21814
- await fs29.access(fullPath);
22147
+ await fs30.access(fullPath);
21815
22148
  } catch {
21816
22149
  return { success: false, message: `File not found: ${notePath}`, path: notePath };
21817
22150
  }
@@ -22092,15 +22425,15 @@ async function rollbackChanges(vaultPath2, originalContents, filesModified) {
22092
22425
  const pathCheck = await validatePathSecure(vaultPath2, filePath);
22093
22426
  if (!pathCheck.valid) continue;
22094
22427
  const original = originalContents.get(filePath);
22095
- const fullPath = path31.join(vaultPath2, filePath);
22428
+ const fullPath = path32.join(vaultPath2, filePath);
22096
22429
  if (original === null) {
22097
22430
  try {
22098
- await fs29.unlink(fullPath);
22431
+ await fs30.unlink(fullPath);
22099
22432
  } catch {
22100
22433
  }
22101
22434
  } else if (original !== void 0) {
22102
22435
  try {
22103
- await fs29.writeFile(fullPath, original);
22436
+ await fs30.writeFile(fullPath, original);
22104
22437
  } catch {
22105
22438
  }
22106
22439
  }
@@ -22146,27 +22479,27 @@ async function previewPolicy(policy, vaultPath2, variables) {
22146
22479
  }
22147
22480
 
22148
22481
  // src/core/write/policy/storage.ts
22149
- import fs30 from "fs/promises";
22150
- import path32 from "path";
22482
+ import fs31 from "fs/promises";
22483
+ import path33 from "path";
22151
22484
  async function listPolicies(vaultPath2) {
22152
22485
  await ensureMigrated(vaultPath2);
22153
22486
  const dir = getPoliciesDir(vaultPath2);
22154
22487
  const policies = [];
22155
22488
  try {
22156
- const files = await fs30.readdir(dir);
22489
+ const files = await fs31.readdir(dir);
22157
22490
  for (const file of files) {
22158
22491
  if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
22159
22492
  continue;
22160
22493
  }
22161
- const filePath = path32.join(dir, file);
22162
- const stat5 = await fs30.stat(filePath);
22163
- const content = await fs30.readFile(filePath, "utf-8");
22494
+ const filePath = path33.join(dir, file);
22495
+ const stat4 = await fs31.stat(filePath);
22496
+ const content = await fs31.readFile(filePath, "utf-8");
22164
22497
  const metadata = extractPolicyMetadata(content);
22165
22498
  policies.push({
22166
22499
  name: metadata.name || file.replace(/\.ya?ml$/, ""),
22167
22500
  description: metadata.description || "No description",
22168
22501
  path: file,
22169
- lastModified: stat5.mtime,
22502
+ lastModified: stat4.mtime,
22170
22503
  version: metadata.version || "1.0",
22171
22504
  requiredVariables: metadata.variables || []
22172
22505
  });
@@ -22184,10 +22517,10 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
22184
22517
  const dir = getPoliciesDir(vaultPath2);
22185
22518
  await ensurePoliciesDir(vaultPath2);
22186
22519
  const filename = `${policyName}.yaml`;
22187
- const filePath = path32.join(dir, filename);
22520
+ const filePath = path33.join(dir, filename);
22188
22521
  if (!overwrite) {
22189
22522
  try {
22190
- await fs30.access(filePath);
22523
+ await fs31.access(filePath);
22191
22524
  return {
22192
22525
  success: false,
22193
22526
  path: filename,
@@ -22204,7 +22537,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
22204
22537
  message: `Invalid policy: ${validation.errors.map((e) => e.message).join("; ")}`
22205
22538
  };
22206
22539
  }
22207
- await fs30.writeFile(filePath, content, "utf-8");
22540
+ await fs31.writeFile(filePath, content, "utf-8");
22208
22541
  return {
22209
22542
  success: true,
22210
22543
  path: filename,
@@ -22298,7 +22631,7 @@ function registerPolicyTools(server2, getVaultPath, getSearchFn) {
22298
22631
  const policies = await listPolicies(vaultPath2);
22299
22632
  const response = {
22300
22633
  success: true,
22301
- vault: path33.basename(vaultPath2),
22634
+ vault: path34.basename(vaultPath2),
22302
22635
  vault_path: vaultPath2,
22303
22636
  count: policies.length,
22304
22637
  policies: policies.map((p) => ({
@@ -22731,8 +23064,8 @@ function registerPolicyTools(server2, getVaultPath, getSearchFn) {
22731
23064
  import { z as z21 } from "zod";
22732
23065
 
22733
23066
  // src/core/write/tagRename.ts
22734
- import * as fs31 from "fs/promises";
22735
- import * as path34 from "path";
23067
+ import * as fs32 from "fs/promises";
23068
+ import * as path35 from "path";
22736
23069
  import matter8 from "gray-matter";
22737
23070
  import { getProtectedZones as getProtectedZones2 } from "@velvetmonkey/vault-core";
22738
23071
  function getNotesInFolder3(index, folder) {
@@ -22838,10 +23171,10 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
22838
23171
  const previews = [];
22839
23172
  let totalChanges = 0;
22840
23173
  for (const note of affectedNotes) {
22841
- const fullPath = path34.join(vaultPath2, note.path);
23174
+ const fullPath = path35.join(vaultPath2, note.path);
22842
23175
  let fileContent;
22843
23176
  try {
22844
- fileContent = await fs31.readFile(fullPath, "utf-8");
23177
+ fileContent = await fs32.readFile(fullPath, "utf-8");
22845
23178
  } catch {
22846
23179
  continue;
22847
23180
  }
@@ -22914,7 +23247,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
22914
23247
  previews.push(preview);
22915
23248
  if (!dryRun) {
22916
23249
  const newContent = matter8.stringify(updatedContent, fm);
22917
- await fs31.writeFile(fullPath, newContent, "utf-8");
23250
+ await fs32.writeFile(fullPath, newContent, "utf-8");
22918
23251
  }
22919
23252
  }
22920
23253
  }
@@ -23916,9 +24249,9 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb4) {
23916
24249
  async ({ mode, key, value }) => {
23917
24250
  switch (mode) {
23918
24251
  case "get": {
23919
- const config = getConfig2();
24252
+ const config2 = getConfig2();
23920
24253
  return {
23921
- content: [{ type: "text", text: JSON.stringify(config, null, 2) }]
24254
+ content: [{ type: "text", text: JSON.stringify(config2, null, 2) }]
23922
24255
  };
23923
24256
  }
23924
24257
  case "set": {
@@ -23967,9 +24300,9 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb4) {
23967
24300
  init_wikilinks();
23968
24301
  init_wikilinkFeedback();
23969
24302
  import { z as z28 } from "zod";
23970
- import * as fs32 from "fs/promises";
23971
- import * as path35 from "path";
23972
- import { scanVaultEntities as scanVaultEntities3, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
24303
+ import * as fs33 from "fs/promises";
24304
+ import * as path36 from "path";
24305
+ import { scanVaultEntities as scanVaultEntities4, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
23973
24306
  init_embeddings();
23974
24307
  function hasSkipWikilinks(content) {
23975
24308
  if (!content.startsWith("---")) return false;
@@ -23981,16 +24314,16 @@ function hasSkipWikilinks(content) {
23981
24314
  async function collectMarkdownFiles(dirPath, basePath, excludeFolders) {
23982
24315
  const results = [];
23983
24316
  try {
23984
- const entries = await fs32.readdir(dirPath, { withFileTypes: true });
24317
+ const entries = await fs33.readdir(dirPath, { withFileTypes: true });
23985
24318
  for (const entry of entries) {
23986
24319
  if (entry.name.startsWith(".")) continue;
23987
- const fullPath = path35.join(dirPath, entry.name);
24320
+ const fullPath = path36.join(dirPath, entry.name);
23988
24321
  if (entry.isDirectory()) {
23989
24322
  if (excludeFolders.some((f) => entry.name.toLowerCase() === f.toLowerCase())) continue;
23990
24323
  const sub = await collectMarkdownFiles(fullPath, basePath, excludeFolders);
23991
24324
  results.push(...sub);
23992
24325
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
23993
- results.push(path35.relative(basePath, fullPath));
24326
+ results.push(path36.relative(basePath, fullPath));
23994
24327
  }
23995
24328
  }
23996
24329
  } catch {
@@ -24020,7 +24353,7 @@ var EXCLUDE_FOLDERS = [
24020
24353
  ];
24021
24354
  function buildStatusReport(stateDb2, vaultPath2) {
24022
24355
  const recommendations = [];
24023
- const dbPath = path35.join(vaultPath2, ".flywheel", "state.db");
24356
+ const dbPath = path36.join(vaultPath2, ".flywheel", "state.db");
24024
24357
  const statedbExists = stateDb2 !== null;
24025
24358
  if (!statedbExists) {
24026
24359
  recommendations.push("StateDb not initialized \u2014 server needs restart");
@@ -24087,8 +24420,8 @@ async function executeRun(stateDb2, vaultPath2) {
24087
24420
  if (entityCount === 0) {
24088
24421
  const start = Date.now();
24089
24422
  try {
24090
- const config = loadConfig(stateDb2);
24091
- const entityIndex2 = await scanVaultEntities3(vaultPath2, { excludeFolders: EXCLUDE_FOLDERS, customCategories: config.custom_categories });
24423
+ const config2 = loadConfig(stateDb2);
24424
+ const entityIndex2 = await scanVaultEntities4(vaultPath2, { excludeFolders: EXCLUDE_FOLDERS, customCategories: config2.custom_categories });
24092
24425
  stateDb2.replaceAllEntities(entityIndex2);
24093
24426
  const newCount = entityIndex2._metadata.total_entities;
24094
24427
  steps.push({
@@ -24147,10 +24480,10 @@ async function executeRun(stateDb2, vaultPath2) {
24147
24480
  const allFiles = await collectMarkdownFiles(vaultPath2, vaultPath2, EXCLUDE_FOLDERS);
24148
24481
  let eligible = 0;
24149
24482
  for (const relativePath of allFiles) {
24150
- const fullPath = path35.join(vaultPath2, relativePath);
24483
+ const fullPath = path36.join(vaultPath2, relativePath);
24151
24484
  let content;
24152
24485
  try {
24153
- content = await fs32.readFile(fullPath, "utf-8");
24486
+ content = await fs33.readFile(fullPath, "utf-8");
24154
24487
  } catch {
24155
24488
  continue;
24156
24489
  }
@@ -24205,10 +24538,10 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
24205
24538
  const eligible = [];
24206
24539
  let notesSkipped = 0;
24207
24540
  for (const relativePath of allFiles) {
24208
- const fullPath = path35.join(vaultPath2, relativePath);
24541
+ const fullPath = path36.join(vaultPath2, relativePath);
24209
24542
  let content;
24210
24543
  try {
24211
- content = await fs32.readFile(fullPath, "utf-8");
24544
+ content = await fs33.readFile(fullPath, "utf-8");
24212
24545
  } catch {
24213
24546
  continue;
24214
24547
  }
@@ -24235,8 +24568,8 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
24235
24568
  match_count: result.linksAdded
24236
24569
  });
24237
24570
  if (!dryRun) {
24238
- const fullPath = path35.join(vaultPath2, relativePath);
24239
- await fs32.writeFile(fullPath, result.content, "utf-8");
24571
+ const fullPath = path36.join(vaultPath2, relativePath);
24572
+ await fs33.writeFile(fullPath, result.content, "utf-8");
24240
24573
  notesModified++;
24241
24574
  if (stateDb2) {
24242
24575
  trackWikilinkApplications(stateDb2, relativePath, entities, "enrichment");
@@ -24413,8 +24746,8 @@ function registerMetricsTools(server2, getIndex, getStateDb4) {
24413
24746
  import { z as z30 } from "zod";
24414
24747
 
24415
24748
  // src/core/read/similarity.ts
24416
- import * as fs33 from "fs";
24417
- import * as path36 from "path";
24749
+ import * as fs34 from "fs";
24750
+ import * as path37 from "path";
24418
24751
  init_embeddings();
24419
24752
 
24420
24753
  // src/core/read/mmr.ts
@@ -24484,10 +24817,10 @@ function extractKeyTerms(content, maxTerms = 15) {
24484
24817
  }
24485
24818
  function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
24486
24819
  const limit = options.limit ?? 10;
24487
- const absPath = path36.join(vaultPath2, sourcePath);
24820
+ const absPath = path37.join(vaultPath2, sourcePath);
24488
24821
  let content;
24489
24822
  try {
24490
- content = fs33.readFileSync(absPath, "utf-8");
24823
+ content = fs34.readFileSync(absPath, "utf-8");
24491
24824
  } catch {
24492
24825
  return [];
24493
24826
  }
@@ -24626,7 +24959,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
24626
24959
  diversity: z30.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
24627
24960
  }
24628
24961
  },
24629
- async ({ path: path39, limit, diversity }) => {
24962
+ async ({ path: path40, limit, diversity }) => {
24630
24963
  const index = getIndex();
24631
24964
  const vaultPath2 = getVaultPath();
24632
24965
  const stateDb2 = getStateDb4();
@@ -24635,10 +24968,10 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
24635
24968
  content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
24636
24969
  };
24637
24970
  }
24638
- if (!index.notes.has(path39)) {
24971
+ if (!index.notes.has(path40)) {
24639
24972
  return {
24640
24973
  content: [{ type: "text", text: JSON.stringify({
24641
- error: `Note not found: ${path39}`,
24974
+ error: `Note not found: ${path40}`,
24642
24975
  hint: "Use the full relative path including .md extension"
24643
24976
  }, null, 2) }]
24644
24977
  };
@@ -24650,12 +24983,12 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
24650
24983
  };
24651
24984
  const useHybrid = hasEmbeddingsIndex();
24652
24985
  const method = useHybrid ? "hybrid" : "bm25";
24653
- const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path39, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path39, opts);
24986
+ const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path40, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path40, opts);
24654
24987
  return {
24655
24988
  content: [{
24656
24989
  type: "text",
24657
24990
  text: JSON.stringify({
24658
- source: path39,
24991
+ source: path40,
24659
24992
  method,
24660
24993
  count: results.length,
24661
24994
  similar: results
@@ -24669,7 +25002,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
24669
25002
  // src/tools/read/semantic.ts
24670
25003
  init_embeddings();
24671
25004
  import { z as z31 } from "zod";
24672
- import { getAllEntitiesFromDb as getAllEntitiesFromDb3 } from "@velvetmonkey/vault-core";
25005
+ import { getAllEntitiesFromDb as getAllEntitiesFromDb4 } from "@velvetmonkey/vault-core";
24673
25006
  function registerSemanticTools(server2, getVaultPath, getStateDb4) {
24674
25007
  server2.registerTool(
24675
25008
  "init_semantic",
@@ -24734,7 +25067,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb4) {
24734
25067
  const embedded = progress.total - progress.skipped;
24735
25068
  let entityEmbedded = 0;
24736
25069
  try {
24737
- const allEntities = getAllEntitiesFromDb3(stateDb2);
25070
+ const allEntities = getAllEntitiesFromDb4(stateDb2);
24738
25071
  const entityMap = /* @__PURE__ */ new Map();
24739
25072
  for (const e of allEntities) {
24740
25073
  entityMap.set(e.name, {
@@ -24796,7 +25129,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb4) {
24796
25129
  // src/tools/read/merges.ts
24797
25130
  init_levenshtein();
24798
25131
  import { z as z32 } from "zod";
24799
- import { getAllEntitiesFromDb as getAllEntitiesFromDb4, getDismissedMergePairs, recordMergeDismissal } from "@velvetmonkey/vault-core";
25132
+ import { getAllEntitiesFromDb as getAllEntitiesFromDb5, getDismissedMergePairs, recordMergeDismissal } from "@velvetmonkey/vault-core";
24800
25133
  function normalizeName(name) {
24801
25134
  return name.toLowerCase().replace(/[.\-_]/g, "").replace(/js$/, "").replace(/ts$/, "");
24802
25135
  }
@@ -24814,7 +25147,7 @@ function registerMergeTools2(server2, getStateDb4) {
24814
25147
  content: [{ type: "text", text: JSON.stringify({ suggestions: [], error: "StateDb not available" }) }]
24815
25148
  };
24816
25149
  }
24817
- const entities = getAllEntitiesFromDb4(stateDb2);
25150
+ const entities = getAllEntitiesFromDb5(stateDb2);
24818
25151
  if (entities.length === 0) {
24819
25152
  return {
24820
25153
  content: [{ type: "text", text: JSON.stringify({ suggestions: [] }) }]
@@ -26297,7 +26630,7 @@ function queryFlywheelAgeDays(stateDb2) {
26297
26630
  if (!row?.first_ts) return 0;
26298
26631
  return Math.floor((Date.now() - row.first_ts) / (24 * 60 * 60 * 1e3));
26299
26632
  }
26300
- function getCalibrationExport(stateDb2, metrics, config, daysBack = 30, includeVaultId = true) {
26633
+ function getCalibrationExport(stateDb2, metrics, config2, daysBack = 30, includeVaultId = true) {
26301
26634
  const now = /* @__PURE__ */ new Date();
26302
26635
  const start = new Date(now);
26303
26636
  start.setDate(start.getDate() - daysBack + 1);
@@ -26320,8 +26653,8 @@ function getCalibrationExport(stateDb2, metrics, config, daysBack = 30, includeV
26320
26653
  connected_ratio: round(metrics.connected_ratio),
26321
26654
  semantic_enabled: hasEmbeddingsIndex(),
26322
26655
  flywheel_age_days: queryFlywheelAgeDays(stateDb2),
26323
- strictness_mode: config.wikilink_strictness ?? "balanced",
26324
- adaptive_strictness: config.adaptive_strictness ?? true
26656
+ strictness_mode: config2.wikilink_strictness ?? "balanced",
26657
+ adaptive_strictness: config2.adaptive_strictness ?? true
26325
26658
  },
26326
26659
  entity_distribution: queryEntityDistribution(stateDb2),
26327
26660
  funnel: queryFunnel2(stateDb2, startMs, startIso, endIso),
@@ -26355,11 +26688,11 @@ function registerCalibrationExportTools(server2, getIndex, getStateDb4, getConfi
26355
26688
  }
26356
26689
  const index = getIndex();
26357
26690
  const metrics = computeMetrics(index, stateDb2);
26358
- const config = getConfig2();
26691
+ const config2 = getConfig2();
26359
26692
  const report = getCalibrationExport(
26360
26693
  stateDb2,
26361
26694
  metrics,
26362
- config,
26695
+ config2,
26363
26696
  args.days_back ?? 30,
26364
26697
  args.include_vault_id ?? true
26365
26698
  );
@@ -26529,7 +26862,7 @@ function registerVaultResources(server2, getIndex) {
26529
26862
  }
26530
26863
 
26531
26864
  // src/tool-registry.ts
26532
- var __trFilename = fileURLToPath(import.meta.url);
26865
+ var __trFilename = fileURLToPath2(import.meta.url);
26533
26866
  var __trDirname = dirname5(__trFilename);
26534
26867
  var trPkg = JSON.parse(readFileSync5(join18(__trDirname, "../package.json"), "utf-8"));
26535
26868
  var ACTIVATION_PATTERNS = [
@@ -26613,7 +26946,7 @@ function extractSearchMethod(result) {
26613
26946
  }
26614
26947
  return void 0;
26615
26948
  }
26616
- function applyToolGating(targetServer, categories, getDb4, registry, getVaultPath, vaultCallbacks, tierMode = "off", onTierStateChange, isFullToolset = false) {
26949
+ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPath, vaultCallbacks, tierMode = "off", onTierStateChange, isFullToolset = false, onToolCall) {
26617
26950
  let _registered = 0;
26618
26951
  let _skipped = 0;
26619
26952
  let tierOverride = "auto";
@@ -26706,6 +27039,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
26706
27039
  }
26707
27040
  function wrapWithTracking(toolName, handler) {
26708
27041
  return async (...args) => {
27042
+ onToolCall?.();
26709
27043
  const start = Date.now();
26710
27044
  let success = true;
26711
27045
  let notePaths;
@@ -26760,7 +27094,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
26760
27094
  let totalBytes = 0;
26761
27095
  for (const p of notePaths) {
26762
27096
  try {
26763
- totalBytes += statSync6(path37.join(vp, p)).size;
27097
+ totalBytes += statSync6(path38.join(vp, p)).size;
26764
27098
  } catch {
26765
27099
  }
26766
27100
  }
@@ -26934,7 +27268,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
26934
27268
  const serverAny = targetServer;
26935
27269
  serverAny.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
26936
27270
  try {
26937
- const tool = serverAny._registeredTools[request.params.name];
27271
+ const tool = toolHandles.get(request.params.name);
26938
27272
  if (!tool) {
26939
27273
  throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
26940
27274
  }
@@ -27111,8 +27445,8 @@ function registerAllTools(targetServer, ctx, controller) {
27111
27445
  }
27112
27446
 
27113
27447
  // src/index.ts
27114
- var __filename = fileURLToPath2(import.meta.url);
27115
- var __dirname = dirname7(__filename);
27448
+ var __filename2 = fileURLToPath3(import.meta.url);
27449
+ var __dirname = dirname7(__filename2);
27116
27450
  var pkg = JSON.parse(readFileSync6(join20(__dirname, "../package.json"), "utf-8"));
27117
27451
  var vaultPath;
27118
27452
  var resolvedVaultPath;
@@ -27125,6 +27459,10 @@ var httpListener = null;
27125
27459
  var watchdogTimer = null;
27126
27460
  var serverReady = false;
27127
27461
  var shutdownRequested = false;
27462
+ var lastMcpRequestAt = 0;
27463
+ var lastFullRebuildAt = 0;
27464
+ var startupScanFiles = null;
27465
+ var deferredScheduler = null;
27128
27466
  function getWatcherStatus() {
27129
27467
  if (vaultRegistry) {
27130
27468
  const name = globalThis.__flywheel_active_vault;
@@ -27158,8 +27496,8 @@ function handleTierStateChange(controller) {
27158
27496
  syncRuntimeTierState(controller);
27159
27497
  invalidateHttpPool();
27160
27498
  }
27161
- function getConfigToolTierOverride(config) {
27162
- return config.tool_tier_override ?? "auto";
27499
+ function getConfigToolTierOverride(config2) {
27500
+ return config2.tool_tier_override ?? "auto";
27163
27501
  }
27164
27502
  function buildRegistryContext() {
27165
27503
  return {
@@ -27191,7 +27529,10 @@ function createConfiguredServer() {
27191
27529
  buildVaultCallbacks(),
27192
27530
  toolTierMode,
27193
27531
  handleTierStateChange,
27194
- toolConfig.isFullToolset
27532
+ toolConfig.isFullToolset,
27533
+ () => {
27534
+ lastMcpRequestAt = Date.now();
27535
+ }
27195
27536
  );
27196
27537
  registerAllTools(s, ctx, toolTierController);
27197
27538
  toolTierController.setOverride(runtimeToolTierOverride);
@@ -27245,7 +27586,10 @@ var _gatingResult = applyToolGating(
27245
27586
  buildVaultCallbacks(),
27246
27587
  toolTierMode,
27247
27588
  handleTierStateChange,
27248
- toolConfig.isFullToolset
27589
+ toolConfig.isFullToolset,
27590
+ () => {
27591
+ lastMcpRequestAt = Date.now();
27592
+ }
27249
27593
  );
27250
27594
  registerAllTools(server, _registryCtx, _gatingResult);
27251
27595
  _gatingResult.setOverride(runtimeToolTierOverride);
@@ -27326,7 +27670,7 @@ function buildVaultScope(ctx) {
27326
27670
  pipelineActivity: ctx.pipelineActivity
27327
27671
  };
27328
27672
  }
27329
- function activateVault(ctx) {
27673
+ function activateVault(ctx, skipEmbeddingLoad = false) {
27330
27674
  globalThis.__flywheel_active_vault = ctx.name;
27331
27675
  if (ctx.stateDb) {
27332
27676
  setWriteStateDb(ctx.stateDb);
@@ -27335,7 +27679,9 @@ function activateVault(ctx) {
27335
27679
  setProspectStateDb(ctx.stateDb);
27336
27680
  setTaskCacheDatabase(ctx.stateDb.db);
27337
27681
  setEmbeddingsDatabase(ctx.stateDb.db);
27338
- loadEntityEmbeddingsToMemory();
27682
+ if (!skipEmbeddingLoad) {
27683
+ loadEntityEmbeddingsToMemory();
27684
+ }
27339
27685
  }
27340
27686
  setWikilinkConfig(ctx.flywheelConfig);
27341
27687
  setCooccurrenceIndex(ctx.cooccurrenceIndex);
@@ -27368,17 +27714,17 @@ function updateVaultIndex(index) {
27368
27714
  const ctx = getActiveVaultContext();
27369
27715
  if (ctx) ctx.vaultIndex = index;
27370
27716
  }
27371
- function updateFlywheelConfig(config) {
27372
- flywheelConfig = config;
27373
- setWikilinkConfig(config);
27717
+ function updateFlywheelConfig(config2) {
27718
+ flywheelConfig = config2;
27719
+ setWikilinkConfig(config2);
27374
27720
  if (toolTierMode === "tiered" && primaryToolTierController) {
27375
- primaryToolTierController.setOverride(getConfigToolTierOverride(config));
27721
+ primaryToolTierController.setOverride(getConfigToolTierOverride(config2));
27376
27722
  syncRuntimeTierState(primaryToolTierController);
27377
27723
  invalidateHttpPool();
27378
27724
  }
27379
27725
  const ctx = getActiveVaultContext();
27380
27726
  if (ctx) {
27381
- ctx.flywheelConfig = config;
27727
+ ctx.flywheelConfig = config2;
27382
27728
  setActiveScope(buildVaultScope(ctx));
27383
27729
  }
27384
27730
  }
@@ -27412,6 +27758,7 @@ async function bootVault(ctx, startTime) {
27412
27758
  if (sd) {
27413
27759
  try {
27414
27760
  const files = await scanVault(vp);
27761
+ startupScanFiles = files;
27415
27762
  const noteCount = files.length;
27416
27763
  serverLog("index", `[${ctx.name}] Found ${noteCount} markdown files`);
27417
27764
  const newestMtime = files.reduce((max, f) => f.modified > max ? f.modified : max, /* @__PURE__ */ new Date(0));
@@ -27433,6 +27780,7 @@ async function bootVault(ctx, startTime) {
27433
27780
  note_count: cachedIndex.notes.size
27434
27781
  });
27435
27782
  }
27783
+ lastFullRebuildAt = Date.now();
27436
27784
  await runPostIndexWork(ctx);
27437
27785
  } else {
27438
27786
  serverLog("index", `[${ctx.name}] Cache miss: building from scratch`);
@@ -27457,6 +27805,7 @@ async function bootVault(ctx, startTime) {
27457
27805
  serverLog("index", `[${ctx.name}] Failed to save index cache: ${err instanceof Error ? err.message : err}`, "error");
27458
27806
  }
27459
27807
  }
27808
+ lastFullRebuildAt = Date.now();
27460
27809
  await runPostIndexWork(ctx);
27461
27810
  } catch (err) {
27462
27811
  updateIndexState("error", err instanceof Error ? err : new Error(String(err)));
@@ -27495,13 +27844,13 @@ async function main() {
27495
27844
  const primaryCtx2 = await initializeVault(vaultConfigs[0].name, vaultConfigs[0].path);
27496
27845
  vaultRegistry.addContext(primaryCtx2);
27497
27846
  stateDb = primaryCtx2.stateDb;
27498
- activateVault(primaryCtx2);
27847
+ activateVault(primaryCtx2, true);
27499
27848
  } else {
27500
27849
  vaultRegistry = new VaultRegistry("default");
27501
27850
  const ctx = await initializeVault("default", vaultPath);
27502
27851
  vaultRegistry.addContext(ctx);
27503
27852
  stateDb = ctx.stateDb;
27504
- activateVault(ctx);
27853
+ activateVault(ctx, true);
27505
27854
  }
27506
27855
  await initToolRouting();
27507
27856
  if (stateDb) {
@@ -27677,11 +28026,11 @@ async function updateEntitiesInStateDb(vp, sd) {
27677
28026
  const vault = vp ?? vaultPath;
27678
28027
  if (!db4) return;
27679
28028
  try {
27680
- const config = loadConfig(db4);
27681
- const excludeFolders = config.exclude_entity_folders?.length ? config.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
27682
- const entityIndex2 = await scanVaultEntities4(vault, {
28029
+ const config2 = loadConfig(db4);
28030
+ const excludeFolders = config2.exclude_entity_folders?.length ? config2.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
28031
+ const entityIndex2 = await scanVaultEntities5(vault, {
27683
28032
  excludeFolders,
27684
- customCategories: config.custom_categories
28033
+ customCategories: config2.custom_categories
27685
28034
  });
27686
28035
  db4.replaceAllEntities(entityIndex2);
27687
28036
  serverLog("index", `Updated ${entityIndex2._metadata.total_entities} entities in StateDb`);
@@ -27689,37 +28038,11 @@ async function updateEntitiesInStateDb(vp, sd) {
27689
28038
  serverLog("index", `Failed to update entities in StateDb: ${e instanceof Error ? e.message : e}`, "error");
27690
28039
  }
27691
28040
  }
27692
- async function buildStartupCatchupBatch(vaultPath2, sinceMs) {
27693
- const events = [];
27694
- async function scanDir(dir) {
27695
- let entries;
27696
- try {
27697
- entries = await fs34.readdir(dir, { withFileTypes: true });
27698
- } catch {
27699
- return;
27700
- }
27701
- for (const entry of entries) {
27702
- const fullPath = path38.join(dir, entry.name);
27703
- if (entry.isDirectory()) {
27704
- if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
27705
- await scanDir(fullPath);
27706
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
27707
- try {
27708
- const stat5 = await fs34.stat(fullPath);
27709
- if (stat5.mtimeMs > sinceMs) {
27710
- events.push({
27711
- type: "upsert",
27712
- path: path38.relative(vaultPath2, fullPath),
27713
- originalEvents: []
27714
- });
27715
- }
27716
- } catch {
27717
- }
27718
- }
27719
- }
28041
+ function buildStartupCatchupBatch(vaultPath2, sinceMs, preScannedFiles) {
28042
+ if (preScannedFiles) {
28043
+ return preScannedFiles.filter((f) => f.modified.getTime() > sinceMs).map((f) => ({ type: "upsert", path: f.path, originalEvents: [] }));
27720
28044
  }
27721
- await scanDir(vaultPath2);
27722
- return events;
28045
+ return [];
27723
28046
  }
27724
28047
  var lastPurgeAt = Date.now();
27725
28048
  function runPeriodicMaintenance(db4) {
@@ -27828,7 +28151,7 @@ async function runPostIndexWork(ctx) {
27828
28151
  serverLog("semantic", "Embeddings up-to-date, skipping build");
27829
28152
  loadEntityEmbeddingsToMemory();
27830
28153
  if (sd) {
27831
- const entities = getAllEntitiesFromDb5(sd);
28154
+ const entities = getAllEntitiesFromDb6(sd);
27832
28155
  if (entities.length > 0) {
27833
28156
  saveInferredCategories(classifyUncategorizedEntities(
27834
28157
  entities.map((entity) => ({
@@ -27865,7 +28188,7 @@ async function runPostIndexWork(ctx) {
27865
28188
  }
27866
28189
  });
27867
28190
  if (sd) {
27868
- const entities = getAllEntitiesFromDb5(sd);
28191
+ const entities = getAllEntitiesFromDb6(sd);
27869
28192
  if (entities.length > 0) {
27870
28193
  const entityMap = new Map(entities.map((e) => [e.name, {
27871
28194
  name: e.name,
@@ -27880,7 +28203,7 @@ async function runPostIndexWork(ctx) {
27880
28203
  activateVault(ctx);
27881
28204
  loadEntityEmbeddingsToMemory();
27882
28205
  if (sd) {
27883
- const entities = getAllEntitiesFromDb5(sd);
28206
+ const entities = getAllEntitiesFromDb6(sd);
27884
28207
  if (entities.length > 0) {
27885
28208
  saveInferredCategories(classifyUncategorizedEntities(
27886
28209
  entities.map((entity) => ({
@@ -27901,7 +28224,7 @@ async function runPostIndexWork(ctx) {
27901
28224
  if (attempt < MAX_BUILD_RETRIES) {
27902
28225
  const delay = 1e4;
27903
28226
  serverLog("semantic", `Build failed (attempt ${attempt}/${MAX_BUILD_RETRIES}): ${msg}. Retrying in ${delay / 1e3}s...`, "error");
27904
- await new Promise((resolve2) => setTimeout(resolve2, delay));
28227
+ await new Promise((resolve3) => setTimeout(resolve3, delay));
27905
28228
  return attemptBuild(attempt + 1);
27906
28229
  }
27907
28230
  serverLog("semantic", `Embeddings build failed after ${MAX_BUILD_RETRIES} attempts: ${msg}`, "error");
@@ -27918,7 +28241,7 @@ async function runPostIndexWork(ctx) {
27918
28241
  serverLog("semantic", "Skipping \u2014 FLYWHEEL_SKIP_EMBEDDINGS");
27919
28242
  }
27920
28243
  if (process.env.FLYWHEEL_WATCH !== "false") {
27921
- const config = parseWatcherConfig();
28244
+ const config2 = parseWatcherConfig();
27922
28245
  const lastContentHashes = /* @__PURE__ */ new Map();
27923
28246
  if (sd) {
27924
28247
  const persisted = loadContentHashes(sd);
@@ -27927,7 +28250,15 @@ async function runPostIndexWork(ctx) {
27927
28250
  serverLog("watcher", `Loaded ${persisted.size} persisted content hashes`);
27928
28251
  }
27929
28252
  }
27930
- serverLog("watcher", `File watcher enabled (debounce: ${config.debounceMs}ms)`);
28253
+ serverLog("watcher", `File watcher enabled (debounce: ${config2.debounceMs}ms)`);
28254
+ deferredScheduler = new DeferredStepScheduler();
28255
+ deferredScheduler.setExecutor({
28256
+ ctx,
28257
+ vp,
28258
+ sd,
28259
+ getVaultIndex: () => vaultIndex,
28260
+ updateEntitiesInStateDb
28261
+ });
27931
28262
  const handleBatch = async (batch) => {
27932
28263
  const vaultPrefixes = /* @__PURE__ */ new Set([
27933
28264
  normalizePath(vp),
@@ -27949,8 +28280,8 @@ async function runPostIndexWork(ctx) {
27949
28280
  }
27950
28281
  } catch {
27951
28282
  try {
27952
- const dir = path38.dirname(rawPath);
27953
- const base = path38.basename(rawPath);
28283
+ const dir = path39.dirname(rawPath);
28284
+ const base = path39.basename(rawPath);
27954
28285
  const resolvedDir = realpathSync(dir).replace(/\\/g, "/");
27955
28286
  for (const prefix of vaultPrefixes) {
27956
28287
  if (resolvedDir.startsWith(prefix + "/") || resolvedDir === prefix) {
@@ -27982,7 +28313,7 @@ async function runPostIndexWork(ctx) {
27982
28313
  continue;
27983
28314
  }
27984
28315
  try {
27985
- const content = await fs34.readFile(path38.join(vp, event.path), "utf-8");
28316
+ const content = await fs35.readFile(path39.join(vp, event.path), "utf-8");
27986
28317
  const hash = createHash4("sha256").update(content).digest("hex").slice(0, 16);
27987
28318
  if (lastContentHashes.get(event.path) === hash) {
27988
28319
  serverLog("watcher", `Hash unchanged, skipping: ${event.path}`);
@@ -28068,13 +28399,14 @@ async function runPostIndexWork(ctx) {
28068
28399
  updateVaultIndex,
28069
28400
  updateEntitiesInStateDb,
28070
28401
  getVaultIndex: () => vaultIndex,
28071
- buildVaultIndex
28402
+ buildVaultIndex,
28403
+ deferredScheduler: deferredScheduler ?? void 0
28072
28404
  });
28073
28405
  await runner.run();
28074
28406
  };
28075
28407
  const watcher = createVaultWatcher({
28076
28408
  vaultPath: vp,
28077
- config,
28409
+ config: config2,
28078
28410
  onBatch: handleBatch,
28079
28411
  onStateChange: (status) => {
28080
28412
  if (status.state === "dirty") {
@@ -28090,7 +28422,7 @@ async function runPostIndexWork(ctx) {
28090
28422
  if (sd) {
28091
28423
  const lastPipelineEvent = getRecentPipelineEvent(sd);
28092
28424
  if (lastPipelineEvent) {
28093
- const catchupEvents = await buildStartupCatchupBatch(vp, lastPipelineEvent.timestamp);
28425
+ const catchupEvents = buildStartupCatchupBatch(vp, lastPipelineEvent.timestamp, startupScanFiles);
28094
28426
  if (catchupEvents.length > 0) {
28095
28427
  console.error(`[Flywheel] Startup catch-up: ${catchupEvents.length} file(s) modified while offline`);
28096
28428
  await handleBatch({ events: catchupEvents, renames: [], timestamp: Date.now() });
@@ -28110,11 +28442,24 @@ async function runPostIndexWork(ctx) {
28110
28442
  watcher.start();
28111
28443
  serverLog("watcher", "File watcher started");
28112
28444
  }
28445
+ startupScanFiles = null;
28113
28446
  if (process.env.FLYWHEEL_WATCH !== "false") {
28114
28447
  startSweepTimer(() => ctx.vaultIndex, void 0, () => {
28115
28448
  if (sd) runPeriodicMaintenance(sd);
28116
28449
  });
28117
28450
  serverLog("server", "Sweep timer started (5 min interval)");
28451
+ const maintenanceIntervalMs = parseInt(process.env.FLYWHEEL_MAINTENANCE_INTERVAL_MINUTES ?? "120", 10) * 60 * 1e3;
28452
+ startMaintenanceTimer({
28453
+ ctx,
28454
+ vp,
28455
+ sd,
28456
+ getVaultIndex: () => ctx.vaultIndex,
28457
+ updateEntitiesInStateDb,
28458
+ updateFlywheelConfig,
28459
+ getLastMcpRequestAt: () => lastMcpRequestAt,
28460
+ getLastFullRebuildAt: () => lastFullRebuildAt
28461
+ }, maintenanceIntervalMs);
28462
+ serverLog("server", `Maintenance timer started (~${Math.round(maintenanceIntervalMs / 6e4)}min interval)`);
28118
28463
  }
28119
28464
  const postDuration = Date.now() - postStart;
28120
28465
  serverLog("server", `Post-index work complete in ${postDuration}ms`);
@@ -28162,7 +28507,12 @@ function gracefulShutdown(signal) {
28162
28507
  watcherInstance?.stop();
28163
28508
  } catch {
28164
28509
  }
28510
+ try {
28511
+ deferredScheduler?.cancelAll();
28512
+ } catch {
28513
+ }
28165
28514
  stopSweepTimer();
28515
+ stopMaintenanceTimer();
28166
28516
  flushLogs().catch(() => {
28167
28517
  }).finally(() => process.exit(0));
28168
28518
  setTimeout(() => process.exit(0), 2e3).unref();