@velvetmonkey/flywheel-memory 2.1.3 → 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 +208 -31
- package/package.json +3 -3
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,8 +14254,8 @@ 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;
|
|
@@ -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();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "MCP tools that search, write, and auto-link your Obsidian vault
|
|
3
|
+
"version": "2.1.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",
|
|
7
7
|
"bin": {
|
|
@@ -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",
|