@velvetmonkey/flywheel-memory 2.1.2 → 2.1.4
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 +223 -45
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7658,6 +7658,18 @@ function computeEntityDiff(before, after) {
|
|
|
7658
7658
|
}
|
|
7659
7659
|
return { added, removed, alias_changes };
|
|
7660
7660
|
}
|
|
7661
|
+
function getLastSuccessfulEvent(stateDb2) {
|
|
7662
|
+
const row = stateDb2.db.prepare(
|
|
7663
|
+
"SELECT * FROM index_events WHERE success = 1 ORDER BY timestamp DESC LIMIT 1"
|
|
7664
|
+
).get();
|
|
7665
|
+
return row ? rowToEvent(row) : null;
|
|
7666
|
+
}
|
|
7667
|
+
function getLastEventByTrigger(stateDb2, trigger) {
|
|
7668
|
+
const row = stateDb2.db.prepare(
|
|
7669
|
+
"SELECT * FROM index_events WHERE trigger = ? AND success = 1 ORDER BY timestamp DESC LIMIT 1"
|
|
7670
|
+
).get(trigger);
|
|
7671
|
+
return row ? rowToEvent(row) : null;
|
|
7672
|
+
}
|
|
7661
7673
|
function getRecentIndexEvents(stateDb2, limit = 20) {
|
|
7662
7674
|
const rows = stateDb2.db.prepare(
|
|
7663
7675
|
"SELECT * FROM index_events ORDER BY timestamp DESC LIMIT ?"
|
|
@@ -8515,6 +8527,24 @@ function refreshIfStale(vaultPath2, index, excludeTags) {
|
|
|
8515
8527
|
init_wikilinkFeedback();
|
|
8516
8528
|
init_corrections();
|
|
8517
8529
|
init_edgeWeights();
|
|
8530
|
+
var PIPELINE_TOTAL_STEPS = 22;
|
|
8531
|
+
var pipelineActivity = {
|
|
8532
|
+
busy: false,
|
|
8533
|
+
trigger: null,
|
|
8534
|
+
started_at: null,
|
|
8535
|
+
current_step: null,
|
|
8536
|
+
completed_steps: 0,
|
|
8537
|
+
total_steps: PIPELINE_TOTAL_STEPS,
|
|
8538
|
+
pending_events: 0,
|
|
8539
|
+
last_completed_at: null,
|
|
8540
|
+
last_completed_trigger: null,
|
|
8541
|
+
last_completed_duration_ms: null,
|
|
8542
|
+
last_completed_files: null,
|
|
8543
|
+
last_completed_steps: []
|
|
8544
|
+
};
|
|
8545
|
+
function getPipelineActivity() {
|
|
8546
|
+
return pipelineActivity;
|
|
8547
|
+
}
|
|
8518
8548
|
async function runStep(name, tracker, meta, fn) {
|
|
8519
8549
|
tracker.start(name, meta);
|
|
8520
8550
|
try {
|
|
@@ -8528,7 +8558,22 @@ async function runStep(name, tracker, meta, fn) {
|
|
|
8528
8558
|
var PipelineRunner = class {
|
|
8529
8559
|
constructor(p) {
|
|
8530
8560
|
this.p = p;
|
|
8531
|
-
|
|
8561
|
+
const baseTracker = createStepTracker();
|
|
8562
|
+
this.tracker = {
|
|
8563
|
+
steps: baseTracker.steps,
|
|
8564
|
+
start(name, input) {
|
|
8565
|
+
pipelineActivity.current_step = name;
|
|
8566
|
+
baseTracker.start(name, input);
|
|
8567
|
+
},
|
|
8568
|
+
end(output) {
|
|
8569
|
+
baseTracker.end(output);
|
|
8570
|
+
pipelineActivity.completed_steps = baseTracker.steps.length;
|
|
8571
|
+
},
|
|
8572
|
+
skip(name, reason) {
|
|
8573
|
+
baseTracker.skip(name, reason);
|
|
8574
|
+
pipelineActivity.completed_steps = baseTracker.steps.length;
|
|
8575
|
+
}
|
|
8576
|
+
};
|
|
8532
8577
|
this.batchStart = Date.now();
|
|
8533
8578
|
}
|
|
8534
8579
|
tracker;
|
|
@@ -8544,6 +8589,13 @@ var PipelineRunner = class {
|
|
|
8544
8589
|
suggestionResults = [];
|
|
8545
8590
|
async run() {
|
|
8546
8591
|
const { p, tracker } = this;
|
|
8592
|
+
pipelineActivity.busy = true;
|
|
8593
|
+
pipelineActivity.trigger = "watcher";
|
|
8594
|
+
pipelineActivity.started_at = this.batchStart;
|
|
8595
|
+
pipelineActivity.current_step = null;
|
|
8596
|
+
pipelineActivity.completed_steps = 0;
|
|
8597
|
+
pipelineActivity.total_steps = PIPELINE_TOTAL_STEPS;
|
|
8598
|
+
pipelineActivity.pending_events = p.events.length;
|
|
8547
8599
|
try {
|
|
8548
8600
|
await runStep("drain_proactive_queue", tracker, {}, () => this.drainQueue());
|
|
8549
8601
|
await this.indexRebuild();
|
|
@@ -8583,6 +8635,13 @@ var PipelineRunner = class {
|
|
|
8583
8635
|
steps: tracker.steps
|
|
8584
8636
|
});
|
|
8585
8637
|
}
|
|
8638
|
+
pipelineActivity.busy = false;
|
|
8639
|
+
pipelineActivity.current_step = null;
|
|
8640
|
+
pipelineActivity.last_completed_at = Date.now();
|
|
8641
|
+
pipelineActivity.last_completed_trigger = "watcher";
|
|
8642
|
+
pipelineActivity.last_completed_duration_ms = duration;
|
|
8643
|
+
pipelineActivity.last_completed_files = p.events.length;
|
|
8644
|
+
pipelineActivity.last_completed_steps = tracker.steps.map((s) => s.name);
|
|
8586
8645
|
serverLog("watcher", `Batch complete: ${p.events.length} files, ${duration}ms, ${tracker.steps.length} steps`);
|
|
8587
8646
|
} catch (err) {
|
|
8588
8647
|
p.updateIndexState("error", err instanceof Error ? err : new Error(String(err)));
|
|
@@ -8598,6 +8657,13 @@ var PipelineRunner = class {
|
|
|
8598
8657
|
steps: tracker.steps
|
|
8599
8658
|
});
|
|
8600
8659
|
}
|
|
8660
|
+
pipelineActivity.busy = false;
|
|
8661
|
+
pipelineActivity.current_step = null;
|
|
8662
|
+
pipelineActivity.last_completed_at = Date.now();
|
|
8663
|
+
pipelineActivity.last_completed_trigger = "watcher";
|
|
8664
|
+
pipelineActivity.last_completed_duration_ms = duration;
|
|
8665
|
+
pipelineActivity.last_completed_files = p.events.length;
|
|
8666
|
+
pipelineActivity.last_completed_steps = tracker.steps.map((s) => s.name);
|
|
8601
8667
|
serverLog("watcher", `Failed to rebuild index: ${err instanceof Error ? err.message : err}`, "error");
|
|
8602
8668
|
}
|
|
8603
8669
|
}
|
|
@@ -8618,6 +8684,7 @@ var PipelineRunner = class {
|
|
|
8618
8684
|
};
|
|
8619
8685
|
const batchResult = await processBatch(vaultIndex2, p.vp, absoluteBatch);
|
|
8620
8686
|
this.hasEntityRelevantChanges = batchResult.hasEntityRelevantChanges;
|
|
8687
|
+
vaultIndex2.builtAt = /* @__PURE__ */ new Date();
|
|
8621
8688
|
serverLog("watcher", `Incremental: ${batchResult.successful}/${batchResult.total} files in ${batchResult.durationMs}ms`);
|
|
8622
8689
|
}
|
|
8623
8690
|
p.updateIndexState("ready");
|
|
@@ -10681,8 +10748,9 @@ var TOOL_CATEGORY = {
|
|
|
10681
10748
|
predict_stale_notes: "temporal",
|
|
10682
10749
|
track_concept_evolution: "temporal",
|
|
10683
10750
|
temporal_summary: "temporal",
|
|
10684
|
-
// diagnostics (
|
|
10751
|
+
// diagnostics (21 tools) -- vault health, stats, config, activity, merges, doctor, trust, benchmark, history, learning report, calibration export, pipeline status
|
|
10685
10752
|
health_check: "diagnostics",
|
|
10753
|
+
pipeline_status: "diagnostics",
|
|
10686
10754
|
get_vault_stats: "diagnostics",
|
|
10687
10755
|
get_folder_structure: "diagnostics",
|
|
10688
10756
|
refresh_index: "diagnostics",
|
|
@@ -13982,6 +14050,17 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
13982
14050
|
tasks_building: z5.boolean().describe("Whether the task cache is currently rebuilding"),
|
|
13983
14051
|
watcher_state: z5.enum(["starting", "ready", "rebuilding", "dirty", "error"]).optional().describe("Current file watcher state"),
|
|
13984
14052
|
watcher_pending: z5.coerce.number().optional().describe("Number of pending file events in the watcher queue"),
|
|
14053
|
+
last_index_activity_at: z5.number().optional().describe("Epoch ms of latest successful index event (any trigger)"),
|
|
14054
|
+
last_index_activity_ago_seconds: z5.coerce.number().optional().describe("Seconds since last successful index event"),
|
|
14055
|
+
last_full_rebuild_at: z5.number().optional().describe("Epoch ms of latest startup_build or manual_refresh event"),
|
|
14056
|
+
last_watcher_batch_at: z5.number().optional().describe("Epoch ms of latest watcher batch event"),
|
|
14057
|
+
pipeline_activity: z5.object({
|
|
14058
|
+
busy: z5.boolean(),
|
|
14059
|
+
current_step: z5.string().nullable(),
|
|
14060
|
+
started_at: z5.number().nullable(),
|
|
14061
|
+
progress: z5.string().nullable(),
|
|
14062
|
+
last_completed_ago_seconds: z5.number().nullable()
|
|
14063
|
+
}).optional().describe("Live pipeline activity state"),
|
|
13985
14064
|
dead_link_count: z5.coerce.number().describe("Total number of broken/dead wikilinks across the vault"),
|
|
13986
14065
|
top_dead_link_targets: z5.array(z5.object({
|
|
13987
14066
|
target: z5.string().describe("The dead link target"),
|
|
@@ -14008,11 +14087,14 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14008
14087
|
"health_check",
|
|
14009
14088
|
{
|
|
14010
14089
|
title: "Health Check",
|
|
14011
|
-
description:
|
|
14012
|
-
inputSchema: {
|
|
14090
|
+
description: 'Check MCP server health status. Returns vault accessibility, index freshness, and recommendations. Use at session start to verify MCP is working correctly. Pass mode="summary" (default) for lightweight polling or mode="full" for complete diagnostics.',
|
|
14091
|
+
inputSchema: {
|
|
14092
|
+
mode: z5.enum(["summary", "full"]).optional().default("summary").describe('Output mode: "summary" omits config, periodic notes, dead links, sweep, and recent pipelines; "full" returns everything')
|
|
14093
|
+
},
|
|
14013
14094
|
outputSchema: HealthCheckOutputSchema
|
|
14014
14095
|
},
|
|
14015
|
-
async () => {
|
|
14096
|
+
async ({ mode = "summary" }) => {
|
|
14097
|
+
const isFull = mode === "full";
|
|
14016
14098
|
const index = getIndex();
|
|
14017
14099
|
const vaultPath2 = getVaultPath();
|
|
14018
14100
|
const recommendations = [];
|
|
@@ -14043,7 +14125,23 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14043
14125
|
}
|
|
14044
14126
|
}
|
|
14045
14127
|
const indexBuilt = indexState2 === "ready" && index !== void 0 && index.notes !== void 0;
|
|
14046
|
-
|
|
14128
|
+
let lastIndexActivityAt;
|
|
14129
|
+
let lastFullRebuildAt;
|
|
14130
|
+
let lastWatcherBatchAt;
|
|
14131
|
+
if (stateDb2) {
|
|
14132
|
+
try {
|
|
14133
|
+
const lastAny = getLastSuccessfulEvent(stateDb2);
|
|
14134
|
+
if (lastAny) lastIndexActivityAt = lastAny.timestamp;
|
|
14135
|
+
const lastBuild = getLastEventByTrigger(stateDb2, "startup_build");
|
|
14136
|
+
const lastManual = getLastEventByTrigger(stateDb2, "manual_refresh");
|
|
14137
|
+
lastFullRebuildAt = Math.max(lastBuild?.timestamp ?? 0, lastManual?.timestamp ?? 0) || void 0;
|
|
14138
|
+
const lastWatcher = getLastEventByTrigger(stateDb2, "watcher");
|
|
14139
|
+
if (lastWatcher) lastWatcherBatchAt = lastWatcher.timestamp;
|
|
14140
|
+
} catch {
|
|
14141
|
+
}
|
|
14142
|
+
}
|
|
14143
|
+
const freshnessTimestamp = lastIndexActivityAt ?? (indexBuilt && index.builtAt ? index.builtAt.getTime() : void 0);
|
|
14144
|
+
const indexAge = freshnessTimestamp ? Math.floor((Date.now() - freshnessTimestamp) / 1e3) : -1;
|
|
14047
14145
|
const indexStale = indexBuilt && indexAge > STALE_THRESHOLD_SECONDS;
|
|
14048
14146
|
if (indexState2 === "building") {
|
|
14049
14147
|
const { parsed, total } = indexProgress2;
|
|
@@ -14073,7 +14171,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14073
14171
|
status = "healthy";
|
|
14074
14172
|
}
|
|
14075
14173
|
let periodicNotes;
|
|
14076
|
-
if (indexBuilt) {
|
|
14174
|
+
if (isFull && indexBuilt) {
|
|
14077
14175
|
const types = ["daily", "weekly", "monthly", "quarterly", "yearly"];
|
|
14078
14176
|
periodicNotes = types.map((type) => {
|
|
14079
14177
|
const result = detectPeriodicNotes(index, type);
|
|
@@ -14087,8 +14185,11 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14087
14185
|
};
|
|
14088
14186
|
}).filter((p) => p.detected);
|
|
14089
14187
|
}
|
|
14090
|
-
|
|
14091
|
-
|
|
14188
|
+
let configInfo;
|
|
14189
|
+
if (isFull) {
|
|
14190
|
+
const config = getConfig2();
|
|
14191
|
+
configInfo = Object.keys(config).length > 0 ? config : void 0;
|
|
14192
|
+
}
|
|
14092
14193
|
let lastRebuild;
|
|
14093
14194
|
if (stateDb2) {
|
|
14094
14195
|
try {
|
|
@@ -14122,25 +14223,28 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14122
14223
|
}
|
|
14123
14224
|
} catch {
|
|
14124
14225
|
}
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14226
|
+
if (isFull) {
|
|
14227
|
+
try {
|
|
14228
|
+
const events = getRecentIndexEvents(stateDb2, 10).filter((e) => e.steps && e.steps.length > 0).slice(0, 5);
|
|
14229
|
+
if (events.length > 0) {
|
|
14230
|
+
recentPipelines = events.map((e) => ({
|
|
14231
|
+
timestamp: e.timestamp,
|
|
14232
|
+
trigger: e.trigger,
|
|
14233
|
+
duration_ms: e.duration_ms,
|
|
14234
|
+
files_changed: e.files_changed,
|
|
14235
|
+
changed_paths: e.changed_paths,
|
|
14236
|
+
steps: e.steps
|
|
14237
|
+
}));
|
|
14238
|
+
}
|
|
14239
|
+
} catch {
|
|
14136
14240
|
}
|
|
14137
|
-
} catch {
|
|
14138
14241
|
}
|
|
14139
14242
|
}
|
|
14140
14243
|
const ftsState = getFTS5State();
|
|
14141
14244
|
let deadLinkCount = 0;
|
|
14142
|
-
|
|
14143
|
-
if (indexBuilt) {
|
|
14245
|
+
let topDeadLinkTargets = [];
|
|
14246
|
+
if (isFull && indexBuilt) {
|
|
14247
|
+
const deadTargetCounts = /* @__PURE__ */ new Map();
|
|
14144
14248
|
for (const note of index.notes.values()) {
|
|
14145
14249
|
for (const link of note.outlinks) {
|
|
14146
14250
|
if (!resolveTarget(index, link.target)) {
|
|
@@ -14150,15 +14254,15 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14150
14254
|
}
|
|
14151
14255
|
}
|
|
14152
14256
|
}
|
|
14257
|
+
topDeadLinkTargets = Array.from(deadTargetCounts.entries()).map(([target, mention_count]) => ({ target, mention_count })).sort((a, b) => b.mention_count - a.mention_count).slice(0, 5);
|
|
14153
14258
|
}
|
|
14154
|
-
const topDeadLinkTargets = Array.from(deadTargetCounts.entries()).map(([target, mention_count]) => ({ target, mention_count })).sort((a, b) => b.mention_count - a.mention_count).slice(0, 5);
|
|
14155
14259
|
let vault_health_score = 0;
|
|
14156
14260
|
if (indexBuilt && noteCount > 0) {
|
|
14157
14261
|
const avgOutlinks = linkCount / noteCount;
|
|
14158
14262
|
const linkDensity = Math.min(1, avgOutlinks / 3);
|
|
14159
14263
|
let orphanCount = 0;
|
|
14160
14264
|
for (const note of index.notes.values()) {
|
|
14161
|
-
const bl = index.backlinks.get(note.
|
|
14265
|
+
const bl = index.backlinks.get(normalizeTarget(note.path));
|
|
14162
14266
|
if (!bl || bl.length === 0) orphanCount++;
|
|
14163
14267
|
}
|
|
14164
14268
|
const orphanRatio = 1 - orphanCount / noteCount;
|
|
@@ -14180,6 +14284,14 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14180
14284
|
linkDensity * 25 + orphanRatio * 20 + deadLinkRatio * 15 + fmCoverage * 15 + freshness * 15 + entityCoverage * 10
|
|
14181
14285
|
);
|
|
14182
14286
|
}
|
|
14287
|
+
const activity = getPipelineActivity();
|
|
14288
|
+
const pipelineActivity2 = {
|
|
14289
|
+
busy: activity.busy,
|
|
14290
|
+
current_step: activity.current_step,
|
|
14291
|
+
started_at: activity.started_at,
|
|
14292
|
+
progress: activity.busy && activity.total_steps > 0 ? `${activity.completed_steps}/${activity.total_steps} steps` : null,
|
|
14293
|
+
last_completed_ago_seconds: activity.last_completed_at ? Math.floor((Date.now() - activity.last_completed_at) / 1e3) : null
|
|
14294
|
+
};
|
|
14183
14295
|
const output = {
|
|
14184
14296
|
status,
|
|
14185
14297
|
vault_health_score,
|
|
@@ -14207,14 +14319,19 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14207
14319
|
embeddings_ready: hasEmbeddingsIndex(),
|
|
14208
14320
|
embeddings_count: getEmbeddingsCount(),
|
|
14209
14321
|
embedding_model: hasEmbeddingsIndex() ? getActiveModelId() : void 0,
|
|
14210
|
-
embedding_diagnosis: hasEmbeddingsIndex() ? diagnoseEmbeddings(vaultPath2) : void 0,
|
|
14322
|
+
embedding_diagnosis: isFull && hasEmbeddingsIndex() ? diagnoseEmbeddings(vaultPath2) : void 0,
|
|
14211
14323
|
tasks_ready: isTaskCacheReady(),
|
|
14212
14324
|
tasks_building: isTaskCacheBuilding(),
|
|
14213
14325
|
watcher_state: getWatcherStatus2()?.state,
|
|
14214
14326
|
watcher_pending: getWatcherStatus2()?.pendingEvents,
|
|
14327
|
+
last_index_activity_at: lastIndexActivityAt,
|
|
14328
|
+
last_index_activity_ago_seconds: lastIndexActivityAt ? Math.floor((Date.now() - lastIndexActivityAt) / 1e3) : void 0,
|
|
14329
|
+
last_full_rebuild_at: lastFullRebuildAt,
|
|
14330
|
+
last_watcher_batch_at: lastWatcherBatchAt,
|
|
14331
|
+
pipeline_activity: pipelineActivity2,
|
|
14215
14332
|
dead_link_count: deadLinkCount,
|
|
14216
14333
|
top_dead_link_targets: topDeadLinkTargets,
|
|
14217
|
-
sweep: getSweepResults() ?? void 0,
|
|
14334
|
+
sweep: isFull ? getSweepResults() ?? void 0 : void 0,
|
|
14218
14335
|
recommendations
|
|
14219
14336
|
};
|
|
14220
14337
|
return {
|
|
@@ -14228,6 +14345,56 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14228
14345
|
};
|
|
14229
14346
|
}
|
|
14230
14347
|
);
|
|
14348
|
+
server2.registerTool(
|
|
14349
|
+
"pipeline_status",
|
|
14350
|
+
{
|
|
14351
|
+
title: "Pipeline Status",
|
|
14352
|
+
description: "Live pipeline activity: whether a batch is running, current step, and recent completions. Lightweight process-local read \u2014 no DB queries unless detail=true.",
|
|
14353
|
+
inputSchema: {
|
|
14354
|
+
detail: z5.boolean().optional().default(false).describe("Include per-step timings for recent runs")
|
|
14355
|
+
}
|
|
14356
|
+
},
|
|
14357
|
+
async ({ detail = false }) => {
|
|
14358
|
+
const activity = getPipelineActivity();
|
|
14359
|
+
const now = Date.now();
|
|
14360
|
+
const output = {
|
|
14361
|
+
busy: activity.busy,
|
|
14362
|
+
trigger: activity.trigger,
|
|
14363
|
+
started_at: activity.started_at,
|
|
14364
|
+
age_ms: activity.busy && activity.started_at ? now - activity.started_at : null,
|
|
14365
|
+
current_step: activity.current_step,
|
|
14366
|
+
progress: activity.busy && activity.total_steps > 0 ? `${activity.completed_steps}/${activity.total_steps} steps` : null,
|
|
14367
|
+
pending_events: activity.pending_events,
|
|
14368
|
+
last_completed: activity.last_completed_at ? {
|
|
14369
|
+
at: activity.last_completed_at,
|
|
14370
|
+
ago_seconds: Math.floor((now - activity.last_completed_at) / 1e3),
|
|
14371
|
+
trigger: activity.last_completed_trigger,
|
|
14372
|
+
duration_ms: activity.last_completed_duration_ms,
|
|
14373
|
+
files: activity.last_completed_files,
|
|
14374
|
+
steps: activity.last_completed_steps
|
|
14375
|
+
} : null
|
|
14376
|
+
};
|
|
14377
|
+
if (detail) {
|
|
14378
|
+
const stateDb2 = getStateDb3();
|
|
14379
|
+
if (stateDb2) {
|
|
14380
|
+
try {
|
|
14381
|
+
const events = getRecentIndexEvents(stateDb2, 10).filter((e) => e.steps && e.steps.length > 0).slice(0, 5);
|
|
14382
|
+
output.recent_runs = events.map((e) => ({
|
|
14383
|
+
timestamp: e.timestamp,
|
|
14384
|
+
trigger: e.trigger,
|
|
14385
|
+
duration_ms: e.duration_ms,
|
|
14386
|
+
files_changed: e.files_changed,
|
|
14387
|
+
steps: e.steps
|
|
14388
|
+
}));
|
|
14389
|
+
} catch {
|
|
14390
|
+
}
|
|
14391
|
+
}
|
|
14392
|
+
}
|
|
14393
|
+
return {
|
|
14394
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
14395
|
+
};
|
|
14396
|
+
}
|
|
14397
|
+
);
|
|
14231
14398
|
const TagStatSchema = z5.object({
|
|
14232
14399
|
tag: z5.string().describe("The tag name"),
|
|
14233
14400
|
count: z5.coerce.number().describe("Number of notes with this tag")
|
|
@@ -14437,18 +14604,28 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
14437
14604
|
const indexState2 = getIndexState();
|
|
14438
14605
|
const indexBuilt = indexState2 === "ready" && index?.notes !== void 0;
|
|
14439
14606
|
if (indexState2 === "ready" && indexBuilt) {
|
|
14440
|
-
|
|
14607
|
+
let activityAge = null;
|
|
14608
|
+
if (stateDb2) {
|
|
14609
|
+
try {
|
|
14610
|
+
const lastEvt = getLastSuccessfulEvent(stateDb2);
|
|
14611
|
+
if (lastEvt) activityAge = Math.floor((Date.now() - lastEvt.timestamp) / 1e3);
|
|
14612
|
+
} catch {
|
|
14613
|
+
}
|
|
14614
|
+
}
|
|
14615
|
+
const age = activityAge ?? Math.floor((Date.now() - index.builtAt.getTime()) / 1e3);
|
|
14441
14616
|
if (age > STALE_THRESHOLD_SECONDS) {
|
|
14442
|
-
checks.push({ name: "
|
|
14617
|
+
checks.push({ name: "index_activity", status: "warning", detail: `Last index activity ${Math.floor(age / 60)} minutes ago`, fix: "Run refresh_index to rebuild" });
|
|
14443
14618
|
} else {
|
|
14444
|
-
checks.push({ name: "
|
|
14619
|
+
checks.push({ name: "index_activity", status: "ok", detail: `Last activity ${age}s ago, ${index.notes.size} notes, ${index.entities.size} entities` });
|
|
14445
14620
|
}
|
|
14621
|
+
const snapshotAge = Math.floor((Date.now() - index.builtAt.getTime()) / 1e3);
|
|
14622
|
+
checks.push({ name: "index_snapshot_age", status: "ok", detail: `In-memory snapshot built ${snapshotAge}s ago` });
|
|
14446
14623
|
} else if (indexState2 === "building") {
|
|
14447
14624
|
const progress = getIndexProgress();
|
|
14448
|
-
checks.push({ name: "
|
|
14625
|
+
checks.push({ name: "index_activity", status: "warning", detail: `Index building (${progress.parsed}/${progress.total} files)` });
|
|
14449
14626
|
} else {
|
|
14450
14627
|
const err = getIndexError();
|
|
14451
|
-
checks.push({ name: "
|
|
14628
|
+
checks.push({ name: "index_activity", status: "error", detail: `Index in ${indexState2} state${err ? ": " + err.message : ""}`, fix: "Run refresh_index" });
|
|
14452
14629
|
}
|
|
14453
14630
|
const embReady = hasEmbeddingsIndex();
|
|
14454
14631
|
const embCount = getEmbeddingsCount();
|
|
@@ -16339,8 +16516,8 @@ function findContradictions2(index, entity) {
|
|
|
16339
16516
|
entitiesToCheck.push([name, entityPath]);
|
|
16340
16517
|
}
|
|
16341
16518
|
}
|
|
16342
|
-
for (const [entityName,
|
|
16343
|
-
const backlinks = index.backlinks.get(
|
|
16519
|
+
for (const [entityName, entityPath] of entitiesToCheck) {
|
|
16520
|
+
const backlinks = index.backlinks.get(normalizeTarget(entityPath));
|
|
16344
16521
|
if (!backlinks || backlinks.length < 2) continue;
|
|
16345
16522
|
const sourcePaths = [...new Set(backlinks.map((bl) => bl.source))];
|
|
16346
16523
|
if (sourcePaths.length < 2) continue;
|
|
@@ -16575,8 +16752,8 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb3
|
|
|
16575
16752
|
} else {
|
|
16576
16753
|
frontmatterScore = 1;
|
|
16577
16754
|
}
|
|
16578
|
-
const
|
|
16579
|
-
const backlinks = index.backlinks.get(
|
|
16755
|
+
const normalizedPath = normalizeTarget(note.path);
|
|
16756
|
+
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
16580
16757
|
const backlinkCount = backlinks.length;
|
|
16581
16758
|
const backlinkScore = backlinkCount === 0 ? 0 : backlinkCount <= 2 ? 0.5 : 1;
|
|
16582
16759
|
const maturity = (wordScore + outlinkScore + frontmatterScore + backlinkScore) / 4;
|
|
@@ -17458,7 +17635,7 @@ async function computeFrontmatter(index, notePath, vaultPath2, fields) {
|
|
|
17458
17635
|
break;
|
|
17459
17636
|
}
|
|
17460
17637
|
case "backlink_count": {
|
|
17461
|
-
const backlinks = index.backlinks.get(note?.
|
|
17638
|
+
const backlinks = index.backlinks.get(normalizeTarget(note?.path ?? ""));
|
|
17462
17639
|
value = backlinks?.length ?? 0;
|
|
17463
17640
|
method = "backlink_index";
|
|
17464
17641
|
break;
|
|
@@ -17600,7 +17777,8 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
17600
17777
|
const suggestions = matches.filter((m) => {
|
|
17601
17778
|
if (m.similarity < 0.3) return false;
|
|
17602
17779
|
if (excludeTags.size > 0) {
|
|
17603
|
-
const
|
|
17780
|
+
const entityPath = index.entities.get(m.entityName.toLowerCase());
|
|
17781
|
+
const entityNote = entityPath ? index.notes.get(entityPath) : [...index.notes.values()].find((n) => n.title.toLowerCase() === m.entityName.toLowerCase());
|
|
17604
17782
|
if (entityNote) {
|
|
17605
17783
|
const noteTags = Object.keys(entityNote.frontmatter).filter((k) => k === "tags").flatMap((k) => {
|
|
17606
17784
|
const v = entityNote.frontmatter[k];
|
|
@@ -22756,9 +22934,9 @@ function registerActivityTools(server2, getStateDb3, getSessionId2) {
|
|
|
22756
22934
|
import { z as z31 } from "zod";
|
|
22757
22935
|
|
|
22758
22936
|
// src/core/read/similarity.ts
|
|
22759
|
-
init_embeddings();
|
|
22760
22937
|
import * as fs33 from "fs";
|
|
22761
22938
|
import * as path36 from "path";
|
|
22939
|
+
init_embeddings();
|
|
22762
22940
|
|
|
22763
22941
|
// src/core/read/mmr.ts
|
|
22764
22942
|
init_embeddings();
|
|
@@ -22858,8 +23036,8 @@ function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
|
|
|
22858
23036
|
const resolved = index.entities.get(link.target.toLowerCase());
|
|
22859
23037
|
if (resolved) linkedPaths.add(resolved);
|
|
22860
23038
|
}
|
|
22861
|
-
const
|
|
22862
|
-
const backlinks = index.backlinks.get(
|
|
23039
|
+
const normalizedPath = normalizeTarget(note.path);
|
|
23040
|
+
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
22863
23041
|
for (const bl of backlinks) {
|
|
22864
23042
|
linkedPaths.add(bl.source);
|
|
22865
23043
|
}
|
|
@@ -22884,8 +23062,8 @@ function getLinkedPaths(index, sourcePath) {
|
|
|
22884
23062
|
const resolved = index.entities.get(link.target.toLowerCase());
|
|
22885
23063
|
if (resolved) linkedPaths.add(resolved);
|
|
22886
23064
|
}
|
|
22887
|
-
const
|
|
22888
|
-
const backlinks = index.backlinks.get(
|
|
23065
|
+
const normalizedPath = normalizeTarget(note.path);
|
|
23066
|
+
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
22889
23067
|
for (const bl of backlinks) {
|
|
22890
23068
|
linkedPaths.add(bl.source);
|
|
22891
23069
|
}
|
|
@@ -24648,8 +24826,8 @@ function registerVaultResources(server2, getIndex) {
|
|
|
24648
24826
|
}
|
|
24649
24827
|
let orphanCount = 0;
|
|
24650
24828
|
for (const note of index.notes.values()) {
|
|
24651
|
-
const
|
|
24652
|
-
const backlinks = index.backlinks.get(
|
|
24829
|
+
const normalizedPath = normalizeTarget(note.path);
|
|
24830
|
+
const backlinks = index.backlinks.get(normalizedPath);
|
|
24653
24831
|
if (!backlinks || backlinks.length === 0) {
|
|
24654
24832
|
orphanCount++;
|
|
24655
24833
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "MCP tools that search, write, and auto-link your Obsidian vault — and learn from your edits.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -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.1.
|
|
57
|
+
"@velvetmonkey/vault-core": "^2.1.4",
|
|
58
58
|
"better-sqlite3": "^12.0.0",
|
|
59
59
|
"chokidar": "^4.0.0",
|
|
60
60
|
"gray-matter": "^4.0.3",
|