@velvetmonkey/flywheel-memory 2.0.40 → 2.0.41
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 +286 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3721,13 +3721,25 @@ function getStoredNoteLinks(stateDb2, notePath) {
|
|
|
3721
3721
|
return new Set(rows.map((r) => r.target));
|
|
3722
3722
|
}
|
|
3723
3723
|
function updateStoredNoteLinks(stateDb2, notePath, currentLinks) {
|
|
3724
|
-
const
|
|
3725
|
-
|
|
3724
|
+
const ins = stateDb2.db.prepare(
|
|
3725
|
+
"INSERT OR IGNORE INTO note_links (note_path, target) VALUES (?, ?)"
|
|
3726
|
+
);
|
|
3727
|
+
const del = stateDb2.db.prepare(
|
|
3728
|
+
"DELETE FROM note_links WHERE note_path = ? AND target = ?"
|
|
3729
|
+
);
|
|
3730
|
+
const existing = stateDb2.db.prepare(
|
|
3731
|
+
"SELECT target FROM note_links WHERE note_path = ?"
|
|
3732
|
+
);
|
|
3726
3733
|
const tx = stateDb2.db.transaction(() => {
|
|
3727
|
-
del.run(notePath);
|
|
3728
3734
|
for (const target of currentLinks) {
|
|
3729
3735
|
ins.run(notePath, target);
|
|
3730
3736
|
}
|
|
3737
|
+
const rows = existing.all(notePath);
|
|
3738
|
+
for (const row of rows) {
|
|
3739
|
+
if (!currentLinks.has(row.target)) {
|
|
3740
|
+
del.run(notePath, row.target);
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3731
3743
|
});
|
|
3732
3744
|
tx();
|
|
3733
3745
|
}
|
|
@@ -7315,7 +7327,7 @@ var GetBacklinksOutputSchema = {
|
|
|
7315
7327
|
returned_count: z.coerce.number().describe("Number of backlinks returned (may be limited)"),
|
|
7316
7328
|
backlinks: z.array(BacklinkItemSchema).describe("List of backlinks")
|
|
7317
7329
|
};
|
|
7318
|
-
function registerGraphTools(server2, getIndex, getVaultPath) {
|
|
7330
|
+
function registerGraphTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
7319
7331
|
server2.registerTool(
|
|
7320
7332
|
"get_backlinks",
|
|
7321
7333
|
{
|
|
@@ -7437,6 +7449,89 @@ function registerGraphTools(server2, getIndex, getVaultPath) {
|
|
|
7437
7449
|
};
|
|
7438
7450
|
}
|
|
7439
7451
|
);
|
|
7452
|
+
server2.tool(
|
|
7453
|
+
"get_weighted_links",
|
|
7454
|
+
"Get outgoing links from a note ranked by edge weight. Weights reflect link survival, co-session access, and source activity. Time decay is applied at query time.",
|
|
7455
|
+
{
|
|
7456
|
+
path: z.string().describe('Path to the note (e.g., "daily/2026-02-24.md")'),
|
|
7457
|
+
min_weight: z.number().default(0).describe("Minimum weight threshold (default 0 = all links)"),
|
|
7458
|
+
limit: z.number().default(20).describe("Maximum number of results to return")
|
|
7459
|
+
},
|
|
7460
|
+
async ({ path: notePath, min_weight, limit: requestedLimit }) => {
|
|
7461
|
+
const stateDb2 = getStateDb?.();
|
|
7462
|
+
if (!stateDb2) {
|
|
7463
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "StateDb not initialized" }) }] };
|
|
7464
|
+
}
|
|
7465
|
+
const limit = Math.min(requestedLimit ?? 20, MAX_LIMIT);
|
|
7466
|
+
const now = Date.now();
|
|
7467
|
+
const rows = stateDb2.db.prepare(`
|
|
7468
|
+
SELECT target, weight, weight_updated_at
|
|
7469
|
+
FROM note_links
|
|
7470
|
+
WHERE note_path = ?
|
|
7471
|
+
ORDER BY weight DESC
|
|
7472
|
+
`).all(notePath);
|
|
7473
|
+
const results = rows.map((row) => {
|
|
7474
|
+
const daysSinceUpdated = row.weight_updated_at ? (now - row.weight_updated_at) / (1e3 * 60 * 60 * 24) : 0;
|
|
7475
|
+
const decayFactor = Math.max(0.1, 1 - daysSinceUpdated / 180);
|
|
7476
|
+
const effectiveWeight = Math.round(row.weight * decayFactor * 1e3) / 1e3;
|
|
7477
|
+
return {
|
|
7478
|
+
target: row.target,
|
|
7479
|
+
weight: row.weight,
|
|
7480
|
+
weight_effective: effectiveWeight,
|
|
7481
|
+
last_updated: row.weight_updated_at
|
|
7482
|
+
};
|
|
7483
|
+
}).filter((r) => r.weight_effective >= min_weight).slice(0, limit);
|
|
7484
|
+
return {
|
|
7485
|
+
content: [{
|
|
7486
|
+
type: "text",
|
|
7487
|
+
text: JSON.stringify({
|
|
7488
|
+
note: notePath,
|
|
7489
|
+
count: results.length,
|
|
7490
|
+
links: results
|
|
7491
|
+
}, null, 2)
|
|
7492
|
+
}]
|
|
7493
|
+
};
|
|
7494
|
+
}
|
|
7495
|
+
);
|
|
7496
|
+
server2.tool(
|
|
7497
|
+
"get_strong_connections",
|
|
7498
|
+
"Get bidirectional connections for a note ranked by combined edge weight. Returns both outgoing and incoming links.",
|
|
7499
|
+
{
|
|
7500
|
+
path: z.string().describe('Path to the note (e.g., "daily/2026-02-24.md")'),
|
|
7501
|
+
limit: z.number().default(20).describe("Maximum number of results to return")
|
|
7502
|
+
},
|
|
7503
|
+
async ({ path: notePath, limit: requestedLimit }) => {
|
|
7504
|
+
const stateDb2 = getStateDb?.();
|
|
7505
|
+
if (!stateDb2) {
|
|
7506
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "StateDb not initialized" }) }] };
|
|
7507
|
+
}
|
|
7508
|
+
const limit = Math.min(requestedLimit ?? 20, MAX_LIMIT);
|
|
7509
|
+
const stem2 = notePath.replace(/\.md$/, "").split("/").pop()?.toLowerCase() ?? "";
|
|
7510
|
+
const rows = stateDb2.db.prepare(`
|
|
7511
|
+
SELECT target AS node, weight, 'outgoing' AS direction
|
|
7512
|
+
FROM note_links WHERE note_path = ?
|
|
7513
|
+
UNION ALL
|
|
7514
|
+
SELECT note_path AS node, weight, 'incoming' AS direction
|
|
7515
|
+
FROM note_links WHERE target = ?
|
|
7516
|
+
ORDER BY weight DESC
|
|
7517
|
+
LIMIT ?
|
|
7518
|
+
`).all(notePath, stem2, limit);
|
|
7519
|
+
return {
|
|
7520
|
+
content: [{
|
|
7521
|
+
type: "text",
|
|
7522
|
+
text: JSON.stringify({
|
|
7523
|
+
note: notePath,
|
|
7524
|
+
count: rows.length,
|
|
7525
|
+
connections: rows.map((r) => ({
|
|
7526
|
+
node: r.node,
|
|
7527
|
+
weight: r.weight,
|
|
7528
|
+
direction: r.direction
|
|
7529
|
+
}))
|
|
7530
|
+
}, null, 2)
|
|
7531
|
+
}]
|
|
7532
|
+
};
|
|
7533
|
+
}
|
|
7534
|
+
);
|
|
7440
7535
|
}
|
|
7441
7536
|
|
|
7442
7537
|
// src/tools/read/wikilinks.ts
|
|
@@ -8827,9 +8922,11 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
8827
8922
|
// Entity options (used with scope "entities")
|
|
8828
8923
|
prefix: z4.boolean().default(false).describe("Enable prefix matching for entity search (autocomplete)"),
|
|
8829
8924
|
// Pagination
|
|
8830
|
-
limit: z4.number().default(20).describe("Maximum number of results to return")
|
|
8925
|
+
limit: z4.number().default(20).describe("Maximum number of results to return"),
|
|
8926
|
+
// Context boost (edge weights)
|
|
8927
|
+
context_note: z4.string().optional().describe("Path of the note providing context. When set, results connected to this note via weighted edges get an RRF boost.")
|
|
8831
8928
|
},
|
|
8832
|
-
async ({ query, scope, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit }) => {
|
|
8929
|
+
async ({ query, scope, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit, context_note }) => {
|
|
8833
8930
|
const limit = Math.min(requestedLimit ?? 20, MAX_LIMIT);
|
|
8834
8931
|
const index = getIndex();
|
|
8835
8932
|
const vaultPath2 = getVaultPath();
|
|
@@ -8940,17 +9037,47 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
8940
9037
|
}
|
|
8941
9038
|
}
|
|
8942
9039
|
}
|
|
9040
|
+
let edgeRanked = [];
|
|
9041
|
+
if (context_note) {
|
|
9042
|
+
const ctxStateDb = getStateDb();
|
|
9043
|
+
if (ctxStateDb) {
|
|
9044
|
+
try {
|
|
9045
|
+
const edgeRows = ctxStateDb.db.prepare(`
|
|
9046
|
+
SELECT nl.target, nl.weight FROM note_links nl
|
|
9047
|
+
WHERE nl.note_path = ? AND nl.weight > 1.0
|
|
9048
|
+
ORDER BY nl.weight DESC LIMIT ?
|
|
9049
|
+
`).all(context_note, limit);
|
|
9050
|
+
if (edgeRows.length > 0) {
|
|
9051
|
+
const entityRows = ctxStateDb.db.prepare(
|
|
9052
|
+
"SELECT path, name_lower FROM entities"
|
|
9053
|
+
).all();
|
|
9054
|
+
const targetToPath = /* @__PURE__ */ new Map();
|
|
9055
|
+
for (const e of entityRows) {
|
|
9056
|
+
targetToPath.set(e.name_lower, e.path);
|
|
9057
|
+
}
|
|
9058
|
+
edgeRanked = edgeRows.map((r) => {
|
|
9059
|
+
const entityPath = targetToPath.get(r.target);
|
|
9060
|
+
return entityPath ? { path: entityPath, title: r.target } : null;
|
|
9061
|
+
}).filter((r) => r !== null);
|
|
9062
|
+
}
|
|
9063
|
+
} catch {
|
|
9064
|
+
}
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
8943
9067
|
if (hasEmbeddingsIndex()) {
|
|
8944
9068
|
try {
|
|
8945
9069
|
const semanticResults = await semanticSearch(query, limit);
|
|
8946
9070
|
const fts5Ranked = fts5Results.map((r) => ({ path: r.path, title: r.title, snippet: r.snippet }));
|
|
8947
9071
|
const semanticRanked = semanticResults.map((r) => ({ path: r.path, title: r.title }));
|
|
8948
|
-
const
|
|
8949
|
-
const
|
|
9072
|
+
const entityRankedList = entityResults.map((r) => ({ path: r.path, title: r.name }));
|
|
9073
|
+
const rrfLists = [fts5Ranked, semanticRanked, entityRankedList];
|
|
9074
|
+
if (edgeRanked.length > 0) rrfLists.push(edgeRanked);
|
|
9075
|
+
const rrfScores = reciprocalRankFusion(...rrfLists);
|
|
8950
9076
|
const allPaths = /* @__PURE__ */ new Set([
|
|
8951
9077
|
...fts5Results.map((r) => r.path),
|
|
8952
9078
|
...semanticResults.map((r) => r.path),
|
|
8953
|
-
...entityResults.map((r) => r.path)
|
|
9079
|
+
...entityResults.map((r) => r.path),
|
|
9080
|
+
...edgeRanked.map((r) => r.path)
|
|
8954
9081
|
]);
|
|
8955
9082
|
const fts5Map = new Map(fts5Results.map((r) => [r.path, r]));
|
|
8956
9083
|
const semanticMap = new Map(semanticResults.map((r) => [r.path, r]));
|
|
@@ -9075,6 +9202,123 @@ function suggestEntityAliases(stateDb2, folder) {
|
|
|
9075
9202
|
return suggestions;
|
|
9076
9203
|
}
|
|
9077
9204
|
|
|
9205
|
+
// src/core/write/edgeWeights.ts
|
|
9206
|
+
var moduleStateDb5 = null;
|
|
9207
|
+
function setEdgeWeightStateDb(stateDb2) {
|
|
9208
|
+
moduleStateDb5 = stateDb2;
|
|
9209
|
+
}
|
|
9210
|
+
function buildPathToTargetsMap(stateDb2) {
|
|
9211
|
+
const map = /* @__PURE__ */ new Map();
|
|
9212
|
+
const rows = stateDb2.db.prepare(
|
|
9213
|
+
"SELECT path, name_lower, aliases_json FROM entities"
|
|
9214
|
+
).all();
|
|
9215
|
+
for (const row of rows) {
|
|
9216
|
+
const targets = /* @__PURE__ */ new Set();
|
|
9217
|
+
targets.add(row.name_lower);
|
|
9218
|
+
if (row.aliases_json) {
|
|
9219
|
+
try {
|
|
9220
|
+
const aliases = JSON.parse(row.aliases_json);
|
|
9221
|
+
for (const alias of aliases) {
|
|
9222
|
+
targets.add(alias.toLowerCase());
|
|
9223
|
+
}
|
|
9224
|
+
} catch {
|
|
9225
|
+
}
|
|
9226
|
+
}
|
|
9227
|
+
map.set(row.path, targets);
|
|
9228
|
+
}
|
|
9229
|
+
return map;
|
|
9230
|
+
}
|
|
9231
|
+
function pathToFallbackTarget(filePath) {
|
|
9232
|
+
return filePath.replace(/\.md$/, "").split("/").pop()?.toLowerCase() ?? filePath.toLowerCase();
|
|
9233
|
+
}
|
|
9234
|
+
function recomputeEdgeWeights(stateDb2) {
|
|
9235
|
+
const start = Date.now();
|
|
9236
|
+
const edges = stateDb2.db.prepare(
|
|
9237
|
+
"SELECT note_path, target FROM note_links"
|
|
9238
|
+
).all();
|
|
9239
|
+
if (edges.length === 0) {
|
|
9240
|
+
return { edges_updated: 0, duration_ms: Date.now() - start };
|
|
9241
|
+
}
|
|
9242
|
+
const survivalMap = /* @__PURE__ */ new Map();
|
|
9243
|
+
const historyRows = stateDb2.db.prepare(
|
|
9244
|
+
"SELECT note_path, target, edits_survived FROM note_link_history"
|
|
9245
|
+
).all();
|
|
9246
|
+
for (const row of historyRows) {
|
|
9247
|
+
survivalMap.set(`${row.note_path}\0${row.target}`, row.edits_survived);
|
|
9248
|
+
}
|
|
9249
|
+
const pathToTargets = buildPathToTargetsMap(stateDb2);
|
|
9250
|
+
const targetToPaths = /* @__PURE__ */ new Map();
|
|
9251
|
+
for (const [entityPath, targets] of pathToTargets) {
|
|
9252
|
+
for (const target of targets) {
|
|
9253
|
+
let paths = targetToPaths.get(target);
|
|
9254
|
+
if (!paths) {
|
|
9255
|
+
paths = /* @__PURE__ */ new Set();
|
|
9256
|
+
targetToPaths.set(target, paths);
|
|
9257
|
+
}
|
|
9258
|
+
paths.add(entityPath);
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
const sessionRows = stateDb2.db.prepare(
|
|
9262
|
+
`SELECT session_id, note_paths FROM tool_invocations
|
|
9263
|
+
WHERE note_paths IS NOT NULL AND note_paths != '[]'`
|
|
9264
|
+
).all();
|
|
9265
|
+
const sessionPaths = /* @__PURE__ */ new Map();
|
|
9266
|
+
for (const row of sessionRows) {
|
|
9267
|
+
try {
|
|
9268
|
+
const paths = JSON.parse(row.note_paths);
|
|
9269
|
+
if (!Array.isArray(paths) || paths.length === 0) continue;
|
|
9270
|
+
let existing = sessionPaths.get(row.session_id);
|
|
9271
|
+
if (!existing) {
|
|
9272
|
+
existing = /* @__PURE__ */ new Set();
|
|
9273
|
+
sessionPaths.set(row.session_id, existing);
|
|
9274
|
+
}
|
|
9275
|
+
for (const p of paths) {
|
|
9276
|
+
existing.add(p);
|
|
9277
|
+
}
|
|
9278
|
+
} catch {
|
|
9279
|
+
}
|
|
9280
|
+
}
|
|
9281
|
+
const coSessionCount = /* @__PURE__ */ new Map();
|
|
9282
|
+
const sourceActivityCount = /* @__PURE__ */ new Map();
|
|
9283
|
+
for (const [, paths] of sessionPaths) {
|
|
9284
|
+
const sessionTargets = /* @__PURE__ */ new Set();
|
|
9285
|
+
for (const p of paths) {
|
|
9286
|
+
const targets = pathToTargets.get(p);
|
|
9287
|
+
if (targets) {
|
|
9288
|
+
for (const t of targets) sessionTargets.add(t);
|
|
9289
|
+
} else {
|
|
9290
|
+
sessionTargets.add(pathToFallbackTarget(p));
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
for (const edge of edges) {
|
|
9294
|
+
if (paths.has(edge.note_path)) {
|
|
9295
|
+
const srcKey = edge.note_path;
|
|
9296
|
+
sourceActivityCount.set(srcKey, (sourceActivityCount.get(srcKey) ?? 0) + 1);
|
|
9297
|
+
if (sessionTargets.has(edge.target)) {
|
|
9298
|
+
const edgeKey = `${edge.note_path}\0${edge.target}`;
|
|
9299
|
+
coSessionCount.set(edgeKey, (coSessionCount.get(edgeKey) ?? 0) + 1);
|
|
9300
|
+
}
|
|
9301
|
+
}
|
|
9302
|
+
}
|
|
9303
|
+
}
|
|
9304
|
+
const now = Date.now();
|
|
9305
|
+
const update = stateDb2.db.prepare(
|
|
9306
|
+
"UPDATE note_links SET weight = ?, weight_updated_at = ? WHERE note_path = ? AND target = ?"
|
|
9307
|
+
);
|
|
9308
|
+
const tx = stateDb2.db.transaction(() => {
|
|
9309
|
+
for (const edge of edges) {
|
|
9310
|
+
const edgeKey = `${edge.note_path}\0${edge.target}`;
|
|
9311
|
+
const editsSurvived = survivalMap.get(edgeKey) ?? 0;
|
|
9312
|
+
const coSessions = coSessionCount.get(edgeKey) ?? 0;
|
|
9313
|
+
const sourceAccess = sourceActivityCount.get(edge.note_path) ?? 0;
|
|
9314
|
+
const weight = 1 + editsSurvived * 0.5 + Math.min(coSessions * 0.5, 3) + Math.min(sourceAccess * 0.2, 2);
|
|
9315
|
+
update.run(Math.round(weight * 1e3) / 1e3, now, edge.note_path, edge.target);
|
|
9316
|
+
}
|
|
9317
|
+
});
|
|
9318
|
+
tx();
|
|
9319
|
+
return { edges_updated: edges.length, duration_ms: Date.now() - start };
|
|
9320
|
+
}
|
|
9321
|
+
|
|
9078
9322
|
// src/tools/read/system.ts
|
|
9079
9323
|
function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfig, getStateDb) {
|
|
9080
9324
|
const RefreshIndexOutputSchema = {
|
|
@@ -9082,6 +9326,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
9082
9326
|
notes_count: z5.number().describe("Number of notes indexed"),
|
|
9083
9327
|
entities_count: z5.number().describe("Number of entities (titles + aliases)"),
|
|
9084
9328
|
fts5_notes: z5.number().describe("Number of notes in FTS5 search index"),
|
|
9329
|
+
edges_recomputed: z5.number().optional().describe("Number of edges with recomputed weights"),
|
|
9085
9330
|
duration_ms: z5.number().describe("Time taken to rebuild index")
|
|
9086
9331
|
};
|
|
9087
9332
|
server2.registerTool(
|
|
@@ -9149,6 +9394,16 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
9149
9394
|
} catch (err) {
|
|
9150
9395
|
console.error("[Flywheel] FTS5 rebuild failed:", err);
|
|
9151
9396
|
}
|
|
9397
|
+
let edgesRecomputed = 0;
|
|
9398
|
+
if (stateDb2) {
|
|
9399
|
+
try {
|
|
9400
|
+
const edgeResult = recomputeEdgeWeights(stateDb2);
|
|
9401
|
+
edgesRecomputed = edgeResult.edges_updated;
|
|
9402
|
+
console.error(`[Flywheel] Edge weights: ${edgeResult.edges_updated} edges in ${edgeResult.duration_ms}ms`);
|
|
9403
|
+
} catch (err) {
|
|
9404
|
+
console.error("[Flywheel] Edge weight recompute failed:", err);
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9152
9407
|
const duration = Date.now() - startTime;
|
|
9153
9408
|
if (stateDb2) {
|
|
9154
9409
|
recordIndexEvent(stateDb2, {
|
|
@@ -9162,6 +9417,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
9162
9417
|
notes_count: newIndex.notes.size,
|
|
9163
9418
|
entities_count: newIndex.entities.size,
|
|
9164
9419
|
fts5_notes: fts5Notes,
|
|
9420
|
+
edges_recomputed: edgesRecomputed,
|
|
9165
9421
|
duration_ms: duration
|
|
9166
9422
|
};
|
|
9167
9423
|
return {
|
|
@@ -16953,7 +17209,7 @@ registerSystemTools(
|
|
|
16953
17209
|
},
|
|
16954
17210
|
() => stateDb
|
|
16955
17211
|
);
|
|
16956
|
-
registerGraphTools(server, () => vaultIndex, () => vaultPath);
|
|
17212
|
+
registerGraphTools(server, () => vaultIndex, () => vaultPath, () => stateDb);
|
|
16957
17213
|
registerWikilinkTools(server, () => vaultIndex, () => vaultPath);
|
|
16958
17214
|
registerQueryTools(server, () => vaultIndex, () => vaultPath, () => stateDb);
|
|
16959
17215
|
registerPrimitiveTools(server, () => vaultIndex, () => vaultPath, () => flywheelConfig);
|
|
@@ -17007,6 +17263,7 @@ async function main() {
|
|
|
17007
17263
|
loadEntityEmbeddingsToMemory();
|
|
17008
17264
|
setWriteStateDb(stateDb);
|
|
17009
17265
|
setRecencyStateDb(stateDb);
|
|
17266
|
+
setEdgeWeightStateDb(stateDb);
|
|
17010
17267
|
} catch (err) {
|
|
17011
17268
|
const msg = err instanceof Error ? err.message : String(err);
|
|
17012
17269
|
serverLog("statedb", `StateDb initialization failed: ${msg}`, "error");
|
|
@@ -17104,6 +17361,7 @@ async function main() {
|
|
|
17104
17361
|
}
|
|
17105
17362
|
var DEFAULT_ENTITY_EXCLUDE_FOLDERS = ["node_modules", "templates", "attachments", "tmp"];
|
|
17106
17363
|
var lastCooccurrenceRebuildAt = 0;
|
|
17364
|
+
var lastEdgeWeightRebuildAt = 0;
|
|
17107
17365
|
async function updateEntitiesInStateDb() {
|
|
17108
17366
|
if (!stateDb) return;
|
|
17109
17367
|
try {
|
|
@@ -17462,6 +17720,24 @@ async function runPostIndexWork(index) {
|
|
|
17462
17720
|
tracker.end({ error: String(e) });
|
|
17463
17721
|
serverLog("watcher", `Co-occurrence: failed: ${e}`);
|
|
17464
17722
|
}
|
|
17723
|
+
if (stateDb) {
|
|
17724
|
+
tracker.start("edge_weights", {});
|
|
17725
|
+
try {
|
|
17726
|
+
const edgeWeightAgeMs = lastEdgeWeightRebuildAt > 0 ? Date.now() - lastEdgeWeightRebuildAt : Infinity;
|
|
17727
|
+
if (edgeWeightAgeMs >= 60 * 60 * 1e3) {
|
|
17728
|
+
const result = recomputeEdgeWeights(stateDb);
|
|
17729
|
+
lastEdgeWeightRebuildAt = Date.now();
|
|
17730
|
+
tracker.end({ rebuilt: true, edges: result.edges_updated, duration_ms: result.duration_ms });
|
|
17731
|
+
serverLog("watcher", `Edge weights: ${result.edges_updated} edges in ${result.duration_ms}ms`);
|
|
17732
|
+
} else {
|
|
17733
|
+
tracker.end({ rebuilt: false, age_ms: edgeWeightAgeMs });
|
|
17734
|
+
serverLog("watcher", `Edge weights: cache valid (${Math.round(edgeWeightAgeMs / 1e3)}s old)`);
|
|
17735
|
+
}
|
|
17736
|
+
} catch (e) {
|
|
17737
|
+
tracker.end({ error: String(e) });
|
|
17738
|
+
serverLog("watcher", `Edge weights: failed: ${e}`);
|
|
17739
|
+
}
|
|
17740
|
+
}
|
|
17465
17741
|
if (hasEmbeddingsIndex()) {
|
|
17466
17742
|
tracker.start("note_embeddings", { files: filteredEvents.length });
|
|
17467
17743
|
let embUpdated = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.41",
|
|
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",
|