@velvetmonkey/flywheel-memory 2.4.1 → 2.4.3
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 +420 -72
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2069,14 +2069,15 @@ function getAllSuppressionPenalties(stateDb2, now) {
|
|
|
2069
2069
|
}
|
|
2070
2070
|
return penalties;
|
|
2071
2071
|
}
|
|
2072
|
-
function trackWikilinkApplications(stateDb2, notePath, entities) {
|
|
2072
|
+
function trackWikilinkApplications(stateDb2, notePath, entities, source = "tool") {
|
|
2073
2073
|
const upsert = stateDb2.db.prepare(`
|
|
2074
|
-
INSERT INTO wikilink_applications (entity, note_path, matched_term, applied_at, status)
|
|
2075
|
-
VALUES (?, ?, ?, datetime('now'), 'applied')
|
|
2074
|
+
INSERT INTO wikilink_applications (entity, note_path, matched_term, applied_at, status, source)
|
|
2075
|
+
VALUES (?, ?, ?, datetime('now'), 'applied', ?)
|
|
2076
2076
|
ON CONFLICT(entity, note_path) DO UPDATE SET
|
|
2077
2077
|
matched_term = COALESCE(?, matched_term),
|
|
2078
2078
|
applied_at = datetime('now'),
|
|
2079
|
-
status = 'applied'
|
|
2079
|
+
status = 'applied',
|
|
2080
|
+
source = ?
|
|
2080
2081
|
`);
|
|
2081
2082
|
const lookupCanonical = stateDb2.db.prepare(
|
|
2082
2083
|
`SELECT name FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1`
|
|
@@ -2087,7 +2088,7 @@ function trackWikilinkApplications(stateDb2, notePath, entities) {
|
|
|
2087
2088
|
const matchedTerm = typeof item === "string" ? null : item.matchedTerm ?? null;
|
|
2088
2089
|
const row = lookupCanonical.get(entityName);
|
|
2089
2090
|
const canonicalName = row?.name ?? entityName;
|
|
2090
|
-
upsert.run(canonicalName, notePath, matchedTerm, matchedTerm);
|
|
2091
|
+
upsert.run(canonicalName, notePath, matchedTerm, source, matchedTerm, source);
|
|
2091
2092
|
}
|
|
2092
2093
|
});
|
|
2093
2094
|
transaction();
|
|
@@ -4718,7 +4719,7 @@ async function applyProactiveSuggestions(filePath, vaultPath2, suggestions, conf
|
|
|
4718
4719
|
return { applied: [], skipped: candidates.map((c) => c.entity) };
|
|
4719
4720
|
}
|
|
4720
4721
|
if (stateDb2) {
|
|
4721
|
-
trackWikilinkApplications(stateDb2, filePath, result.linkedEntities);
|
|
4722
|
+
trackWikilinkApplications(stateDb2, filePath, result.linkedEntities, "proactive");
|
|
4722
4723
|
try {
|
|
4723
4724
|
const markApplied = stateDb2.db.prepare(
|
|
4724
4725
|
`UPDATE suggestion_events SET applied = 1
|
|
@@ -5001,7 +5002,7 @@ async function drainProactiveQueue(stateDb2, vaultPath2, config, applyFn) {
|
|
|
5001
5002
|
todayMidnight.setHours(0, 0, 0, 0);
|
|
5002
5003
|
const todayStr = todayMidnight.toISOString().slice(0, 10);
|
|
5003
5004
|
const countTodayApplied = stateDb2.db.prepare(
|
|
5004
|
-
`SELECT COUNT(*) as cnt FROM wikilink_applications WHERE note_path = ? AND applied_at >=
|
|
5005
|
+
`SELECT COUNT(*) as cnt FROM wikilink_applications WHERE note_path = ? AND applied_at >= ? AND source = 'proactive'`
|
|
5005
5006
|
);
|
|
5006
5007
|
for (const [filePath, suggestions] of byFile) {
|
|
5007
5008
|
const fullPath = path12.join(vaultPath2, filePath);
|
|
@@ -8391,6 +8392,155 @@ function createStepTracker() {
|
|
|
8391
8392
|
}
|
|
8392
8393
|
};
|
|
8393
8394
|
}
|
|
8395
|
+
function compactStep(step) {
|
|
8396
|
+
const out = step.output ?? {};
|
|
8397
|
+
let summary;
|
|
8398
|
+
switch (step.name) {
|
|
8399
|
+
case "entity_scan":
|
|
8400
|
+
summary = {
|
|
8401
|
+
entity_count: asNum(out.entity_count),
|
|
8402
|
+
added_count: asArrayLen(out.added),
|
|
8403
|
+
removed_count: asArrayLen(out.removed),
|
|
8404
|
+
alias_change_count: asArrayLen(out.alias_changes),
|
|
8405
|
+
category_change_count: asArrayLen(out.category_changes)
|
|
8406
|
+
};
|
|
8407
|
+
break;
|
|
8408
|
+
case "hub_scores":
|
|
8409
|
+
summary = {
|
|
8410
|
+
updated: asNum(out.updated),
|
|
8411
|
+
diff_count: asArrayLen(out.diffs)
|
|
8412
|
+
};
|
|
8413
|
+
break;
|
|
8414
|
+
case "forward_links":
|
|
8415
|
+
summary = {
|
|
8416
|
+
total_resolved: asNum(out.total_resolved),
|
|
8417
|
+
total_dead: asNum(out.total_dead),
|
|
8418
|
+
new_dead_count: asArrayLen(out.new_dead_links),
|
|
8419
|
+
diff_count: asArrayLen(out.link_diffs)
|
|
8420
|
+
};
|
|
8421
|
+
break;
|
|
8422
|
+
case "wikilink_check":
|
|
8423
|
+
summary = {
|
|
8424
|
+
tracked_count: asArrayLen(out.tracked),
|
|
8425
|
+
mention_count: asArrayLen(out.mentions)
|
|
8426
|
+
};
|
|
8427
|
+
break;
|
|
8428
|
+
case "prospect_scan": {
|
|
8429
|
+
const prospects = Array.isArray(out.prospects) ? out.prospects : [];
|
|
8430
|
+
summary = {
|
|
8431
|
+
implicit_count: prospects.reduce((s, p) => s + (Array.isArray(p.implicit) ? p.implicit.length : 0), 0),
|
|
8432
|
+
dead_match_count: prospects.reduce((s, p) => s + (Array.isArray(p.deadLinkMatches) ? p.deadLinkMatches.length : 0), 0)
|
|
8433
|
+
};
|
|
8434
|
+
break;
|
|
8435
|
+
}
|
|
8436
|
+
case "suggestion_scoring":
|
|
8437
|
+
summary = { scored_files: asNum(out.scored_files) };
|
|
8438
|
+
break;
|
|
8439
|
+
case "implicit_feedback":
|
|
8440
|
+
summary = {
|
|
8441
|
+
removal_count: asArrayLen(out.removals),
|
|
8442
|
+
addition_count: asArrayLen(out.additions),
|
|
8443
|
+
suppressed_count: asArrayLen(out.newly_suppressed)
|
|
8444
|
+
};
|
|
8445
|
+
break;
|
|
8446
|
+
case "note_embeddings":
|
|
8447
|
+
summary = { updated: asNum(out.updated), removed: asNum(out.removed) };
|
|
8448
|
+
break;
|
|
8449
|
+
case "entity_embeddings":
|
|
8450
|
+
summary = { updated: asNum(out.updated) };
|
|
8451
|
+
break;
|
|
8452
|
+
case "fts5_incremental":
|
|
8453
|
+
summary = { updated: asNum(out.updated), removed: asNum(out.removed) };
|
|
8454
|
+
break;
|
|
8455
|
+
case "index_rebuild":
|
|
8456
|
+
summary = {
|
|
8457
|
+
note_count: asNum(out.note_count),
|
|
8458
|
+
entity_count: asNum(out.entity_count),
|
|
8459
|
+
tag_count: asNum(out.tag_count)
|
|
8460
|
+
};
|
|
8461
|
+
break;
|
|
8462
|
+
case "index_cache":
|
|
8463
|
+
summary = { saved: asBool(out.saved) };
|
|
8464
|
+
break;
|
|
8465
|
+
case "task_cache":
|
|
8466
|
+
summary = { updated: asNum(out.updated), removed: asNum(out.removed) };
|
|
8467
|
+
break;
|
|
8468
|
+
case "tag_scan":
|
|
8469
|
+
summary = { added_count: asNum(out.total_added), removed_count: asNum(out.total_removed) };
|
|
8470
|
+
break;
|
|
8471
|
+
case "drain_proactive_queue":
|
|
8472
|
+
summary = {
|
|
8473
|
+
total_applied: asNum(out.total_applied),
|
|
8474
|
+
expired: asNum(out.expired),
|
|
8475
|
+
skipped_mtime: asNum(out.skipped_mtime),
|
|
8476
|
+
skipped_daily_cap: asNum(out.skipped_daily_cap)
|
|
8477
|
+
};
|
|
8478
|
+
break;
|
|
8479
|
+
case "proactive_enqueue":
|
|
8480
|
+
summary = { enqueued: asNum(out.enqueued), total_candidates: asNum(out.total_candidates) };
|
|
8481
|
+
break;
|
|
8482
|
+
case "recency":
|
|
8483
|
+
case "cooccurrence":
|
|
8484
|
+
case "edge_weights":
|
|
8485
|
+
summary = { rebuilt: asBool(out.rebuilt) };
|
|
8486
|
+
break;
|
|
8487
|
+
case "incremental_recency":
|
|
8488
|
+
summary = { entities_updated: asNum(out.entities_updated) };
|
|
8489
|
+
break;
|
|
8490
|
+
case "corrections":
|
|
8491
|
+
summary = { processed: asNum(out.processed) };
|
|
8492
|
+
break;
|
|
8493
|
+
case "retrieval_cooccurrence":
|
|
8494
|
+
summary = { pairs_inserted: asNum(out.pairs_inserted) };
|
|
8495
|
+
break;
|
|
8496
|
+
case "integrity_check":
|
|
8497
|
+
case "maintenance":
|
|
8498
|
+
summary = {
|
|
8499
|
+
...out.skipped != null ? { skipped: asBool(out.skipped) } : {},
|
|
8500
|
+
...typeof out.reason === "string" ? { reason: out.reason } : {}
|
|
8501
|
+
};
|
|
8502
|
+
break;
|
|
8503
|
+
case "note_moves":
|
|
8504
|
+
summary = { count: asNum(out.count ?? (Array.isArray(out.renames) ? out.renames.length : 0)) };
|
|
8505
|
+
break;
|
|
8506
|
+
default:
|
|
8507
|
+
summary = {};
|
|
8508
|
+
for (const [k, v] of Object.entries(out)) {
|
|
8509
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "string") {
|
|
8510
|
+
summary[k] = v;
|
|
8511
|
+
}
|
|
8512
|
+
}
|
|
8513
|
+
break;
|
|
8514
|
+
}
|
|
8515
|
+
return {
|
|
8516
|
+
name: step.name,
|
|
8517
|
+
duration_ms: step.duration_ms,
|
|
8518
|
+
...step.skipped ? { skipped: true, skip_reason: step.skip_reason } : {},
|
|
8519
|
+
summary
|
|
8520
|
+
};
|
|
8521
|
+
}
|
|
8522
|
+
function compactPipelineRun(event) {
|
|
8523
|
+
const paths = event.changed_paths ?? [];
|
|
8524
|
+
return {
|
|
8525
|
+
timestamp: event.timestamp,
|
|
8526
|
+
trigger: event.trigger,
|
|
8527
|
+
duration_ms: event.duration_ms,
|
|
8528
|
+
files_changed: event.files_changed,
|
|
8529
|
+
changed_paths_total: event.files_changed ?? paths.length,
|
|
8530
|
+
changed_paths_sample: paths.slice(0, 3),
|
|
8531
|
+
step_count: event.steps?.length ?? 0,
|
|
8532
|
+
steps: (event.steps ?? []).map(compactStep)
|
|
8533
|
+
};
|
|
8534
|
+
}
|
|
8535
|
+
function asNum(v) {
|
|
8536
|
+
return typeof v === "number" ? v : 0;
|
|
8537
|
+
}
|
|
8538
|
+
function asBool(v) {
|
|
8539
|
+
return typeof v === "boolean" ? v : false;
|
|
8540
|
+
}
|
|
8541
|
+
function asArrayLen(v) {
|
|
8542
|
+
return Array.isArray(v) ? v.length : 0;
|
|
8543
|
+
}
|
|
8394
8544
|
function recordIndexEvent(stateDb2, event) {
|
|
8395
8545
|
stateDb2.db.prepare(
|
|
8396
8546
|
`INSERT INTO index_events (timestamp, trigger, duration_ms, success, note_count, files_changed, changed_paths, error, steps)
|
|
@@ -10013,7 +10163,7 @@ var PipelineRunner = class {
|
|
|
10013
10163
|
}
|
|
10014
10164
|
}
|
|
10015
10165
|
if (newlyTracked.length > 0) {
|
|
10016
|
-
trackWikilinkApplications(p.sd, diff.file, newlyTracked);
|
|
10166
|
+
trackWikilinkApplications(p.sd, diff.file, newlyTracked, "manual_detected");
|
|
10017
10167
|
}
|
|
10018
10168
|
}
|
|
10019
10169
|
}
|
|
@@ -11406,7 +11556,7 @@ function getSessionHistory(stateDb2, sessionId) {
|
|
|
11406
11556
|
}));
|
|
11407
11557
|
}
|
|
11408
11558
|
function getSessionDetail(stateDb2, sessionId, options = {}) {
|
|
11409
|
-
const { include_children = true, limit =
|
|
11559
|
+
const { include_children = true, limit = 50 } = options;
|
|
11410
11560
|
const rows = include_children ? stateDb2.db.prepare(`
|
|
11411
11561
|
SELECT * FROM tool_invocations
|
|
11412
11562
|
WHERE session_id = ? OR session_id LIKE ?
|
|
@@ -13329,7 +13479,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
13329
13479
|
async ({ query, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit, detail_count: requestedDetailCount, context_note, consumer }) => {
|
|
13330
13480
|
requireIndex();
|
|
13331
13481
|
const limit = Math.min(requestedLimit ?? 10, MAX_LIMIT);
|
|
13332
|
-
const
|
|
13482
|
+
const enrichN = Math.min(requestedDetailCount ?? 5, limit);
|
|
13483
|
+
const expandN = Math.min(enrichN, 8);
|
|
13333
13484
|
const index = getIndex();
|
|
13334
13485
|
const vaultPath2 = getVaultPath();
|
|
13335
13486
|
if (prefix && query) {
|
|
@@ -13383,7 +13534,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
13383
13534
|
const limitedNotes = matchingNotes.slice(0, limit);
|
|
13384
13535
|
const stateDb2 = getStateDb4();
|
|
13385
13536
|
const notes = limitedNotes.map(
|
|
13386
|
-
(note, i) => (i <
|
|
13537
|
+
(note, i) => (i < enrichN ? enrichResult : enrichResultLight)({ path: note.path, title: note.title }, index, stateDb2)
|
|
13387
13538
|
);
|
|
13388
13539
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
13389
13540
|
total_matches: totalMatches,
|
|
@@ -13518,7 +13669,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
13518
13669
|
await enhanceSnippets(results2, query, vaultPath2);
|
|
13519
13670
|
if (consumer === "llm") {
|
|
13520
13671
|
applySandwichOrdering(results2);
|
|
13521
|
-
await expandToSections(results2, index, vaultPath2,
|
|
13672
|
+
await expandToSections(results2, index, vaultPath2, expandN);
|
|
13522
13673
|
stripInternalFields(results2);
|
|
13523
13674
|
}
|
|
13524
13675
|
const entitySection2 = await entitySectionPromise;
|
|
@@ -13566,7 +13717,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
13566
13717
|
await enhanceSnippets(results2, query, vaultPath2);
|
|
13567
13718
|
if (consumer === "llm") {
|
|
13568
13719
|
applySandwichOrdering(results2);
|
|
13569
|
-
await expandToSections(results2, index, vaultPath2,
|
|
13720
|
+
await expandToSections(results2, index, vaultPath2, expandN);
|
|
13570
13721
|
stripInternalFields(results2);
|
|
13571
13722
|
}
|
|
13572
13723
|
const entitySection2 = await entitySectionPromise;
|
|
@@ -13593,7 +13744,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
13593
13744
|
await enhanceSnippets(results, query, vaultPath2);
|
|
13594
13745
|
if (consumer === "llm") {
|
|
13595
13746
|
applySandwichOrdering(results);
|
|
13596
|
-
await expandToSections(results, index, vaultPath2,
|
|
13747
|
+
await expandToSections(results, index, vaultPath2, expandN);
|
|
13597
13748
|
stripInternalFields(results);
|
|
13598
13749
|
}
|
|
13599
13750
|
const entitySection = await entitySectionPromise;
|
|
@@ -14532,13 +14683,28 @@ function registerGraphExportTools(server2, getIndex, getVaultPath, getStateDb4)
|
|
|
14532
14683
|
include_cooccurrence: z3.boolean().default(true).describe("Include co-occurrence edges between entities"),
|
|
14533
14684
|
min_edge_weight: z3.number().default(0).describe("Minimum edge weight threshold (filters weighted edges)"),
|
|
14534
14685
|
center_entity: z3.string().optional().describe("Center the export on this entity (ego network). Only includes nodes within `depth` hops."),
|
|
14535
|
-
depth: z3.number().default(1).describe("Hops from center_entity to include (default 1). Ignored without center_entity.")
|
|
14686
|
+
depth: z3.number().default(1).describe("Hops from center_entity to include (default 1). Ignored without center_entity."),
|
|
14687
|
+
max_nodes: z3.number().default(500).describe("Maximum nodes in export when no center_entity. Pass higher for full vault exports.")
|
|
14536
14688
|
},
|
|
14537
|
-
async ({ format, include_cooccurrence, min_edge_weight, center_entity, depth }) => {
|
|
14689
|
+
async ({ format, include_cooccurrence, min_edge_weight, center_entity, depth, max_nodes }) => {
|
|
14538
14690
|
requireIndex();
|
|
14539
14691
|
const index = getIndex();
|
|
14540
14692
|
const stateDb2 = getStateDb4?.() ?? null;
|
|
14541
14693
|
const data = buildGraphData(index, stateDb2, { include_cooccurrence, min_edge_weight, center_entity, depth });
|
|
14694
|
+
if (!center_entity && data.nodes.length > max_nodes) {
|
|
14695
|
+
return {
|
|
14696
|
+
content: [{
|
|
14697
|
+
type: "text",
|
|
14698
|
+
text: JSON.stringify({
|
|
14699
|
+
error: `Graph has ${data.nodes.length} nodes and ${data.edges.length} edges, exceeding max_nodes=${max_nodes}. Use center_entity to scope, or pass a higher max_nodes.`,
|
|
14700
|
+
node_count: data.nodes.length,
|
|
14701
|
+
edge_count: data.edges.length,
|
|
14702
|
+
max_nodes
|
|
14703
|
+
})
|
|
14704
|
+
}],
|
|
14705
|
+
isError: true
|
|
14706
|
+
};
|
|
14707
|
+
}
|
|
14542
14708
|
let output;
|
|
14543
14709
|
if (format === "json") {
|
|
14544
14710
|
output = JSON.stringify(data, null, 2);
|
|
@@ -15402,6 +15568,55 @@ function getActivitySummary(index, days) {
|
|
|
15402
15568
|
import { SCHEMA_VERSION } from "@velvetmonkey/vault-core";
|
|
15403
15569
|
init_embeddings();
|
|
15404
15570
|
init_serverLog();
|
|
15571
|
+
|
|
15572
|
+
// src/core/shared/proactiveLinkingStats.ts
|
|
15573
|
+
function toSqliteTimestamp(date) {
|
|
15574
|
+
return date.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "");
|
|
15575
|
+
}
|
|
15576
|
+
function getProactiveLinkingSummary(stateDb2, daysBack = 1) {
|
|
15577
|
+
const now = /* @__PURE__ */ new Date();
|
|
15578
|
+
const since = new Date(now.getTime() - daysBack * 24 * 60 * 60 * 1e3);
|
|
15579
|
+
const sinceStr = toSqliteTimestamp(since);
|
|
15580
|
+
const untilStr = toSqliteTimestamp(now);
|
|
15581
|
+
const survived = stateDb2.db.prepare(
|
|
15582
|
+
`SELECT COUNT(*) as cnt FROM wikilink_applications
|
|
15583
|
+
WHERE source = 'proactive' AND applied_at >= ? AND status = 'applied'`
|
|
15584
|
+
).get(sinceStr);
|
|
15585
|
+
const removed = stateDb2.db.prepare(
|
|
15586
|
+
`SELECT COUNT(*) as cnt FROM wikilink_applications
|
|
15587
|
+
WHERE source = 'proactive' AND applied_at >= ? AND status = 'removed'`
|
|
15588
|
+
).get(sinceStr);
|
|
15589
|
+
const files = stateDb2.db.prepare(
|
|
15590
|
+
`SELECT COUNT(DISTINCT note_path) as cnt FROM wikilink_applications
|
|
15591
|
+
WHERE source = 'proactive' AND applied_at >= ?`
|
|
15592
|
+
).get(sinceStr);
|
|
15593
|
+
const recent = stateDb2.db.prepare(
|
|
15594
|
+
`SELECT entity, note_path, applied_at, status FROM wikilink_applications
|
|
15595
|
+
WHERE source = 'proactive' AND applied_at >= ?
|
|
15596
|
+
ORDER BY applied_at DESC LIMIT 10`
|
|
15597
|
+
).all(sinceStr);
|
|
15598
|
+
const totalApplied = survived.cnt + removed.cnt;
|
|
15599
|
+
const survivalRate = totalApplied > 0 ? survived.cnt / totalApplied : null;
|
|
15600
|
+
return {
|
|
15601
|
+
window: { kind: "rolling_24h", since: sinceStr, until: untilStr },
|
|
15602
|
+
total_applied: totalApplied,
|
|
15603
|
+
survived: survived.cnt,
|
|
15604
|
+
removed: removed.cnt,
|
|
15605
|
+
files_touched: files.cnt,
|
|
15606
|
+
survival_rate: survivalRate,
|
|
15607
|
+
recent
|
|
15608
|
+
};
|
|
15609
|
+
}
|
|
15610
|
+
function getProactiveLinkingOneLiner(stateDb2, daysBack = 1) {
|
|
15611
|
+
const summary = getProactiveLinkingSummary(stateDb2, daysBack);
|
|
15612
|
+
if (summary.total_applied === 0) return null;
|
|
15613
|
+
const linkWord = summary.total_applied === 1 ? "link" : "links";
|
|
15614
|
+
const noteWord = summary.files_touched === 1 ? "note" : "notes";
|
|
15615
|
+
const rate = summary.survival_rate !== null ? `${Math.round(summary.survival_rate * 100)}%` : "n/a";
|
|
15616
|
+
return `${summary.total_applied} ${linkWord} applied across ${summary.files_touched} ${noteWord} (${summary.survived} survived, ${rate} rate)`;
|
|
15617
|
+
}
|
|
15618
|
+
|
|
15619
|
+
// src/tools/read/health.ts
|
|
15405
15620
|
init_wikilinkFeedback();
|
|
15406
15621
|
init_embeddings();
|
|
15407
15622
|
var STALE_THRESHOLD_SECONDS = 300;
|
|
@@ -15447,31 +15662,33 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15447
15662
|
trigger: z5.string(),
|
|
15448
15663
|
duration_ms: z5.number(),
|
|
15449
15664
|
files_changed: z5.number().nullable(),
|
|
15450
|
-
|
|
15665
|
+
changed_paths_total: z5.number().describe("Total number of changed files"),
|
|
15666
|
+
changed_paths_sample: z5.array(z5.string()).describe("Up to 3 sample changed paths"),
|
|
15667
|
+
step_count: z5.number().describe("Number of pipeline steps"),
|
|
15451
15668
|
steps: z5.array(z5.object({
|
|
15452
15669
|
name: z5.string(),
|
|
15453
15670
|
duration_ms: z5.number(),
|
|
15454
|
-
input: z5.record(z5.unknown()),
|
|
15455
|
-
output: z5.record(z5.unknown()),
|
|
15456
15671
|
skipped: z5.boolean().optional(),
|
|
15457
|
-
skip_reason: z5.string().optional()
|
|
15458
|
-
|
|
15459
|
-
|
|
15672
|
+
skip_reason: z5.string().optional(),
|
|
15673
|
+
summary: z5.record(z5.union([z5.number(), z5.boolean(), z5.string()]))
|
|
15674
|
+
})).optional().describe("Compact step summaries (full mode only)")
|
|
15675
|
+
}).optional().describe("Most recent watcher pipeline run"),
|
|
15460
15676
|
recent_pipelines: z5.array(z5.object({
|
|
15461
15677
|
timestamp: z5.number(),
|
|
15462
15678
|
trigger: z5.string(),
|
|
15463
15679
|
duration_ms: z5.number(),
|
|
15464
15680
|
files_changed: z5.number().nullable(),
|
|
15465
|
-
|
|
15681
|
+
changed_paths_total: z5.number(),
|
|
15682
|
+
changed_paths_sample: z5.array(z5.string()),
|
|
15683
|
+
step_count: z5.number(),
|
|
15466
15684
|
steps: z5.array(z5.object({
|
|
15467
15685
|
name: z5.string(),
|
|
15468
15686
|
duration_ms: z5.number(),
|
|
15469
|
-
input: z5.record(z5.unknown()),
|
|
15470
|
-
output: z5.record(z5.unknown()),
|
|
15471
15687
|
skipped: z5.boolean().optional(),
|
|
15472
|
-
skip_reason: z5.string().optional()
|
|
15688
|
+
skip_reason: z5.string().optional(),
|
|
15689
|
+
summary: z5.record(z5.union([z5.number(), z5.boolean(), z5.string()]))
|
|
15473
15690
|
}))
|
|
15474
|
-
})).optional().describe("Up to 5 most recent pipeline runs with
|
|
15691
|
+
})).optional().describe("Up to 5 most recent pipeline runs with compact step summaries (full mode only)"),
|
|
15475
15692
|
fts5_ready: z5.boolean().describe("Whether the FTS5 keyword search index is ready"),
|
|
15476
15693
|
fts5_building: z5.boolean().describe("Whether the FTS5 keyword search index is currently building"),
|
|
15477
15694
|
embeddings_building: z5.boolean().describe("Whether semantic embeddings are currently building"),
|
|
@@ -15527,6 +15744,15 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15527
15744
|
unlinked_mentions: z5.number()
|
|
15528
15745
|
})).describe("Entities with the most unlinked plain-text mentions")
|
|
15529
15746
|
}).optional().describe("Background sweep results (graph hygiene metrics, updated every 5 min)"),
|
|
15747
|
+
proactive_linking: z5.object({
|
|
15748
|
+
enabled: z5.boolean().describe("Whether proactive linking is enabled in config"),
|
|
15749
|
+
queue_pending: z5.number().describe("Number of queued proactive suggestions awaiting drain"),
|
|
15750
|
+
summary: z5.string().nullable().describe('One-liner: "12 links applied across 8 notes (11 survived, 92% rate)"'),
|
|
15751
|
+
total_applied_24h: z5.number().describe("Total proactive applications in last 24h"),
|
|
15752
|
+
survived_24h: z5.number().describe("Proactive links still present"),
|
|
15753
|
+
removed_24h: z5.number().describe("Proactive links removed by user"),
|
|
15754
|
+
files_24h: z5.number().describe("Distinct files touched by proactive linking")
|
|
15755
|
+
}).optional().describe("Proactive linking observability (full mode only)"),
|
|
15530
15756
|
recommendations: z5.array(z5.string()).describe("Suggested actions if any issues detected")
|
|
15531
15757
|
};
|
|
15532
15758
|
server2.registerTool(
|
|
@@ -15659,14 +15885,13 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15659
15885
|
try {
|
|
15660
15886
|
const evt = getRecentPipelineEvent(stateDb2);
|
|
15661
15887
|
if (evt && evt.steps && evt.steps.length > 0) {
|
|
15662
|
-
|
|
15663
|
-
|
|
15664
|
-
|
|
15665
|
-
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
|
|
15669
|
-
};
|
|
15888
|
+
const compact = compactPipelineRun(evt);
|
|
15889
|
+
if (isFull) {
|
|
15890
|
+
lastPipeline = compact;
|
|
15891
|
+
} else {
|
|
15892
|
+
const { steps: _steps, ...metadataOnly } = compact;
|
|
15893
|
+
lastPipeline = metadataOnly;
|
|
15894
|
+
}
|
|
15670
15895
|
}
|
|
15671
15896
|
} catch {
|
|
15672
15897
|
}
|
|
@@ -15674,14 +15899,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15674
15899
|
try {
|
|
15675
15900
|
const events = getRecentIndexEvents(stateDb2, 10).filter((e) => e.steps && e.steps.length > 0).slice(0, 5);
|
|
15676
15901
|
if (events.length > 0) {
|
|
15677
|
-
recentPipelines = events.map((e) => (
|
|
15678
|
-
timestamp: e.timestamp,
|
|
15679
|
-
trigger: e.trigger,
|
|
15680
|
-
duration_ms: e.duration_ms,
|
|
15681
|
-
files_changed: e.files_changed,
|
|
15682
|
-
changed_paths: e.changed_paths,
|
|
15683
|
-
steps: e.steps
|
|
15684
|
-
}));
|
|
15902
|
+
recentPipelines = events.map((e) => compactPipelineRun(e));
|
|
15685
15903
|
}
|
|
15686
15904
|
} catch {
|
|
15687
15905
|
}
|
|
@@ -15779,6 +15997,24 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15779
15997
|
dead_link_count: isFull ? deadLinkCount : void 0,
|
|
15780
15998
|
top_dead_link_targets: isFull ? topDeadLinkTargets : void 0,
|
|
15781
15999
|
sweep: isFull ? getSweepResults() ?? void 0 : void 0,
|
|
16000
|
+
proactive_linking: isFull && stateDb2 ? (() => {
|
|
16001
|
+
const config = getConfig2();
|
|
16002
|
+
const enabled = config.proactive_linking !== false;
|
|
16003
|
+
const queuePending = stateDb2.db.prepare(
|
|
16004
|
+
`SELECT COUNT(*) as cnt FROM proactive_queue WHERE status = 'pending'`
|
|
16005
|
+
).get();
|
|
16006
|
+
const summary = getProactiveLinkingSummary(stateDb2, 1);
|
|
16007
|
+
const oneLiner = getProactiveLinkingOneLiner(stateDb2, 1);
|
|
16008
|
+
return {
|
|
16009
|
+
enabled,
|
|
16010
|
+
queue_pending: queuePending.cnt,
|
|
16011
|
+
summary: oneLiner,
|
|
16012
|
+
total_applied_24h: summary.total_applied,
|
|
16013
|
+
survived_24h: summary.survived,
|
|
16014
|
+
removed_24h: summary.removed,
|
|
16015
|
+
files_24h: summary.files_touched
|
|
16016
|
+
};
|
|
16017
|
+
})() : void 0,
|
|
15782
16018
|
recommendations
|
|
15783
16019
|
};
|
|
15784
16020
|
return {
|
|
@@ -15826,13 +16062,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15826
16062
|
if (stateDb2) {
|
|
15827
16063
|
try {
|
|
15828
16064
|
const events = getRecentIndexEvents(stateDb2, 10).filter((e) => e.steps && e.steps.length > 0).slice(0, 5);
|
|
15829
|
-
output.recent_runs = events.map((e) => (
|
|
15830
|
-
timestamp: e.timestamp,
|
|
15831
|
-
trigger: e.trigger,
|
|
15832
|
-
duration_ms: e.duration_ms,
|
|
15833
|
-
files_changed: e.files_changed,
|
|
15834
|
-
steps: e.steps
|
|
15835
|
-
}));
|
|
16065
|
+
output.recent_runs = events.map((e) => compactPipelineRun(e));
|
|
15836
16066
|
} catch {
|
|
15837
16067
|
}
|
|
15838
16068
|
}
|
|
@@ -17060,7 +17290,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
17060
17290
|
description: "Use when listing all linkable entities grouped by category. Produces the full entity index from the state database with names, aliases, hub scores, and categories. Returns an array of entity profiles. Does not search note content \u2014 only returns entity metadata from the index.",
|
|
17061
17291
|
inputSchema: {
|
|
17062
17292
|
category: z6.string().optional().describe('Filter to a specific category (e.g. "people", "technologies")'),
|
|
17063
|
-
limit: z6.coerce.number().default(
|
|
17293
|
+
limit: z6.coerce.number().default(200).describe("Maximum entities per category (default 200; pass higher for full hydration)")
|
|
17064
17294
|
}
|
|
17065
17295
|
},
|
|
17066
17296
|
async ({
|
|
@@ -17215,10 +17445,11 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17215
17445
|
description: "Use after search identifies a note you need detail on. Produces heading tree, frontmatter, tags, word count, backlink and outlink counts, and optionally full section content. Returns enriched note structure with entity metadata when available. Does not search \u2014 requires an exact path from a prior search result.",
|
|
17216
17446
|
inputSchema: {
|
|
17217
17447
|
path: z7.string().describe("Path to the note"),
|
|
17218
|
-
include_content: z7.boolean().default(
|
|
17448
|
+
include_content: z7.boolean().default(false).describe("Include the text content under each top-level section. Default false for structure only."),
|
|
17449
|
+
max_content_chars: z7.number().default(2e4).describe("Max total chars of section content to include. Sections are truncated at paragraph boundaries.")
|
|
17219
17450
|
}
|
|
17220
17451
|
},
|
|
17221
|
-
async ({ path: path40, include_content }) => {
|
|
17452
|
+
async ({ path: path40, include_content, max_content_chars }) => {
|
|
17222
17453
|
const index = getIndex();
|
|
17223
17454
|
const vaultPath2 = getVaultPath();
|
|
17224
17455
|
const result = await getNoteStructure(index, path40, vaultPath2);
|
|
@@ -17227,11 +17458,26 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17227
17458
|
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path40 }, null, 2) }]
|
|
17228
17459
|
};
|
|
17229
17460
|
}
|
|
17461
|
+
let totalChars = 0;
|
|
17462
|
+
let truncated = false;
|
|
17230
17463
|
if (include_content) {
|
|
17231
17464
|
for (const section of result.sections) {
|
|
17465
|
+
if (totalChars >= max_content_chars) {
|
|
17466
|
+
truncated = true;
|
|
17467
|
+
break;
|
|
17468
|
+
}
|
|
17232
17469
|
const sectionResult = await getSectionContent(index, path40, section.heading.text, vaultPath2, true);
|
|
17233
17470
|
if (sectionResult) {
|
|
17234
|
-
|
|
17471
|
+
let content = sectionResult.content;
|
|
17472
|
+
const remaining = max_content_chars - totalChars;
|
|
17473
|
+
if (content.length > remaining) {
|
|
17474
|
+
const sliced = content.slice(0, remaining);
|
|
17475
|
+
const lastBreak = sliced.lastIndexOf("\n\n");
|
|
17476
|
+
content = lastBreak > 0 ? sliced.slice(0, lastBreak) : sliced;
|
|
17477
|
+
truncated = true;
|
|
17478
|
+
}
|
|
17479
|
+
section.content = content;
|
|
17480
|
+
totalChars += content.length;
|
|
17235
17481
|
}
|
|
17236
17482
|
}
|
|
17237
17483
|
}
|
|
@@ -17258,6 +17504,10 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17258
17504
|
} catch {
|
|
17259
17505
|
}
|
|
17260
17506
|
}
|
|
17507
|
+
if (include_content) {
|
|
17508
|
+
enriched.truncated = truncated;
|
|
17509
|
+
enriched.returned_chars = totalChars;
|
|
17510
|
+
}
|
|
17261
17511
|
return {
|
|
17262
17512
|
content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }]
|
|
17263
17513
|
};
|
|
@@ -17271,10 +17521,11 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17271
17521
|
inputSchema: {
|
|
17272
17522
|
path: z7.string().describe("Path to the note"),
|
|
17273
17523
|
heading: z7.string().describe("Heading text to find"),
|
|
17274
|
-
include_subheadings: z7.boolean().default(true).describe("Include content under subheadings")
|
|
17524
|
+
include_subheadings: z7.boolean().default(true).describe("Include content under subheadings"),
|
|
17525
|
+
max_content_chars: z7.number().default(1e4).describe("Max chars of section content. Truncated at paragraph boundaries.")
|
|
17275
17526
|
}
|
|
17276
17527
|
},
|
|
17277
|
-
async ({ path: path40, heading, include_subheadings }) => {
|
|
17528
|
+
async ({ path: path40, heading, include_subheadings, max_content_chars }) => {
|
|
17278
17529
|
const index = getIndex();
|
|
17279
17530
|
const vaultPath2 = getVaultPath();
|
|
17280
17531
|
const result = await getSectionContent(index, path40, heading, vaultPath2, include_subheadings);
|
|
@@ -17287,8 +17538,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17287
17538
|
}, null, 2) }]
|
|
17288
17539
|
};
|
|
17289
17540
|
}
|
|
17541
|
+
let truncated = false;
|
|
17542
|
+
if (result.content.length > max_content_chars) {
|
|
17543
|
+
const sliced = result.content.slice(0, max_content_chars);
|
|
17544
|
+
const lastBreak = sliced.lastIndexOf("\n\n");
|
|
17545
|
+
result.content = lastBreak > 0 ? sliced.slice(0, lastBreak) : sliced;
|
|
17546
|
+
truncated = true;
|
|
17547
|
+
}
|
|
17290
17548
|
return {
|
|
17291
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
17549
|
+
content: [{ type: "text", text: JSON.stringify({ ...result, truncated }, null, 2) }]
|
|
17292
17550
|
};
|
|
17293
17551
|
}
|
|
17294
17552
|
);
|
|
@@ -24028,6 +24286,24 @@ function buildVaultPulseSection(stateDb2) {
|
|
|
24028
24286
|
estimated_tokens: estimateTokens2(content)
|
|
24029
24287
|
};
|
|
24030
24288
|
}
|
|
24289
|
+
function buildProactiveLinkingSection(stateDb2) {
|
|
24290
|
+
const summary = getProactiveLinkingSummary(stateDb2, 1);
|
|
24291
|
+
if (summary.total_applied === 0) return null;
|
|
24292
|
+
const content = {
|
|
24293
|
+
summary: `${summary.total_applied} ${summary.total_applied === 1 ? "link" : "links"} applied across ${summary.files_touched} ${summary.files_touched === 1 ? "note" : "notes"} (${summary.survived} survived, ${summary.survival_rate !== null ? Math.round(summary.survival_rate * 100) + "%" : "n/a"} rate)`,
|
|
24294
|
+
total_applied_24h: summary.total_applied,
|
|
24295
|
+
survived_24h: summary.survived,
|
|
24296
|
+
removed_24h: summary.removed,
|
|
24297
|
+
files_24h: summary.files_touched,
|
|
24298
|
+
recent: summary.recent
|
|
24299
|
+
};
|
|
24300
|
+
return {
|
|
24301
|
+
name: "proactive_linking",
|
|
24302
|
+
priority: 6,
|
|
24303
|
+
content,
|
|
24304
|
+
estimated_tokens: estimateTokens2(content)
|
|
24305
|
+
};
|
|
24306
|
+
}
|
|
24031
24307
|
function registerBriefTools(server2, getStateDb4) {
|
|
24032
24308
|
server2.tool(
|
|
24033
24309
|
"brief",
|
|
@@ -24049,23 +24325,33 @@ function registerBriefTools(server2, getStateDb4) {
|
|
|
24049
24325
|
buildActiveEntitiesSection(stateDb2, 10),
|
|
24050
24326
|
buildActiveMemoriesSection(stateDb2, 20),
|
|
24051
24327
|
buildCorrectionsSection(stateDb2, 10),
|
|
24052
|
-
buildVaultPulseSection(stateDb2)
|
|
24053
|
-
|
|
24328
|
+
buildVaultPulseSection(stateDb2),
|
|
24329
|
+
buildProactiveLinkingSection(stateDb2)
|
|
24330
|
+
].filter((s) => s !== null);
|
|
24054
24331
|
if (args.max_tokens) {
|
|
24055
24332
|
let totalTokens2 = 0;
|
|
24056
24333
|
sections.sort((a, b) => a.priority - b.priority);
|
|
24334
|
+
const kept = [];
|
|
24057
24335
|
for (const section of sections) {
|
|
24058
|
-
totalTokens2
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24336
|
+
if (totalTokens2 >= args.max_tokens) {
|
|
24337
|
+
break;
|
|
24338
|
+
}
|
|
24339
|
+
if (totalTokens2 + section.estimated_tokens <= args.max_tokens) {
|
|
24340
|
+
totalTokens2 += section.estimated_tokens;
|
|
24341
|
+
kept.push(section);
|
|
24342
|
+
} else if (Array.isArray(section.content)) {
|
|
24343
|
+
const remaining = args.max_tokens - totalTokens2;
|
|
24344
|
+
const itemTokens = section.estimated_tokens / Math.max(1, section.content.length);
|
|
24345
|
+
const keepCount = Math.max(1, Math.floor(remaining / itemTokens));
|
|
24346
|
+
section.content = section.content.slice(0, keepCount);
|
|
24347
|
+
section.estimated_tokens = estimateTokens2(section.content);
|
|
24348
|
+
totalTokens2 += section.estimated_tokens;
|
|
24349
|
+
kept.push(section);
|
|
24350
|
+
break;
|
|
24067
24351
|
}
|
|
24068
24352
|
}
|
|
24353
|
+
sections.length = 0;
|
|
24354
|
+
sections.push(...kept);
|
|
24069
24355
|
}
|
|
24070
24356
|
const response = {};
|
|
24071
24357
|
let totalTokens = 0;
|
|
@@ -24448,7 +24734,7 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
|
|
|
24448
24734
|
await fs33.writeFile(fullPath, result.content, "utf-8");
|
|
24449
24735
|
notesModified++;
|
|
24450
24736
|
if (stateDb2) {
|
|
24451
|
-
trackWikilinkApplications(stateDb2, relativePath, entities);
|
|
24737
|
+
trackWikilinkApplications(stateDb2, relativePath, entities, "enrichment");
|
|
24452
24738
|
const newLinks = extractLinkedEntities(result.content);
|
|
24453
24739
|
updateStoredNoteLinks(stateDb2, relativePath, newLinks);
|
|
24454
24740
|
}
|
|
@@ -24576,7 +24862,32 @@ function registerMetricsTools(server2, getIndex, getStateDb4) {
|
|
|
24576
24862
|
const recentEvents = getRecentIndexEvents(stateDb2, eventLimit ?? 20);
|
|
24577
24863
|
result = {
|
|
24578
24864
|
mode: "index_activity",
|
|
24579
|
-
index_activity: {
|
|
24865
|
+
index_activity: {
|
|
24866
|
+
summary,
|
|
24867
|
+
recent_events: recentEvents.map((e) => {
|
|
24868
|
+
const base = {
|
|
24869
|
+
id: e.id,
|
|
24870
|
+
timestamp: e.timestamp,
|
|
24871
|
+
trigger: e.trigger,
|
|
24872
|
+
duration_ms: e.duration_ms,
|
|
24873
|
+
success: e.success,
|
|
24874
|
+
note_count: e.note_count,
|
|
24875
|
+
files_changed: e.files_changed,
|
|
24876
|
+
error: e.error
|
|
24877
|
+
};
|
|
24878
|
+
if (e.steps) {
|
|
24879
|
+
const compact = compactPipelineRun(e);
|
|
24880
|
+
return {
|
|
24881
|
+
...base,
|
|
24882
|
+
changed_paths_total: compact.changed_paths_total,
|
|
24883
|
+
changed_paths_sample: compact.changed_paths_sample,
|
|
24884
|
+
step_count: compact.step_count,
|
|
24885
|
+
steps: compact.steps
|
|
24886
|
+
};
|
|
24887
|
+
}
|
|
24888
|
+
return base;
|
|
24889
|
+
})
|
|
24890
|
+
}
|
|
24580
24891
|
};
|
|
24581
24892
|
break;
|
|
24582
24893
|
}
|
|
@@ -24602,7 +24913,7 @@ function registerActivityTools(server2, getStateDb4, getSessionId2) {
|
|
|
24602
24913
|
title: "Vault Activity",
|
|
24603
24914
|
description: "Use when checking what tools have been used and what notes have been accessed. Produces tool invocation records with session context and note paths. Returns activity entries filtered by tool name, session, or time range. Does not modify tracking data \u2014 read-only activity log.",
|
|
24604
24915
|
inputSchema: {
|
|
24605
|
-
mode: z31.enum(["session", "sessions", "note_access", "tool_usage"]).describe("Activity query mode"),
|
|
24916
|
+
mode: z31.enum(["session", "sessions", "note_access", "tool_usage", "proactive_linking"]).describe("Activity query mode"),
|
|
24606
24917
|
session_id: z31.string().optional().describe("Specific session ID (for session mode, defaults to current)"),
|
|
24607
24918
|
days_back: z31.number().optional().describe("Number of days to look back (default: 30)"),
|
|
24608
24919
|
limit: z31.number().optional().describe("Maximum results to return (default: 20)")
|
|
@@ -24666,6 +24977,24 @@ function registerActivityTools(server2, getStateDb4, getSessionId2) {
|
|
|
24666
24977
|
}, null, 2) }]
|
|
24667
24978
|
};
|
|
24668
24979
|
}
|
|
24980
|
+
case "proactive_linking": {
|
|
24981
|
+
const since = new Date(Date.now() - daysBack * 24 * 60 * 60 * 1e3);
|
|
24982
|
+
const sinceStr = since.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "");
|
|
24983
|
+
const rows = stateDb2.db.prepare(
|
|
24984
|
+
`SELECT entity, note_path, applied_at, status, matched_term
|
|
24985
|
+
FROM wikilink_applications
|
|
24986
|
+
WHERE source = 'proactive' AND applied_at >= ?
|
|
24987
|
+
ORDER BY applied_at DESC LIMIT ?`
|
|
24988
|
+
).all(sinceStr, limit);
|
|
24989
|
+
return {
|
|
24990
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
24991
|
+
mode: "proactive_linking",
|
|
24992
|
+
days_back: daysBack,
|
|
24993
|
+
count: rows.length,
|
|
24994
|
+
applications: rows
|
|
24995
|
+
}, null, 2) }]
|
|
24996
|
+
};
|
|
24997
|
+
}
|
|
24669
24998
|
}
|
|
24670
24999
|
}
|
|
24671
25000
|
);
|
|
@@ -25816,7 +26145,7 @@ function registerSessionHistoryTools(server2, getStateDb4) {
|
|
|
25816
26145
|
{
|
|
25817
26146
|
session_id: z36.string().optional().describe("Session ID for detail view. Omit for recent sessions list."),
|
|
25818
26147
|
include_children: z36.boolean().optional().describe("Include child sessions (default: true)"),
|
|
25819
|
-
limit: z36.number().min(1).max(500).optional().describe("Max invocations to return in detail view (default:
|
|
26148
|
+
limit: z36.number().min(1).max(500).optional().describe("Max invocations to return in detail view (default: 50)")
|
|
25820
26149
|
},
|
|
25821
26150
|
async (args) => {
|
|
25822
26151
|
const stateDb2 = getStateDb4();
|
|
@@ -26178,6 +26507,25 @@ function getLearningReport(stateDb2, entityCount, linkCount, daysBack = 7, compa
|
|
|
26178
26507
|
funnel: queryFunnel(stateDb2, bounds.start, bounds.end, bounds.startMs, bounds.endMs),
|
|
26179
26508
|
graph: { link_count: linkCount, entity_count: entityCount }
|
|
26180
26509
|
};
|
|
26510
|
+
const sourceRows = stateDb2.db.prepare(`
|
|
26511
|
+
SELECT source,
|
|
26512
|
+
COUNT(*) as total_applied,
|
|
26513
|
+
SUM(CASE WHEN status = 'applied' THEN 1 ELSE 0 END) as survived,
|
|
26514
|
+
SUM(CASE WHEN status = 'removed' THEN 1 ELSE 0 END) as removed
|
|
26515
|
+
FROM wikilink_applications
|
|
26516
|
+
WHERE applied_at >= ? AND applied_at <= ?
|
|
26517
|
+
GROUP BY source
|
|
26518
|
+
ORDER BY total_applied DESC
|
|
26519
|
+
`).all(bounds.start, bounds.end);
|
|
26520
|
+
if (sourceRows.length > 0) {
|
|
26521
|
+
report.source_breakdown = sourceRows.map((r) => ({
|
|
26522
|
+
source: r.source,
|
|
26523
|
+
total_applied: r.total_applied,
|
|
26524
|
+
survived: r.survived,
|
|
26525
|
+
removed: r.removed,
|
|
26526
|
+
survival_rate: r.total_applied > 0 ? r.survived / r.total_applied : null
|
|
26527
|
+
}));
|
|
26528
|
+
}
|
|
26181
26529
|
const toolSelection = getToolSelectionReport(stateDb2, daysBack);
|
|
26182
26530
|
if (toolSelection) {
|
|
26183
26531
|
report.tool_selection = toolSelection;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
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",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@huggingface/transformers": "^3.8.1",
|
|
57
57
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
58
|
-
"@velvetmonkey/vault-core": "^2.4.
|
|
58
|
+
"@velvetmonkey/vault-core": "^2.4.3",
|
|
59
59
|
"better-sqlite3": "^12.0.0",
|
|
60
60
|
"chokidar": "^4.0.0",
|
|
61
61
|
"gray-matter": "^4.0.3",
|