@velvetmonkey/flywheel-memory 2.0.43 → 2.0.44
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 +96 -16
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3627,9 +3627,12 @@ function getSuppressedCount(stateDb2) {
|
|
|
3627
3627
|
return row.count;
|
|
3628
3628
|
}
|
|
3629
3629
|
function getSuppressedEntities(stateDb2) {
|
|
3630
|
-
return stateDb2.db.prepare(
|
|
3631
|
-
|
|
3632
|
-
|
|
3630
|
+
return stateDb2.db.prepare(`
|
|
3631
|
+
SELECT s.entity, s.false_positive_rate,
|
|
3632
|
+
COALESCE((SELECT COUNT(*) FROM wikilink_feedback WHERE entity = s.entity), 0) as total
|
|
3633
|
+
FROM wikilink_suppressions s
|
|
3634
|
+
ORDER BY s.false_positive_rate DESC
|
|
3635
|
+
`).all();
|
|
3633
3636
|
}
|
|
3634
3637
|
function computeBoostFromAccuracy(accuracy, sampleCount) {
|
|
3635
3638
|
if (sampleCount < FEEDBACK_BOOST_MIN_SAMPLES) return 0;
|
|
@@ -3784,6 +3787,9 @@ function processImplicitFeedback(stateDb2, notePath, currentContent) {
|
|
|
3784
3787
|
}
|
|
3785
3788
|
});
|
|
3786
3789
|
transaction();
|
|
3790
|
+
if (removed.length > 0) {
|
|
3791
|
+
updateSuppressionList(stateDb2);
|
|
3792
|
+
}
|
|
3787
3793
|
return removed;
|
|
3788
3794
|
}
|
|
3789
3795
|
var TIER_LABELS = [
|
|
@@ -5407,7 +5413,7 @@ function recomputeEdgeWeights(stateDb2) {
|
|
|
5407
5413
|
"SELECT note_path, target FROM note_links"
|
|
5408
5414
|
).all();
|
|
5409
5415
|
if (edges.length === 0) {
|
|
5410
|
-
return { edges_updated: 0, duration_ms: Date.now() - start, total_weighted: 0, avg_weight: 0, strong_count: 0 };
|
|
5416
|
+
return { edges_updated: 0, duration_ms: Date.now() - start, total_weighted: 0, avg_weight: 0, strong_count: 0, top_changes: [] };
|
|
5411
5417
|
}
|
|
5412
5418
|
const survivalMap = /* @__PURE__ */ new Map();
|
|
5413
5419
|
const historyRows = stateDb2.db.prepare(
|
|
@@ -5471,10 +5477,18 @@ function recomputeEdgeWeights(stateDb2) {
|
|
|
5471
5477
|
}
|
|
5472
5478
|
}
|
|
5473
5479
|
}
|
|
5480
|
+
const oldWeights = /* @__PURE__ */ new Map();
|
|
5481
|
+
const oldRows = stateDb2.db.prepare(
|
|
5482
|
+
"SELECT note_path, target, weight FROM note_links"
|
|
5483
|
+
).all();
|
|
5484
|
+
for (const row of oldRows) {
|
|
5485
|
+
oldWeights.set(`${row.note_path}\0${row.target}`, row.weight);
|
|
5486
|
+
}
|
|
5474
5487
|
const now = Date.now();
|
|
5475
5488
|
const update = stateDb2.db.prepare(
|
|
5476
5489
|
"UPDATE note_links SET weight = ?, weight_updated_at = ? WHERE note_path = ? AND target = ?"
|
|
5477
5490
|
);
|
|
5491
|
+
const changes = [];
|
|
5478
5492
|
const tx = stateDb2.db.transaction(() => {
|
|
5479
5493
|
for (const edge of edges) {
|
|
5480
5494
|
const edgeKey = `${edge.note_path}\0${edge.target}`;
|
|
@@ -5482,10 +5496,27 @@ function recomputeEdgeWeights(stateDb2) {
|
|
|
5482
5496
|
const coSessions = coSessionCount.get(edgeKey) ?? 0;
|
|
5483
5497
|
const sourceAccess = sourceActivityCount.get(edge.note_path) ?? 0;
|
|
5484
5498
|
const weight = 1 + editsSurvived * 0.5 + Math.min(coSessions * 0.5, 3) + Math.min(sourceAccess * 0.2, 2);
|
|
5485
|
-
|
|
5499
|
+
const roundedWeight = Math.round(weight * 1e3) / 1e3;
|
|
5500
|
+
const oldWeight = oldWeights.get(edgeKey) ?? 1;
|
|
5501
|
+
const delta = roundedWeight - oldWeight;
|
|
5502
|
+
if (Math.abs(delta) >= 1e-3) {
|
|
5503
|
+
changes.push({
|
|
5504
|
+
note_path: edge.note_path,
|
|
5505
|
+
target: edge.target,
|
|
5506
|
+
old_weight: oldWeight,
|
|
5507
|
+
new_weight: roundedWeight,
|
|
5508
|
+
delta: Math.round(delta * 1e3) / 1e3,
|
|
5509
|
+
edits_survived: editsSurvived,
|
|
5510
|
+
co_sessions: coSessions,
|
|
5511
|
+
source_access: sourceAccess
|
|
5512
|
+
});
|
|
5513
|
+
}
|
|
5514
|
+
update.run(roundedWeight, now, edge.note_path, edge.target);
|
|
5486
5515
|
}
|
|
5487
5516
|
});
|
|
5488
5517
|
tx();
|
|
5518
|
+
changes.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
|
|
5519
|
+
const top_changes = changes.slice(0, 10);
|
|
5489
5520
|
const stats = stateDb2.db.prepare(`
|
|
5490
5521
|
SELECT
|
|
5491
5522
|
COUNT(*) as total_weighted,
|
|
@@ -5499,7 +5530,8 @@ function recomputeEdgeWeights(stateDb2) {
|
|
|
5499
5530
|
duration_ms: Date.now() - start,
|
|
5500
5531
|
total_weighted: stats?.total_weighted ?? 0,
|
|
5501
5532
|
avg_weight: Math.round((stats?.avg_weight ?? 0) * 100) / 100,
|
|
5502
|
-
strong_count: stats?.strong_count ?? 0
|
|
5533
|
+
strong_count: stats?.strong_count ?? 0,
|
|
5534
|
+
top_changes
|
|
5503
5535
|
};
|
|
5504
5536
|
}
|
|
5505
5537
|
function getEntityEdgeWeightMap(stateDb2) {
|
|
@@ -6202,6 +6234,12 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6202
6234
|
if (linkedEntities.has(entityName.toLowerCase())) {
|
|
6203
6235
|
continue;
|
|
6204
6236
|
}
|
|
6237
|
+
if (moduleStateDb5 && !disabled.has("feedback")) {
|
|
6238
|
+
const noteFolder2 = notePath ? notePath.split("/").slice(0, -1).join("/") : void 0;
|
|
6239
|
+
if (isSuppressed(moduleStateDb5, entityName, noteFolder2)) {
|
|
6240
|
+
continue;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6205
6243
|
const contentScore = disabled.has("exact_match") && disabled.has("stem_match") ? 0 : scoreEntity(entity, contentTokens, contentStems, config);
|
|
6206
6244
|
let score = contentScore;
|
|
6207
6245
|
if (contentScore > 0) {
|
|
@@ -6251,6 +6289,10 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6251
6289
|
if (!disabled.has("length_filter") && entityName.length > MAX_ENTITY_LENGTH) continue;
|
|
6252
6290
|
if (!disabled.has("article_filter") && isLikelyArticleTitle(entityName)) continue;
|
|
6253
6291
|
if (linkedEntities.has(entityName.toLowerCase())) continue;
|
|
6292
|
+
if (moduleStateDb5 && !disabled.has("feedback")) {
|
|
6293
|
+
const noteFolder2 = notePath ? notePath.split("/").slice(0, -1).join("/") : void 0;
|
|
6294
|
+
if (isSuppressed(moduleStateDb5, entityName, noteFolder2)) continue;
|
|
6295
|
+
}
|
|
6254
6296
|
const boost = getCooccurrenceBoost(entityName, directlyMatchedEntities, cooccurrenceIndex, recencyIndex);
|
|
6255
6297
|
if (boost > 0) {
|
|
6256
6298
|
const existing = scoredEntities.find((e) => e.name === entityName);
|
|
@@ -6315,6 +6357,10 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6315
6357
|
existing.score += boost;
|
|
6316
6358
|
existing.breakdown.semanticBoost = boost;
|
|
6317
6359
|
} else if (!linkedEntities.has(match.entityName.toLowerCase())) {
|
|
6360
|
+
if (moduleStateDb5 && !disabled.has("feedback")) {
|
|
6361
|
+
const noteFolder2 = notePath ? notePath.split("/").slice(0, -1).join("/") : void 0;
|
|
6362
|
+
if (isSuppressed(moduleStateDb5, match.entityName, noteFolder2)) continue;
|
|
6363
|
+
}
|
|
6318
6364
|
const entityWithType = entitiesWithTypes.find(
|
|
6319
6365
|
(et) => et.entity.name === match.entityName
|
|
6320
6366
|
);
|
|
@@ -6419,7 +6465,9 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6419
6465
|
if (topSuggestions.length === 0) {
|
|
6420
6466
|
return emptyResult;
|
|
6421
6467
|
}
|
|
6422
|
-
const
|
|
6468
|
+
const MIN_SUFFIX_SCORE = 12;
|
|
6469
|
+
const suffixEntries = topEntries.filter((e) => e.score >= MIN_SUFFIX_SCORE);
|
|
6470
|
+
const suffix = suffixEntries.length > 0 ? "\u2192 " + suffixEntries.map((e) => `[[${e.name}]]`).join(", ") : "";
|
|
6423
6471
|
const result = {
|
|
6424
6472
|
suggestions: topSuggestions,
|
|
6425
6473
|
suffix
|
|
@@ -12892,7 +12940,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
12892
12940
|
wikilinkInfo: wikilinkInfo || "none"
|
|
12893
12941
|
};
|
|
12894
12942
|
let suggestInfo;
|
|
12895
|
-
if (suggestOutgoingLinks && !skipWikilinks) {
|
|
12943
|
+
if (suggestOutgoingLinks && !skipWikilinks && processedContent.length >= 100) {
|
|
12896
12944
|
const result = await suggestRelatedLinks(processedContent, { maxSuggestions, notePath });
|
|
12897
12945
|
if (result.suffix) {
|
|
12898
12946
|
processedContent = processedContent + " " + result.suffix;
|
|
@@ -13011,7 +13059,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13011
13059
|
}
|
|
13012
13060
|
let workingReplacement = validationResult.content;
|
|
13013
13061
|
let { content: processedReplacement } = maybeApplyWikilinks(workingReplacement, skipWikilinks, notePath);
|
|
13014
|
-
if (suggestOutgoingLinks && !skipWikilinks) {
|
|
13062
|
+
if (suggestOutgoingLinks && !skipWikilinks && processedReplacement.length >= 100) {
|
|
13015
13063
|
const result = await suggestRelatedLinks(processedReplacement, { maxSuggestions, notePath });
|
|
13016
13064
|
if (result.suffix) {
|
|
13017
13065
|
processedReplacement = processedReplacement + " " + result.suffix;
|
|
@@ -13392,7 +13440,7 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
13392
13440
|
}
|
|
13393
13441
|
let { content: processedContent, wikilinkInfo } = maybeApplyWikilinks(effectiveContent, skipWikilinks, notePath);
|
|
13394
13442
|
let suggestInfo;
|
|
13395
|
-
if (suggestOutgoingLinks && !skipWikilinks) {
|
|
13443
|
+
if (suggestOutgoingLinks && !skipWikilinks && processedContent.length >= 100) {
|
|
13396
13444
|
const result = await suggestRelatedLinks(processedContent, { maxSuggestions, notePath });
|
|
13397
13445
|
if (result.suffix) {
|
|
13398
13446
|
processedContent = processedContent + " " + result.suffix;
|
|
@@ -15814,10 +15862,11 @@ function registerWikilinkFeedbackTools(server2, getStateDb) {
|
|
|
15814
15862
|
days_back: z21.number().optional().describe("Days to look back (default: 30)"),
|
|
15815
15863
|
granularity: z21.enum(["day", "week"]).optional().describe("Time bucket granularity for layer_timeseries (default: day)"),
|
|
15816
15864
|
timestamp_before: z21.number().optional().describe("Earlier timestamp for snapshot_diff"),
|
|
15817
|
-
timestamp_after: z21.number().optional().describe("Later timestamp for snapshot_diff")
|
|
15865
|
+
timestamp_after: z21.number().optional().describe("Later timestamp for snapshot_diff"),
|
|
15866
|
+
skip_status_update: z21.boolean().optional().describe("Skip marking application as removed (caller will trigger implicit detection via file edit)")
|
|
15818
15867
|
}
|
|
15819
15868
|
},
|
|
15820
|
-
async ({ mode, entity, note_path, context, correct, limit, days_back, granularity, timestamp_before, timestamp_after }) => {
|
|
15869
|
+
async ({ mode, entity, note_path, context, correct, limit, days_back, granularity, timestamp_before, timestamp_after, skip_status_update }) => {
|
|
15821
15870
|
const stateDb2 = getStateDb();
|
|
15822
15871
|
if (!stateDb2) {
|
|
15823
15872
|
return {
|
|
@@ -15845,6 +15894,11 @@ function registerWikilinkFeedbackTools(server2, getStateDb) {
|
|
|
15845
15894
|
isError: true
|
|
15846
15895
|
};
|
|
15847
15896
|
}
|
|
15897
|
+
if (!correct && note_path && !skip_status_update) {
|
|
15898
|
+
stateDb2.db.prepare(
|
|
15899
|
+
`UPDATE wikilink_applications SET status = 'removed' WHERE entity = ? AND note_path = ? COLLATE NOCASE`
|
|
15900
|
+
).run(entity, note_path);
|
|
15901
|
+
}
|
|
15848
15902
|
const suppressionUpdated = updateSuppressionList(stateDb2) > 0;
|
|
15849
15903
|
result = {
|
|
15850
15904
|
mode: "report",
|
|
@@ -17869,7 +17923,15 @@ async function runPostIndexWork(index) {
|
|
|
17869
17923
|
if (edgeWeightAgeMs >= 60 * 60 * 1e3) {
|
|
17870
17924
|
const result = recomputeEdgeWeights(stateDb);
|
|
17871
17925
|
lastEdgeWeightRebuildAt = Date.now();
|
|
17872
|
-
tracker.end({
|
|
17926
|
+
tracker.end({
|
|
17927
|
+
rebuilt: true,
|
|
17928
|
+
edges: result.edges_updated,
|
|
17929
|
+
duration_ms: result.duration_ms,
|
|
17930
|
+
total_weighted: result.total_weighted,
|
|
17931
|
+
avg_weight: result.avg_weight,
|
|
17932
|
+
strong_count: result.strong_count,
|
|
17933
|
+
top_changes: result.top_changes
|
|
17934
|
+
});
|
|
17873
17935
|
serverLog("watcher", `Edge weights: ${result.edges_updated} edges in ${result.duration_ms}ms`);
|
|
17874
17936
|
} else {
|
|
17875
17937
|
tracker.end({ rebuilt: false, age_ms: edgeWeightAgeMs });
|
|
@@ -18154,9 +18216,27 @@ async function runPostIndexWork(index) {
|
|
|
18154
18216
|
}
|
|
18155
18217
|
}
|
|
18156
18218
|
}
|
|
18157
|
-
|
|
18158
|
-
if (
|
|
18159
|
-
|
|
18219
|
+
const additionResults = [];
|
|
18220
|
+
if (stateDb && linkDiffs.length > 0) {
|
|
18221
|
+
const checkApplication = stateDb.db.prepare(
|
|
18222
|
+
`SELECT 1 FROM wikilink_applications WHERE LOWER(entity) = LOWER(?) AND note_path = ? AND status = 'applied'`
|
|
18223
|
+
);
|
|
18224
|
+
for (const diff of linkDiffs) {
|
|
18225
|
+
for (const target of diff.added) {
|
|
18226
|
+
if (checkApplication.get(target, diff.file)) continue;
|
|
18227
|
+
const entity = entitiesAfter.find(
|
|
18228
|
+
(e) => e.nameLower === target || (e.aliases ?? []).some((a) => a.toLowerCase() === target)
|
|
18229
|
+
);
|
|
18230
|
+
if (entity) {
|
|
18231
|
+
recordFeedback(stateDb, entity.name, "implicit:manual_added", diff.file, true);
|
|
18232
|
+
additionResults.push({ entity: entity.name, file: diff.file });
|
|
18233
|
+
}
|
|
18234
|
+
}
|
|
18235
|
+
}
|
|
18236
|
+
}
|
|
18237
|
+
tracker.end({ removals: feedbackResults, additions: additionResults });
|
|
18238
|
+
if (feedbackResults.length > 0 || additionResults.length > 0) {
|
|
18239
|
+
serverLog("watcher", `Implicit feedback: ${feedbackResults.length} removals, ${additionResults.length} manual additions detected`);
|
|
18160
18240
|
}
|
|
18161
18241
|
tracker.start("tag_scan", { files: filteredEvents.length });
|
|
18162
18242
|
const tagDiffs = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.44",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 42 tools for search, backlinks, graph queries, mutations, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
55
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
55
|
+
"@velvetmonkey/vault-core": "^2.0.44",
|
|
56
56
|
"better-sqlite3": "^11.0.0",
|
|
57
57
|
"chokidar": "^4.0.0",
|
|
58
58
|
"gray-matter": "^4.0.3",
|