@velvetmonkey/flywheel-memory 2.0.134 → 2.0.136
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 +268 -33
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3303,8 +3303,10 @@ __export(wikilinks_exports, {
|
|
|
3303
3303
|
initializeEntityIndex: () => initializeEntityIndex,
|
|
3304
3304
|
isEntityIndexReady: () => isEntityIndexReady,
|
|
3305
3305
|
isLikelyArticleTitle: () => isLikelyArticleTitle,
|
|
3306
|
+
isValidWikilinkText: () => isValidWikilinkText,
|
|
3306
3307
|
maybeApplyWikilinks: () => maybeApplyWikilinks,
|
|
3307
3308
|
processWikilinks: () => processWikilinks,
|
|
3309
|
+
sanitizeWikilinks: () => sanitizeWikilinks,
|
|
3308
3310
|
setCooccurrenceIndex: () => setCooccurrenceIndex,
|
|
3309
3311
|
setWikilinkConfig: () => setWikilinkConfig,
|
|
3310
3312
|
setWriteStateDb: () => setWriteStateDb,
|
|
@@ -3473,6 +3475,35 @@ function sortEntitiesByPriority(entities, notePath) {
|
|
|
3473
3475
|
return priorityB - priorityA;
|
|
3474
3476
|
});
|
|
3475
3477
|
}
|
|
3478
|
+
function isValidWikilinkText(text) {
|
|
3479
|
+
const target = text.includes("|") ? text.split("|")[0] : text;
|
|
3480
|
+
if (target !== target.trim()) return false;
|
|
3481
|
+
const trimmed = target.trim();
|
|
3482
|
+
if (trimmed.length === 0) return false;
|
|
3483
|
+
if (/[?!;]/.test(trimmed)) return false;
|
|
3484
|
+
if (/[,.]$/.test(trimmed)) return false;
|
|
3485
|
+
if (trimmed.includes(">")) return false;
|
|
3486
|
+
if (/^[*#\-]/.test(trimmed)) return false;
|
|
3487
|
+
if (trimmed.length > 60) return false;
|
|
3488
|
+
const words = trimmed.split(/\s+/);
|
|
3489
|
+
if (words.length > 5) return false;
|
|
3490
|
+
if (words.length > 3 && trimmed === trimmed.toLowerCase()) return false;
|
|
3491
|
+
if (words.length > 2 && /\w'\w/.test(trimmed)) return false;
|
|
3492
|
+
if (words.length > 3 && /(?:ing|tion|ment|ness|ould|ould|ight)$/i.test(words[words.length - 1])) return false;
|
|
3493
|
+
return true;
|
|
3494
|
+
}
|
|
3495
|
+
function sanitizeWikilinks(content) {
|
|
3496
|
+
const removed = [];
|
|
3497
|
+
const sanitized = content.replace(/\[\[([^\]]+?)\]\]/g, (fullMatch, inner) => {
|
|
3498
|
+
if (isValidWikilinkText(inner)) {
|
|
3499
|
+
return fullMatch;
|
|
3500
|
+
}
|
|
3501
|
+
removed.push(inner);
|
|
3502
|
+
const display = inner.includes("|") ? inner.split("|")[1] : inner;
|
|
3503
|
+
return display;
|
|
3504
|
+
});
|
|
3505
|
+
return { content: sanitized, removed };
|
|
3506
|
+
}
|
|
3476
3507
|
function processWikilinks(content, notePath, existingContent) {
|
|
3477
3508
|
if (!isEntityIndexReady() || !entityIndex) {
|
|
3478
3509
|
return {
|
|
@@ -3514,8 +3545,9 @@ function processWikilinks(content, notePath, existingContent) {
|
|
|
3514
3545
|
caseInsensitive: true,
|
|
3515
3546
|
alreadyLinked: step1LinkedEntities
|
|
3516
3547
|
});
|
|
3548
|
+
const wordCount = content.split(/\s+/).length;
|
|
3517
3549
|
const cfg = getConfig();
|
|
3518
|
-
const implicitEnabled = cfg?.implicit_detection !== false;
|
|
3550
|
+
const implicitEnabled = cfg?.implicit_detection !== false && wordCount <= 500;
|
|
3519
3551
|
const validPatterns = new Set(ALL_IMPLICIT_PATTERNS);
|
|
3520
3552
|
const implicitPatterns = cfg?.implicit_patterns?.length ? cfg.implicit_patterns.filter((p) => validPatterns.has(p)) : [...ALL_IMPLICIT_PATTERNS];
|
|
3521
3553
|
const implicitMatches = detectImplicitEntities(result.content, {
|
|
@@ -3551,23 +3583,22 @@ function processWikilinks(content, notePath, existingContent) {
|
|
|
3551
3583
|
}
|
|
3552
3584
|
}
|
|
3553
3585
|
newImplicits = nonOverlapping;
|
|
3586
|
+
let finalContent = result.content;
|
|
3587
|
+
let implicitEntities;
|
|
3554
3588
|
if (newImplicits.length > 0) {
|
|
3555
|
-
let processedContent = result.content;
|
|
3556
3589
|
for (let i = newImplicits.length - 1; i >= 0; i--) {
|
|
3557
3590
|
const m = newImplicits[i];
|
|
3558
|
-
|
|
3591
|
+
finalContent = finalContent.slice(0, m.start) + `[[${m.text}]]` + finalContent.slice(m.end);
|
|
3559
3592
|
}
|
|
3560
|
-
|
|
3561
|
-
content: processedContent,
|
|
3562
|
-
linksAdded: resolved.linksAdded + result.linksAdded + newImplicits.length,
|
|
3563
|
-
linkedEntities: [...resolved.linkedEntities, ...result.linkedEntities],
|
|
3564
|
-
implicitEntities: newImplicits.map((m) => m.text)
|
|
3565
|
-
};
|
|
3593
|
+
implicitEntities = newImplicits.map((m) => m.text);
|
|
3566
3594
|
}
|
|
3595
|
+
const { content: sanitizedContent, removed } = sanitizeWikilinks(finalContent);
|
|
3596
|
+
const totalLinksAdded = resolved.linksAdded + result.linksAdded + (newImplicits.length - removed.length);
|
|
3567
3597
|
return {
|
|
3568
|
-
content:
|
|
3569
|
-
linksAdded:
|
|
3570
|
-
linkedEntities: [...resolved.linkedEntities, ...result.linkedEntities]
|
|
3598
|
+
content: sanitizedContent,
|
|
3599
|
+
linksAdded: Math.max(0, totalLinksAdded),
|
|
3600
|
+
linkedEntities: [...resolved.linkedEntities, ...result.linkedEntities],
|
|
3601
|
+
...implicitEntities ? { implicitEntities } : {}
|
|
3571
3602
|
};
|
|
3572
3603
|
}
|
|
3573
3604
|
function maybeApplyWikilinks(content, skipWikilinks, notePath, existingContent) {
|
|
@@ -4545,6 +4576,125 @@ var init_wikilinks = __esm({
|
|
|
4545
4576
|
}
|
|
4546
4577
|
});
|
|
4547
4578
|
|
|
4579
|
+
// src/core/write/proactiveQueue.ts
|
|
4580
|
+
var proactiveQueue_exports = {};
|
|
4581
|
+
__export(proactiveQueue_exports, {
|
|
4582
|
+
drainProactiveQueue: () => drainProactiveQueue,
|
|
4583
|
+
enqueueProactiveSuggestions: () => enqueueProactiveSuggestions,
|
|
4584
|
+
expireStaleEntries: () => expireStaleEntries
|
|
4585
|
+
});
|
|
4586
|
+
function enqueueProactiveSuggestions(stateDb2, entries) {
|
|
4587
|
+
if (entries.length === 0) return 0;
|
|
4588
|
+
const now = Date.now();
|
|
4589
|
+
const expiresAt = now + QUEUE_TTL_MS;
|
|
4590
|
+
const upsert = stateDb2.db.prepare(`
|
|
4591
|
+
INSERT INTO proactive_queue (note_path, entity, score, confidence, queued_at, expires_at, status)
|
|
4592
|
+
VALUES (?, ?, ?, ?, ?, ?, 'pending')
|
|
4593
|
+
ON CONFLICT(note_path, entity) DO UPDATE SET
|
|
4594
|
+
score = CASE WHEN excluded.score > proactive_queue.score THEN excluded.score ELSE proactive_queue.score END,
|
|
4595
|
+
confidence = excluded.confidence,
|
|
4596
|
+
queued_at = excluded.queued_at,
|
|
4597
|
+
expires_at = excluded.expires_at,
|
|
4598
|
+
status = 'pending'
|
|
4599
|
+
WHERE proactive_queue.status = 'pending'
|
|
4600
|
+
`);
|
|
4601
|
+
let enqueued = 0;
|
|
4602
|
+
for (const entry of entries) {
|
|
4603
|
+
try {
|
|
4604
|
+
const result = upsert.run(entry.notePath, entry.entity, entry.score, entry.confidence, now, expiresAt);
|
|
4605
|
+
if (result.changes > 0) enqueued++;
|
|
4606
|
+
} catch {
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
return enqueued;
|
|
4610
|
+
}
|
|
4611
|
+
async function drainProactiveQueue(stateDb2, vaultPath2, currentBatchPaths, config, applyFn) {
|
|
4612
|
+
const result = {
|
|
4613
|
+
applied: [],
|
|
4614
|
+
expired: 0,
|
|
4615
|
+
skippedActiveEdit: 0,
|
|
4616
|
+
skippedMtimeGuard: 0,
|
|
4617
|
+
skippedDailyCap: 0
|
|
4618
|
+
};
|
|
4619
|
+
result.expired = expireStaleEntries(stateDb2);
|
|
4620
|
+
const now = Date.now();
|
|
4621
|
+
const pending = stateDb2.db.prepare(`
|
|
4622
|
+
SELECT note_path, entity, score, confidence
|
|
4623
|
+
FROM proactive_queue
|
|
4624
|
+
WHERE status = 'pending' AND expires_at > ?
|
|
4625
|
+
ORDER BY note_path, score DESC
|
|
4626
|
+
`).all(now);
|
|
4627
|
+
if (pending.length === 0) return result;
|
|
4628
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
4629
|
+
for (const row of pending) {
|
|
4630
|
+
if (!byFile.has(row.note_path)) byFile.set(row.note_path, []);
|
|
4631
|
+
byFile.get(row.note_path).push({ entity: row.entity, score: row.score, confidence: row.confidence });
|
|
4632
|
+
}
|
|
4633
|
+
const markApplied = stateDb2.db.prepare(
|
|
4634
|
+
`UPDATE proactive_queue SET status = 'applied', applied_at = ? WHERE note_path = ? AND entity = ? AND status = 'pending'`
|
|
4635
|
+
);
|
|
4636
|
+
const todayMidnight = /* @__PURE__ */ new Date();
|
|
4637
|
+
todayMidnight.setHours(0, 0, 0, 0);
|
|
4638
|
+
const todayStr = todayMidnight.toISOString().slice(0, 10);
|
|
4639
|
+
const countTodayApplied = stateDb2.db.prepare(
|
|
4640
|
+
`SELECT COUNT(*) as cnt FROM wikilink_applications WHERE note_path = ? AND applied_at >= ?`
|
|
4641
|
+
);
|
|
4642
|
+
for (const [filePath, suggestions] of byFile) {
|
|
4643
|
+
if (currentBatchPaths.has(filePath)) {
|
|
4644
|
+
result.skippedActiveEdit += suggestions.length;
|
|
4645
|
+
continue;
|
|
4646
|
+
}
|
|
4647
|
+
const todayCount = countTodayApplied.get(filePath, todayStr).cnt;
|
|
4648
|
+
if (todayCount >= config.maxPerDay) {
|
|
4649
|
+
result.skippedDailyCap += suggestions.length;
|
|
4650
|
+
for (const s of suggestions) {
|
|
4651
|
+
try {
|
|
4652
|
+
stateDb2.db.prepare(
|
|
4653
|
+
`UPDATE proactive_queue SET status = 'expired' WHERE note_path = ? AND entity = ? AND status = 'pending'`
|
|
4654
|
+
).run(filePath, s.entity);
|
|
4655
|
+
} catch {
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
continue;
|
|
4659
|
+
}
|
|
4660
|
+
const remaining = config.maxPerDay - todayCount;
|
|
4661
|
+
const capped = suggestions.slice(0, remaining);
|
|
4662
|
+
try {
|
|
4663
|
+
const applyResult = await applyFn(filePath, vaultPath2, capped, config);
|
|
4664
|
+
if (applyResult.applied.length > 0) {
|
|
4665
|
+
result.applied.push({ file: filePath, entities: applyResult.applied });
|
|
4666
|
+
const appliedAt = Date.now();
|
|
4667
|
+
for (const entity of applyResult.applied) {
|
|
4668
|
+
try {
|
|
4669
|
+
markApplied.run(appliedAt, filePath, entity);
|
|
4670
|
+
} catch {
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
if (applyResult.applied.length === 0 && applyResult.skipped.length > 0) {
|
|
4675
|
+
result.skippedMtimeGuard += applyResult.skipped.length;
|
|
4676
|
+
}
|
|
4677
|
+
} catch (e) {
|
|
4678
|
+
serverLog("watcher", `Proactive drain: error applying to ${filePath}: ${e}`, "error");
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
return result;
|
|
4682
|
+
}
|
|
4683
|
+
function expireStaleEntries(stateDb2) {
|
|
4684
|
+
const result = stateDb2.db.prepare(
|
|
4685
|
+
`UPDATE proactive_queue SET status = 'expired' WHERE status = 'pending' AND expires_at <= ?`
|
|
4686
|
+
).run(Date.now());
|
|
4687
|
+
return result.changes;
|
|
4688
|
+
}
|
|
4689
|
+
var QUEUE_TTL_MS;
|
|
4690
|
+
var init_proactiveQueue = __esm({
|
|
4691
|
+
"src/core/write/proactiveQueue.ts"() {
|
|
4692
|
+
"use strict";
|
|
4693
|
+
init_serverLog();
|
|
4694
|
+
QUEUE_TTL_MS = 60 * 60 * 1e3;
|
|
4695
|
+
}
|
|
4696
|
+
});
|
|
4697
|
+
|
|
4548
4698
|
// src/core/write/constants.ts
|
|
4549
4699
|
function estimateTokens(content) {
|
|
4550
4700
|
const str = typeof content === "string" ? content : JSON.stringify(content);
|
|
@@ -7908,6 +8058,7 @@ async function exportHubScores(vaultIndex2, stateDb2) {
|
|
|
7908
8058
|
init_recency();
|
|
7909
8059
|
init_cooccurrence();
|
|
7910
8060
|
init_wikilinks();
|
|
8061
|
+
init_proactiveQueue();
|
|
7911
8062
|
init_retrievalCooccurrence();
|
|
7912
8063
|
init_embeddings();
|
|
7913
8064
|
|
|
@@ -8305,6 +8456,7 @@ var PipelineRunner = class {
|
|
|
8305
8456
|
async run() {
|
|
8306
8457
|
const { p, tracker } = this;
|
|
8307
8458
|
try {
|
|
8459
|
+
await runStep("drain_proactive_queue", tracker, {}, () => this.drainQueue());
|
|
8308
8460
|
await this.indexRebuild();
|
|
8309
8461
|
this.noteMoves();
|
|
8310
8462
|
await this.entityScan();
|
|
@@ -8929,33 +9081,61 @@ var PipelineRunner = class {
|
|
|
8929
9081
|
serverLog("watcher", `Suggestion scoring: ${this.suggestionResults.length} files scored`);
|
|
8930
9082
|
}
|
|
8931
9083
|
}
|
|
8932
|
-
// ── Step
|
|
9084
|
+
// ── Step 0.5: Drain proactive queue ──────────────────────────────
|
|
9085
|
+
async drainQueue() {
|
|
9086
|
+
const { p } = this;
|
|
9087
|
+
if (!p.sd || p.flywheelConfig?.proactive_linking === false) {
|
|
9088
|
+
return { skipped: true };
|
|
9089
|
+
}
|
|
9090
|
+
const currentBatchPaths = new Set(p.events.map((e) => e.path));
|
|
9091
|
+
const result = await drainProactiveQueue(
|
|
9092
|
+
p.sd,
|
|
9093
|
+
p.vp,
|
|
9094
|
+
currentBatchPaths,
|
|
9095
|
+
{
|
|
9096
|
+
minScore: p.flywheelConfig?.proactive_min_score ?? 20,
|
|
9097
|
+
maxPerFile: p.flywheelConfig?.proactive_max_per_file ?? 3,
|
|
9098
|
+
maxPerDay: p.flywheelConfig?.proactive_max_per_day ?? 10
|
|
9099
|
+
},
|
|
9100
|
+
applyProactiveSuggestions
|
|
9101
|
+
);
|
|
9102
|
+
const totalApplied = result.applied.reduce((s, r) => s + r.entities.length, 0);
|
|
9103
|
+
if (totalApplied > 0) {
|
|
9104
|
+
serverLog("watcher", `Proactive drain: applied ${totalApplied} links in ${result.applied.length} files`);
|
|
9105
|
+
}
|
|
9106
|
+
return {
|
|
9107
|
+
applied: result.applied,
|
|
9108
|
+
total_applied: totalApplied,
|
|
9109
|
+
expired: result.expired,
|
|
9110
|
+
skipped_active: result.skippedActiveEdit,
|
|
9111
|
+
skipped_mtime: result.skippedMtimeGuard,
|
|
9112
|
+
skipped_daily_cap: result.skippedDailyCap
|
|
9113
|
+
};
|
|
9114
|
+
}
|
|
9115
|
+
// ── Step 12.5: Proactive enqueue ───────────────────────────────────
|
|
8933
9116
|
async proactiveLinking() {
|
|
8934
9117
|
const { p, tracker } = this;
|
|
8935
9118
|
if (p.flywheelConfig?.proactive_linking === false || this.suggestionResults.length === 0) return;
|
|
8936
|
-
|
|
9119
|
+
if (!p.sd) return;
|
|
9120
|
+
tracker.start("proactive_enqueue", { files: this.suggestionResults.length });
|
|
8937
9121
|
try {
|
|
8938
|
-
const
|
|
9122
|
+
const minScore = p.flywheelConfig?.proactive_min_score ?? 20;
|
|
9123
|
+
const maxPerFile = p.flywheelConfig?.proactive_max_per_file ?? 3;
|
|
9124
|
+
const entries = [];
|
|
8939
9125
|
for (const { file, top } of this.suggestionResults) {
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
maxPerFile: p.flywheelConfig?.proactive_max_per_file ?? 3
|
|
8944
|
-
});
|
|
8945
|
-
if (result.applied.length > 0) {
|
|
8946
|
-
proactiveResults.push({ file, applied: result.applied });
|
|
8947
|
-
}
|
|
8948
|
-
} catch {
|
|
9126
|
+
const candidates = top.filter((s) => s.score >= minScore && s.confidence === "high").slice(0, maxPerFile);
|
|
9127
|
+
for (const c of candidates) {
|
|
9128
|
+
entries.push({ notePath: file, entity: c.entity, score: c.score, confidence: c.confidence });
|
|
8949
9129
|
}
|
|
8950
9130
|
}
|
|
8951
|
-
const
|
|
8952
|
-
tracker.end({
|
|
8953
|
-
if (
|
|
8954
|
-
serverLog("watcher", `Proactive
|
|
9131
|
+
const enqueued = enqueueProactiveSuggestions(p.sd, entries);
|
|
9132
|
+
tracker.end({ enqueued, total_candidates: entries.length });
|
|
9133
|
+
if (enqueued > 0) {
|
|
9134
|
+
serverLog("watcher", `Proactive enqueue: ${enqueued} suggestions queued for deferred application`);
|
|
8955
9135
|
}
|
|
8956
9136
|
} catch (e) {
|
|
8957
9137
|
tracker.end({ error: String(e) });
|
|
8958
|
-
serverLog("watcher", `Proactive
|
|
9138
|
+
serverLog("watcher", `Proactive enqueue failed: ${e}`, "error");
|
|
8959
9139
|
}
|
|
8960
9140
|
}
|
|
8961
9141
|
// ── Step 13: Tag scan ─────────────────────────────────────────────
|
|
@@ -10081,6 +10261,45 @@ function buildGraphData(index, stateDb2, options) {
|
|
|
10081
10261
|
}
|
|
10082
10262
|
}
|
|
10083
10263
|
}
|
|
10264
|
+
if (options.center_entity) {
|
|
10265
|
+
const centerLower = options.center_entity.toLowerCase();
|
|
10266
|
+
const maxDepth = options.depth ?? 1;
|
|
10267
|
+
const centerNode = nodes.find((n) => n.label.toLowerCase() === centerLower);
|
|
10268
|
+
if (centerNode) {
|
|
10269
|
+
const adj = /* @__PURE__ */ new Map();
|
|
10270
|
+
for (const edge of edges) {
|
|
10271
|
+
if (!adj.has(edge.source)) adj.set(edge.source, /* @__PURE__ */ new Set());
|
|
10272
|
+
if (!adj.has(edge.target)) adj.set(edge.target, /* @__PURE__ */ new Set());
|
|
10273
|
+
adj.get(edge.source).add(edge.target);
|
|
10274
|
+
adj.get(edge.target).add(edge.source);
|
|
10275
|
+
}
|
|
10276
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
10277
|
+
const queue = [{ id: centerNode.id, depth: 0 }];
|
|
10278
|
+
reachable.add(centerNode.id);
|
|
10279
|
+
while (queue.length > 0) {
|
|
10280
|
+
const { id, depth } = queue.shift();
|
|
10281
|
+
if (depth >= maxDepth) continue;
|
|
10282
|
+
for (const neighbor of adj.get(id) ?? []) {
|
|
10283
|
+
if (!reachable.has(neighbor)) {
|
|
10284
|
+
reachable.add(neighbor);
|
|
10285
|
+
queue.push({ id: neighbor, depth: depth + 1 });
|
|
10286
|
+
}
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
10289
|
+
const filteredNodes = nodes.filter((n) => reachable.has(n.id));
|
|
10290
|
+
const filteredEdges = edges.filter((e) => reachable.has(e.source) && reachable.has(e.target));
|
|
10291
|
+
return {
|
|
10292
|
+
nodes: filteredNodes,
|
|
10293
|
+
edges: filteredEdges,
|
|
10294
|
+
metadata: {
|
|
10295
|
+
note_count: filteredNodes.filter((n) => n.type === "note").length,
|
|
10296
|
+
entity_count: filteredNodes.filter((n) => n.type === "entity").length,
|
|
10297
|
+
edge_count: filteredEdges.length,
|
|
10298
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
10299
|
+
}
|
|
10300
|
+
};
|
|
10301
|
+
}
|
|
10302
|
+
}
|
|
10084
10303
|
return {
|
|
10085
10304
|
nodes,
|
|
10086
10305
|
edges,
|
|
@@ -10152,17 +10371,19 @@ function toGraphML(data) {
|
|
|
10152
10371
|
function registerGraphExportTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
10153
10372
|
server2.tool(
|
|
10154
10373
|
"export_graph",
|
|
10155
|
-
|
|
10374
|
+
'Export the vault knowledge graph as GraphML (for Gephi/yEd/Cytoscape) or JSON. Includes notes, entities, wikilinks, edge weights, and co-occurrence relationships. Use center_entity + depth for focused ego-network exports (e.g., "everything within 2 hops of Acme Corp").',
|
|
10156
10375
|
{
|
|
10157
10376
|
format: z2.enum(["graphml", "json"]).default("graphml").describe('Output format: "graphml" for graph tools (Gephi, yEd, Cytoscape), "json" for programmatic use'),
|
|
10158
10377
|
include_cooccurrence: z2.boolean().default(true).describe("Include co-occurrence edges between entities"),
|
|
10159
|
-
min_edge_weight: z2.number().default(0).describe("Minimum edge weight threshold (filters weighted edges)")
|
|
10378
|
+
min_edge_weight: z2.number().default(0).describe("Minimum edge weight threshold (filters weighted edges)"),
|
|
10379
|
+
center_entity: z2.string().optional().describe("Center the export on this entity (ego network). Only includes nodes within `depth` hops."),
|
|
10380
|
+
depth: z2.number().default(1).describe("Hops from center_entity to include (default 1). Ignored without center_entity.")
|
|
10160
10381
|
},
|
|
10161
|
-
async ({ format, include_cooccurrence, min_edge_weight }) => {
|
|
10382
|
+
async ({ format, include_cooccurrence, min_edge_weight, center_entity, depth }) => {
|
|
10162
10383
|
requireIndex();
|
|
10163
10384
|
const index = getIndex();
|
|
10164
10385
|
const stateDb2 = getStateDb2?.() ?? null;
|
|
10165
|
-
const data = buildGraphData(index, stateDb2, { include_cooccurrence, min_edge_weight });
|
|
10386
|
+
const data = buildGraphData(index, stateDb2, { include_cooccurrence, min_edge_weight, center_entity, depth });
|
|
10166
10387
|
let output;
|
|
10167
10388
|
if (format === "json") {
|
|
10168
10389
|
output = JSON.stringify(data, null, 2);
|
|
@@ -24236,6 +24457,9 @@ async function runPostIndexWork(ctx) {
|
|
|
24236
24457
|
const renameWikilinkApplications = sd.db.prepare(
|
|
24237
24458
|
"UPDATE wikilink_applications SET note_path = ? WHERE note_path = ?"
|
|
24238
24459
|
);
|
|
24460
|
+
const renameProactiveQueue = sd.db.prepare(
|
|
24461
|
+
"UPDATE proactive_queue SET note_path = ? WHERE note_path = ? AND status = 'pending'"
|
|
24462
|
+
);
|
|
24239
24463
|
for (const rename of batchRenames) {
|
|
24240
24464
|
const oldFolder = rename.oldPath.includes("/") ? rename.oldPath.split("/").slice(0, -1).join("/") : "";
|
|
24241
24465
|
const newFolder = rename.newPath.includes("/") ? rename.newPath.split("/").slice(0, -1).join("/") : "";
|
|
@@ -24244,6 +24468,7 @@ async function runPostIndexWork(ctx) {
|
|
|
24244
24468
|
renameNoteTags.run(rename.newPath, rename.oldPath);
|
|
24245
24469
|
renameNoteLinkHistory.run(rename.newPath, rename.oldPath);
|
|
24246
24470
|
renameWikilinkApplications.run(rename.newPath, rename.oldPath);
|
|
24471
|
+
renameProactiveQueue.run(rename.newPath, rename.oldPath);
|
|
24247
24472
|
const oldHash = lastContentHashes.get(rename.oldPath);
|
|
24248
24473
|
if (oldHash !== void 0) {
|
|
24249
24474
|
lastContentHashes.set(rename.newPath, oldHash);
|
|
@@ -24313,6 +24538,16 @@ async function runPostIndexWork(ctx) {
|
|
|
24313
24538
|
}
|
|
24314
24539
|
}
|
|
24315
24540
|
}
|
|
24541
|
+
if (sd) {
|
|
24542
|
+
try {
|
|
24543
|
+
const { expireStaleEntries: expireStaleEntries2 } = await Promise.resolve().then(() => (init_proactiveQueue(), proactiveQueue_exports));
|
|
24544
|
+
const expired = expireStaleEntries2(sd);
|
|
24545
|
+
if (expired > 0) {
|
|
24546
|
+
serverLog("watcher", `Startup: expired ${expired} stale proactive queue entries`);
|
|
24547
|
+
}
|
|
24548
|
+
} catch {
|
|
24549
|
+
}
|
|
24550
|
+
}
|
|
24316
24551
|
watcher.start();
|
|
24317
24552
|
serverLog("watcher", "File watcher started");
|
|
24318
24553
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.136",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 69 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
56
|
-
"@velvetmonkey/vault-core": "2.0.
|
|
56
|
+
"@velvetmonkey/vault-core": "^2.0.136",
|
|
57
57
|
"better-sqlite3": "^11.0.0",
|
|
58
58
|
"chokidar": "^4.0.0",
|
|
59
59
|
"gray-matter": "^4.0.3",
|