@velvetmonkey/flywheel-memory 2.0.126 → 2.0.128

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +242 -174
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -4065,8 +4065,8 @@ async function suggestRelatedLinks(content, options = {}) {
4065
4065
  return emptyResult;
4066
4066
  }
4067
4067
  const MAX_SUFFIX_ENTRIES = 3;
4068
- const MIN_SUFFIX_SCORE = 12;
4069
- const MIN_SUFFIX_CONTENT = 3;
4068
+ const MIN_SUFFIX_SCORE = noteContext === "daily" ? 8 : 12;
4069
+ const MIN_SUFFIX_CONTENT = noteContext === "daily" ? 2 : 3;
4070
4070
  const suffixEntries = topEntries.filter(
4071
4071
  (e) => e.score >= MIN_SUFFIX_SCORE && (e.breakdown.contentMatch >= MIN_SUFFIX_CONTENT || e.breakdown.cooccurrenceBoost >= MIN_SUFFIX_CONTENT || (e.breakdown.semanticBoost ?? 0) >= MIN_SUFFIX_CONTENT)
4072
4072
  ).slice(0, MAX_SUFFIX_ENTRIES);
@@ -4552,7 +4552,7 @@ var init_constants = __esm({
4552
4552
  });
4553
4553
 
4554
4554
  // src/core/write/writer.ts
4555
- import fs19 from "fs/promises";
4555
+ import fs20 from "fs/promises";
4556
4556
  import path21 from "path";
4557
4557
  import matter5 from "gray-matter";
4558
4558
  import { createHash as createHash2 } from "node:crypto";
