@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.
- package/dist/index.js +67 -12
- 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
|
-
|
|
8707
|
-
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
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",
|