@velvetmonkey/flywheel-memory 2.0.42 → 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 +285 -165
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3058,7 +3058,7 @@ var DEFAULT_WATCHER_CONFIG = {
|
|
|
3058
3058
|
flushMs: 1e3,
|
|
3059
3059
|
batchSize: 50,
|
|
3060
3060
|
usePolling: false,
|
|
3061
|
-
pollInterval:
|
|
3061
|
+
pollInterval: 1e4
|
|
3062
3062
|
};
|
|
3063
3063
|
function parseWatcherConfig() {
|
|
3064
3064
|
const debounceMs = parseInt(process.env.FLYWHEEL_DEBOUNCE_MS || "");
|
|
@@ -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 = [
|
|
@@ -5372,16 +5378,186 @@ function getCooccurrenceBoost(entityName, matchedEntities, cooccurrenceIndex2, r
|
|
|
5372
5378
|
return Math.min(boost, MAX_COOCCURRENCE_BOOST);
|
|
5373
5379
|
}
|
|
5374
5380
|
|
|
5375
|
-
// src/core/write/
|
|
5381
|
+
// src/core/write/edgeWeights.ts
|
|
5376
5382
|
var moduleStateDb4 = null;
|
|
5377
|
-
function
|
|
5383
|
+
function setEdgeWeightStateDb(stateDb2) {
|
|
5378
5384
|
moduleStateDb4 = stateDb2;
|
|
5385
|
+
}
|
|
5386
|
+
function buildPathToTargetsMap(stateDb2) {
|
|
5387
|
+
const map = /* @__PURE__ */ new Map();
|
|
5388
|
+
const rows = stateDb2.db.prepare(
|
|
5389
|
+
"SELECT path, name_lower, aliases_json FROM entities"
|
|
5390
|
+
).all();
|
|
5391
|
+
for (const row of rows) {
|
|
5392
|
+
const targets = /* @__PURE__ */ new Set();
|
|
5393
|
+
targets.add(row.name_lower);
|
|
5394
|
+
if (row.aliases_json) {
|
|
5395
|
+
try {
|
|
5396
|
+
const aliases = JSON.parse(row.aliases_json);
|
|
5397
|
+
for (const alias of aliases) {
|
|
5398
|
+
targets.add(alias.toLowerCase());
|
|
5399
|
+
}
|
|
5400
|
+
} catch {
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
map.set(row.path, targets);
|
|
5404
|
+
}
|
|
5405
|
+
return map;
|
|
5406
|
+
}
|
|
5407
|
+
function pathToFallbackTarget(filePath) {
|
|
5408
|
+
return filePath.replace(/\.md$/, "").split("/").pop()?.toLowerCase() ?? filePath.toLowerCase();
|
|
5409
|
+
}
|
|
5410
|
+
function recomputeEdgeWeights(stateDb2) {
|
|
5411
|
+
const start = Date.now();
|
|
5412
|
+
const edges = stateDb2.db.prepare(
|
|
5413
|
+
"SELECT note_path, target FROM note_links"
|
|
5414
|
+
).all();
|
|
5415
|
+
if (edges.length === 0) {
|
|
5416
|
+
return { edges_updated: 0, duration_ms: Date.now() - start, total_weighted: 0, avg_weight: 0, strong_count: 0, top_changes: [] };
|
|
5417
|
+
}
|
|
5418
|
+
const survivalMap = /* @__PURE__ */ new Map();
|
|
5419
|
+
const historyRows = stateDb2.db.prepare(
|
|
5420
|
+
"SELECT note_path, target, edits_survived FROM note_link_history"
|
|
5421
|
+
).all();
|
|
5422
|
+
for (const row of historyRows) {
|
|
5423
|
+
survivalMap.set(`${row.note_path}\0${row.target}`, row.edits_survived);
|
|
5424
|
+
}
|
|
5425
|
+
const pathToTargets = buildPathToTargetsMap(stateDb2);
|
|
5426
|
+
const targetToPaths = /* @__PURE__ */ new Map();
|
|
5427
|
+
for (const [entityPath, targets] of pathToTargets) {
|
|
5428
|
+
for (const target of targets) {
|
|
5429
|
+
let paths = targetToPaths.get(target);
|
|
5430
|
+
if (!paths) {
|
|
5431
|
+
paths = /* @__PURE__ */ new Set();
|
|
5432
|
+
targetToPaths.set(target, paths);
|
|
5433
|
+
}
|
|
5434
|
+
paths.add(entityPath);
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
const sessionRows = stateDb2.db.prepare(
|
|
5438
|
+
`SELECT session_id, note_paths FROM tool_invocations
|
|
5439
|
+
WHERE note_paths IS NOT NULL AND note_paths != '[]'`
|
|
5440
|
+
).all();
|
|
5441
|
+
const sessionPaths = /* @__PURE__ */ new Map();
|
|
5442
|
+
for (const row of sessionRows) {
|
|
5443
|
+
try {
|
|
5444
|
+
const paths = JSON.parse(row.note_paths);
|
|
5445
|
+
if (!Array.isArray(paths) || paths.length === 0) continue;
|
|
5446
|
+
let existing = sessionPaths.get(row.session_id);
|
|
5447
|
+
if (!existing) {
|
|
5448
|
+
existing = /* @__PURE__ */ new Set();
|
|
5449
|
+
sessionPaths.set(row.session_id, existing);
|
|
5450
|
+
}
|
|
5451
|
+
for (const p of paths) {
|
|
5452
|
+
existing.add(p);
|
|
5453
|
+
}
|
|
5454
|
+
} catch {
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
const coSessionCount = /* @__PURE__ */ new Map();
|
|
5458
|
+
const sourceActivityCount = /* @__PURE__ */ new Map();
|
|
5459
|
+
for (const [, paths] of sessionPaths) {
|
|
5460
|
+
const sessionTargets = /* @__PURE__ */ new Set();
|
|
5461
|
+
for (const p of paths) {
|
|
5462
|
+
const targets = pathToTargets.get(p);
|
|
5463
|
+
if (targets) {
|
|
5464
|
+
for (const t of targets) sessionTargets.add(t);
|
|
5465
|
+
} else {
|
|
5466
|
+
sessionTargets.add(pathToFallbackTarget(p));
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
for (const edge of edges) {
|
|
5470
|
+
if (paths.has(edge.note_path)) {
|
|
5471
|
+
const srcKey = edge.note_path;
|
|
5472
|
+
sourceActivityCount.set(srcKey, (sourceActivityCount.get(srcKey) ?? 0) + 1);
|
|
5473
|
+
if (sessionTargets.has(edge.target)) {
|
|
5474
|
+
const edgeKey = `${edge.note_path}\0${edge.target}`;
|
|
5475
|
+
coSessionCount.set(edgeKey, (coSessionCount.get(edgeKey) ?? 0) + 1);
|
|
5476
|
+
}
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
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
|
+
}
|
|
5487
|
+
const now = Date.now();
|
|
5488
|
+
const update = stateDb2.db.prepare(
|
|
5489
|
+
"UPDATE note_links SET weight = ?, weight_updated_at = ? WHERE note_path = ? AND target = ?"
|
|
5490
|
+
);
|
|
5491
|
+
const changes = [];
|
|
5492
|
+
const tx = stateDb2.db.transaction(() => {
|
|
5493
|
+
for (const edge of edges) {
|
|
5494
|
+
const edgeKey = `${edge.note_path}\0${edge.target}`;
|
|
5495
|
+
const editsSurvived = survivalMap.get(edgeKey) ?? 0;
|
|
5496
|
+
const coSessions = coSessionCount.get(edgeKey) ?? 0;
|
|
5497
|
+
const sourceAccess = sourceActivityCount.get(edge.note_path) ?? 0;
|
|
5498
|
+
const weight = 1 + editsSurvived * 0.5 + Math.min(coSessions * 0.5, 3) + Math.min(sourceAccess * 0.2, 2);
|
|
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);
|
|
5515
|
+
}
|
|
5516
|
+
});
|
|
5517
|
+
tx();
|
|
5518
|
+
changes.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
|
|
5519
|
+
const top_changes = changes.slice(0, 10);
|
|
5520
|
+
const stats = stateDb2.db.prepare(`
|
|
5521
|
+
SELECT
|
|
5522
|
+
COUNT(*) as total_weighted,
|
|
5523
|
+
AVG(weight) as avg_weight,
|
|
5524
|
+
SUM(CASE WHEN weight > 3.0 THEN 1 ELSE 0 END) as strong_count
|
|
5525
|
+
FROM note_links
|
|
5526
|
+
WHERE weight > 1.0
|
|
5527
|
+
`).get();
|
|
5528
|
+
return {
|
|
5529
|
+
edges_updated: edges.length,
|
|
5530
|
+
duration_ms: Date.now() - start,
|
|
5531
|
+
total_weighted: stats?.total_weighted ?? 0,
|
|
5532
|
+
avg_weight: Math.round((stats?.avg_weight ?? 0) * 100) / 100,
|
|
5533
|
+
strong_count: stats?.strong_count ?? 0,
|
|
5534
|
+
top_changes
|
|
5535
|
+
};
|
|
5536
|
+
}
|
|
5537
|
+
function getEntityEdgeWeightMap(stateDb2) {
|
|
5538
|
+
const rows = stateDb2.db.prepare(`
|
|
5539
|
+
SELECT LOWER(target) as target_lower, AVG(weight) as avg_weight
|
|
5540
|
+
FROM note_links
|
|
5541
|
+
WHERE weight > 1.0
|
|
5542
|
+
GROUP BY LOWER(target)
|
|
5543
|
+
`).all();
|
|
5544
|
+
const map = /* @__PURE__ */ new Map();
|
|
5545
|
+
for (const row of rows) {
|
|
5546
|
+
map.set(row.target_lower, row.avg_weight);
|
|
5547
|
+
}
|
|
5548
|
+
return map;
|
|
5549
|
+
}
|
|
5550
|
+
|
|
5551
|
+
// src/core/write/wikilinks.ts
|
|
5552
|
+
var moduleStateDb5 = null;
|
|
5553
|
+
function setWriteStateDb(stateDb2) {
|
|
5554
|
+
moduleStateDb5 = stateDb2;
|
|
5379
5555
|
setGitStateDb(stateDb2);
|
|
5380
5556
|
setHintsStateDb(stateDb2);
|
|
5381
5557
|
setRecencyStateDb(stateDb2);
|
|
5382
5558
|
}
|
|
5383
5559
|
function getWriteStateDb() {
|
|
5384
|
-
return
|
|
5560
|
+
return moduleStateDb5;
|
|
5385
5561
|
}
|
|
5386
5562
|
var moduleConfig = null;
|
|
5387
5563
|
var ALL_IMPLICIT_PATTERNS = ["proper-nouns", "single-caps", "quoted-terms", "camel-case", "acronyms"];
|
|
@@ -5436,9 +5612,9 @@ var DEFAULT_EXCLUDE_FOLDERS = [
|
|
|
5436
5612
|
];
|
|
5437
5613
|
async function initializeEntityIndex(vaultPath2) {
|
|
5438
5614
|
try {
|
|
5439
|
-
if (
|
|
5615
|
+
if (moduleStateDb5) {
|
|
5440
5616
|
try {
|
|
5441
|
-
const dbIndex = getEntityIndexFromDb(
|
|
5617
|
+
const dbIndex = getEntityIndexFromDb(moduleStateDb5);
|
|
5442
5618
|
if (dbIndex._metadata.total_entities > 0) {
|
|
5443
5619
|
entityIndex = dbIndex;
|
|
5444
5620
|
indexReady = true;
|
|
@@ -5466,9 +5642,9 @@ async function rebuildIndex(vaultPath2) {
|
|
|
5466
5642
|
lastLoadedAt = Date.now();
|
|
5467
5643
|
const entityDuration = Date.now() - startTime;
|
|
5468
5644
|
console.error(`[Flywheel] Entity index built: ${entityIndex._metadata.total_entities} entities in ${entityDuration}ms`);
|
|
5469
|
-
if (
|
|
5645
|
+
if (moduleStateDb5) {
|
|
5470
5646
|
try {
|
|
5471
|
-
|
|
5647
|
+
moduleStateDb5.replaceAllEntities(entityIndex);
|
|
5472
5648
|
console.error(`[Flywheel] Saved entities to StateDb`);
|
|
5473
5649
|
} catch (e) {
|
|
5474
5650
|
console.error(`[Flywheel] Failed to save entities to StateDb: ${e}`);
|
|
@@ -5505,14 +5681,14 @@ function isEntityIndexReady() {
|
|
|
5505
5681
|
return indexReady && entityIndex !== null;
|
|
5506
5682
|
}
|
|
5507
5683
|
function checkAndRefreshIfStale() {
|
|
5508
|
-
if (!
|
|
5684
|
+
if (!moduleStateDb5 || !indexReady) return;
|
|
5509
5685
|
try {
|
|
5510
|
-
const metadata = getStateDbMetadata(
|
|
5686
|
+
const metadata = getStateDbMetadata(moduleStateDb5);
|
|
5511
5687
|
if (!metadata.entitiesBuiltAt) return;
|
|
5512
5688
|
const dbBuiltAt = new Date(metadata.entitiesBuiltAt).getTime();
|
|
5513
5689
|
if (dbBuiltAt > lastLoadedAt) {
|
|
5514
5690
|
console.error("[Flywheel] Entity index stale, reloading from StateDb...");
|
|
5515
|
-
const dbIndex = getEntityIndexFromDb(
|
|
5691
|
+
const dbIndex = getEntityIndexFromDb(moduleStateDb5);
|
|
5516
5692
|
if (dbIndex._metadata.total_entities > 0) {
|
|
5517
5693
|
entityIndex = dbIndex;
|
|
5518
5694
|
lastLoadedAt = Date.now();
|
|
@@ -5554,11 +5730,11 @@ function processWikilinks(content, notePath, existingContent) {
|
|
|
5554
5730
|
}
|
|
5555
5731
|
let entities = getAllEntities(entityIndex);
|
|
5556
5732
|
console.error(`[Flywheel:DEBUG] Processing wikilinks with ${entities.length} entities`);
|
|
5557
|
-
if (
|
|
5733
|
+
if (moduleStateDb5) {
|
|
5558
5734
|
const folder = notePath ? notePath.split("/")[0] : void 0;
|
|
5559
5735
|
entities = entities.filter((e) => {
|
|
5560
5736
|
const name = getEntityName2(e);
|
|
5561
|
-
return !isSuppressed(
|
|
5737
|
+
return !isSuppressed(moduleStateDb5, name, folder);
|
|
5562
5738
|
});
|
|
5563
5739
|
}
|
|
5564
5740
|
const sortedEntities = sortEntitiesByPriority(entities, notePath);
|
|
@@ -5638,8 +5814,8 @@ function maybeApplyWikilinks(content, skipWikilinks, notePath, existingContent)
|
|
|
5638
5814
|
checkAndRefreshIfStale();
|
|
5639
5815
|
const result = processWikilinks(content, notePath, existingContent);
|
|
5640
5816
|
if (result.linksAdded > 0) {
|
|
5641
|
-
if (
|
|
5642
|
-
trackWikilinkApplications(
|
|
5817
|
+
if (moduleStateDb5 && notePath) {
|
|
5818
|
+
trackWikilinkApplications(moduleStateDb5, notePath, result.linkedEntities);
|
|
5643
5819
|
}
|
|
5644
5820
|
const implicitCount = result.implicitEntities?.length ?? 0;
|
|
5645
5821
|
const implicitInfo = implicitCount > 0 ? ` + ${implicitCount} implicit: ${result.implicitEntities.join(", ")}` : "";
|
|
@@ -5993,6 +6169,11 @@ function scoreEntity(entity, contentTokens, contentStems, config) {
|
|
|
5993
6169
|
}
|
|
5994
6170
|
return score;
|
|
5995
6171
|
}
|
|
6172
|
+
function getEdgeWeightBoostScore(entityName, map) {
|
|
6173
|
+
const avgWeight = map.get(entityName.toLowerCase());
|
|
6174
|
+
if (!avgWeight) return 0;
|
|
6175
|
+
return Math.min((avgWeight - 1) * 2, 4);
|
|
6176
|
+
}
|
|
5996
6177
|
async function suggestRelatedLinks(content, options = {}) {
|
|
5997
6178
|
const {
|
|
5998
6179
|
maxSuggestions = 3,
|
|
@@ -6036,7 +6217,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6036
6217
|
}
|
|
6037
6218
|
const linkedEntities = excludeLinked ? extractLinkedEntities(content) : /* @__PURE__ */ new Set();
|
|
6038
6219
|
const noteFolder = notePath ? notePath.split("/")[0] : void 0;
|
|
6039
|
-
const feedbackBoosts =
|
|
6220
|
+
const feedbackBoosts = moduleStateDb5 ? getAllFeedbackBoosts(moduleStateDb5, noteFolder) : /* @__PURE__ */ new Map();
|
|
6221
|
+
const edgeWeightMap = moduleStateDb5 ? getEntityEdgeWeightMap(moduleStateDb5) : /* @__PURE__ */ new Map();
|
|
6040
6222
|
const scoredEntities = [];
|
|
6041
6223
|
const directlyMatchedEntities = /* @__PURE__ */ new Set();
|
|
6042
6224
|
const entitiesWithContentMatch = /* @__PURE__ */ new Set();
|
|
@@ -6052,6 +6234,12 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6052
6234
|
if (linkedEntities.has(entityName.toLowerCase())) {
|
|
6053
6235
|
continue;
|
|
6054
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
|
+
}
|
|
6055
6243
|
const contentScore = disabled.has("exact_match") && disabled.has("stem_match") ? 0 : scoreEntity(entity, contentTokens, contentStems, config);
|
|
6056
6244
|
let score = contentScore;
|
|
6057
6245
|
if (contentScore > 0) {
|
|
@@ -6069,6 +6257,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6069
6257
|
score += layerHubBoost;
|
|
6070
6258
|
const layerFeedbackAdj = disabled.has("feedback") ? 0 : feedbackBoosts.get(entityName) ?? 0;
|
|
6071
6259
|
score += layerFeedbackAdj;
|
|
6260
|
+
const layerEdgeWeightBoost = disabled.has("edge_weight") ? 0 : getEdgeWeightBoostScore(entityName, edgeWeightMap);
|
|
6261
|
+
score += layerEdgeWeightBoost;
|
|
6072
6262
|
if (score > 0) {
|
|
6073
6263
|
directlyMatchedEntities.add(entityName);
|
|
6074
6264
|
}
|
|
@@ -6086,7 +6276,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6086
6276
|
recencyBoost: layerRecencyBoost,
|
|
6087
6277
|
crossFolderBoost: layerCrossFolderBoost,
|
|
6088
6278
|
hubBoost: layerHubBoost,
|
|
6089
|
-
feedbackAdjustment: layerFeedbackAdj
|
|
6279
|
+
feedbackAdjustment: layerFeedbackAdj,
|
|
6280
|
+
edgeWeightBoost: layerEdgeWeightBoost
|
|
6090
6281
|
}
|
|
6091
6282
|
});
|
|
6092
6283
|
}
|
|
@@ -6098,6 +6289,10 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6098
6289
|
if (!disabled.has("length_filter") && entityName.length > MAX_ENTITY_LENGTH) continue;
|
|
6099
6290
|
if (!disabled.has("article_filter") && isLikelyArticleTitle(entityName)) continue;
|
|
6100
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
|
+
}
|
|
6101
6296
|
const boost = getCooccurrenceBoost(entityName, directlyMatchedEntities, cooccurrenceIndex, recencyIndex);
|
|
6102
6297
|
if (boost > 0) {
|
|
6103
6298
|
const existing = scoredEntities.find((e) => e.name === entityName);
|
|
@@ -6119,7 +6314,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6119
6314
|
const crossFolderBoost = disabled.has("cross_folder") ? 0 : notePath && entity.path ? getCrossFolderBoost(entity.path, notePath) : 0;
|
|
6120
6315
|
const hubBoost = disabled.has("hub_boost") ? 0 : getHubBoost(entity);
|
|
6121
6316
|
const feedbackAdj = disabled.has("feedback") ? 0 : feedbackBoosts.get(entityName) ?? 0;
|
|
6122
|
-
const
|
|
6317
|
+
const edgeWeightBoost = disabled.has("edge_weight") ? 0 : getEdgeWeightBoostScore(entityName, edgeWeightMap);
|
|
6318
|
+
const totalBoost = boost + typeBoost + contextBoost + recencyBoostVal + crossFolderBoost + hubBoost + feedbackAdj + edgeWeightBoost;
|
|
6123
6319
|
if (totalBoost >= adaptiveMinScore) {
|
|
6124
6320
|
scoredEntities.push({
|
|
6125
6321
|
name: entityName,
|
|
@@ -6134,7 +6330,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6134
6330
|
recencyBoost: recencyBoostVal,
|
|
6135
6331
|
crossFolderBoost,
|
|
6136
6332
|
hubBoost,
|
|
6137
|
-
feedbackAdjustment: feedbackAdj
|
|
6333
|
+
feedbackAdjustment: feedbackAdj,
|
|
6334
|
+
edgeWeightBoost
|
|
6138
6335
|
}
|
|
6139
6336
|
});
|
|
6140
6337
|
}
|
|
@@ -6160,6 +6357,10 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6160
6357
|
existing.score += boost;
|
|
6161
6358
|
existing.breakdown.semanticBoost = boost;
|
|
6162
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
|
+
}
|
|
6163
6364
|
const entityWithType = entitiesWithTypes.find(
|
|
6164
6365
|
(et) => et.entity.name === match.entityName
|
|
6165
6366
|
);
|
|
@@ -6172,7 +6373,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6172
6373
|
const layerHubBoost = disabled.has("hub_boost") ? 0 : getHubBoost(entity);
|
|
6173
6374
|
const layerCrossFolderBoost = disabled.has("cross_folder") ? 0 : notePath && entity.path ? getCrossFolderBoost(entity.path, notePath) : 0;
|
|
6174
6375
|
const layerFeedbackAdj = disabled.has("feedback") ? 0 : feedbackBoosts.get(match.entityName) ?? 0;
|
|
6175
|
-
const
|
|
6376
|
+
const layerEdgeWeightBoost = disabled.has("edge_weight") ? 0 : getEdgeWeightBoostScore(match.entityName, edgeWeightMap);
|
|
6377
|
+
const totalScore = boost + layerTypeBoost + layerContextBoost + layerHubBoost + layerCrossFolderBoost + layerFeedbackAdj + layerEdgeWeightBoost;
|
|
6176
6378
|
if (totalScore >= adaptiveMinScore) {
|
|
6177
6379
|
scoredEntities.push({
|
|
6178
6380
|
name: match.entityName,
|
|
@@ -6188,7 +6390,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6188
6390
|
crossFolderBoost: layerCrossFolderBoost,
|
|
6189
6391
|
hubBoost: layerHubBoost,
|
|
6190
6392
|
feedbackAdjustment: layerFeedbackAdj,
|
|
6191
|
-
semanticBoost: boost
|
|
6393
|
+
semanticBoost: boost,
|
|
6394
|
+
edgeWeightBoost: layerEdgeWeightBoost
|
|
6192
6395
|
}
|
|
6193
6396
|
});
|
|
6194
6397
|
entitiesWithContentMatch.add(match.entityName);
|
|
@@ -6213,15 +6416,15 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6213
6416
|
}
|
|
6214
6417
|
return 0;
|
|
6215
6418
|
});
|
|
6216
|
-
if (
|
|
6419
|
+
if (moduleStateDb5 && notePath) {
|
|
6217
6420
|
try {
|
|
6218
6421
|
const now = Date.now();
|
|
6219
|
-
const insertStmt =
|
|
6422
|
+
const insertStmt = moduleStateDb5.db.prepare(`
|
|
6220
6423
|
INSERT OR IGNORE INTO suggestion_events
|
|
6221
6424
|
(timestamp, note_path, entity, total_score, breakdown_json, threshold, passed, strictness, applied, pipeline_event_id)
|
|
6222
6425
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, NULL)
|
|
6223
6426
|
`);
|
|
6224
|
-
const persistTransaction =
|
|
6427
|
+
const persistTransaction = moduleStateDb5.db.transaction(() => {
|
|
6225
6428
|
for (const e of relevantEntities) {
|
|
6226
6429
|
insertStmt.run(
|
|
6227
6430
|
now,
|
|
@@ -6262,13 +6465,15 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6262
6465
|
if (topSuggestions.length === 0) {
|
|
6263
6466
|
return emptyResult;
|
|
6264
6467
|
}
|
|
6265
|
-
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(", ") : "";
|
|
6266
6471
|
const result = {
|
|
6267
6472
|
suggestions: topSuggestions,
|
|
6268
6473
|
suffix
|
|
6269
6474
|
};
|
|
6270
6475
|
if (detail) {
|
|
6271
|
-
const feedbackStats =
|
|
6476
|
+
const feedbackStats = moduleStateDb5 ? getEntityStats(moduleStateDb5) : [];
|
|
6272
6477
|
const feedbackMap = new Map(feedbackStats.map((s) => [s.entity, s]));
|
|
6273
6478
|
result.detailed = topEntries.map((e) => {
|
|
6274
6479
|
const fb = feedbackMap.get(e.name);
|
|
@@ -6287,9 +6492,9 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
6287
6492
|
return result;
|
|
6288
6493
|
}
|
|
6289
6494
|
function detectAliasCollisions(noteName, aliases = []) {
|
|
6290
|
-
if (!
|
|
6495
|
+
if (!moduleStateDb5) return [];
|
|
6291
6496
|
const collisions = [];
|
|
6292
|
-
const nameAsAlias = getEntitiesByAlias(
|
|
6497
|
+
const nameAsAlias = getEntitiesByAlias(moduleStateDb5, noteName);
|
|
6293
6498
|
for (const entity of nameAsAlias) {
|
|
6294
6499
|
if (entity.name.toLowerCase() === noteName.toLowerCase()) continue;
|
|
6295
6500
|
collisions.push({
|
|
@@ -6303,7 +6508,7 @@ function detectAliasCollisions(noteName, aliases = []) {
|
|
|
6303
6508
|
});
|
|
6304
6509
|
}
|
|
6305
6510
|
for (const alias of aliases) {
|
|
6306
|
-
const existingByName = getEntityByName(
|
|
6511
|
+
const existingByName = getEntityByName(moduleStateDb5, alias);
|
|
6307
6512
|
if (existingByName && existingByName.name.toLowerCase() !== noteName.toLowerCase()) {
|
|
6308
6513
|
collisions.push({
|
|
6309
6514
|
term: alias,
|
|
@@ -6315,7 +6520,7 @@ function detectAliasCollisions(noteName, aliases = []) {
|
|
|
6315
6520
|
}
|
|
6316
6521
|
});
|
|
6317
6522
|
}
|
|
6318
|
-
const existingByAlias = getEntitiesByAlias(
|
|
6523
|
+
const existingByAlias = getEntitiesByAlias(moduleStateDb5, alias);
|
|
6319
6524
|
for (const entity of existingByAlias) {
|
|
6320
6525
|
if (entity.name.toLowerCase() === noteName.toLowerCase()) continue;
|
|
6321
6526
|
if (existingByName && existingByName.name.toLowerCase() === entity.name.toLowerCase()) continue;
|
|
@@ -6339,8 +6544,8 @@ function suggestAliases(noteName, existingAliases = [], category) {
|
|
|
6339
6544
|
function isSafe(alias) {
|
|
6340
6545
|
if (existingLower.has(alias.toLowerCase())) return false;
|
|
6341
6546
|
if (alias.toLowerCase() === noteName.toLowerCase()) return false;
|
|
6342
|
-
if (!
|
|
6343
|
-
const existing = getEntityByName(
|
|
6547
|
+
if (!moduleStateDb5) return true;
|
|
6548
|
+
const existing = getEntityByName(moduleStateDb5, alias);
|
|
6344
6549
|
return !existing;
|
|
6345
6550
|
}
|
|
6346
6551
|
const inferredCategory = category || inferCategoryFromName(noteName);
|
|
@@ -6382,8 +6587,8 @@ function inferCategoryFromName(name) {
|
|
|
6382
6587
|
}
|
|
6383
6588
|
async function checkPreflightSimilarity(noteName) {
|
|
6384
6589
|
const result = { similarEntities: [] };
|
|
6385
|
-
if (!
|
|
6386
|
-
const exact = getEntityByName(
|
|
6590
|
+
if (!moduleStateDb5) return result;
|
|
6591
|
+
const exact = getEntityByName(moduleStateDb5, noteName);
|
|
6387
6592
|
if (exact) {
|
|
6388
6593
|
result.existingEntity = {
|
|
6389
6594
|
name: exact.name,
|
|
@@ -6393,7 +6598,7 @@ async function checkPreflightSimilarity(noteName) {
|
|
|
6393
6598
|
}
|
|
6394
6599
|
const ftsNames = /* @__PURE__ */ new Set();
|
|
6395
6600
|
try {
|
|
6396
|
-
const searchResults = searchEntitiesDb(
|
|
6601
|
+
const searchResults = searchEntitiesDb(moduleStateDb5, noteName, 5);
|
|
6397
6602
|
for (const sr of searchResults) {
|
|
6398
6603
|
if (sr.name.toLowerCase() === noteName.toLowerCase()) continue;
|
|
6399
6604
|
ftsNames.add(sr.name.toLowerCase());
|
|
@@ -6414,7 +6619,7 @@ async function checkPreflightSimilarity(noteName) {
|
|
|
6414
6619
|
if (match.similarity < 0.85) continue;
|
|
6415
6620
|
if (match.entityName.toLowerCase() === noteName.toLowerCase()) continue;
|
|
6416
6621
|
if (ftsNames.has(match.entityName.toLowerCase())) continue;
|
|
6417
|
-
const entity = getEntityByName(
|
|
6622
|
+
const entity = getEntityByName(moduleStateDb5, match.entityName);
|
|
6418
6623
|
if (entity) {
|
|
6419
6624
|
result.similarEntities.push({
|
|
6420
6625
|
name: entity.name,
|
|
@@ -9202,123 +9407,6 @@ function suggestEntityAliases(stateDb2, folder) {
|
|
|
9202
9407
|
return suggestions;
|
|
9203
9408
|
}
|
|
9204
9409
|
|
|
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
|
-
|
|
9322
9410
|
// src/tools/read/system.ts
|
|
9323
9411
|
function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfig, getStateDb) {
|
|
9324
9412
|
const RefreshIndexOutputSchema = {
|
|
@@ -12852,7 +12940,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
12852
12940
|
wikilinkInfo: wikilinkInfo || "none"
|
|
12853
12941
|
};
|
|
12854
12942
|
let suggestInfo;
|
|
12855
|
-
if (suggestOutgoingLinks && !skipWikilinks) {
|
|
12943
|
+
if (suggestOutgoingLinks && !skipWikilinks && processedContent.length >= 100) {
|
|
12856
12944
|
const result = await suggestRelatedLinks(processedContent, { maxSuggestions, notePath });
|
|
12857
12945
|
if (result.suffix) {
|
|
12858
12946
|
processedContent = processedContent + " " + result.suffix;
|
|
@@ -12971,7 +13059,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
12971
13059
|
}
|
|
12972
13060
|
let workingReplacement = validationResult.content;
|
|
12973
13061
|
let { content: processedReplacement } = maybeApplyWikilinks(workingReplacement, skipWikilinks, notePath);
|
|
12974
|
-
if (suggestOutgoingLinks && !skipWikilinks) {
|
|
13062
|
+
if (suggestOutgoingLinks && !skipWikilinks && processedReplacement.length >= 100) {
|
|
12975
13063
|
const result = await suggestRelatedLinks(processedReplacement, { maxSuggestions, notePath });
|
|
12976
13064
|
if (result.suffix) {
|
|
12977
13065
|
processedReplacement = processedReplacement + " " + result.suffix;
|
|
@@ -13352,7 +13440,7 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
13352
13440
|
}
|
|
13353
13441
|
let { content: processedContent, wikilinkInfo } = maybeApplyWikilinks(effectiveContent, skipWikilinks, notePath);
|
|
13354
13442
|
let suggestInfo;
|
|
13355
|
-
if (suggestOutgoingLinks && !skipWikilinks) {
|
|
13443
|
+
if (suggestOutgoingLinks && !skipWikilinks && processedContent.length >= 100) {
|
|
13356
13444
|
const result = await suggestRelatedLinks(processedContent, { maxSuggestions, notePath });
|
|
13357
13445
|
if (result.suffix) {
|
|
13358
13446
|
processedContent = processedContent + " " + result.suffix;
|
|
@@ -15774,10 +15862,11 @@ function registerWikilinkFeedbackTools(server2, getStateDb) {
|
|
|
15774
15862
|
days_back: z21.number().optional().describe("Days to look back (default: 30)"),
|
|
15775
15863
|
granularity: z21.enum(["day", "week"]).optional().describe("Time bucket granularity for layer_timeseries (default: day)"),
|
|
15776
15864
|
timestamp_before: z21.number().optional().describe("Earlier timestamp for snapshot_diff"),
|
|
15777
|
-
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)")
|
|
15778
15867
|
}
|
|
15779
15868
|
},
|
|
15780
|
-
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 }) => {
|
|
15781
15870
|
const stateDb2 = getStateDb();
|
|
15782
15871
|
if (!stateDb2) {
|
|
15783
15872
|
return {
|
|
@@ -15805,6 +15894,11 @@ function registerWikilinkFeedbackTools(server2, getStateDb) {
|
|
|
15805
15894
|
isError: true
|
|
15806
15895
|
};
|
|
15807
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
|
+
}
|
|
15808
15902
|
const suppressionUpdated = updateSuppressionList(stateDb2) > 0;
|
|
15809
15903
|
result = {
|
|
15810
15904
|
mode: "report",
|
|
@@ -17829,7 +17923,15 @@ async function runPostIndexWork(index) {
|
|
|
17829
17923
|
if (edgeWeightAgeMs >= 60 * 60 * 1e3) {
|
|
17830
17924
|
const result = recomputeEdgeWeights(stateDb);
|
|
17831
17925
|
lastEdgeWeightRebuildAt = Date.now();
|
|
17832
|
-
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
|
+
});
|
|
17833
17935
|
serverLog("watcher", `Edge weights: ${result.edges_updated} edges in ${result.duration_ms}ms`);
|
|
17834
17936
|
} else {
|
|
17835
17937
|
tracker.end({ rebuilt: false, age_ms: edgeWeightAgeMs });
|
|
@@ -18114,9 +18216,27 @@ async function runPostIndexWork(index) {
|
|
|
18114
18216
|
}
|
|
18115
18217
|
}
|
|
18116
18218
|
}
|
|
18117
|
-
|
|
18118
|
-
if (
|
|
18119
|
-
|
|
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`);
|
|
18120
18240
|
}
|
|
18121
18241
|
tracker.start("tag_scan", { files: filteredEvents.length });
|
|
18122
18242
|
const tagDiffs = [];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
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.",
|
|
3
|
+
"version": "2.0.44",
|
|
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",
|
|
7
7
|
"bin": {
|
|
@@ -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",
|