@velvetmonkey/flywheel-memory 2.0.23 → 2.0.25

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 +56 -10
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1839,6 +1839,7 @@ var MAX_FILE_SIZE2 = 5 * 1024 * 1024;
1839
1839
  var db = null;
1840
1840
  var pipeline = null;
1841
1841
  var initPromise = null;
1842
+ var embeddingsBuilding = false;
1842
1843
  var embeddingCache = /* @__PURE__ */ new Map();
1843
1844
  var EMBEDDING_CACHE_MAX = 500;
1844
1845
  var entityEmbeddingsMap = /* @__PURE__ */ new Map();
@@ -1894,6 +1895,7 @@ async function buildEmbeddingsIndex(vaultPath2, onProgress) {
1894
1895
  if (!db) {
1895
1896
  throw new Error("Embeddings database not initialized. Call setEmbeddingsDatabase() first.");
1896
1897
  }
1898
+ embeddingsBuilding = true;
1897
1899
  await initEmbeddings();
1898
1900
  const files = await scanVault(vaultPath2);
1899
1901
  const indexable = files.filter((f) => shouldIndexFile(f.path));
@@ -1937,6 +1939,7 @@ async function buildEmbeddingsIndex(vaultPath2, onProgress) {
1937
1939
  deleteStmt.run(existingPath);
1938
1940
  }
1939
1941
  }
1942
+ embeddingsBuilding = false;
1940
1943
  console.error(`[Semantic] Indexed ${progress.current - progress.skipped} notes, skipped ${progress.skipped}`);
1941
1944
  return progress;
1942
1945
  }
@@ -2034,6 +2037,12 @@ function reciprocalRankFusion(...lists) {
2034
2037
  }
2035
2038
  return scores;
2036
2039
  }
2040
+ function isEmbeddingsBuilding() {
2041
+ return embeddingsBuilding;
2042
+ }
2043
+ function setEmbeddingsBuilding(value) {
2044
+ embeddingsBuilding = value;
2045
+ }
2037
2046
  function hasEmbeddingsIndex() {
2038
2047
  if (!db) return false;
2039
2048
  try {
@@ -6934,6 +6943,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
6934
6943
  }).optional().describe("Most recent index rebuild event"),
6935
6944
  fts5_ready: z3.boolean().describe("Whether the FTS5 keyword search index is ready"),
6936
6945
  fts5_building: z3.boolean().describe("Whether the FTS5 keyword search index is currently building"),
6946
+ embeddings_building: z3.boolean().describe("Whether semantic embeddings are currently building"),
6937
6947
  embeddings_ready: z3.boolean().describe("Whether semantic embeddings have been built (enables hybrid keyword+semantic search)"),
6938
6948
  embeddings_count: z3.coerce.number().describe("Number of notes with semantic embeddings"),
6939
6949
  recommendations: z3.array(z3.string()).describe("Suggested actions if any issues detected")
@@ -7041,6 +7051,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
7041
7051
  last_rebuild: lastRebuild,
7042
7052
  fts5_ready: ftsState.ready,
7043
7053
  fts5_building: ftsState.building,
7054
+ embeddings_building: isEmbeddingsBuilding(),
7044
7055
  embeddings_ready: hasEmbeddingsIndex(),
7045
7056
  embeddings_count: getEmbeddingsCount(),
7046
7057
  recommendations
