@velvetmonkey/flywheel-memory 2.0.32 → 2.0.33
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 +107 -31
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2486,7 +2486,8 @@ import {
|
|
|
2486
2486
|
var DEFAULT_CONFIG = {
|
|
2487
2487
|
exclude_task_tags: [],
|
|
2488
2488
|
exclude_analysis_tags: [],
|
|
2489
|
-
exclude_entities: []
|
|
2489
|
+
exclude_entities: [],
|
|
2490
|
+
exclude_entity_folders: []
|
|
2490
2491
|
};
|
|
2491
2492
|
function loadConfig(stateDb2) {
|
|
2492
2493
|
if (stateDb2) {
|
|
@@ -3403,10 +3404,10 @@ function getAllFeedbackBoosts(stateDb2, folder) {
|
|
|
3403
3404
|
for (const row of globalRows) {
|
|
3404
3405
|
let accuracy;
|
|
3405
3406
|
let sampleCount;
|
|
3406
|
-
const
|
|
3407
|
-
if (
|
|
3408
|
-
accuracy =
|
|
3409
|
-
sampleCount =
|
|
3407
|
+
const fs31 = folderStats?.get(row.entity);
|
|
3408
|
+
if (fs31 && fs31.count >= FEEDBACK_BOOST_MIN_SAMPLES) {
|
|
3409
|
+
accuracy = fs31.accuracy;
|
|
3410
|
+
sampleCount = fs31.count;
|
|
3410
3411
|
} else {
|
|
3411
3412
|
accuracy = row.correct_count / row.total;
|
|
3412
3413
|
sampleCount = row.total;
|
|
@@ -5078,12 +5079,22 @@ function processWikilinks(content, notePath) {
|
|
|
5078
5079
|
}
|
|
5079
5080
|
}
|
|
5080
5081
|
const currentNoteName = notePath ? notePath.replace(/\.md$/, "").split("/").pop()?.toLowerCase() : null;
|
|
5081
|
-
|
|
5082
|
+
let newImplicits = implicitMatches.filter((m) => {
|
|
5082
5083
|
const normalized = m.text.toLowerCase();
|
|
5083
5084
|
if (alreadyLinked.has(normalized)) return false;
|
|
5084
5085
|
if (currentNoteName && normalized === currentNoteName) return false;
|
|
5085
5086
|
return true;
|
|
5086
5087
|
});
|
|
5088
|
+
const nonOverlapping = [];
|
|
5089
|
+
for (const match of newImplicits) {
|
|
5090
|
+
const overlaps = nonOverlapping.some(
|
|
5091
|
+
(existing) => match.start >= existing.start && match.start < existing.end || match.end > existing.start && match.end <= existing.end || match.start <= existing.start && match.end >= existing.end
|
|
5092
|
+
);
|
|
5093
|
+
if (!overlaps) {
|
|
5094
|
+
nonOverlapping.push(match);
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
newImplicits = nonOverlapping;
|
|
5087
5098
|
if (newImplicits.length > 0) {
|
|
5088
5099
|
let processedContent = result.content;
|
|
5089
5100
|
for (let i = newImplicits.length - 1; i >= 0; i--) {
|
|
@@ -7414,7 +7425,7 @@ function computeEntityDiff(before, after) {
|
|
|
7414
7425
|
const alias_changes = [];
|
|
7415
7426
|
for (const [key, entity] of afterMap) {
|
|
7416
7427
|
if (!beforeMap.has(key)) {
|
|
7417
|
-
added.push(entity.name);
|
|
7428
|
+
added.push({ name: entity.name, category: entity.category, path: entity.path });
|
|
7418
7429
|
} else {
|
|
7419
7430
|
const prev = beforeMap.get(key);
|
|
7420
7431
|
const prevAliases = JSON.stringify(prev.aliases.sort());
|
|
@@ -7426,7 +7437,7 @@ function computeEntityDiff(before, after) {
|
|
|
7426
7437
|
}
|
|
7427
7438
|
for (const [key, entity] of beforeMap) {
|
|
7428
7439
|
if (!afterMap.has(key)) {
|
|
7429
|
-
removed.push(entity.name);
|
|
7440
|
+
removed.push({ name: entity.name, category: entity.category, path: entity.path });
|
|
7430
7441
|
}
|
|
7431
7442
|
}
|
|
7432
7443
|
return { added, removed, alias_changes };
|
|
@@ -7520,6 +7531,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
7520
7531
|
trigger: z3.string(),
|
|
7521
7532
|
duration_ms: z3.number(),
|
|
7522
7533
|
files_changed: z3.number().nullable(),
|
|
7534
|
+
changed_paths: z3.array(z3.string()).nullable(),
|
|
7523
7535
|
steps: z3.array(z3.object({
|
|
7524
7536
|
name: z3.string(),
|
|
7525
7537
|
duration_ms: z3.number(),
|
|
@@ -7529,6 +7541,21 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
7529
7541
|
skip_reason: z3.string().optional()
|
|
7530
7542
|
}))
|
|
7531
7543
|
}).optional().describe("Most recent watcher pipeline run with per-step timing"),
|
|
7544
|
+
recent_pipelines: z3.array(z3.object({
|
|
7545
|
+
timestamp: z3.number(),
|
|
7546
|
+
trigger: z3.string(),
|
|
7547
|
+
duration_ms: z3.number(),
|
|
7548
|
+
files_changed: z3.number().nullable(),
|
|
7549
|
+
changed_paths: z3.array(z3.string()).nullable(),
|
|
7550
|
+
steps: z3.array(z3.object({
|
|
7551
|
+
name: z3.string(),
|
|
7552
|
+
duration_ms: z3.number(),
|
|
7553
|
+
input: z3.record(z3.unknown()),
|
|
7554
|
+
output: z3.record(z3.unknown()),
|
|
7555
|
+
skipped: z3.boolean().optional(),
|
|
7556
|
+
skip_reason: z3.string().optional()
|
|
7557
|
+
}))
|
|
7558
|
+
})).optional().describe("Up to 5 most recent pipeline runs with steps data"),
|
|
7532
7559
|
fts5_ready: z3.boolean().describe("Whether the FTS5 keyword search index is ready"),
|
|
7533
7560
|
fts5_building: z3.boolean().describe("Whether the FTS5 keyword search index is currently building"),
|
|
7534
7561
|
embeddings_building: z3.boolean().describe("Whether semantic embeddings are currently building"),
|
|
@@ -7626,6 +7653,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
7626
7653
|
}
|
|
7627
7654
|
}
|
|
7628
7655
|
let lastPipeline;
|
|
7656
|
+
let recentPipelines;
|
|
7629
7657
|
if (stateDb2) {
|
|
7630
7658
|
try {
|
|
7631
7659
|
const evt = getRecentPipelineEvent(stateDb2);
|
|
@@ -7635,11 +7663,26 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
7635
7663
|
trigger: evt.trigger,
|
|
7636
7664
|
duration_ms: evt.duration_ms,
|
|
7637
7665
|
files_changed: evt.files_changed,
|
|
7666
|
+
changed_paths: evt.changed_paths,
|
|
7638
7667
|
steps: evt.steps
|
|
7639
7668
|
};
|
|
7640
7669
|
}
|
|
7641
7670
|
} catch {
|
|
7642
7671
|
}
|
|
7672
|
+
try {
|
|
7673
|
+
const events = getRecentIndexEvents(stateDb2, 10).filter((e) => e.steps && e.steps.length > 0).slice(0, 5);
|
|
7674
|
+
if (events.length > 0) {
|
|
7675
|
+
recentPipelines = events.map((e) => ({
|
|
7676
|
+
timestamp: e.timestamp,
|
|
7677
|
+
trigger: e.trigger,
|
|
7678
|
+
duration_ms: e.duration_ms,
|
|
7679
|
+
files_changed: e.files_changed,
|
|
7680
|
+
changed_paths: e.changed_paths,
|
|
7681
|
+
steps: e.steps
|
|
7682
|
+
}));
|
|
7683
|
+
}
|
|
7684
|
+
} catch {
|
|
7685
|
+
}
|
|
7643
7686
|
}
|
|
7644
7687
|
const ftsState = getFTS5State();
|
|
7645
7688
|
const output = {
|
|
@@ -7661,6 +7704,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
7661
7704
|
config: configInfo,
|
|
7662
7705
|
last_rebuild: lastRebuild,
|
|
7663
7706
|
last_pipeline: lastPipeline,
|
|
7707
|
+
recent_pipelines: recentPipelines,
|
|
7664
7708
|
fts5_ready: ftsState.ready,
|
|
7665
7709
|
fts5_building: ftsState.building,
|
|
7666
7710
|
embeddings_building: isEmbeddingsBuilding(),
|
|
@@ -15542,6 +15586,9 @@ function registerMergeTools2(server2, getStateDb) {
|
|
|
15542
15586
|
);
|
|
15543
15587
|
}
|
|
15544
15588
|
|
|
15589
|
+
// src/index.ts
|
|
15590
|
+
import * as fs30 from "node:fs/promises";
|
|
15591
|
+
|
|
15545
15592
|
// src/resources/vault.ts
|
|
15546
15593
|
function registerVaultResources(server2, getIndex) {
|
|
15547
15594
|
server2.registerResource(
|
|
@@ -16034,31 +16081,14 @@ async function main() {
|
|
|
16034
16081
|
}
|
|
16035
16082
|
}
|
|
16036
16083
|
}
|
|
16084
|
+
var DEFAULT_ENTITY_EXCLUDE_FOLDERS = ["node_modules", "templates", "attachments", "tmp"];
|
|
16037
16085
|
async function updateEntitiesInStateDb() {
|
|
16038
16086
|
if (!stateDb) return;
|
|
16039
16087
|
try {
|
|
16088
|
+
const config = loadConfig(stateDb);
|
|
16089
|
+
const excludeFolders = config.exclude_entity_folders?.length ? config.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
|
|
16040
16090
|
const entityIndex2 = await scanVaultEntities3(vaultPath, {
|
|
16041
|
-
excludeFolders
|
|
16042
|
-
"daily-notes",
|
|
16043
|
-
"daily",
|
|
16044
|
-
"weekly",
|
|
16045
|
-
"weekly-notes",
|
|
16046
|
-
"monthly",
|
|
16047
|
-
"monthly-notes",
|
|
16048
|
-
"quarterly",
|
|
16049
|
-
"yearly-notes",
|
|
16050
|
-
"periodic",
|
|
16051
|
-
"journal",
|
|
16052
|
-
"inbox",
|
|
16053
|
-
"templates",
|
|
16054
|
-
"attachments",
|
|
16055
|
-
"tmp",
|
|
16056
|
-
"clippings",
|
|
16057
|
-
"readwise",
|
|
16058
|
-
"articles",
|
|
16059
|
-
"bookmarks",
|
|
16060
|
-
"web-clips"
|
|
16061
|
-
]
|
|
16091
|
+
excludeFolders
|
|
16062
16092
|
});
|
|
16063
16093
|
stateDb.replaceAllEntities(entityIndex2);
|
|
16064
16094
|
serverLog("index", `Updated ${entityIndex2._metadata.total_entities} entities in StateDb`);
|
|
@@ -16177,9 +16207,22 @@ async function runPostIndexWork(index) {
|
|
|
16177
16207
|
const entityDiff = computeEntityDiff(entitiesBefore, entitiesAfter);
|
|
16178
16208
|
tracker.end({ entity_count: entitiesAfter.length, ...entityDiff });
|
|
16179
16209
|
serverLog("watcher", `Entity scan: ${entitiesAfter.length} entities`);
|
|
16210
|
+
const hubBefore = /* @__PURE__ */ new Map();
|
|
16211
|
+
if (stateDb) {
|
|
16212
|
+
const rows = stateDb.db.prepare("SELECT name, hub_score FROM entities").all();
|
|
16213
|
+
for (const r of rows) hubBefore.set(r.name, r.hub_score);
|
|
16214
|
+
}
|
|
16180
16215
|
tracker.start("hub_scores", { entity_count: entitiesAfter.length });
|
|
16181
16216
|
const hubUpdated = await exportHubScores(vaultIndex, stateDb);
|
|
16182
|
-
|
|
16217
|
+
const hubDiffs = [];
|
|
16218
|
+
if (stateDb) {
|
|
16219
|
+
const rows = stateDb.db.prepare("SELECT name, hub_score FROM entities").all();
|
|
16220
|
+
for (const r of rows) {
|
|
16221
|
+
const prev = hubBefore.get(r.name) ?? 0;
|
|
16222
|
+
if (prev !== r.hub_score) hubDiffs.push({ entity: r.name, before: prev, after: r.hub_score });
|
|
16223
|
+
}
|
|
16224
|
+
}
|
|
16225
|
+
tracker.end({ updated: hubUpdated ?? 0, diffs: hubDiffs.slice(0, 10) });
|
|
16183
16226
|
serverLog("watcher", `Hub scores: ${hubUpdated ?? 0} updated`);
|
|
16184
16227
|
if (hasEmbeddingsIndex()) {
|
|
16185
16228
|
tracker.start("note_embeddings", { files: batch.events.length });
|
|
@@ -16206,6 +16249,7 @@ async function runPostIndexWork(index) {
|
|
|
16206
16249
|
if (hasEntityEmbeddingsIndex() && stateDb) {
|
|
16207
16250
|
tracker.start("entity_embeddings", { files: batch.events.length });
|
|
16208
16251
|
let entEmbUpdated = 0;
|
|
16252
|
+
const entEmbNames = [];
|
|
16209
16253
|
try {
|
|
16210
16254
|
const allEntities = getAllEntitiesFromDb3(stateDb);
|
|
16211
16255
|
for (const event of batch.events) {
|
|
@@ -16219,11 +16263,12 @@ async function runPostIndexWork(index) {
|
|
|
16219
16263
|
aliases: entity.aliases
|
|
16220
16264
|
}, vaultPath);
|
|
16221
16265
|
entEmbUpdated++;
|
|
16266
|
+
entEmbNames.push(entity.name);
|
|
16222
16267
|
}
|
|
16223
16268
|
}
|
|
16224
16269
|
} catch {
|
|
16225
16270
|
}
|
|
16226
|
-
tracker.end({ updated: entEmbUpdated });
|
|
16271
|
+
tracker.end({ updated: entEmbUpdated, updated_entities: entEmbNames.slice(0, 10) });
|
|
16227
16272
|
serverLog("watcher", `Entity embeddings: ${entEmbUpdated} updated`);
|
|
16228
16273
|
} else {
|
|
16229
16274
|
tracker.skip("entity_embeddings", !stateDb ? "no stateDb" : "not built");
|
|
@@ -16258,6 +16303,37 @@ async function runPostIndexWork(index) {
|
|
|
16258
16303
|
}
|
|
16259
16304
|
tracker.end({ updated: taskUpdated, removed: taskRemoved });
|
|
16260
16305
|
serverLog("watcher", `Task cache: ${taskUpdated} updated, ${taskRemoved} removed`);
|
|
16306
|
+
tracker.start("wikilink_check", { files: batch.events.length });
|
|
16307
|
+
const trackedLinks = [];
|
|
16308
|
+
if (stateDb) {
|
|
16309
|
+
for (const event of batch.events) {
|
|
16310
|
+
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
16311
|
+
try {
|
|
16312
|
+
const apps = getTrackedApplications(stateDb, event.path);
|
|
16313
|
+
if (apps.length > 0) trackedLinks.push({ file: event.path, entities: apps });
|
|
16314
|
+
} catch {
|
|
16315
|
+
}
|
|
16316
|
+
}
|
|
16317
|
+
}
|
|
16318
|
+
tracker.end({ tracked: trackedLinks });
|
|
16319
|
+
serverLog("watcher", `Wikilink check: ${trackedLinks.reduce((s, t) => s + t.entities.length, 0)} tracked links in ${trackedLinks.length} files`);
|
|
16320
|
+
tracker.start("implicit_feedback", { files: batch.events.length });
|
|
16321
|
+
const feedbackResults = [];
|
|
16322
|
+
if (stateDb) {
|
|
16323
|
+
for (const event of batch.events) {
|
|
16324
|
+
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
16325
|
+
try {
|
|
16326
|
+
const content = await fs30.readFile(path29.join(vaultPath, event.path), "utf-8");
|
|
16327
|
+
const removed = processImplicitFeedback(stateDb, event.path, content);
|
|
16328
|
+
for (const entity of removed) feedbackResults.push({ entity, file: event.path });
|
|
16329
|
+
} catch {
|
|
16330
|
+
}
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
16333
|
+
tracker.end({ removals: feedbackResults });
|
|
16334
|
+
if (feedbackResults.length > 0) {
|
|
16335
|
+
serverLog("watcher", `Implicit feedback: ${feedbackResults.length} removals detected`);
|
|
16336
|
+
}
|
|
16261
16337
|
const duration = Date.now() - batchStart;
|
|
16262
16338
|
if (stateDb) {
|
|
16263
16339
|
recordIndexEvent(stateDb, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.33",
|
|
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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
53
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
53
|
+
"@velvetmonkey/vault-core": "^2.0.33",
|
|
54
54
|
"better-sqlite3": "^11.0.0",
|
|
55
55
|
"chokidar": "^4.0.0",
|
|
56
56
|
"gray-matter": "^4.0.3",
|