@velvetmonkey/flywheel-memory 2.0.77 → 2.0.78

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 +75 -21
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9592,7 +9592,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
9592
9592
  import { z as z4 } from "zod";
9593
9593
  import {
9594
9594
  searchEntities,
9595
- searchEntitiesPrefix
9595
+ searchEntitiesPrefix,
9596
+ getEntityByName as getEntityByName2
9596
9597
  } from "@velvetmonkey/vault-core";
9597
9598
  function matchesFrontmatter(note, where) {
9598
9599
  for (const [key, value] of Object.entries(where)) {
@@ -9640,6 +9641,37 @@ function inFolder(note, folder) {
9640
9641
  const normalizedFolder = folder.endsWith("/") ? folder : folder + "/";
9641
9642
  return note.path.startsWith(normalizedFolder) || note.path.split("/")[0] === folder.replace("/", "");
9642
9643
  }
9644
+ function enrichResult(result, index, stateDb2) {
9645
+ const note = index.notes.get(result.path);
9646
+ const normalizedPath = result.path.toLowerCase().replace(/\.md$/, "");
9647
+ const backlinks = index.backlinks.get(normalizedPath) || [];
9648
+ const enriched = {
9649
+ path: result.path,
9650
+ title: result.title
9651
+ };
9652
+ if (result.snippet) enriched.snippet = result.snippet;
9653
+ if (note) {
9654
+ enriched.frontmatter = note.frontmatter;
9655
+ enriched.tags = note.tags;
9656
+ enriched.aliases = note.aliases;
9657
+ enriched.backlink_count = backlinks.length;
9658
+ enriched.outlink_count = note.outlinks.length;
9659
+ enriched.modified = note.modified.toISOString();
9660
+ if (note.created) enriched.created = note.created.toISOString();
9661
+ }
9662
+ if (stateDb2) {
9663
+ try {
9664
+ const entity = getEntityByName2(stateDb2, result.title);
9665
+ if (entity) {
9666
+ enriched.category = entity.category;
9667
+ enriched.hub_score = entity.hubScore;
9668
+ if (entity.description) enriched.description = entity.description;
9669
+ }
9670
+ } catch {
9671
+ }
9672
+ }
9673
+ return enriched;
9674
+ }
9643
9675
  function sortNotes(notes, sortBy, order) {
9644
9676
  const sorted = [...notes];
9645
9677
  sorted.sort((a, b) => {
@@ -9847,22 +9879,20 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
9847
9879
  const fts5Map = new Map(fts5Results.map((r) => [normalizePath2(r.path), r]));
9848
9880
  const semanticMap = new Map(semanticResults.map((r) => [normalizePath2(r.path), r]));
9849
9881
  const entityMap = new Map(entityResults.map((r) => [normalizePath2(r.path), r]));
9850
- const merged = Array.from(allPaths).map((p) => ({
9851
- path: p,
9852
- title: fts5Map.get(p)?.title || semanticMap.get(p)?.title || entityMap.get(p)?.name || p.replace(/\.md$/, "").split("/").pop() || p,
9853
- snippet: fts5Map.get(p)?.snippet,
9854
- rrf_score: Math.round((rrfScores.get(p) || 0) * 1e4) / 1e4,
9855
- in_fts5: fts5Map.has(p),
9856
- in_semantic: semanticMap.has(p),
9857
- in_entity: entityMap.has(p)
9858
- }));
9859
- merged.sort((a, b) => b.rrf_score - a.rrf_score);
9882
+ const merged = Array.from(allPaths).map((p) => {
9883
+ const title = fts5Map.get(p)?.title || semanticMap.get(p)?.title || entityMap.get(p)?.name || p.replace(/\.md$/, "").split("/").pop() || p;
9884
+ const snippet = fts5Map.get(p)?.snippet;
9885
+ const rrfScore = rrfScores.get(p) || 0;
9886
+ return { rrfScore, ...enrichResult({ path: p, title, snippet }, index, getStateDb()) };
9887
+ });
9888
+ merged.sort((a, b) => b.rrfScore - a.rrfScore);
9889
+ const results = merged.slice(0, limit).map(({ rrfScore, ...rest }) => rest);
9860
9890
  return { content: [{ type: "text", text: JSON.stringify({
9861
9891
  scope,
9862
9892
  method: "hybrid",
9863
9893
  query,
9864
9894
  total_results: Math.min(merged.length, limit),
9865
- results: merged.slice(0, limit)
9895
+ results
9866
9896
  }, null, 2) }] };
9867
9897
  } catch (err) {
9868
9898
  console.error("[Semantic] Hybrid search failed, falling back to FTS5:", err instanceof Error ? err.message : err);
@@ -9871,11 +9901,10 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
9871
9901
  if (entityResults.length > 0) {
9872
9902
  const normalizePath2 = (p) => p.replace(/\\/g, "/").replace(/\/+/g, "/");
9873
9903
  const fts5Map = new Map(fts5Results.map((r) => [normalizePath2(r.path), r]));
9874
- const entityNormMap = new Map(entityResults.map((r) => [normalizePath2(r.path), r]));
9875
9904
  const entityRanked = entityResults.filter((r) => !fts5Map.has(normalizePath2(r.path)));
9876
9905
  const merged = [
9877
- ...fts5Results.map((r) => ({ path: r.path, title: r.title, snippet: r.snippet, in_entity: entityNormMap.has(normalizePath2(r.path)) })),
9878
- ...entityRanked.map((r) => ({ path: r.path, title: r.name, snippet: void 0, in_entity: true }))
9906
+ ...fts5Results.map((r) => enrichResult({ path: r.path, title: r.title, snippet: r.snippet }, index, getStateDb())),
9907
+ ...entityRanked.map((r) => enrichResult({ path: r.path, title: r.name }, index, getStateDb()))
9879
9908
  ];
9880
9909
  return { content: [{ type: "text", text: JSON.stringify({
9881
9910
  scope: "content",
@@ -9885,12 +9914,13 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
9885
9914
  results: merged.slice(0, limit)
9886
9915
  }, null, 2) }] };
9887
9916
  }
9917
+ const enrichedFts5 = fts5Results.map((r) => enrichResult({ path: r.path, title: r.title, snippet: r.snippet }, index, getStateDb()));
9888
9918
  return { content: [{ type: "text", text: JSON.stringify({
9889
9919
  scope: "content",
9890
9920
  method: "fts5",
9891
9921
  query,
9892
- total_results: fts5Results.length,
9893
- results: fts5Results
9922
+ total_results: enrichedFts5.length,
9923
+ results: enrichedFts5
9894
9924
  }, null, 2) }] };
9895
9925
  }
9896
9926
  return { content: [{ type: "text", text: JSON.stringify({ error: "Invalid scope" }, null, 2) }] };
@@ -10701,7 +10731,8 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
10701
10731
  }
10702
10732
 
10703
10733
  // src/tools/read/primitives.ts
10704
- function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = () => ({})) {
10734
+ import { getEntityByName as getEntityByName3 } from "@velvetmonkey/vault-core";
10735
+ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = () => ({}), getStateDb = () => null) {
10705
10736
  server2.registerTool(
10706
10737
  "get_note_structure",
10707
10738
  {
@@ -10729,8 +10760,31 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
10729
10760
  }
10730
10761
  }
10731
10762
  }
10763
+ const note = index.notes.get(path33);
10764
+ const enriched = { ...result };
10765
+ if (note) {
10766
+ enriched.frontmatter = note.frontmatter;
10767
+ enriched.tags = note.tags;
10768
+ enriched.aliases = note.aliases;
10769
+ const normalizedPath = path33.toLowerCase().replace(/\.md$/, "");
10770
+ const backlinks = index.backlinks.get(normalizedPath) || [];
10771
+ enriched.backlink_count = backlinks.length;
10772
+ enriched.outlink_count = note.outlinks.length;
10773
+ }
10774
+ const stateDb2 = getStateDb();
10775
+ if (stateDb2 && note) {
10776
+ try {
10777
+ const entity = getEntityByName3(stateDb2, note.title);
10778
+ if (entity) {
10779
+ enriched.category = entity.category;
10780
+ enriched.hub_score = entity.hubScore;
10781
+ if (entity.description) enriched.description = entity.description;
10782
+ }
10783
+ } catch {
10784
+ }
10785
+ }
10732
10786
  return {
10733
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
10787
+ content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }]
10734
10788
  };
