@velvetmonkey/flywheel-memory 2.0.157 → 2.0.158
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 +110 -31
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -557,6 +557,18 @@ function hasEmbeddingsIndex() {
|
|
|
557
557
|
const row = db4.prepare("SELECT COUNT(*) as count FROM note_embeddings").get();
|
|
558
558
|
return row.count > 0;
|
|
559
559
|
}
|
|
560
|
+
if (!isEmbeddingsBuilding()) {
|
|
561
|
+
const row = db4.prepare("SELECT COUNT(*) as count FROM note_embeddings").get();
|
|
562
|
+
if (row.count > 0) {
|
|
563
|
+
setEmbeddingsBuildState("complete");
|
|
564
|
+
console.error("[Semantic] Recovered stale embeddings_state \u2192 complete");
|
|
565
|
+
return true;
|
|
566
|
+
} else {
|
|
567
|
+
setEmbeddingsBuildState("none");
|
|
568
|
+
console.error("[Semantic] Recovered stale embeddings_state \u2192 none (no rows)");
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
560
572
|
return false;
|
|
561
573
|
} catch {
|
|
562
574
|
return false;
|
|
@@ -666,7 +678,7 @@ function diagnoseEmbeddings(vaultPath2) {
|
|
|
666
678
|
checks.push({ name: "entity_orphans", status: "ok", detail: "0 orphaned" });
|
|
667
679
|
}
|
|
668
680
|
} catch {
|
|
669
|
-
checks.push({ name: "entity_orphans", status: "
|
|
681
|
+
checks.push({ name: "entity_orphans", status: "warning", detail: "Could not check entity embeddings" });
|
|
670
682
|
}
|
|
671
683
|
try {
|
|
672
684
|
const samples = db4.prepare("SELECT embedding FROM note_embeddings ORDER BY RANDOM() LIMIT 3").all();
|
|
@@ -7701,6 +7713,38 @@ function purgeOldNoteLinkHistory(stateDb2, retentionDays = 90) {
|
|
|
7701
7713
|
return result.changes;
|
|
7702
7714
|
}
|
|
7703
7715
|
|
|
7716
|
+
// src/core/read/identity.ts
|
|
7717
|
+
function normalizeResolvedPath(notePath) {
|
|
7718
|
+
return notePath.toLowerCase().replace(/\.md$/, "");
|
|
7719
|
+
}
|
|
7720
|
+
function getInboundTargetsForNote(stateDb2, notePath) {
|
|
7721
|
+
const targets = /* @__PURE__ */ new Set();
|
|
7722
|
+
const stem2 = notePath.replace(/\.md$/, "").split("/").pop()?.toLowerCase();
|
|
7723
|
+
const normalizedPath = normalizeResolvedPath(notePath);
|
|
7724
|
+
if (stateDb2) {
|
|
7725
|
+
try {
|
|
7726
|
+
const row = stateDb2.db.prepare(
|
|
7727
|
+
`SELECT name_lower, aliases_json FROM entities
|
|
7728
|
+
WHERE LOWER(REPLACE(path, '.md', '')) = ?`
|
|
7729
|
+
).get(normalizedPath);
|
|
7730
|
+
if (row) {
|
|
7731
|
+
targets.add(row.name_lower);
|
|
7732
|
+
if (row.aliases_json) {
|
|
7733
|
+
try {
|
|
7734
|
+
for (const alias of JSON.parse(row.aliases_json)) {
|
|
7735
|
+
targets.add(alias.toLowerCase());
|
|
7736
|
+
}
|
|
7737
|
+
} catch {
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
}
|
|
7741
|
+
} catch {
|
|
7742
|
+
}
|
|
7743
|
+
}
|
|
7744
|
+
if (stem2) targets.add(stem2);
|
|
7745
|
+
return [...targets];
|
|
7746
|
+
}
|
|
7747
|
+
|
|
7704
7748
|
// src/core/shared/hubExport.ts
|
|
7705
7749
|
var EIGEN_ITERATIONS = 50;
|
|
7706
7750
|
function computeHubScores(index) {
|
|
@@ -7784,15 +7828,16 @@ function computeHubScores(index) {
|
|
|
7784
7828
|
}
|
|
7785
7829
|
return { scores: result, edgeCount };
|
|
7786
7830
|
}
|
|
7787
|
-
function updateHubScoresInDb(stateDb2, hubScores) {
|
|
7788
|
-
const
|
|
7789
|
-
|
|
7790
|
-
`);
|
|
7831
|
+
function updateHubScoresInDb(stateDb2, hubScores, pathToId) {
|
|
7832
|
+
const resetAll = stateDb2.db.prepare("UPDATE entities SET hub_score = 0");
|
|
7833
|
+
const updateById = stateDb2.db.prepare("UPDATE entities SET hub_score = ? WHERE id = ?");
|
|
7791
7834
|
let updated = 0;
|
|
7792
7835
|
const transaction = stateDb2.db.transaction(() => {
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7836
|
+
resetAll.run();
|
|
7837
|
+
for (const [normalizedPath, score] of hubScores) {
|
|
7838
|
+
const entityId = pathToId.get(normalizedPath);
|
|
7839
|
+
if (entityId !== void 0) {
|
|
7840
|
+
updateById.run(score, entityId);
|
|
7796
7841
|
updated++;
|
|
7797
7842
|
}
|
|
7798
7843
|
}
|
|
@@ -7807,9 +7852,14 @@ async function exportHubScores(vaultIndex2, stateDb2) {
|
|
|
7807
7852
|
}
|
|
7808
7853
|
const { scores: hubScores, edgeCount } = computeHubScores(vaultIndex2);
|
|
7809
7854
|
console.error(`[Flywheel] Computed hub scores for ${hubScores.size} notes (${edgeCount} edges in graph)`);
|
|
7855
|
+
const entityRows = stateDb2.db.prepare("SELECT id, path FROM entities").all();
|
|
7856
|
+
const pathToId = /* @__PURE__ */ new Map();
|
|
7857
|
+
for (const row of entityRows) {
|
|
7858
|
+
pathToId.set(normalizeResolvedPath(row.path), row.id);
|
|
7859
|
+
}
|
|
7810
7860
|
try {
|
|
7811
|
-
const updated = updateHubScoresInDb(stateDb2, hubScores);
|
|
7812
|
-
console.error(`[Flywheel]
|
|
7861
|
+
const updated = updateHubScoresInDb(stateDb2, hubScores, pathToId);
|
|
7862
|
+
console.error(`[Flywheel] Hub scores: ${updated}/${hubScores.size} scores mapped to entities (all others reset to 0)`);
|
|
7813
7863
|
return updated;
|
|
7814
7864
|
} catch (e) {
|
|
7815
7865
|
console.error("[Flywheel] Failed to update hub scores in StateDb:", e);
|
|
@@ -8752,7 +8802,12 @@ var PipelineRunner = class {
|
|
|
8752
8802
|
} catch {
|
|
8753
8803
|
}
|
|
8754
8804
|
}
|
|
8755
|
-
|
|
8805
|
+
let orphansRemoved = 0;
|
|
8806
|
+
try {
|
|
8807
|
+
orphansRemoved = removeOrphanedNoteEmbeddings();
|
|
8808
|
+
} catch (e) {
|
|
8809
|
+
serverLog("watcher", `Note embedding orphan cleanup failed: ${e}`, "error");
|
|
8810
|
+
}
|
|
8756
8811
|
tracker.end({ updated: embUpdated, removed: embRemoved, orphans_removed: orphansRemoved });
|
|
8757
8812
|
serverLog("watcher", `Note embeddings: ${embUpdated} updated, ${embRemoved} removed, ${orphansRemoved} orphans cleaned`);
|
|
8758
8813
|
} else {
|
|
@@ -8785,7 +8840,8 @@ var PipelineRunner = class {
|
|
|
8785
8840
|
}
|
|
8786
8841
|
const currentNames = new Set(allEntities.map((e) => e.name));
|
|
8787
8842
|
entEmbOrphansRemoved = removeOrphanedEntityEmbeddings(currentNames);
|
|
8788
|
-
} catch {
|
|
8843
|
+
} catch (e) {
|
|
8844
|
+
serverLog("watcher", `Entity embedding update/orphan cleanup failed: ${e}`, "error");
|
|
8789
8845
|
}
|
|
8790
8846
|
tracker.end({ updated: entEmbUpdated, updated_entities: entEmbNames.slice(0, 10), orphans_removed: entEmbOrphansRemoved });
|
|
8791
8847
|
serverLog("watcher", `Entity embeddings: ${entEmbUpdated} updated, ${entEmbOrphansRemoved} orphans cleaned`);
|
|
@@ -9336,10 +9392,15 @@ var PipelineRunner = class {
|
|
|
9336
9392
|
return { skipped: true, reason: "vacuumed recently" };
|
|
9337
9393
|
}
|
|
9338
9394
|
p.sd.db.pragma("incremental_vacuum");
|
|
9339
|
-
p.sd.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9395
|
+
const walResult = p.sd.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
9396
|
+
const checkpointed = walResult?.[0]?.busy === 0;
|
|
9397
|
+
if (checkpointed) {
|
|
9398
|
+
p.sd.setMetadataValue.run("last_incremental_vacuum", String(Date.now()));
|
|
9399
|
+
serverLog("watcher", "Incremental vacuum + WAL checkpoint completed");
|
|
9400
|
+
} else {
|
|
9401
|
+
serverLog("watcher", "Incremental vacuum done, WAL checkpoint skipped (busy readers)");
|
|
9402
|
+
}
|
|
9403
|
+
return { vacuumed: true, wal_checkpointed: checkpointed };
|
|
9343
9404
|
}
|
|
9344
9405
|
};
|
|
9345
9406
|
|
|
@@ -9842,11 +9903,9 @@ function computeMetrics(index, stateDb2) {
|
|
|
9842
9903
|
for (const bl of backlinks) {
|
|
9843
9904
|
connectedNotes.add(bl.source);
|
|
9844
9905
|
}
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
connectedNotes.add(note.path);
|
|
9849
|
-
}
|
|
9906
|
+
const targetPath = index.entities.get(target);
|
|
9907
|
+
if (targetPath && index.notes.has(targetPath)) {
|
|
9908
|
+
connectedNotes.add(targetPath);
|
|
9850
9909
|
}
|
|
9851
9910
|
}
|
|
9852
9911
|
let orphanCount = 0;
|
|
@@ -10878,14 +10937,18 @@ function rankOutlinks(outlinks, notePath, index, stateDb2, maxLinks = TOP_LINKS)
|
|
|
10878
10937
|
}).sort((a, b) => (b.weight ?? 1) - (a.weight ?? 1)).slice(0, maxLinks);
|
|
10879
10938
|
}
|
|
10880
10939
|
function rankBacklinks(backlinks, notePath, index, stateDb2, maxLinks = TOP_LINKS) {
|
|
10881
|
-
const
|
|
10940
|
+
const targets = getInboundTargetsForNote(stateDb2, notePath);
|
|
10882
10941
|
const weightMap = /* @__PURE__ */ new Map();
|
|
10883
|
-
if (stateDb2 &&
|
|
10942
|
+
if (stateDb2 && targets.length > 0) {
|
|
10884
10943
|
try {
|
|
10944
|
+
const placeholders = targets.map(() => "?").join(",");
|
|
10885
10945
|
const rows = stateDb2.db.prepare(
|
|
10886
|
-
|
|
10887
|
-
).all(
|
|
10888
|
-
for (const row of rows)
|
|
10946
|
+
`SELECT note_path, weight FROM note_links WHERE target IN (${placeholders})`
|
|
10947
|
+
).all(...targets);
|
|
10948
|
+
for (const row of rows) {
|
|
10949
|
+
const existing = weightMap.get(row.note_path) ?? 0;
|
|
10950
|
+
weightMap.set(row.note_path, Math.max(existing, row.weight));
|
|
10951
|
+
}
|
|
10889
10952
|
} catch {
|
|
10890
10953
|
}
|
|
10891
10954
|
}
|
|
@@ -12757,16 +12820,18 @@ function registerGraphTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
|
12757
12820
|
return { content: [{ type: "text", text: JSON.stringify({ error: "StateDb not initialized" }) }] };
|
|
12758
12821
|
}
|
|
12759
12822
|
const limit = Math.min(requestedLimit ?? 20, MAX_LIMIT);
|
|
12760
|
-
const
|
|
12823
|
+
const targets = getInboundTargetsForNote(stateDb2, notePath);
|
|
12824
|
+
const inPlaceholders = targets.map(() => "?").join(",");
|
|
12761
12825
|
const rows = stateDb2.db.prepare(`
|
|
12762
12826
|
SELECT target AS node, weight, 'outgoing' AS direction
|
|
12763
12827
|
FROM note_links WHERE note_path = ?
|
|
12764
12828
|
UNION ALL
|
|
12765
|
-
SELECT note_path AS node, weight, 'incoming' AS direction
|
|
12766
|
-
FROM note_links WHERE target
|
|
12829
|
+
SELECT note_path AS node, MAX(weight) AS weight, 'incoming' AS direction
|
|
12830
|
+
FROM note_links WHERE target IN (${inPlaceholders})
|
|
12831
|
+
GROUP BY note_path
|
|
12767
12832
|
ORDER BY weight DESC
|
|
12768
12833
|
LIMIT ?
|
|
12769
|
-
`).all(notePath,
|
|
12834
|
+
`).all(notePath, ...targets, limit);
|
|
12770
12835
|
return {
|
|
12771
12836
|
content: [{
|
|
12772
12837
|
type: "text",
|
|
@@ -22974,6 +23039,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb3) {
|
|
|
22974
23039
|
if (!force) {
|
|
22975
23040
|
const diagnosis2 = diagnoseEmbeddings(vaultPath2);
|
|
22976
23041
|
if (diagnosis2.healthy) {
|
|
23042
|
+
setEmbeddingsBuildState("complete");
|
|
22977
23043
|
return {
|
|
22978
23044
|
content: [{
|
|
22979
23045
|
type: "text",
|
|
@@ -25265,7 +25331,7 @@ async function main() {
|
|
|
25265
25331
|
serverLog("server", `Starting Flywheel Memory v${pkg.version}...`);
|
|
25266
25332
|
serverLog("server", `Vault: ${vaultPath}`);
|
|
25267
25333
|
const startTime = Date.now();
|
|
25268
|
-
const vaultConfigs =
|
|
25334
|
+
const vaultConfigs = parseVaultConfig();
|
|
25269
25335
|
if (vaultConfigs) {
|
|
25270
25336
|
vaultRegistry = new VaultRegistry(vaultConfigs[0].name);
|
|
25271
25337
|
serverLog("server", `Multi-vault mode: ${vaultConfigs.map((v) => v.name).join(", ")}`);
|
|
@@ -25782,6 +25848,19 @@ if (process.argv.includes("--init-semantic")) {
|
|
|
25782
25848
|
process.exit(1);
|
|
25783
25849
|
});
|
|
25784
25850
|
}
|
|
25851
|
+
function gracefulShutdown(signal) {
|
|
25852
|
+
console.error(`[Memory] Received ${signal}, shutting down...`);
|
|
25853
|
+
try {
|
|
25854
|
+
watcherInstance?.stop();
|
|
25855
|
+
} catch {
|
|
25856
|
+
}
|
|
25857
|
+
stopSweepTimer();
|
|
25858
|
+
flushLogs().catch(() => {
|
|
25859
|
+
}).finally(() => process.exit(0));
|
|
25860
|
+
setTimeout(() => process.exit(0), 2e3).unref();
|
|
25861
|
+
}
|
|
25862
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
25863
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
25785
25864
|
process.on("beforeExit", async () => {
|
|
25786
25865
|
stopSweepTimer();
|
|
25787
25866
|
await flushLogs();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "MCP tools that search, write, and auto-link your Obsidian vault
|
|
3
|
+
"version": "2.0.158",
|
|
4
|
+
"description": "MCP tools that search, write, and auto-link your Obsidian vault \u2014 and learn from your edits.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -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.158",
|
|
58
58
|
"better-sqlite3": "^12.0.0",
|
|
59
59
|
"chokidar": "^4.0.0",
|
|
60
60
|
"gray-matter": "^4.0.3",
|