@@ -4940,9 +4940,9 @@ async function validatePathSecure(vaultPath2, notePath) {
4940
4940
  try {
4941
4941
  const fullPath = path21.join(vaultPath2, notePath);
4942
4942
  try {
4943
- await fs19.access(fullPath);
4944
- const realPath = await fs19.realpath(fullPath);
4945
- const realVaultPath = await fs19.realpath(vaultPath2);
4943
+ await fs20.access(fullPath);
4944
+ const realPath = await fs20.realpath(fullPath);
4945
+ const realVaultPath = await fs20.realpath(vaultPath2);
4946
4946
  if (!realPath.startsWith(realVaultPath)) {
4947
4947
  return {
4948
4948
  valid: false,
@@ -4959,9 +4959,9 @@ async function validatePathSecure(vaultPath2, notePath) {
4959
4959
  } catch {
4960
4960
  const parentDir = path21.dirname(fullPath);
4961
4961
  try {
4962
- await fs19.access(parentDir);
4963
- const realParentPath = await fs19.realpath(parentDir);
4964
- const realVaultPath = await fs19.realpath(vaultPath2);
4962
+ await fs20.access(parentDir);
4963
+ const realParentPath = await fs20.realpath(parentDir);
4964
+ const realVaultPath = await fs20.realpath(vaultPath2);
4965
4965
  if (!realParentPath.startsWith(realVaultPath)) {
4966
4966
  return {
4967
4967
  valid: false,
@@ -4988,8 +4988,8 @@ async function readVaultFile(vaultPath2, notePath) {
4988
4988
  }
4989
4989
  const fullPath = path21.join(vaultPath2, notePath);
4990
4990
  const [rawContent, stat5] = await Promise.all([
4991
- fs19.readFile(fullPath, "utf-8"),
4992
- fs19.stat(fullPath)
4991
+ fs20.readFile(fullPath, "utf-8"),
4992
+ fs20.stat(fullPath)
4993
4993
  ]);
4994
4994
  const contentHash2 = computeContentHash(rawContent);
4995
4995
  const lineEnding = detectLineEnding(rawContent);
@@ -5043,7 +5043,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
5043
5043
  }
5044
5044
  const fullPath = path21.join(vaultPath2, notePath);
5045
5045
  if (expectedHash) {
5046
- const currentRaw = await fs19.readFile(fullPath, "utf-8");
5046
+ const currentRaw = await fs20.readFile(fullPath, "utf-8");
5047
5047
  const currentHash = computeContentHash(currentRaw);
5048
5048
  if (currentHash !== expectedHash) {
5049
5049
  throw new WriteConflictError(notePath);
@@ -5052,7 +5052,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
5052
5052
  let output = matter5.stringify(content, frontmatter);
5053
5053
  output = normalizeTrailingNewline(output);
5054
5054
  output = convertLineEndings(output, lineEnding);
5055
- await fs19.writeFile(fullPath, output, "utf-8");
5055
+ await fs20.writeFile(fullPath, output, "utf-8");
5056
5056
  }
5057
5057
  function removeFromSection(content, section, pattern, mode = "first", useRegex = false) {
5058
5058
  const lines = content.split("\n");
@@ -5899,7 +5899,7 @@ __export(conditions_exports, {
5899
5899
  evaluateCondition: () => evaluateCondition,
5900
5900
  shouldStepExecute: () => shouldStepExecute
5901
5901
  });
5902
- import fs26 from "fs/promises";
5902
+ import fs27 from "fs/promises";
5903
5903
  import path27 from "path";
5904
5904
  async function evaluateCondition(condition, vaultPath2, context) {
5905
5905
  const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
@@ -5955,7 +5955,7 @@ async function evaluateCondition(condition, vaultPath2, context) {
5955
5955
  async function evaluateFileExists(vaultPath2, notePath, expectExists) {
5956
5956
  const fullPath = path27.join(vaultPath2, notePath);
5957
5957
  try {
5958
- await fs26.access(fullPath);
5958
+ await fs27.access(fullPath);
5959
5959
  return {
5960
5960
  met: expectExists,
5961
5961
  reason: expectExists ? `File exists: ${notePath}` : `File exists (expected not to): ${notePath}`
@@ -5970,7 +5970,7 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
5970
5970
  async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
5971
5971
  const fullPath = path27.join(vaultPath2, notePath);
5972
5972
  try {
5973
- await fs26.access(fullPath);
5973
+ await fs27.access(fullPath);
5974
5974
  } catch {
5975
5975
  return {
5976
5976
  met: !expectExists,
@@ -6001,7 +6001,7 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
6001
6001
  async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
6002
6002
  const fullPath = path27.join(vaultPath2, notePath);
6003
6003
  try {
6004
- await fs26.access(fullPath);
6004
+ await fs27.access(fullPath);
6005
6005
  } catch {
6006
6006
  return {
6007
6007
  met: !expectExists,
@@ -6032,7 +6032,7 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
6032
6032
  async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
6033
6033
  const fullPath = path27.join(vaultPath2, notePath);
6034
6034
  try {
6035
- await fs26.access(fullPath);
6035
+ await fs27.access(fullPath);
6036
6036
  } catch {
6037
6037
  return {
6038
6038
  met: false,
@@ -11206,6 +11206,149 @@ function expandQuery(expansionTerms, primaryResults, index, stateDb2) {
11206
11206
  }
11207
11207
 
11208
11208
  // src/tools/read/query.ts
11209
+ init_wikilinkFeedback();
11210
+ init_cooccurrence();
11211
+ init_wikilinks();
11212
+ init_recency();
11213
+ init_edgeWeights();
11214
+
11215
+ // src/core/read/snippets.ts
11216
+ init_embeddings();
11217
+ init_stemmer();
11218
+ import * as fs12 from "fs";
11219
+ function stripFrontmatter(content) {
11220
+ const match = content.match(/^---[\s\S]*?---\n([\s\S]*)$/);
11221
+ return match ? match[1] : content;
11222
+ }
11223
+ function splitIntoParagraphs(content, maxChunkChars) {
11224
+ const MIN_PARAGRAPH_CHARS = 50;
11225
+ const raw = content.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
11226
+ const merged = [];
11227
+ let buffer2 = "";
11228
+ for (const paragraph of raw) {
11229
+ if (buffer2) {
11230
+ buffer2 += "\n\n" + paragraph;
11231
+ if (buffer2.length >= MIN_PARAGRAPH_CHARS) {
11232
+ merged.push(buffer2.slice(0, maxChunkChars));
11233
+ buffer2 = "";
11234
+ }
11235
+ } else if (paragraph.length < MIN_PARAGRAPH_CHARS) {
11236
+ buffer2 = paragraph;
11237
+ } else {
11238
+ merged.push(paragraph.slice(0, maxChunkChars));
11239
+ }
11240
+ }
11241
+ if (buffer2) {
11242
+ merged.push(buffer2.slice(0, maxChunkChars));
11243
+ }
11244
+ return merged;
11245
+ }
11246
+ function scoreByKeywords(chunk, queryTokens, queryStems) {
11247
+ const chunkTokens = new Set(tokenize(chunk.toLowerCase()));
11248
+ const chunkStems = new Set([...chunkTokens].map((t) => stem(t)));
11249
+ let score = 0;
11250
+ for (let i = 0; i < queryTokens.length; i++) {
11251
+ if (chunkTokens.has(queryTokens[i])) {
11252
+ score += 10;
11253
+ } else if (chunkStems.has(queryStems[i])) {
11254
+ score += 5;
11255
+ }
11256
+ }
11257
+ return score;
11258
+ }
11259
+ async function extractBestSnippets(filePath, queryEmbedding, queryTokens, options) {
11260
+ const maxSnippets = options?.maxSnippets ?? 1;
11261
+ const maxChunkChars = options?.maxChunkChars ?? 500;
11262
+ let content;
11263
+ try {
11264
+ content = fs12.readFileSync(filePath, "utf-8");
11265
+ } catch {
11266
+ return [];
11267
+ }
11268
+ const body = stripFrontmatter(content);
11269
+ if (body.length < 50) {
11270
+ return body.length > 0 ? [{ text: body, score: 1 }] : [];
11271
+ }
11272
+ const paragraphs = splitIntoParagraphs(body, maxChunkChars);
11273
+ if (paragraphs.length === 0) return [];
11274
+ const queryStems = queryTokens.map((t) => stem(t));
11275
+ const scored = paragraphs.map((text, idx) => ({
11276
+ text,
11277
+ idx,
11278
+ keywordScore: scoreByKeywords(text, queryTokens, queryStems)
11279
+ }));
11280
+ scored.sort((a, b) => b.keywordScore - a.keywordScore);
11281
+ const topKeyword = scored.slice(0, 5);
11282
+ if (queryEmbedding && hasEmbeddingsIndex()) {
11283
+ try {
11284
+ const reranked = [];
11285
+ for (const chunk of topKeyword) {
11286
+ const chunkEmbedding = await embedTextCached(chunk.text);
11287
+ const sim = cosineSimilarity(queryEmbedding, chunkEmbedding);
11288
+ reranked.push({ text: chunk.text, score: sim });
11289
+ }
11290
+ reranked.sort((a, b) => b.score - a.score);
11291
+ return reranked.slice(0, maxSnippets);
11292
+ } catch {
11293
+ }
11294
+ }
11295
+ return topKeyword.slice(0, maxSnippets).map((c) => ({
11296
+ text: c.text,
11297
+ score: c.keywordScore
11298
+ }));
11299
+ }
11300
+
11301
+ // src/tools/read/query.ts
11302
+ init_stemmer();
11303
+ function applyGraphReranking(results, stateDb2) {
11304
+ if (!stateDb2) return;
11305
+ const cooccurrenceIndex2 = getCooccurrenceIndex();
11306
+ const recencyIndex2 = loadRecencyFromStateDb();
11307
+ const feedbackBoosts = getAllFeedbackBoosts(stateDb2);
11308
+ const edgeWeightMap = getEntityEdgeWeightMap(stateDb2);
11309
+ if (!cooccurrenceIndex2 && !recencyIndex2) return;
11310
+ const seedEntities = /* @__PURE__ */ new Set();
11311
+ for (const r of results) {
11312
+ const name = r.title || r.path?.replace(/\.md$/, "").split("/").pop() || "";
11313
+ if (name) seedEntities.add(name);
11314
+ }
11315
+ for (const r of results) {
11316
+ const name = r.title || r.path?.replace(/\.md$/, "").split("/").pop() || "";
11317
+ let graphBoost = 0;
11318
+ if (cooccurrenceIndex2) graphBoost += getCooccurrenceBoost(name, seedEntities, cooccurrenceIndex2, recencyIndex2);
11319
+ if (recencyIndex2) graphBoost += getRecencyBoost(name, recencyIndex2);
11320
+ graphBoost += feedbackBoosts.get(name) ?? 0;
11321
+ const avgWeight = edgeWeightMap.get(name.toLowerCase());
11322
+ if (avgWeight && avgWeight > 1) graphBoost += Math.min((avgWeight - 1) * 3, 6);
11323
+ if (graphBoost > 0) {
11324
+ r.graph_boost = graphBoost;
11325
+ const baseScore = r.rrf_score ?? 0;
11326
+ r._combined_score = baseScore + graphBoost / 50;
11327
+ }
11328
+ }
11329
+ results.sort(
11330
+ (a, b) => (b._combined_score ?? b.rrf_score ?? 0) - (a._combined_score ?? a.rrf_score ?? 0)
11331
+ );
11332
+ }
11333
+ async function enhanceSnippets(results, query, vaultPath2) {
11334
+ if (!hasEmbeddingsIndex()) return;
11335
+ const queryTokens = tokenize(query).map((t) => t.toLowerCase());
11336
+ let queryEmb = null;
11337
+ try {
11338
+ queryEmb = await embedTextCached(query);
11339
+ } catch {
11340
+ }
11341
+ for (const r of results) {
11342
+ if (!r.path) continue;
11343
+ try {
11344
+ const snippets = await extractBestSnippets(`${vaultPath2}/${r.path}`, queryEmb, queryTokens);
11345
+ if (snippets.length > 0 && snippets[0].text.length > 0) {
11346
+ r.snippet = snippets[0].text;
11347
+ }
11348
+ } catch {
11349
+ }
11350
+ }
11351
+ }
11209
11352
  function matchesFrontmatter(note, where) {
11210
11353
  for (const [key, value] of Object.entries(where)) {
11211
11354
  const noteValue = note.frontmatter[key];
@@ -11469,6 +11612,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
11469
11612
  const expansionTerms2 = extractExpansionTerms(results2, query, index);
11470
11613
  const expansionResults2 = expandQuery(expansionTerms2, [...results2, ...hopResults2], index, stateDb2);
11471
11614
  results2.push(...hopResults2, ...expansionResults2);
11615
+ applyGraphReranking(results2, stateDb2);
11616
+ await enhanceSnippets(results2, query, vaultPath2);
11472
11617
  return { content: [{ type: "text", text: JSON.stringify({
11473
11618
  method: "hybrid",
11474
11619
  query,
@@ -11498,6 +11643,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
11498
11643
  const expansionTerms2 = extractExpansionTerms(results2, query, index);
11499
11644
  const expansionResults2 = expandQuery(expansionTerms2, [...results2, ...hopResults2], index, stateDb2);
11500
11645
  results2.push(...hopResults2, ...expansionResults2);
11646
+ applyGraphReranking(results2, stateDb2);
11647
+ await enhanceSnippets(results2, query, vaultPath2);
11501
11648
  return { content: [{ type: "text", text: JSON.stringify({
11502
11649
  method: "fts5",
11503
11650
  query,
@@ -11512,6 +11659,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
11512
11659
  const expansionTerms = extractExpansionTerms(results, query, index);
11513
11660
  const expansionResults = expandQuery(expansionTerms, [...results, ...hopResults], index, stateDbFts);
11514
11661
  results.push(...hopResults, ...expansionResults);
11662
+ applyGraphReranking(results, stateDbFts);
11663
+ await enhanceSnippets(results, query, vaultPath2);
11515
11664
  return { content: [{ type: "text", text: JSON.stringify({
11516
11665
  method: "fts5",
11517
11666
  query,
@@ -11525,7 +11674,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
11525
11674
  }
11526
11675
 
11527
11676
  // src/tools/read/system.ts
11528
- import * as fs12 from "fs";
11677
+ import * as fs13 from "fs";
11529
11678
  import * as path15 from "path";
11530
11679
  import { z as z5 } from "zod";
11531
11680
  import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
@@ -11840,7 +11989,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
11840
11989
  }
11841
11990
  try {
11842
11991
  const fullPath = path15.join(vaultPath2, note.path);
11843
- const content = await fs12.promises.readFile(fullPath, "utf-8");
11992
+ const content = await fs13.promises.readFile(fullPath, "utf-8");
11844
11993
  const lines = content.split("\n");
11845
11994
  for (let i = 0; i < lines.length; i++) {
11846
11995
  const line = lines[i];
@@ -12099,7 +12248,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
12099
12248
  import { z as z6 } from "zod";
12100
12249
 
12101
12250
  // src/tools/read/structure.ts
12102
- import * as fs13 from "fs";
12251
+ import * as fs14 from "fs";
12103
12252
  import * as path16 from "path";
12104
12253
  var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
12105
12254
  function extractHeadings2(content) {
@@ -12157,7 +12306,7 @@ async function getNoteStructure(index, notePath, vaultPath2) {
12157
12306
  const absolutePath = path16.join(vaultPath2, notePath);
12158
12307
  let content;
12159
12308
  try {
12160
- content = await fs13.promises.readFile(absolutePath, "utf-8");
12309
+ content = await fs14.promises.readFile(absolutePath, "utf-8");
12161
12310
  } catch {
12162
12311
  return null;
12163
12312
  }
@@ -12180,7 +12329,7 @@ async function getSectionContent(index, notePath, headingText, vaultPath2, inclu
12180
12329
  const absolutePath = path16.join(vaultPath2, notePath);
12181
12330
  let content;
12182
12331
  try {
12183
- content = await fs13.promises.readFile(absolutePath, "utf-8");
12332
+ content = await fs14.promises.readFile(absolutePath, "utf-8");
12184
12333
  } catch {
12185
12334
  return null;
12186
12335
  }
@@ -12222,7 +12371,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
12222
12371
  const absolutePath = path16.join(vaultPath2, note.path);
12223
12372
  let content;
12224
12373
  try {
12225
- content = await fs13.promises.readFile(absolutePath, "utf-8");
12374
+ content = await fs14.promises.readFile(absolutePath, "utf-8");
12226
12375
  } catch {
12227
12376
  continue;
12228
12377
  }
@@ -12546,7 +12695,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
12546
12695
 
12547
12696
  // src/tools/read/migrations.ts
12548
12697
  import { z as z7 } from "zod";
12549
- import * as fs14 from "fs/promises";
12698
+ import * as fs15 from "fs/promises";
12550
12699
  import * as path17 from "path";
12551
12700
  import matter2 from "gray-matter";
12552
12701
  function getNotesInFolder(index, folder) {
@@ -12562,7 +12711,7 @@ function getNotesInFolder(index, folder) {
12562
12711
  async function readFileContent(notePath, vaultPath2) {
12563
12712
  const fullPath = path17.join(vaultPath2, notePath);
12564
12713
  try {
12565
- return await fs14.readFile(fullPath, "utf-8");
12714
+ return await fs15.readFile(fullPath, "utf-8");
12566
12715
  } catch {
12567
12716
  return null;
12568
12717
  }
@@ -12570,7 +12719,7 @@ async function readFileContent(notePath, vaultPath2) {
12570
12719
  async function writeFileContent(notePath, vaultPath2, content) {
12571
12720
  const fullPath = path17.join(vaultPath2, notePath);
12572
12721
  try {
12573
- await fs14.writeFile(fullPath, content, "utf-8");
12722
+ await fs15.writeFile(fullPath, content, "utf-8");
12574
12723
  return true;
12575
12724
  } catch {
12576
12725
  return false;
@@ -12748,7 +12897,7 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
12748
12897
  }
12749
12898
 
12750
12899
  // src/tools/read/graphAnalysis.ts
12751
- import fs15 from "node:fs";
12900
+ import fs16 from "node:fs";
12752
12901
  import path18 from "node:path";
12753
12902
  import { z as z8 } from "zod";
12754
12903
 
@@ -13462,7 +13611,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb2
13462
13611
  const scored = allNotes.map((note) => {
13463
13612
  let wordCount = 0;
13464
13613
  try {
13465
- const content = fs15.readFileSync(path18.join(vaultPath2, note.path), "utf-8");
13614
+ const content = fs16.readFileSync(path18.join(vaultPath2, note.path), "utf-8");
13466
13615
  const body = content.replace(/^---[\s\S]*?---\n?/, "");
13467
13616
  wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
13468
13617
  } catch {
@@ -14092,7 +14241,7 @@ function registerSemanticAnalysisTools(server2, getIndex, getVaultPath) {
14092
14241
  import { z as z11 } from "zod";
14093
14242
 
14094
14243
  // src/tools/read/bidirectional.ts
14095
- import * as fs16 from "fs/promises";
14244
+ import * as fs17 from "fs/promises";
14096
14245
  import * as path19 from "path";
14097
14246
  import matter3 from "gray-matter";
14098
14247
  var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
@@ -14100,7 +14249,7 @@ var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
14100
14249
  async function readFileContent2(notePath, vaultPath2) {
14101
14250
  const fullPath = path19.join(vaultPath2, notePath);
14102
14251
  try {
14103
- return await fs16.readFile(fullPath, "utf-8");
14252
+ return await fs17.readFile(fullPath, "utf-8");
14104
14253
  } catch {
14105
14254
  return null;
14106
14255
  }
@@ -14276,13 +14425,13 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
14276
14425
  }
14277
14426
 
14278
14427
  // src/tools/read/computed.ts
14279
- import * as fs17 from "fs/promises";
14428
+ import * as fs18 from "fs/promises";
14280
14429
  import * as path20 from "path";
14281
14430
  import matter4 from "gray-matter";
14282
14431
  async function readFileContent3(notePath, vaultPath2) {
14283
14432
  const fullPath = path20.join(vaultPath2, notePath);
14284
14433
  try {
14285
- return await fs17.readFile(fullPath, "utf-8");
14434
+ return await fs18.readFile(fullPath, "utf-8");
14286
14435
  } catch {
14287
14436
  return null;
14288
14437
  }
@@ -14290,7 +14439,7 @@ async function readFileContent3(notePath, vaultPath2) {
14290
14439
  async function getFileStats(notePath, vaultPath2) {
14291
14440
  const fullPath = path20.join(vaultPath2, notePath);
14292
14441
  try {
14293
- const stats = await fs17.stat(fullPath);
14442
+ const stats = await fs18.stat(fullPath);
14294
14443
  return {
14295
14444
  modified: stats.mtime,
14296
14445
  created: stats.birthtime
@@ -14420,7 +14569,7 @@ async function computeFrontmatter(index, notePath, vaultPath2, fields) {
14420
14569
 
14421
14570
  // src/tools/read/noteIntelligence.ts
14422
14571
  init_embeddings();
14423
- import fs18 from "node:fs";
14572
+ import fs19 from "node:fs";
14424
14573
  import nodePath from "node:path";
14425
14574
  function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfig2) {
14426
14575
  server2.registerTool(
@@ -14480,7 +14629,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
14480
14629
  }
14481
14630
  let noteContent;
14482
14631
  try {
14483
- noteContent = fs18.readFileSync(nodePath.join(vaultPath2, notePath), "utf-8");
14632
+ noteContent = fs19.readFileSync(nodePath.join(vaultPath2, notePath), "utf-8");
14484
14633
  } catch {
14485
14634
  return {
14486
14635
  content: [{ type: "text", text: JSON.stringify({
@@ -14559,7 +14708,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
14559
14708
  // src/tools/write/mutations.ts
14560
14709
  init_writer();
14561
14710
  import { z as z12 } from "zod";
14562
- import fs21 from "fs/promises";
14711
+ import fs22 from "fs/promises";
14563
14712
  import path23 from "path";
14564
14713
 
14565
14714
  // src/core/write/validator.ts
@@ -14778,7 +14927,7 @@ init_constants();
14778
14927
  init_writer();
14779
14928
  init_wikilinks();
14780
14929
  init_wikilinkFeedback();
14781
- import fs20 from "fs/promises";
14930
+ import fs21 from "fs/promises";
14782
14931
  import path22 from "path";
14783
14932
  function formatMcpResult(result) {
14784
14933
  if (result.tokensEstimate === void 0 || result.tokensEstimate === 0) {
@@ -14828,7 +14977,7 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
14828
14977
  async function getPolicyHint(vaultPath2) {
14829
14978
  try {
14830
14979
  const policiesDir = path22.join(vaultPath2, ".claude", "policies");
14831
- const files = await fs20.readdir(policiesDir);
14980
+ const files = await fs21.readdir(policiesDir);
14832
14981
  const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
14833
14982
  if (yamlFiles.length > 0) {
14834
14983
  const names = yamlFiles.map((f) => f.replace(/\.ya?ml$/, "")).join(", ");
@@ -14841,7 +14990,7 @@ async function getPolicyHint(vaultPath2) {
14841
14990
  async function ensureFileExists(vaultPath2, notePath) {
14842
14991
  const fullPath = path22.join(vaultPath2, notePath);
14843
14992
  try {
14844
- await fs20.access(fullPath);
14993
+ await fs21.access(fullPath);
14845
14994
  return null;
14846
14995
  } catch {
14847
14996
  const hint = await getPolicyHint(vaultPath2);
@@ -15020,14 +15169,14 @@ async function executeCreateNote(options) {
15020
15169
  const fullPath = path22.join(vaultPath2, notePath);
15021
15170
  let fileExists = false;
15022
15171
  try {
15023
- await fs20.access(fullPath);
15172
+ await fs21.access(fullPath);
15024
15173
  fileExists = true;
15025
15174
  } catch {
15026
15175
  }
15027
15176
  if (fileExists && !overwrite) {
15028
15177
  return { success: false, result: errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`), filesWritten: [] };
15029
15178
  }
15030
- await fs20.mkdir(path22.dirname(fullPath), { recursive: true });
15179
+ await fs21.mkdir(path22.dirname(fullPath), { recursive: true });
15031
15180
  const { maybeApplyWikilinks: maybeApplyWikilinks2 } = await Promise.resolve().then(() => (init_wikilinks(), wikilinks_exports));
15032
15181
  const { content: processedContent } = maybeApplyWikilinks2(content, skipWikilinks ?? false, notePath);
15033
15182
  let finalFrontmatter = frontmatter;
@@ -15063,11 +15212,11 @@ async function executeDeleteNote(options) {
15063
15212
  }
15064
15213
  const fullPath = path22.join(vaultPath2, notePath);
15065
15214
  try {
15066
- await fs20.access(fullPath);
15215
+ await fs21.access(fullPath);
15067
15216
  } catch {
15068
15217
  return { success: false, result: errorResult(notePath, `File not found: ${notePath}`), filesWritten: [] };
15069
15218
  }
15070
- await fs20.unlink(fullPath);
15219
+ await fs21.unlink(fullPath);
15071
15220
  const result = successResult(notePath, `Deleted note: ${notePath}`, {});
15072
15221
  return { success: true, result, filesWritten: [notePath] };
15073
15222
  } catch (error) {
@@ -15086,7 +15235,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
15086
15235
  throw new Error(`Path blocked: ${validation.reason}`);
15087
15236
  }
15088
15237
  const fullPath = path23.join(vaultPath2, notePath);
15089
- await fs21.mkdir(path23.dirname(fullPath), { recursive: true });
15238
+ await fs22.mkdir(path23.dirname(fullPath), { recursive: true });
15090
15239
  const templates = config.templates || {};
15091
15240
  const filename = path23.basename(notePath, ".md").toLowerCase();
15092
15241
  let templatePath;
@@ -15110,7 +15259,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
15110
15259
  if (templatePath) {
15111
15260
  try {
15112
15261
  const absTemplatePath = path23.join(vaultPath2, templatePath);
15113
- templateContent = await fs21.readFile(absTemplatePath, "utf-8");
15262
+ templateContent = await fs22.readFile(absTemplatePath, "utf-8");
15114
15263
  } catch {
15115
15264
  const title = path23.basename(notePath, ".md");
15116
15265
  templateContent = `---
@@ -15173,7 +15322,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
15173
15322
  if (create_if_missing && !dry_run) {
15174
15323
  const fullPath = path23.join(vaultPath2, notePath);
15175
15324
  try {
15176
- await fs21.access(fullPath);
15325
+ await fs22.access(fullPath);
15177
15326
  } catch {
15178
15327
  const config = getConfig2();
15179
15328
  const result = await createNoteFromTemplate(vaultPath2, notePath, config);
@@ -15184,7 +15333,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
15184
15333
  if (create_if_missing && dry_run) {
15185
15334
  const fullPath = path23.join(vaultPath2, notePath);
15186
15335
  try {
15187
- await fs21.access(fullPath);
15336
+ await fs22.access(fullPath);
15188
15337
  } catch {
15189
15338
  return {
15190
15339
  content: [{
@@ -15669,7 +15818,7 @@ Example: vault_update_frontmatter({ path: "projects/alpha.md", frontmatter: { st
15669
15818
  init_writer();
15670
15819
  init_wikilinks();
15671
15820
  import { z as z15 } from "zod";
15672
- import fs22 from "fs/promises";
15821
+ import fs23 from "fs/promises";
15673
15822
  import path24 from "path";
15674
15823
  function registerNoteTools(server2, getVaultPath, getIndex) {
15675
15824
  server2.tool(
@@ -15702,13 +15851,13 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
15702
15851
  return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
15703
15852
  }
15704
15853
  const dir = path24.dirname(fullPath);
15705
- await fs22.mkdir(dir, { recursive: true });
15854
+ await fs23.mkdir(dir, { recursive: true });
15706
15855
  let effectiveContent = content;
15707
15856
  let effectiveFrontmatter = frontmatter;
15708
15857
  if (template) {
15709
15858
  const templatePath = path24.join(vaultPath2, template);
15710
15859
  try {
15711
- const raw = await fs22.readFile(templatePath, "utf-8");
15860
+ const raw = await fs23.readFile(templatePath, "utf-8");
15712
15861
  const matter9 = (await import("gray-matter")).default;
15713
15862
  const parsed = matter9(raw);
15714
15863
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -15873,7 +16022,7 @@ ${sources}`;
15873
16022
  return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
15874
16023
  }
15875
16024
  const fullPath = path24.join(vaultPath2, notePath);
15876
- await fs22.unlink(fullPath);
16025
+ await fs23.unlink(fullPath);
15877
16026
  const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
15878
16027
  const message = backlinkWarning ? `Deleted note: ${notePath}
15879
16028
 
@@ -15893,7 +16042,7 @@ init_writer();
15893
16042
  init_git();
15894
16043
  init_wikilinks();
15895
16044
  import { z as z16 } from "zod";
15896
- import fs23 from "fs/promises";
16045
+ import fs24 from "fs/promises";
15897
16046
  import path25 from "path";
15898
16047
  import matter6 from "gray-matter";
15899
16048
  function escapeRegex(str) {
@@ -15920,7 +16069,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
15920
16069
  const allTargets = [targetTitle, ...targetAliases].map((t) => t.toLowerCase());
15921
16070
  async function scanDir(dir) {
15922
16071
  const files = [];
15923
- const entries = await fs23.readdir(dir, { withFileTypes: true });
16072
+ const entries = await fs24.readdir(dir, { withFileTypes: true });
15924
16073
  for (const entry of entries) {
15925
16074
  const fullPath = path25.join(dir, entry.name);
15926
16075
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
@@ -15934,7 +16083,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
15934
16083
  const allFiles = await scanDir(vaultPath2);
15935
16084
  for (const filePath of allFiles) {
15936
16085
  const relativePath = path25.relative(vaultPath2, filePath);
15937
- const content = await fs23.readFile(filePath, "utf-8");
16086
+ const content = await fs24.readFile(filePath, "utf-8");
15938
16087
  const wikilinks = extractWikilinks2(content);
15939
16088
  const matchingLinks = [];
15940
16089
  for (const link of wikilinks) {
@@ -16023,7 +16172,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16023
16172
  const oldFullPath = path25.join(vaultPath2, oldPath);
16024
16173
  const newFullPath = path25.join(vaultPath2, newPath);
16025
16174
  try {
16026
- await fs23.access(oldFullPath);
16175
+ await fs24.access(oldFullPath);
16027
16176
  } catch {
16028
16177
  const result2 = {
16029
16178
  success: false,
@@ -16033,7 +16182,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16033
16182
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
16034
16183
  }
16035
16184
  try {
16036
- await fs23.access(newFullPath);
16185
+ await fs24.access(newFullPath);
16037
16186
  const result2 = {
16038
16187
  success: false,
16039
16188
  message: `Destination already exists: ${newPath}`,
@@ -16042,7 +16191,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16042
16191
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
16043
16192
  } catch {
16044
16193
  }
16045
- const sourceContent = await fs23.readFile(oldFullPath, "utf-8");
16194
+ const sourceContent = await fs24.readFile(oldFullPath, "utf-8");
16046
16195
  const parsed = matter6(sourceContent);
16047
16196
  const aliases = extractAliases2(parsed.data);
16048
16197
  const oldTitle = getTitleFromPath(oldPath);
@@ -16103,8 +16252,8 @@ function registerMoveNoteTools(server2, getVaultPath) {
16103
16252
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
16104
16253
  }
16105
16254
  const destDir = path25.dirname(newFullPath);
16106
- await fs23.mkdir(destDir, { recursive: true });
16107
- await fs23.rename(oldFullPath, newFullPath);
16255
+ await fs24.mkdir(destDir, { recursive: true });
16256
+ await fs24.rename(oldFullPath, newFullPath);
16108
16257
  let gitCommit;
16109
16258
  let undoAvailable;
16110
16259
  let staleLockDetected;
@@ -16183,7 +16332,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16183
16332
  const newPath = dir === "." ? `${sanitizedTitle}.md` : path25.join(dir, `${sanitizedTitle}.md`);
16184
16333
  const newFullPath = path25.join(vaultPath2, newPath);
16185
16334
  try {
16186
- await fs23.access(fullPath);
16335
+ await fs24.access(fullPath);
16187
16336
  } catch {
16188
16337
  const result2 = {
16189
16338
  success: false,
@@ -16194,7 +16343,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16194
16343
  }
16195
16344
  if (fullPath !== newFullPath) {
16196
16345
  try {
16197
- await fs23.access(newFullPath);
16346
+ await fs24.access(newFullPath);
16198
16347
  const result2 = {
16199
16348
  success: false,
16200
16349
  message: `A note with this title already exists: ${newPath}`,
@@ -16204,7 +16353,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16204
16353
  } catch {
16205
16354
  }
16206
16355
  }
16207
- const sourceContent = await fs23.readFile(fullPath, "utf-8");
16356
+ const sourceContent = await fs24.readFile(fullPath, "utf-8");
16208
16357
  const parsed = matter6(sourceContent);
16209
16358
  const aliases = extractAliases2(parsed.data);
16210
16359
  const oldTitle = getTitleFromPath(notePath);
@@ -16264,7 +16413,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16264
16413
  return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
16265
16414
  }
16266
16415
  if (fullPath !== newFullPath) {
16267
- await fs23.rename(fullPath, newFullPath);
16416
+ await fs24.rename(fullPath, newFullPath);
16268
16417
  }
16269
16418
  let gitCommit;
16270
16419
  let undoAvailable;
@@ -16312,7 +16461,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
16312
16461
  init_writer();
16313
16462
  init_wikilinks();
16314
16463
  import { z as z17 } from "zod";
16315
- import fs24 from "fs/promises";
16464
+ import fs25 from "fs/promises";
16316
16465
  function registerMergeTools(server2, getVaultPath) {
16317
16466
  server2.tool(
16318
16467
  "merge_entities",
@@ -16439,7 +16588,7 @@ ${trimmedSource}`;
16439
16588
  }
16440
16589
  await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter, "LF", targetContentHash);
16441
16590
  const fullSourcePath = `${vaultPath2}/${source_path}`;
16442
- await fs24.unlink(fullSourcePath);
16591
+ await fs25.unlink(fullSourcePath);
16443
16592
  initializeEntityIndex(vaultPath2).catch((err) => {
16444
16593
  console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
16445
16594
  });
@@ -16711,7 +16860,7 @@ init_schema();
16711
16860
 
16712
16861
  // src/core/write/policy/parser.ts
16713
16862
  init_schema();
16714
- import fs25 from "fs/promises";
16863
+ import fs26 from "fs/promises";
16715
16864
  import path26 from "path";
16716
16865
  import matter7 from "gray-matter";
16717
16866
  function parseYaml(content) {
@@ -16737,7 +16886,7 @@ function parsePolicyString(yamlContent) {
16737
16886
  }
16738
16887
  async function loadPolicyFile(filePath) {
16739
16888
  try {
16740
- const content = await fs25.readFile(filePath, "utf-8");
16889
+ const content = await fs26.readFile(filePath, "utf-8");
16741
16890
  return parsePolicyString(content);
16742
16891
  } catch (error) {
16743
16892
  if (error.code === "ENOENT") {
@@ -16764,12 +16913,12 @@ async function loadPolicy(vaultPath2, policyName) {
16764
16913
  const policiesDir = path26.join(vaultPath2, ".claude", "policies");
16765
16914
  const policyPath = path26.join(policiesDir, `${policyName}.yaml`);
16766
16915
  try {
16767
- await fs25.access(policyPath);
16916
+ await fs26.access(policyPath);
16768
16917
  return loadPolicyFile(policyPath);
16769
16918
  } catch {
16770
16919
  const ymlPath = path26.join(policiesDir, `${policyName}.yml`);
16771
16920
  try {
16772
- await fs25.access(ymlPath);
16921
+ await fs26.access(ymlPath);
16773
16922
  return loadPolicyFile(ymlPath);
16774
16923
  } catch {
16775
16924
  return {
@@ -16909,7 +17058,7 @@ init_schema();
16909
17058
  init_writer();
16910
17059
  init_git();
16911
17060
  init_wikilinks();
16912
- import fs27 from "fs/promises";
17061
+ import fs28 from "fs/promises";
16913
17062
  import path28 from "path";
16914
17063
  init_constants();
16915
17064
  async function executeStep(step, vaultPath2, context, conditionResults, searchFn) {
@@ -17120,7 +17269,7 @@ async function executeToggleTask(params, vaultPath2) {
17120
17269
  const section = params.section ? String(params.section) : void 0;
17121
17270
  const fullPath = path28.join(vaultPath2, notePath);
17122
17271
  try {
17123
- await fs27.access(fullPath);
17272
+ await fs28.access(fullPath);
17124
17273
  } catch {
17125
17274
  return { success: false, message: `File not found: ${notePath}`, path: notePath };
17126
17275
  }
@@ -17404,12 +17553,12 @@ async function rollbackChanges(vaultPath2, originalContents, filesModified) {
17404
17553
  const fullPath = path28.join(vaultPath2, filePath);
17405
17554
  if (original === null) {
17406
17555
  try {
17407
- await fs27.unlink(fullPath);
17556
+ await fs28.unlink(fullPath);
17408
17557
  } catch {
17409
17558
  }
17410
17559
  } else if (original !== void 0) {
17411
17560
  try {
17412
- await fs27.writeFile(fullPath, original);
17561
+ await fs28.writeFile(fullPath, original);
17413
17562
  } catch {
17414
17563
  }
17415
17564
  }
@@ -17455,27 +17604,27 @@ async function previewPolicy(policy, vaultPath2, variables) {
17455
17604
  }
17456
17605
 
17457
17606
  // src/core/write/policy/storage.ts
17458
- import fs28 from "fs/promises";
17607
+ import fs29 from "fs/promises";
17459
17608
  import path29 from "path";
17460
17609
  function getPoliciesDir(vaultPath2) {
17461
17610
  return path29.join(vaultPath2, ".claude", "policies");
17462
17611
  }
17463
17612
  async function ensurePoliciesDir(vaultPath2) {
17464
17613
  const dir = getPoliciesDir(vaultPath2);
17465
- await fs28.mkdir(dir, { recursive: true });
17614
+ await fs29.mkdir(dir, { recursive: true });
17466
17615
  }
17467
17616
  async function listPolicies(vaultPath2) {
17468
17617
  const dir = getPoliciesDir(vaultPath2);
17469
17618
  const policies = [];
17470
17619
  try {
17471
- const files = await fs28.readdir(dir);
17620
+ const files = await fs29.readdir(dir);
17472
17621
  for (const file of files) {
17473
17622
  if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
17474
17623
  continue;
17475
17624
  }
17476
17625
  const filePath = path29.join(dir, file);
17477
- const stat5 = await fs28.stat(filePath);
17478
- const content = await fs28.readFile(filePath, "utf-8");
17626
+ const stat5 = await fs29.stat(filePath);
17627
+ const content = await fs29.readFile(filePath, "utf-8");
17479
17628
  const metadata = extractPolicyMetadata(content);
17480
17629
  policies.push({
17481
17630
  name: metadata.name || file.replace(/\.ya?ml$/, ""),
@@ -17501,7 +17650,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
17501
17650
  const filePath = path29.join(dir, filename);
17502
17651
  if (!overwrite) {
17503
17652
  try {
17504
- await fs28.access(filePath);
17653
+ await fs29.access(filePath);
17505
17654
  return {
17506
17655
  success: false,
17507
17656
  path: filename,
@@ -17518,7 +17667,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
17518
17667
  message: `Invalid policy: ${validation.errors.map((e) => e.message).join("; ")}`
17519
17668
  };
17520
17669
  }
17521
- await fs28.writeFile(filePath, content, "utf-8");
17670
+ await fs29.writeFile(filePath, content, "utf-8");
17522
17671
  return {
17523
17672
  success: true,
17524
17673
  path: filename,
@@ -18043,7 +18192,7 @@ function registerPolicyTools(server2, getVaultPath, getSearchFn) {
18043
18192
  import { z as z21 } from "zod";
18044
18193
 
18045
18194
  // src/core/write/tagRename.ts
18046
- import * as fs29 from "fs/promises";
18195
+ import * as fs30 from "fs/promises";
18047
18196
  import * as path30 from "path";
18048
18197
  import matter8 from "gray-matter";
18049
18198
  import { getProtectedZones } from "@velvetmonkey/vault-core";
@@ -18153,7 +18302,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
18153
18302
  const fullPath = path30.join(vaultPath2, note.path);
18154
18303
  let fileContent;
18155
18304
  try {
18156
- fileContent = await fs29.readFile(fullPath, "utf-8");
18305
+ fileContent = await fs30.readFile(fullPath, "utf-8");
18157
18306
  } catch {
18158
18307
  continue;
18159
18308
  }
@@ -18226,7 +18375,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
18226
18375
  previews.push(preview);
18227
18376
  if (!dryRun) {
18228
18377
  const newContent = matter8.stringify(updatedContent, fm);
18229
- await fs29.writeFile(fullPath, newContent, "utf-8");
18378
+ await fs30.writeFile(fullPath, newContent, "utf-8");
18230
18379
  }
18231
18380
  }
18232
18381
  }
@@ -19158,92 +19307,6 @@ function selectByMmr(candidates, limit, lambda = 0.7) {
19158
19307
  return selected;
19159
19308
  }
19160
19309
 
19161
- // src/core/read/snippets.ts
19162
- init_embeddings();
19163
- init_stemmer();
19164
- import * as fs30 from "fs";
19165
- function stripFrontmatter(content) {
19166
- const match = content.match(/^---[\s\S]*?---\n([\s\S]*)$/);
19167
- return match ? match[1] : content;
19168
- }
19169
- function splitIntoParagraphs(content, maxChunkChars) {
19170
- const MIN_PARAGRAPH_CHARS = 50;
19171
- const raw = content.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
19172
- const merged = [];
19173
- let buffer2 = "";
19174
- for (const paragraph of raw) {
19175
- if (buffer2) {
19176
- buffer2 += "\n\n" + paragraph;
19177
- if (buffer2.length >= MIN_PARAGRAPH_CHARS) {
19178
- merged.push(buffer2.slice(0, maxChunkChars));
19179
- buffer2 = "";
19180
- }
19181
- } else if (paragraph.length < MIN_PARAGRAPH_CHARS) {
19182
- buffer2 = paragraph;
19183
- } else {
19184
- merged.push(paragraph.slice(0, maxChunkChars));
19185
- }
19186
- }
19187
- if (buffer2) {
19188
- merged.push(buffer2.slice(0, maxChunkChars));
19189
- }
19190
- return merged;
19191
- }
19192
- function scoreByKeywords(chunk, queryTokens, queryStems) {
19193
- const chunkTokens = new Set(tokenize(chunk.toLowerCase()));
19194
- const chunkStems = new Set([...chunkTokens].map((t) => stem(t)));
19195
- let score = 0;
19196
- for (let i = 0; i < queryTokens.length; i++) {
19197
- if (chunkTokens.has(queryTokens[i])) {
19198
- score += 10;
19199
- } else if (chunkStems.has(queryStems[i])) {
19200
- score += 5;
19201
- }
19202
- }
19203
- return score;
19204
- }
19205
- async function extractBestSnippets(filePath, queryEmbedding, queryTokens, options) {
19206
- const maxSnippets = options?.maxSnippets ?? 1;
19207
- const maxChunkChars = options?.maxChunkChars ?? 500;
19208
- let content;
19209
- try {
19210
- content = fs30.readFileSync(filePath, "utf-8");
19211
- } catch {
19212
- return [];
19213
- }
19214
- const body = stripFrontmatter(content);
19215
- if (body.length < 50) {
19216
- return body.length > 0 ? [{ text: body, score: 1 }] : [];
19217
- }
19218
- const paragraphs = splitIntoParagraphs(body, maxChunkChars);
19219
- if (paragraphs.length === 0) return [];
19220
- const queryStems = queryTokens.map((t) => stem(t));
19221
- const scored = paragraphs.map((text, idx) => ({
19222
- text,
19223
- idx,
19224
- keywordScore: scoreByKeywords(text, queryTokens, queryStems)
19225
- }));
19226
- scored.sort((a, b) => b.keywordScore - a.keywordScore);
19227
- const topKeyword = scored.slice(0, 5);
19228
- if (queryEmbedding && hasEmbeddingsIndex()) {
19229
- try {
19230
- const reranked = [];
19231
- for (const chunk of topKeyword) {
19232
- const chunkEmbedding = await embedTextCached(chunk.text);
19233
- const sim = cosineSimilarity(queryEmbedding, chunkEmbedding);
19234
- reranked.push({ text: chunk.text, score: sim });
19235
- }
19236
- reranked.sort((a, b) => b.score - a.score);
19237
- return reranked.slice(0, maxSnippets);
19238
- } catch {
19239
- }
19240
- }
19241
- return topKeyword.slice(0, maxSnippets).map((c) => ({
19242
- text: c.text,
19243
- score: c.keywordScore
19244
- }));
19245
- }
19246
-
19247
19310
  // src/tools/read/recall.ts
19248
19311
  function scoreTextRelevance(query, content) {
19249
19312
  const queryTokens = tokenize(query).map((t) => t.toLowerCase());
@@ -19277,7 +19340,7 @@ async function performRecall(stateDb2, query, options = {}) {
19277
19340
  focus,
19278
19341
  entity,
19279
19342
  max_tokens,
19280
- diversity = 0.7,
19343
+ diversity = 1,
19281
19344
  vaultPath: vaultPath2
19282
19345
  } = options;
19283
19346
  const results = [];
@@ -19534,6 +19597,12 @@ function registerRecallTools(server2, getStateDb2, getVaultPath, getIndex) {
19534
19597
  const hopResults = index ? multiHopBackfill(enrichedNotes, index, stateDb2, {
19535
19598
  maxBackfill: Math.max(5, (args.max_results ?? 10) - notes.length)
19536
19599
  }) : [];
19600
+ if (index) {
19601
+ const allNoteResults = [...enrichedNotes, ...hopResults];
19602
+ const expansionTerms = extractExpansionTerms(allNoteResults, args.query, index);
19603
+ const expansionResults = expandQuery(expansionTerms, allNoteResults, index, stateDb2);
19604
+ hopResults.push(...expansionResults);
19605
+ }
19537
19606
  return {
19538
19607
  content: [{
19539
19608
  type: "text",
@@ -23713,10 +23782,10 @@ async function runPostIndexWork(index) {
23713
23782
  for (const event of filteredEvents) {
23714
23783
  if (event.type === "delete" || !event.path.endsWith(".md")) continue;
23715
23784
  try {
23716
- const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
23785
+ const rawContent = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
23786
+ const content = rawContent.replace(/ → \[\[.*$/gm, "");
23717
23787
  const result = await suggestRelatedLinks(content, {
23718
23788
  maxSuggestions: 5,
23719
- strictness: "balanced",
23720
23789
  notePath: event.path,
23721
23790
  detail: true
23722
23791
  });
@@ -23742,7 +23811,6 @@ async function runPostIndexWork(index) {
23742
23811
  try {
23743
23812
  const proactiveResults = [];
23744
23813
  for (const { file, top } of suggestionResults) {
23745
- if (getNoteContext(file) === "daily") continue;
23746
23814
  try {
23747
23815
  const result = await applyProactiveSuggestions(file, vaultPath, top, {
23748
23816
  minScore: flywheelConfig?.proactive_min_score ?? 20,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.0.126",
3
+ "version": "2.0.128",
4
4
  "description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 69 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@modelcontextprotocol/sdk": "^1.25.1",
56
- "@velvetmonkey/vault-core": "^2.0.122",
56
+ "@velvetmonkey/vault-core": "2.0.128",
57
57
  "better-sqlite3": "^11.0.0",
58
58
  "chokidar": "^4.0.0",
59
59
  "gray-matter": "^4.0.3",