@@ -14871,7 +14882,6 @@ async function main() {
14871
14882
  setEmbeddingsDatabase(stateDb.db);
14872
14883
  loadEntityEmbeddingsToMemory();
14873
14884
  setWriteStateDb(stateDb);
14874
- await initializeEntityIndex(vaultPath);
14875
14885
  } catch (err) {
14876
14886
  const msg = err instanceof Error ? err.message : String(err);
14877
14887
  console.error(`[Memory] StateDb initialization failed: ${msg}`);
@@ -14890,6 +14900,19 @@ async function main() {
14890
14900
  initializeLogger2(vaultPath).catch((err) => {
14891
14901
  console.error(`[Memory] Write logger initialization failed: ${err}`);
14892
14902
  });
14903
+ if (process.env.FLYWHEEL_SKIP_FTS5 !== "true") {
14904
+ if (isIndexStale(vaultPath)) {
14905
+ buildFTS5Index(vaultPath).then(() => {
14906
+ console.error("[Memory] FTS5 search index ready");
14907
+ }).catch((err) => {
14908
+ console.error("[Memory] FTS5 build failed:", err);
14909
+ });
14910
+ } else {
14911
+ console.error("[Memory] FTS5 search index already fresh, skipping rebuild");
14912
+ }
14913
+ } else {
14914
+ console.error("[Memory] FTS5 indexing skipped (FLYWHEEL_SKIP_FTS5=true)");
14915
+ }
14893
14916
  let cachedIndex = null;
14894
14917
  if (stateDb) {
14895
14918
  try {
@@ -14987,6 +15010,7 @@ async function updateEntitiesInStateDb() {
14987
15010
  }
14988
15011
  async function runPostIndexWork(index) {
14989
15012
  await updateEntitiesInStateDb();
15013
+ await initializeEntityIndex(vaultPath);
14990
15014
  await exportHubScores(index, stateDb);
14991
15015
  if (stateDb) {
14992
15016
  try {
@@ -15016,15 +15040,6 @@ async function runPostIndexWork(index) {
15016
15040
  console.error("[Memory] Failed to update suppression list:", err);
15017
15041
  }
15018
15042
  }
15019
- if (isIndexStale(vaultPath)) {
15020
- buildFTS5Index(vaultPath).then(() => {
15021
- console.error("[Memory] FTS5 search index ready");
15022
- }).catch((err) => {
15023
- console.error("[Memory] FTS5 build failed:", err);
15024
- });
15025
- } else {
15026
- console.error("[Memory] FTS5 search index already fresh, skipping rebuild");
15027
- }
15028
15043
  const existing = loadConfig(stateDb);
15029
15044
  const inferred = inferConfig(index, vaultPath);
15030
15045
  if (stateDb) {
@@ -15034,6 +15049,37 @@ async function runPostIndexWork(index) {
15034
15049
  if (flywheelConfig.vault_name) {
15035
15050
  console.error(`[Memory] Vault: ${flywheelConfig.vault_name}`);
15036
15051
  }
15052
+ if (process.env.FLYWHEEL_SKIP_EMBEDDINGS !== "true") {
15053
+ if (hasEmbeddingsIndex()) {
15054
+ console.error("[Memory] Embeddings already built, skipping full scan");
15055
+ } else {
15056
+ setEmbeddingsBuilding(true);
15057
+ buildEmbeddingsIndex(vaultPath, (p) => {
15058
+ if (p.current % 100 === 0 || p.current === p.total) {
15059
+ console.error(`[Semantic] ${p.current}/${p.total}`);
15060
+ }
15061
+ }).then(async () => {
15062
+ if (stateDb) {
15063
+ const entities = getAllEntitiesFromDb2(stateDb);
15064
+ if (entities.length > 0) {
15065
+ const entityMap = new Map(entities.map((e) => [e.name, {
15066
+ name: e.name,
15067
+ path: e.path,
15068
+ category: e.category,
15069
+ aliases: e.aliases
15070
+ }]));
15071
+ await buildEntityEmbeddingsIndex(vaultPath, entityMap);
15072
+ }
15073
+ }
15074
+ loadEntityEmbeddingsToMemory();
15075
+ console.error("[Memory] Embeddings refreshed");
15076
+ }).catch((err) => {
15077
+ console.error("[Memory] Embeddings refresh failed:", err);
15078
+ });
15079
+ }
15080
+ } else {
15081
+ console.error("[Memory] Embeddings skipped (FLYWHEEL_SKIP_EMBEDDINGS=true)");
15082
+ }
15037
15083
  if (process.env.FLYWHEEL_WATCH !== "false") {
15038
15084
  const config = parseWatcherConfig();
15039
15085
  console.error(`[Memory] File watcher enabled (debounce: ${config.debounceMs}ms)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.0.23",
3
+ "version": "2.0.25",
4
4
  "description": "MCP server that gives Claude full read/write access to your Obsidian vault. 42 tools for search, backlinks, graph queries, mutations, and hybrid semantic search.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@modelcontextprotocol/sdk": "^1.25.1",
53
- "@velvetmonkey/vault-core": "^2.0.23",
53
+ "@velvetmonkey/vault-core": "^2.0.25",
54
54
  "better-sqlite3": "^11.0.0",
55
55
  "chokidar": "^4.0.0",
56
56
  "gray-matter": "^4.0.3",