@velvetmonkey/flywheel-memory 2.0.156 → 2.0.157

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 +67 -12
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -575,7 +575,7 @@ function getStoredEmbeddingModel() {
575
575
  function diagnoseEmbeddings(vaultPath2) {
576
576
  const db4 = getDb();
577
577
  const checks = [];
578
- const counts = { embedded: 0, vaultNotes: 0, orphaned: 0, missing: 0 };
578
+ const counts = { embedded: 0, vaultNotes: 0, orphaned: 0, orphanedEntities: 0, missing: 0 };
579
579
  if (!db4) {
580
580
  checks.push({ name: "database", status: "stale", detail: "No database available" });
581
581
  return { healthy: false, checks, counts };
@@ -649,6 +649,25 @@ function diagnoseEmbeddings(vaultPath2) {
649
649
  } catch {
650
650
  checks.push({ name: "orphans", status: "warning", detail: "Could not check" });
651
651
  }
652
+ try {
653
+ const embNames = new Set(
654
+ db4.prepare("SELECT entity_name FROM entity_embeddings").all().map((r) => r.entity_name)
655
+ );
656
+ const entityNames = new Set(
657
+ db4.prepare("SELECT name FROM entities").all().map((r) => r.name)
658
+ );
659
+ counts.orphanedEntities = 0;
660
+ for (const n of embNames) {
661
+ if (!entityNames.has(n)) counts.orphanedEntities++;
662
+ }
663
+ if (counts.orphanedEntities > 0) {
664
+ checks.push({ name: "entity_orphans", status: "warning", detail: `${counts.orphanedEntities} orphaned entity embeddings` });
665
+ } else {
666
+ checks.push({ name: "entity_orphans", status: "ok", detail: "0 orphaned" });
667
+ }
668
+ } catch {
669
+ checks.push({ name: "entity_orphans", status: "ok", detail: "No entity embeddings table" });
670
+ }
652
671
  try {
653
672
  const samples = db4.prepare("SELECT embedding FROM note_embeddings ORDER BY RANDOM() LIMIT 3").all();
654
673
  let corrupt = false;
@@ -732,6 +751,7 @@ async function buildEntityEmbeddingsIndex(vaultPath2, entities, onProgress) {
732
751
  const total = entities.size;
733
752
  let done = 0;
734
753
  let updated = 0;
754
+ let skipped = 0;
735
755
  for (const [name, entity] of entities) {
736
756
  done++;
737
757
  try {
@@ -745,7 +765,11 @@ async function buildEntityEmbeddingsIndex(vaultPath2, entities, onProgress) {
745
765
  const buf = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
746
766
  upsert.run(name, buf, hash, activeModelConfig.id, Date.now());
747
767
  updated++;
748
- } catch {
768
+ } catch (err) {
769
+ skipped++;
770
+ if (skipped <= 3) {
771
+ console.error(`[Semantic] Failed to embed entity ${name}: ${err instanceof Error ? err.message : err}`);
772
+ }
749
773
  }
750
774
  if (onProgress) onProgress(done, total);
751
775
  }
@@ -755,7 +779,7 @@ async function buildEntityEmbeddingsIndex(vaultPath2, entities, onProgress) {
755
779
  deleteStmt.run(existingName);
756
780
  }
757
781
  }
758
- console.error(`[Semantic] Entity embeddings: ${updated} updated, ${total - updated} unchanged`);
782
+ console.error(`[Semantic] Entity embeddings: ${updated} updated, ${total - updated - skipped} unchanged, ${skipped} failed`);
759
783
  return updated;
760
784
  }
761
785
  async function updateEntityEmbedding(entityName, entity, vaultPath2) {
@@ -773,9 +797,34 @@ async function updateEntityEmbedding(entityName, entity, vaultPath2) {
773
797
  VALUES (?, ?, ?, ?, ?)
774
798
  `).run(entityName, buf, hash, activeModelConfig.id, Date.now());
775
799
  entityEmbeddingsMap.set(entityName, embedding);
776
- } catch {
800
+ } catch (err) {
801
+ console.error(`[Semantic] Failed to update entity embedding ${entityName}: ${err instanceof Error ? err.message : err}`);
777
802
  }
778
803
  }
804
+ function removeOrphanedNoteEmbeddings() {
805
+ const db4 = getDb();
806
+ if (!db4) return 0;
807
+ const result = db4.prepare(
808
+ "DELETE FROM note_embeddings WHERE path NOT IN (SELECT path FROM notes_fts)"
809
+ ).run();
810
+ return result.changes;
811
+ }
812
+ function removeOrphanedEntityEmbeddings(currentEntityNames) {
813
+ const db4 = getDb();
814
+ if (!db4) return 0;
815
+ const rows = db4.prepare("SELECT entity_name FROM entity_embeddings").all();
816
+ const deleteStmt = db4.prepare("DELETE FROM entity_embeddings WHERE entity_name = ?");
817
+ const embMap = getEmbMap();
818
+ let removed = 0;
819
+ for (const row of rows) {
820
+ if (!currentEntityNames.has(row.entity_name)) {
821
+ deleteStmt.run(row.entity_name);
822
+ embMap.delete(row.entity_name);
823
+ removed++;
824
+ }
825
+ }
826
+ return removed;
827
+ }
779
828
  function findSemanticallySimilarEntities(queryEmbedding, limit, excludeEntities) {
780
829
  const scored = [];
781
830
  for (const [entityName, embedding] of getEmbMap()) {
@@ -8703,8 +8752,9 @@ var PipelineRunner = class {
8703
8752
  } catch {
8704
8753
  }
8705
8754
  }
8706
- tracker.end({ updated: embUpdated, removed: embRemoved });
8707
- serverLog("watcher", `Note embeddings: ${embUpdated} updated, ${embRemoved} removed`);
8755
+ const orphansRemoved = removeOrphanedNoteEmbeddings();
8756
+ tracker.end({ updated: embUpdated, removed: embRemoved, orphans_removed: orphansRemoved });
8757
+ serverLog("watcher", `Note embeddings: ${embUpdated} updated, ${embRemoved} removed, ${orphansRemoved} orphans cleaned`);
8708
8758
  } else {
8709
8759
  tracker.skip("note_embeddings", "not built");
8710
8760
  }
@@ -8715,6 +8765,7 @@ var PipelineRunner = class {
8715
8765
  if (hasEntityEmbeddingsIndex() && p.sd) {
8716
8766
  tracker.start("entity_embeddings", { files: p.events.length });
8717
8767
  let entEmbUpdated = 0;
8768
+ let entEmbOrphansRemoved = 0;
8718
8769
  const entEmbNames = [];
8719
8770
  try {
8720
8771
  const allEntities = getAllEntitiesFromDb(p.sd);
@@ -8732,10 +8783,12 @@ var PipelineRunner = class {
8732
8783
  entEmbNames.push(entity.name);
8733
8784
  }
8734
8785
  }
8786
+ const currentNames = new Set(allEntities.map((e) => e.name));
8787
+ entEmbOrphansRemoved = removeOrphanedEntityEmbeddings(currentNames);
8735
8788
  } catch {
8736
8789
  }
8737
- tracker.end({ updated: entEmbUpdated, updated_entities: entEmbNames.slice(0, 10) });
8738
- serverLog("watcher", `Entity embeddings: ${entEmbUpdated} updated`);
8790
+ tracker.end({ updated: entEmbUpdated, updated_entities: entEmbNames.slice(0, 10), orphans_removed: entEmbOrphansRemoved });
8791
+ serverLog("watcher", `Entity embeddings: ${entEmbUpdated} updated, ${entEmbOrphansRemoved} orphans cleaned`);
8739
8792
  } else {
8740
8793
  tracker.skip("entity_embeddings", !p.sd ? "no sd" : "not built");
8741
8794
  }
@@ -9283,9 +9336,10 @@ var PipelineRunner = class {
9283
9336
  return { skipped: true, reason: "vacuumed recently" };
9284
9337
  }
9285
9338
  p.sd.db.pragma("incremental_vacuum");
9339
+ p.sd.db.pragma("wal_checkpoint(TRUNCATE)");
9286
9340
  p.sd.setMetadataValue.run("last_incremental_vacuum", String(Date.now()));
9287
- serverLog("watcher", "Incremental vacuum completed");
9288
- return { vacuumed: true };
9341
+ serverLog("watcher", "Incremental vacuum + WAL checkpoint completed");
9342
+ return { vacuumed: true, wal_checkpointed: true };
9289
9343
  }
9290
9344
  };
9291
9345
 
@@ -24932,7 +24986,8 @@ function registerAllTools(targetServer, ctx) {
24932
24986
  var __filename = fileURLToPath2(import.meta.url);
24933
24987
  var __dirname = dirname7(__filename);
24934
24988
  var pkg = JSON.parse(readFileSync6(join21(__dirname, "../package.json"), "utf-8"));
24935
- var vaultPath = process.env.PROJECT_PATH || process.env.VAULT_PATH || findVaultRoot();
24989
+ var _earlyVaultConfigs = parseVaultConfig();
24990
+ var vaultPath = _earlyVaultConfigs ? _earlyVaultConfigs[0].path : process.env.PROJECT_PATH || process.env.VAULT_PATH || findVaultRoot();
24936
24991
  var resolvedVaultPath;
24937
24992
  try {
24938
24993
  resolvedVaultPath = realpathSync(vaultPath).replace(/\\/g, "/");
@@ -25210,7 +25265,7 @@ async function main() {
25210
25265
  serverLog("server", `Starting Flywheel Memory v${pkg.version}...`);
25211
25266
  serverLog("server", `Vault: ${vaultPath}`);
25212
25267
  const startTime = Date.now();
25213
- const vaultConfigs = parseVaultConfig();
25268
+ const vaultConfigs = _earlyVaultConfigs;
25214
25269
  if (vaultConfigs) {
25215
25270
  vaultRegistry = new VaultRegistry(vaultConfigs[0].name);
25216
25271
  serverLog("server", `Multi-vault mode: ${vaultConfigs.map((v) => v.name).join(", ")}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.0.156",
3
+ "version": "2.0.157",
4
4
  "description": "MCP tools that search, write, and auto-link your Obsidian vault — and learn from your edits.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "dependencies": {
55
55
  "@huggingface/transformers": "^3.8.1",
56
56
  "@modelcontextprotocol/sdk": "^1.25.1",
57
- "@velvetmonkey/vault-core": "^2.0.156",
57
+ "@velvetmonkey/vault-core": "^2.0.157",
58
58
  "better-sqlite3": "^12.0.0",
59
59
  "chokidar": "^4.0.0",
60
60
  "gray-matter": "^4.0.3",