10735
10789
  }
10736
10790
  );
@@ -19385,7 +19439,7 @@ var ALL_CATEGORIES = [
19385
19439
  "policy",
19386
19440
  "memory"
19387
19441
  ];
19388
- var DEFAULT_PRESET = "full";
19442
+ var DEFAULT_PRESET = "minimal";
19389
19443
  function parseEnabledCategories() {
19390
19444
  const envValue = (process.env.FLYWHEEL_TOOLS ?? process.env.FLYWHEEL_PRESET)?.trim();
19391
19445
  if (!envValue) {
@@ -19587,7 +19641,7 @@ registerSystemTools(
19587
19641
  registerGraphTools(server, () => vaultIndex, () => vaultPath, () => stateDb);
19588
19642
  registerWikilinkTools(server, () => vaultIndex, () => vaultPath);
19589
19643
  registerQueryTools(server, () => vaultIndex, () => vaultPath, () => stateDb);
19590
- registerPrimitiveTools(server, () => vaultIndex, () => vaultPath, () => flywheelConfig);
19644
+ registerPrimitiveTools(server, () => vaultIndex, () => vaultPath, () => flywheelConfig, () => stateDb);
19591
19645
  registerGraphAnalysisTools(server, () => vaultIndex, () => vaultPath, () => stateDb, () => flywheelConfig);
19592
19646
  registerVaultSchemaTools(server, () => vaultIndex, () => vaultPath);
19593
19647
  registerNoteIntelligenceTools(server, () => vaultIndex, () => vaultPath, () => flywheelConfig);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.0.77",
3
+ "version": "2.0.78",
4
4
  "description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 51 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@modelcontextprotocol/sdk": "^1.25.1",
55
- "@velvetmonkey/vault-core": "^2.0.77",
55
+ "@velvetmonkey/vault-core": "^2.0.78",
56
56
  "better-sqlite3": "^11.0.0",
57
57
  "chokidar": "^4.0.0",
58
58
  "gray-matter": "^4.0.3",