@velvetmonkey/flywheel-memory 2.5.8 → 2.5.10
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 +657 -316
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2672,10 +2672,10 @@ function isLockContentionError(error) {
|
|
|
2672
2672
|
function sleep(ms) {
|
|
2673
2673
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2674
2674
|
}
|
|
2675
|
-
function calculateDelay(attempt,
|
|
2676
|
-
let delay =
|
|
2677
|
-
delay = Math.min(delay,
|
|
2678
|
-
if (
|
|
2675
|
+
function calculateDelay(attempt, config2) {
|
|
2676
|
+
let delay = config2.baseDelayMs * Math.pow(2, attempt);
|
|
2677
|
+
delay = Math.min(delay, config2.maxDelayMs);
|
|
2678
|
+
if (config2.jitter) {
|
|
2679
2679
|
delay = delay + Math.random() * delay * 0.5;
|
|
2680
2680
|
}
|
|
2681
2681
|
return Math.round(delay);
|
|
@@ -3569,8 +3569,8 @@ function setWriteStateDb(stateDb2) {
|
|
|
3569
3569
|
function getWriteStateDb() {
|
|
3570
3570
|
return getActiveScopeOrNull()?.stateDb ?? moduleStateDb5;
|
|
3571
3571
|
}
|
|
3572
|
-
function setWikilinkConfig(
|
|
3573
|
-
moduleConfig =
|
|
3572
|
+
function setWikilinkConfig(config2) {
|
|
3573
|
+
moduleConfig = config2;
|
|
3574
3574
|
}
|
|
3575
3575
|
function getConfig() {
|
|
3576
3576
|
const scope = getActiveScopeOrNull();
|
|
@@ -3938,9 +3938,9 @@ function isCommonWordFalsePositive(entityName, rawContent, category) {
|
|
|
3938
3938
|
if (!IMPLICIT_EXCLUDE_WORDS.has(lowerName) && !COMMON_ENGLISH_WORDS.has(lowerName)) return false;
|
|
3939
3939
|
return !rawContent.includes(entityName);
|
|
3940
3940
|
}
|
|
3941
|
-
function capScoreWithoutContentRelevance(score, contentRelevance,
|
|
3942
|
-
if (contentRelevance <
|
|
3943
|
-
return Math.min(score,
|
|
3941
|
+
function capScoreWithoutContentRelevance(score, contentRelevance, config2) {
|
|
3942
|
+
if (contentRelevance < config2.contentRelevanceFloor) {
|
|
3943
|
+
return Math.min(score, config2.noRelevanceCap);
|
|
3944
3944
|
}
|
|
3945
3945
|
return score;
|
|
3946
3946
|
}
|
|
@@ -3988,7 +3988,7 @@ function getAdaptiveMinScore(contentLength, baseScore) {
|
|
|
3988
3988
|
}
|
|
3989
3989
|
return baseScore;
|
|
3990
3990
|
}
|
|
3991
|
-
function scoreNameAgainstContent(name, contentTokens, contentStems,
|
|
3991
|
+
function scoreNameAgainstContent(name, contentTokens, contentStems, config2, coocIndex, disableExact, disableStem) {
|
|
3992
3992
|
const nameTokens = tokenize(name);
|
|
3993
3993
|
if (nameTokens.length === 0) {
|
|
3994
3994
|
return { exactScore: 0, stemScore: 0, lexicalScore: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0, nameTokens: [], unmatchedTokenIndices: [] };
|
|
@@ -4004,11 +4004,11 @@ function scoreNameAgainstContent(name, contentTokens, contentStems, config, cooc
|
|
|
4004
4004
|
const nameStem = nameStems[i];
|
|
4005
4005
|
const idfWeight = coocIndex ? tokenIdf(token, coocIndex) : 1;
|
|
4006
4006
|
if (!disableExact && contentTokens.has(token)) {
|
|
4007
|
-
exactScore +=
|
|
4007
|
+
exactScore += config2.exactMatchBonus * idfWeight;
|
|
4008
4008
|
matchedWords++;
|
|
4009
4009
|
exactMatches++;
|
|
4010
4010
|
} else if (!disableStem && contentStems.has(nameStem)) {
|
|
4011
|
-
stemScore +=
|
|
4011
|
+
stemScore += config2.stemMatchBonus * idfWeight;
|
|
4012
4012
|
matchedWords++;
|
|
4013
4013
|
} else {
|
|
4014
4014
|
unmatchedTokenIndices.push(i);
|
|
@@ -4017,7 +4017,7 @@ function scoreNameAgainstContent(name, contentTokens, contentStems, config, cooc
|
|
|
4017
4017
|
const lexicalScore = Math.round((exactScore + stemScore) * 10) / 10;
|
|
4018
4018
|
return { exactScore, stemScore, lexicalScore, matchedWords, exactMatches, totalTokens: nameTokens.length, nameTokens, unmatchedTokenIndices };
|
|
4019
4019
|
}
|
|
4020
|
-
function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
|
|
4020
|
+
function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms, config2, disabled, coocIndex, tokenFuzzyCache) {
|
|
4021
4021
|
const zero = { contentMatch: 0, fuzzyMatch: 0, totalLexical: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0 };
|
|
4022
4022
|
const entityName = getEntityName2(entity);
|
|
4023
4023
|
const aliases = getEntityAliases(entity);
|
|
@@ -4026,10 +4026,10 @@ function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
|
|
|
4026
4026
|
const disableFuzzy = disabled.has("fuzzy_match");
|
|
4027
4027
|
const cache = tokenFuzzyCache ?? /* @__PURE__ */ new Map();
|
|
4028
4028
|
const idfFn = (token) => coocIndex ? tokenIdf(token, coocIndex) : 1;
|
|
4029
|
-
const nameResult = scoreNameAgainstContent(entityName, contentTokens, contentStems,
|
|
4029
|
+
const nameResult = scoreNameAgainstContent(entityName, contentTokens, contentStems, config2, coocIndex, disableExact, disableStem);
|
|
4030
4030
|
let bestAliasResult = { exactScore: 0, stemScore: 0, lexicalScore: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0, nameTokens: [], unmatchedTokenIndices: [] };
|
|
4031
4031
|
for (const alias of aliases) {
|
|
4032
|
-
const aliasResult = scoreNameAgainstContent(alias, contentTokens, contentStems,
|
|
4032
|
+
const aliasResult = scoreNameAgainstContent(alias, contentTokens, contentStems, config2, coocIndex, disableExact, disableStem);
|
|
4033
4033
|
if (aliasResult.lexicalScore > bestAliasResult.lexicalScore) {
|
|
4034
4034
|
bestAliasResult = aliasResult;
|
|
4035
4035
|
}
|
|
@@ -4056,7 +4056,7 @@ function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
|
|
|
4056
4056
|
contentTokens,
|
|
4057
4057
|
collapsedContentTerms,
|
|
4058
4058
|
fuzzyTargetName,
|
|
4059
|
-
|
|
4059
|
+
config2.fuzzyMatchBonus,
|
|
4060
4060
|
idfFn,
|
|
4061
4061
|
cache
|
|
4062
4062
|
);
|
|
@@ -4070,11 +4070,11 @@ function scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
|
|
|
4070
4070
|
}
|
|
4071
4071
|
if (totalTokens > 1) {
|
|
4072
4072
|
const matchRatio = matchedWords / totalTokens;
|
|
4073
|
-
if (matchRatio <
|
|
4073
|
+
if (matchRatio < config2.minMatchRatio) {
|
|
4074
4074
|
return zero;
|
|
4075
4075
|
}
|
|
4076
4076
|
}
|
|
4077
|
-
if (
|
|
4077
|
+
if (config2.requireMultipleMatches && totalTokens === 1) {
|
|
4078
4078
|
if (exactMatches === 0 && fuzzyMatchedWords === 0) {
|
|
4079
4079
|
return zero;
|
|
4080
4080
|
}
|
|
@@ -4105,8 +4105,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4105
4105
|
disabledLayers = []
|
|
4106
4106
|
} = options;
|
|
4107
4107
|
const disabled = new Set(disabledLayers);
|
|
4108
|
-
const
|
|
4109
|
-
const adaptiveMinScore = getAdaptiveMinScore(content.length,
|
|
4108
|
+
const config2 = STRICTNESS_CONFIGS[strictness];
|
|
4109
|
+
const adaptiveMinScore = getAdaptiveMinScore(content.length, config2.minSuggestionScore);
|
|
4110
4110
|
const noteContext = notePath ? getNoteContext(notePath) : "general";
|
|
4111
4111
|
const contextBoosts = CONTEXT_BOOST[noteContext];
|
|
4112
4112
|
const emptyResult = { suggestions: [], suffix: "" };
|
|
@@ -4128,7 +4128,7 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4128
4128
|
const contentTokens = /* @__PURE__ */ new Set();
|
|
4129
4129
|
const contentStems = /* @__PURE__ */ new Set();
|
|
4130
4130
|
for (const token of rawTokens) {
|
|
4131
|
-
if (token.length >=
|
|
4131
|
+
if (token.length >= config2.minWordLength && !STOPWORDS_EN2.has(token)) {
|
|
4132
4132
|
contentTokens.add(token);
|
|
4133
4133
|
contentStems.add(stem(token));
|
|
4134
4134
|
}
|
|
@@ -4136,7 +4136,7 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4136
4136
|
if (contentTokens.size === 0) {
|
|
4137
4137
|
return emptyResult;
|
|
4138
4138
|
}
|
|
4139
|
-
const orderedContentTokens = [...rawTokens].filter((token) => token.length >=
|
|
4139
|
+
const orderedContentTokens = [...rawTokens].filter((token) => token.length >= config2.minWordLength && !STOPWORDS_EN2.has(token)).map(normalizeFuzzyTerm).filter((token) => token.length > 0);
|
|
4140
4140
|
const collapsedContentTerms = disabled.has("fuzzy_match") ? /* @__PURE__ */ new Set() : buildCollapsedContentTerms(orderedContentTokens);
|
|
4141
4141
|
const tokenFuzzyCache = /* @__PURE__ */ new Map();
|
|
4142
4142
|
const linkedEntities = excludeLinked ? extractLinkedEntities(content) : /* @__PURE__ */ new Set();
|
|
@@ -4167,7 +4167,7 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4167
4167
|
const paths = correctedPairs.get(entityName.toLowerCase());
|
|
4168
4168
|
if (paths.has(notePath)) continue;
|
|
4169
4169
|
}
|
|
4170
|
-
const entityScore = disabled.has("exact_match") && disabled.has("stem_match") && disabled.has("fuzzy_match") ? { contentMatch: 0, fuzzyMatch: 0, totalLexical: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0 } : scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms,
|
|
4170
|
+
const entityScore = disabled.has("exact_match") && disabled.has("stem_match") && disabled.has("fuzzy_match") ? { contentMatch: 0, fuzzyMatch: 0, totalLexical: 0, matchedWords: 0, exactMatches: 0, totalTokens: 0 } : scoreEntity(entity, contentTokens, contentStems, collapsedContentTerms, config2, disabled, cooccurrenceIndex, tokenFuzzyCache);
|
|
4171
4171
|
const contentScore = entityScore.contentMatch;
|
|
4172
4172
|
const fuzzyMatchScore = entityScore.fuzzyMatch;
|
|
4173
4173
|
const hasLexicalEvidence = entityScore.totalLexical > 0;
|
|
@@ -4204,7 +4204,7 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4204
4204
|
}
|
|
4205
4205
|
const layerSuppressionPenalty = disabled.has("feedback") ? 0 : suppressionPenalties.get(entityName) ?? 0;
|
|
4206
4206
|
score += layerSuppressionPenalty;
|
|
4207
|
-
score = capScoreWithoutContentRelevance(score, contentScore + fuzzyMatchScore,
|
|
4207
|
+
score = capScoreWithoutContentRelevance(score, contentScore + fuzzyMatchScore, config2);
|
|
4208
4208
|
if (hasLexicalEvidence && score >= adaptiveMinScore) {
|
|
4209
4209
|
scoredEntities.push({
|
|
4210
4210
|
name: entityName,
|
|
@@ -4271,13 +4271,13 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4271
4271
|
existing.score += boost;
|
|
4272
4272
|
existing.breakdown.cooccurrenceBoost += boost;
|
|
4273
4273
|
const existingContentRelevance = existing.breakdown.contentMatch + existing.breakdown.fuzzyMatch + (existing.breakdown.semanticBoost ?? 0);
|
|
4274
|
-
existing.score = capScoreWithoutContentRelevance(existing.score, existingContentRelevance,
|
|
4274
|
+
existing.score = capScoreWithoutContentRelevance(existing.score, existingContentRelevance, config2);
|
|
4275
4275
|
} else {
|
|
4276
4276
|
const entityTokens = tokenize(entityName);
|
|
4277
4277
|
const hasContentOverlap = entityTokens.some(
|
|
4278
4278
|
(token) => contentTokens.has(token) || contentStems.has(stem(token))
|
|
4279
4279
|
);
|
|
4280
|
-
const strongCooccurrence = boost >=
|
|
4280
|
+
const strongCooccurrence = boost >= config2.minCooccurrenceGate;
|
|
4281
4281
|
if (!hasContentOverlap && !strongCooccurrence) {
|
|
4282
4282
|
continue;
|
|
4283
4283
|
}
|
|
@@ -4297,7 +4297,7 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4297
4297
|
const suppPenalty = disabled.has("feedback") ? 0 : suppressionPenalties.get(entityName) ?? 0;
|
|
4298
4298
|
let totalBoost = boost + typeBoost + contextBoost + recencyBoostVal + crossFolderBoost + hubBoost + feedbackAdj + edgeWeightBoost + prospectBoost + suppPenalty;
|
|
4299
4299
|
const coocContentRelevance = hasContentOverlap ? 5 : 0;
|
|
4300
|
-
totalBoost = capScoreWithoutContentRelevance(totalBoost, coocContentRelevance,
|
|
4300
|
+
totalBoost = capScoreWithoutContentRelevance(totalBoost, coocContentRelevance, config2);
|
|
4301
4301
|
const effectiveMinScore = !hasContentOverlap ? Math.max(adaptiveMinScore, 7) : adaptiveMinScore;
|
|
4302
4302
|
if (totalBoost >= effectiveMinScore) {
|
|
4303
4303
|
scoredEntities.push({
|
|
@@ -4393,11 +4393,11 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4393
4393
|
}
|
|
4394
4394
|
for (const entry of scoredEntities) {
|
|
4395
4395
|
const contentRelevance = entry.breakdown.contentMatch + entry.breakdown.fuzzyMatch + (entry.breakdown.semanticBoost ?? 0);
|
|
4396
|
-
entry.score = capScoreWithoutContentRelevance(entry.score, contentRelevance,
|
|
4396
|
+
entry.score = capScoreWithoutContentRelevance(entry.score, contentRelevance, config2);
|
|
4397
4397
|
}
|
|
4398
4398
|
const relevantEntities = scoredEntities.filter((e) => {
|
|
4399
4399
|
if (!entitiesWithAnyScoringPath.has(e.name)) return false;
|
|
4400
|
-
if (
|
|
4400
|
+
if (config2.minContentMatch > 0 && e.breakdown.contentMatch < config2.minContentMatch) return false;
|
|
4401
4401
|
return true;
|
|
4402
4402
|
});
|
|
4403
4403
|
if (relevantEntities.length === 0) {
|
|
@@ -4664,9 +4664,9 @@ async function checkPreflightSimilarity(noteName) {
|
|
|
4664
4664
|
}
|
|
4665
4665
|
return result;
|
|
4666
4666
|
}
|
|
4667
|
-
async function applyProactiveSuggestions(filePath, vaultPath2, suggestions,
|
|
4667
|
+
async function applyProactiveSuggestions(filePath, vaultPath2, suggestions, config2) {
|
|
4668
4668
|
const stateDb2 = getWriteStateDb();
|
|
4669
|
-
const candidates = suggestions.filter((s) => s.score >=
|
|
4669
|
+
const candidates = suggestions.filter((s) => s.score >= config2.minScore && s.confidence === "high").slice(0, config2.maxPerFile);
|
|
4670
4670
|
if (candidates.length === 0) {
|
|
4671
4671
|
return { applied: [], skipped: [] };
|
|
4672
4672
|
}
|
|
@@ -4973,7 +4973,7 @@ function enqueueProactiveSuggestions(stateDb2, entries) {
|
|
|
4973
4973
|
}
|
|
4974
4974
|
return enqueued;
|
|
4975
4975
|
}
|
|
4976
|
-
async function drainProactiveQueue(stateDb2, vaultPath2,
|
|
4976
|
+
async function drainProactiveQueue(stateDb2, vaultPath2, config2, applyFn) {
|
|
4977
4977
|
const result = {
|
|
4978
4978
|
applied: [],
|
|
4979
4979
|
expired: 0,
|
|
@@ -5017,7 +5017,7 @@ async function drainProactiveQueue(stateDb2, vaultPath2, config, applyFn) {
|
|
|
5017
5017
|
continue;
|
|
5018
5018
|
}
|
|
5019
5019
|
const todayCount = countTodayApplied.get(filePath, todayStr).cnt;
|
|
5020
|
-
if (todayCount >=
|
|
5020
|
+
if (todayCount >= config2.maxPerDay) {
|
|
5021
5021
|
result.skippedDailyCap += suggestions.length;
|
|
5022
5022
|
for (const s of suggestions) {
|
|
5023
5023
|
try {
|
|
@@ -5029,10 +5029,10 @@ async function drainProactiveQueue(stateDb2, vaultPath2, config, applyFn) {
|
|
|
5029
5029
|
}
|
|
5030
5030
|
continue;
|
|
5031
5031
|
}
|
|
5032
|
-
const remaining =
|
|
5032
|
+
const remaining = config2.maxPerDay - todayCount;
|
|
5033
5033
|
const capped = suggestions.slice(0, remaining);
|
|
5034
5034
|
try {
|
|
5035
|
-
const applyResult = await applyFn(filePath, vaultPath2, capped,
|
|
5035
|
+
const applyResult = await applyFn(filePath, vaultPath2, capped, config2);
|
|
5036
5036
|
if (applyResult.applied.length > 0) {
|
|
5037
5037
|
result.applied.push({ file: filePath, entities: applyResult.applied });
|
|
5038
5038
|
const appliedAt = Date.now();
|
|
@@ -5283,6 +5283,9 @@ function findSection(content, sectionName) {
|
|
|
5283
5283
|
contentStartLine
|
|
5284
5284
|
};
|
|
5285
5285
|
}
|
|
5286
|
+
function isCodeFenceLine(line) {
|
|
5287
|
+
return line.trim().startsWith("```");
|
|
5288
|
+
}
|
|
5286
5289
|
function isPreformattedList(content) {
|
|
5287
5290
|
const trimmed = content.trim();
|
|
5288
5291
|
if (!trimmed) return false;
|
|
@@ -5350,9 +5353,11 @@ function formatContent(content, format) {
|
|
|
5350
5353
|
}
|
|
5351
5354
|
const sanitized = sanitizeForList(trimmed);
|
|
5352
5355
|
const lines = sanitized.split("\n");
|
|
5356
|
+
let inCodeBlock = false;
|
|
5353
5357
|
return lines.map((line, i) => {
|
|
5358
|
+
if (isCodeFenceLine(line)) inCodeBlock = !inCodeBlock;
|
|
5354
5359
|
if (i === 0) return `- ${line}`;
|
|
5355
|
-
if (line === "") return " ";
|
|
5360
|
+
if (line === "") return inCodeBlock ? " " : " <!-- -->";
|
|
5356
5361
|
return ` ${line}`;
|
|
5357
5362
|
}).join("\n");
|
|
5358
5363
|
}
|
|
@@ -5361,9 +5366,11 @@ function formatContent(content, format) {
|
|
|
5361
5366
|
return trimmed;
|
|
5362
5367
|
}
|
|
5363
5368
|
const lines = trimmed.split("\n");
|
|
5369
|
+
let inCodeBlock = false;
|
|
5364
5370
|
return lines.map((line, i) => {
|
|
5371
|
+
if (isCodeFenceLine(line)) inCodeBlock = !inCodeBlock;
|
|
5365
5372
|
if (i === 0) return `- [ ] ${line}`;
|
|
5366
|
-
if (line === "") return " ";
|
|
5373
|
+
if (line === "") return inCodeBlock ? " " : " <!-- -->";
|
|
5367
5374
|
return ` ${line}`;
|
|
5368
5375
|
}).join("\n");
|
|
5369
5376
|
}
|
|
@@ -5372,9 +5379,11 @@ function formatContent(content, format) {
|
|
|
5372
5379
|
return trimmed;
|
|
5373
5380
|
}
|
|
5374
5381
|
const lines = trimmed.split("\n");
|
|
5382
|
+
let inCodeBlock = false;
|
|
5375
5383
|
return lines.map((line, i) => {
|
|
5384
|
+
if (isCodeFenceLine(line)) inCodeBlock = !inCodeBlock;
|
|
5376
5385
|
if (i === 0) return `1. ${line}`;
|
|
5377
|
-
if (line === "") return " ";
|
|
5386
|
+
if (line === "") return inCodeBlock ? " " : " <!-- -->";
|
|
5378
5387
|
return ` ${line}`;
|
|
5379
5388
|
}).join("\n");
|
|
5380
5389
|
}
|
|
@@ -5389,9 +5398,11 @@ function formatContent(content, format) {
|
|
|
5389
5398
|
const prefix = `- **${hours}:${minutes}** `;
|
|
5390
5399
|
const lines = sanitized.split("\n");
|
|
5391
5400
|
const indent = " ";
|
|
5401
|
+
let inCodeBlock = false;
|
|
5392
5402
|
return lines.map((line, i) => {
|
|
5403
|
+
if (isCodeFenceLine(line)) inCodeBlock = !inCodeBlock;
|
|
5393
5404
|
if (i === 0) return `${prefix}${line}`;
|
|
5394
|
-
if (line === "") return indent
|
|
5405
|
+
if (line === "") return inCodeBlock ? indent : `${indent}<!-- -->`;
|
|
5395
5406
|
return `${indent}${line}`;
|
|
5396
5407
|
}).join("\n");
|
|
5397
5408
|
}
|
|
@@ -7364,21 +7375,21 @@ var DEFAULT_CONFIG = {
|
|
|
7364
7375
|
implicit_detection: true,
|
|
7365
7376
|
adaptive_strictness: true
|
|
7366
7377
|
};
|
|
7367
|
-
function migrateExcludeConfig(
|
|
7378
|
+
function migrateExcludeConfig(config2) {
|
|
7368
7379
|
const oldTags = [
|
|
7369
|
-
...
|
|
7370
|
-
...
|
|
7380
|
+
...config2.exclude_task_tags ?? [],
|
|
7381
|
+
...config2.exclude_analysis_tags ?? []
|
|
7371
7382
|
];
|
|
7372
|
-
const oldEntities =
|
|
7373
|
-
if (oldTags.length === 0 && oldEntities.length === 0) return
|
|
7383
|
+
const oldEntities = config2.exclude_entities ?? [];
|
|
7384
|
+
if (oldTags.length === 0 && oldEntities.length === 0) return config2;
|
|
7374
7385
|
const normalizedTags = oldTags.map((t) => t.startsWith("#") ? t : `#${t}`);
|
|
7375
7386
|
const merged = /* @__PURE__ */ new Set([
|
|
7376
|
-
...
|
|
7387
|
+
...config2.exclude ?? [],
|
|
7377
7388
|
...normalizedTags,
|
|
7378
7389
|
...oldEntities
|
|
7379
7390
|
]);
|
|
7380
7391
|
return {
|
|
7381
|
-
...
|
|
7392
|
+
...config2,
|
|
7382
7393
|
exclude: Array.from(merged),
|
|
7383
7394
|
// Clear deprecated fields
|
|
7384
7395
|
exclude_task_tags: void 0,
|
|
@@ -7477,11 +7488,11 @@ function inferConfig(index, vaultPath2) {
|
|
|
7477
7488
|
}
|
|
7478
7489
|
return inferred;
|
|
7479
7490
|
}
|
|
7480
|
-
function getExcludeTags(
|
|
7481
|
-
return (
|
|
7491
|
+
function getExcludeTags(config2) {
|
|
7492
|
+
return (config2.exclude ?? []).filter((e) => e.startsWith("#")).map((e) => e.slice(1));
|
|
7482
7493
|
}
|
|
7483
|
-
function getExcludeEntities(
|
|
7484
|
-
return (
|
|
7494
|
+
function getExcludeEntities(config2) {
|
|
7495
|
+
return (config2.exclude ?? []).filter((e) => !e.startsWith("#"));
|
|
7485
7496
|
}
|
|
7486
7497
|
var TEMPLATE_PATTERNS = {
|
|
7487
7498
|
daily: /^daily[\s._-]*(note|template)?\.md$/i,
|
|
@@ -7801,8 +7812,8 @@ var EventQueue = class {
|
|
|
7801
7812
|
config;
|
|
7802
7813
|
flushTimer = null;
|
|
7803
7814
|
onBatch;
|
|
7804
|
-
constructor(
|
|
7805
|
-
this.config =
|
|
7815
|
+
constructor(config2, onBatch) {
|
|
7816
|
+
this.config = config2;
|
|
7806
7817
|
this.onBatch = onBatch;
|
|
7807
7818
|
}
|
|
7808
7819
|
/**
|
|
@@ -8215,7 +8226,7 @@ init_serverLog();
|
|
|
8215
8226
|
// src/core/read/watch/index.ts
|
|
8216
8227
|
function createVaultWatcher(options) {
|
|
8217
8228
|
const { vaultPath: vaultPath2, onBatch, onStateChange, onError } = options;
|
|
8218
|
-
const
|
|
8229
|
+
const config2 = {
|
|
8219
8230
|
...DEFAULT_WATCHER_CONFIG,
|
|
8220
8231
|
...parseWatcherConfig(),
|
|
8221
8232
|
...options.config
|
|
@@ -8260,7 +8271,7 @@ function createVaultWatcher(options) {
|
|
|
8260
8271
|
}
|
|
8261
8272
|
}
|
|
8262
8273
|
};
|
|
8263
|
-
const eventQueue = new EventQueue(
|
|
8274
|
+
const eventQueue = new EventQueue(config2, processBatch2);
|
|
8264
8275
|
const instance = {
|
|
8265
8276
|
get status() {
|
|
8266
8277
|
return getStatus();
|
|
@@ -8273,8 +8284,8 @@ function createVaultWatcher(options) {
|
|
|
8273
8284
|
console.error("[flywheel] Watcher already started");
|
|
8274
8285
|
return;
|
|
8275
8286
|
}
|
|
8276
|
-
console.error(`[flywheel] Starting file watcher (debounce: ${
|
|
8277
|
-
console.error(`[flywheel] Chokidar options: usePolling=${
|
|
8287
|
+
console.error(`[flywheel] Starting file watcher (debounce: ${config2.debounceMs}ms, flush: ${config2.flushMs}ms)`);
|
|
8288
|
+
console.error(`[flywheel] Chokidar options: usePolling=${config2.usePolling}, interval=${config2.pollInterval}, vaultPath=${vaultPath2}`);
|
|
8278
8289
|
watcher = chokidar.watch(vaultPath2, {
|
|
8279
8290
|
ignored: createIgnoreFunction(vaultPath2),
|
|
8280
8291
|
persistent: true,
|
|
@@ -8283,8 +8294,8 @@ function createVaultWatcher(options) {
|
|
|
8283
8294
|
stabilityThreshold: 300,
|
|
8284
8295
|
pollInterval: 100
|
|
8285
8296
|
},
|
|
8286
|
-
usePolling:
|
|
8287
|
-
interval:
|
|
8297
|
+
usePolling: config2.usePolling,
|
|
8298
|
+
interval: config2.usePolling ? config2.pollInterval : void 0
|
|
8288
8299
|
});
|
|
8289
8300
|
watcher.on("add", (path39) => {
|
|
8290
8301
|
console.error(`[flywheel] RAW EVENT: add ${path39}`);
|
|
@@ -9468,6 +9479,104 @@ function refreshIfStale(vaultPath2, index, excludeTags) {
|
|
|
9468
9479
|
init_wikilinkFeedback();
|
|
9469
9480
|
init_corrections();
|
|
9470
9481
|
init_edgeWeights();
|
|
9482
|
+
var DeferredStepScheduler = class {
|
|
9483
|
+
timers = /* @__PURE__ */ new Map();
|
|
9484
|
+
executor = null;
|
|
9485
|
+
/** Set the executor context (called once during watcher setup) */
|
|
9486
|
+
setExecutor(exec) {
|
|
9487
|
+
this.executor = exec;
|
|
9488
|
+
}
|
|
9489
|
+
/** Schedule a deferred step to run after delayMs. Cancels any existing timer for this step. */
|
|
9490
|
+
schedule(step, delayMs) {
|
|
9491
|
+
this.cancel(step);
|
|
9492
|
+
const timer2 = setTimeout(() => {
|
|
9493
|
+
this.timers.delete(step);
|
|
9494
|
+
this.executeStep(step);
|
|
9495
|
+
}, delayMs);
|
|
9496
|
+
timer2.unref();
|
|
9497
|
+
this.timers.set(step, timer2);
|
|
9498
|
+
serverLog("deferred", `Scheduled ${step} in ${Math.round(delayMs / 1e3)}s`);
|
|
9499
|
+
}
|
|
9500
|
+
/** Cancel a pending deferred step */
|
|
9501
|
+
cancel(step) {
|
|
9502
|
+
const existing = this.timers.get(step);
|
|
9503
|
+
if (existing) {
|
|
9504
|
+
clearTimeout(existing);
|
|
9505
|
+
this.timers.delete(step);
|
|
9506
|
+
}
|
|
9507
|
+
}
|
|
9508
|
+
/** Cancel all pending deferred steps (called on shutdown) */
|
|
9509
|
+
cancelAll() {
|
|
9510
|
+
for (const timer2 of this.timers.values()) clearTimeout(timer2);
|
|
9511
|
+
this.timers.clear();
|
|
9512
|
+
}
|
|
9513
|
+
/** Check if any steps are pending */
|
|
9514
|
+
get pendingCount() {
|
|
9515
|
+
return this.timers.size;
|
|
9516
|
+
}
|
|
9517
|
+
async executeStep(step) {
|
|
9518
|
+
const exec = this.executor;
|
|
9519
|
+
if (!exec) return;
|
|
9520
|
+
if (exec.ctx.pipelineActivity.busy) {
|
|
9521
|
+
serverLog("deferred", `Skipping ${step}: pipeline busy`);
|
|
9522
|
+
return;
|
|
9523
|
+
}
|
|
9524
|
+
const start = Date.now();
|
|
9525
|
+
try {
|
|
9526
|
+
switch (step) {
|
|
9527
|
+
case "entity_scan": {
|
|
9528
|
+
await exec.updateEntitiesInStateDb(exec.vp, exec.sd);
|
|
9529
|
+
exec.ctx.lastEntityScanAt = Date.now();
|
|
9530
|
+
if (exec.sd) {
|
|
9531
|
+
await exportHubScores(exec.getVaultIndex(), exec.sd);
|
|
9532
|
+
exec.ctx.lastHubScoreRebuildAt = Date.now();
|
|
9533
|
+
}
|
|
9534
|
+
break;
|
|
9535
|
+
}
|
|
9536
|
+
case "hub_scores": {
|
|
9537
|
+
await exportHubScores(exec.getVaultIndex(), exec.sd);
|
|
9538
|
+
exec.ctx.lastHubScoreRebuildAt = Date.now();
|
|
9539
|
+
break;
|
|
9540
|
+
}
|
|
9541
|
+
case "recency": {
|
|
9542
|
+
const entities = exec.sd ? getAllEntitiesFromDb(exec.sd) : [];
|
|
9543
|
+
const entityInput = entities.map((e) => ({ name: e.name, path: e.path, aliases: e.aliases }));
|
|
9544
|
+
const recencyIndex2 = await buildRecencyIndex(exec.vp, entityInput);
|
|
9545
|
+
saveRecencyToStateDb(recencyIndex2, exec.sd ?? void 0);
|
|
9546
|
+
break;
|
|
9547
|
+
}
|
|
9548
|
+
case "cooccurrence": {
|
|
9549
|
+
const entities = exec.sd ? getAllEntitiesFromDb(exec.sd) : [];
|
|
9550
|
+
const entityNames = entities.map((e) => e.name);
|
|
9551
|
+
const cooccurrenceIdx = await mineCooccurrences(exec.vp, entityNames);
|
|
9552
|
+
setCooccurrenceIndex(cooccurrenceIdx);
|
|
9553
|
+
exec.ctx.lastCooccurrenceRebuildAt = Date.now();
|
|
9554
|
+
exec.ctx.cooccurrenceIndex = cooccurrenceIdx;
|
|
9555
|
+
if (exec.sd) saveCooccurrenceToStateDb(exec.sd, cooccurrenceIdx);
|
|
9556
|
+
break;
|
|
9557
|
+
}
|
|
9558
|
+
case "edge_weights": {
|
|
9559
|
+
if (exec.sd) {
|
|
9560
|
+
recomputeEdgeWeights(exec.sd);
|
|
9561
|
+
exec.ctx.lastEdgeWeightRebuildAt = Date.now();
|
|
9562
|
+
}
|
|
9563
|
+
break;
|
|
9564
|
+
}
|
|
9565
|
+
}
|
|
9566
|
+
const duration = Date.now() - start;
|
|
9567
|
+
serverLog("deferred", `Completed ${step} in ${duration}ms`);
|
|
9568
|
+
if (exec.sd) {
|
|
9569
|
+
recordIndexEvent(exec.sd, {
|
|
9570
|
+
trigger: "deferred",
|
|
9571
|
+
duration_ms: duration,
|
|
9572
|
+
note_count: exec.getVaultIndex().notes.size
|
|
9573
|
+
});
|
|
9574
|
+
}
|
|
9575
|
+
} catch (err) {
|
|
9576
|
+
serverLog("deferred", `Failed ${step}: ${err instanceof Error ? err.message : err}`, "error");
|
|
9577
|
+
}
|
|
9578
|
+
}
|
|
9579
|
+
};
|
|
9471
9580
|
var PIPELINE_TOTAL_STEPS = 22;
|
|
9472
9581
|
function createEmptyPipelineActivity() {
|
|
9473
9582
|
return {
|
|
@@ -9695,6 +9804,7 @@ var PipelineRunner = class {
|
|
|
9695
9804
|
tracker.skip("entity_scan", `cache valid (${Math.round(entityScanAgeMs / 1e3)}s old)`);
|
|
9696
9805
|
this.entitiesBefore = p.sd ? getAllEntitiesFromDb(p.sd) : [];
|
|
9697
9806
|
this.entitiesAfter = this.entitiesBefore;
|
|
9807
|
+
p.deferredScheduler?.schedule("entity_scan", 5 * 60 * 1e3 - entityScanAgeMs);
|
|
9698
9808
|
serverLog("watcher", `Entity scan: throttled (${Math.round(entityScanAgeMs / 1e3)}s old)`);
|
|
9699
9809
|
return;
|
|
9700
9810
|
}
|
|
@@ -9740,6 +9850,7 @@ var PipelineRunner = class {
|
|
|
9740
9850
|
const { p } = this;
|
|
9741
9851
|
const hubAgeMs = p.ctx.lastHubScoreRebuildAt > 0 ? Date.now() - p.ctx.lastHubScoreRebuildAt : Infinity;
|
|
9742
9852
|
if (hubAgeMs < 5 * 60 * 1e3) {
|
|
9853
|
+
p.deferredScheduler?.schedule("hub_scores", 5 * 60 * 1e3 - hubAgeMs);
|
|
9743
9854
|
serverLog("watcher", `Hub scores: throttled (${Math.round(hubAgeMs / 1e3)}s old)`);
|
|
9744
9855
|
return { skipped: true, age_ms: hubAgeMs };
|
|
9745
9856
|
}
|
|
@@ -9769,6 +9880,7 @@ var PipelineRunner = class {
|
|
|
9769
9880
|
serverLog("watcher", `Recency: rebuilt ${recencyIndex2.lastMentioned.size} entities`);
|
|
9770
9881
|
return { rebuilt: true, entities: recencyIndex2.lastMentioned.size };
|
|
9771
9882
|
}
|
|
9883
|
+
p.deferredScheduler?.schedule("recency", 60 * 60 * 1e3 - cacheAgeMs);
|
|
9772
9884
|
serverLog("watcher", `Recency: cache valid (${Math.round(cacheAgeMs / 1e3)}s old)`);
|
|
9773
9885
|
return { rebuilt: false, cached_age_ms: cacheAgeMs };
|
|
9774
9886
|
}
|
|
@@ -9788,6 +9900,7 @@ var PipelineRunner = class {
|
|
|
9788
9900
|
serverLog("watcher", `Co-occurrence: rebuilt ${cooccurrenceIdx._metadata.total_associations} associations`);
|
|
9789
9901
|
return { rebuilt: true, associations: cooccurrenceIdx._metadata.total_associations };
|
|
9790
9902
|
}
|
|
9903
|
+
p.deferredScheduler?.schedule("cooccurrence", 60 * 60 * 1e3 - cooccurrenceAgeMs);
|
|
9791
9904
|
serverLog("watcher", `Co-occurrence: cache valid (${Math.round(cooccurrenceAgeMs / 1e3)}s old)`);
|
|
9792
9905
|
return { rebuilt: false, age_ms: cooccurrenceAgeMs };
|
|
9793
9906
|
}
|
|
@@ -9810,6 +9923,7 @@ var PipelineRunner = class {
|
|
|
9810
9923
|
top_changes: result.top_changes
|
|
9811
9924
|
};
|
|
9812
9925
|
}
|
|
9926
|
+
p.deferredScheduler?.schedule("edge_weights", 60 * 60 * 1e3 - edgeWeightAgeMs);
|
|
9813
9927
|
serverLog("watcher", `Edge weights: cache valid (${Math.round(edgeWeightAgeMs / 1e3)}s old)`);
|
|
9814
9928
|
return { rebuilt: false, age_ms: edgeWeightAgeMs };
|
|
9815
9929
|
}
|
|
@@ -10799,7 +10913,7 @@ function getToolSelectionReport(stateDb2, daysBack = 7) {
|
|
|
10799
10913
|
}
|
|
10800
10914
|
|
|
10801
10915
|
// src/index.ts
|
|
10802
|
-
import { openStateDb, scanVaultEntities as
|
|
10916
|
+
import { openStateDb, scanVaultEntities as scanVaultEntities5, getAllEntitiesFromDb as getAllEntitiesFromDb6, loadContentHashes, saveContentHashBatch, renameContentHash, checkDbIntegrity as checkDbIntegrity2, safeBackupAsync as safeBackupAsync2, preserveCorruptedDb, deleteStateDbFiles, attemptSalvage } from "@velvetmonkey/vault-core";
|
|
10803
10917
|
|
|
10804
10918
|
// src/core/write/memory.ts
|
|
10805
10919
|
init_wikilinkFeedback();
|
|
@@ -11218,6 +11332,382 @@ function getSweepResults() {
|
|
|
11218
11332
|
return cachedResults;
|
|
11219
11333
|
}
|
|
11220
11334
|
|
|
11335
|
+
// src/core/read/watch/maintenance.ts
|
|
11336
|
+
init_serverLog();
|
|
11337
|
+
import { getAllEntitiesFromDb as getAllEntitiesFromDb2 } from "@velvetmonkey/vault-core";
|
|
11338
|
+
init_recency();
|
|
11339
|
+
init_cooccurrence();
|
|
11340
|
+
init_wikilinks();
|
|
11341
|
+
init_edgeWeights();
|
|
11342
|
+
|
|
11343
|
+
// src/core/shared/graphSnapshots.ts
|
|
11344
|
+
function computeGraphMetrics(index) {
|
|
11345
|
+
const noteCount = index.notes.size;
|
|
11346
|
+
if (noteCount === 0) {
|
|
11347
|
+
return {
|
|
11348
|
+
avg_degree: 0,
|
|
11349
|
+
max_degree: 0,
|
|
11350
|
+
cluster_count: 0,
|
|
11351
|
+
largest_cluster_size: 0,
|
|
11352
|
+
hub_scores_top10: []
|
|
11353
|
+
};
|
|
11354
|
+
}
|
|
11355
|
+
const degreeMap = /* @__PURE__ */ new Map();
|
|
11356
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
11357
|
+
for (const [notePath, note] of index.notes) {
|
|
11358
|
+
if (!adjacency.has(notePath)) adjacency.set(notePath, /* @__PURE__ */ new Set());
|
|
11359
|
+
let degree = note.outlinks.length;
|
|
11360
|
+
for (const link of note.outlinks) {
|
|
11361
|
+
const targetLower = link.target.toLowerCase();
|
|
11362
|
+
const resolvedPath = index.entities.get(targetLower);
|
|
11363
|
+
if (resolvedPath && index.notes.has(resolvedPath)) {
|
|
11364
|
+
adjacency.get(notePath).add(resolvedPath);
|
|
11365
|
+
if (!adjacency.has(resolvedPath)) adjacency.set(resolvedPath, /* @__PURE__ */ new Set());
|
|
11366
|
+
adjacency.get(resolvedPath).add(notePath);
|
|
11367
|
+
}
|
|
11368
|
+
}
|
|
11369
|
+
degreeMap.set(notePath, degree);
|
|
11370
|
+
}
|
|
11371
|
+
for (const [target, backlinks] of index.backlinks) {
|
|
11372
|
+
const targetLower = target.toLowerCase();
|
|
11373
|
+
const resolvedPath = index.entities.get(targetLower);
|
|
11374
|
+
if (resolvedPath && degreeMap.has(resolvedPath)) {
|
|
11375
|
+
degreeMap.set(resolvedPath, degreeMap.get(resolvedPath) + backlinks.length);
|
|
11376
|
+
}
|
|
11377
|
+
}
|
|
11378
|
+
let totalDegree = 0;
|
|
11379
|
+
let maxDegree = 0;
|
|
11380
|
+
let maxDegreeNote = "";
|
|
11381
|
+
for (const [notePath, degree] of degreeMap) {
|
|
11382
|
+
totalDegree += degree;
|
|
11383
|
+
if (degree > maxDegree) {
|
|
11384
|
+
maxDegree = degree;
|
|
11385
|
+
maxDegreeNote = notePath;
|
|
11386
|
+
}
|
|
11387
|
+
}
|
|
11388
|
+
const avgDegree = noteCount > 0 ? Math.round(totalDegree / noteCount * 100) / 100 : 0;
|
|
11389
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11390
|
+
const clusters = [];
|
|
11391
|
+
for (const notePath of index.notes.keys()) {
|
|
11392
|
+
if (visited.has(notePath)) continue;
|
|
11393
|
+
const queue = [notePath];
|
|
11394
|
+
visited.add(notePath);
|
|
11395
|
+
let clusterSize = 0;
|
|
11396
|
+
while (queue.length > 0) {
|
|
11397
|
+
const current = queue.shift();
|
|
11398
|
+
clusterSize++;
|
|
11399
|
+
const neighbors = adjacency.get(current);
|
|
11400
|
+
if (neighbors) {
|
|
11401
|
+
for (const neighbor of neighbors) {
|
|
11402
|
+
if (!visited.has(neighbor)) {
|
|
11403
|
+
visited.add(neighbor);
|
|
11404
|
+
queue.push(neighbor);
|
|
11405
|
+
}
|
|
11406
|
+
}
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11409
|
+
clusters.push(clusterSize);
|
|
11410
|
+
}
|
|
11411
|
+
const clusterCount = clusters.length;
|
|
11412
|
+
const largestClusterSize = clusters.length > 0 ? Math.max(...clusters) : 0;
|
|
11413
|
+
const sorted = Array.from(degreeMap.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
11414
|
+
const hubScoresTop10 = sorted.map(([notePath, degree]) => {
|
|
11415
|
+
const note = index.notes.get(notePath);
|
|
11416
|
+
return {
|
|
11417
|
+
entity: note?.title ?? notePath,
|
|
11418
|
+
degree
|
|
11419
|
+
};
|
|
11420
|
+
});
|
|
11421
|
+
return {
|
|
11422
|
+
avg_degree: avgDegree,
|
|
11423
|
+
max_degree: maxDegree,
|
|
11424
|
+
cluster_count: clusterCount,
|
|
11425
|
+
largest_cluster_size: largestClusterSize,
|
|
11426
|
+
hub_scores_top10: hubScoresTop10
|
|
11427
|
+
};
|
|
11428
|
+
}
|
|
11429
|
+
function recordGraphSnapshot(stateDb2, metrics) {
|
|
11430
|
+
const timestamp = Date.now();
|
|
11431
|
+
const insert = stateDb2.db.prepare(
|
|
11432
|
+
"INSERT INTO graph_snapshots (timestamp, metric, value, details) VALUES (?, ?, ?, ?)"
|
|
11433
|
+
);
|
|
11434
|
+
const transaction = stateDb2.db.transaction(() => {
|
|
11435
|
+
insert.run(timestamp, "avg_degree", metrics.avg_degree, null);
|
|
11436
|
+
insert.run(timestamp, "max_degree", metrics.max_degree, null);
|
|
11437
|
+
insert.run(timestamp, "cluster_count", metrics.cluster_count, null);
|
|
11438
|
+
insert.run(timestamp, "largest_cluster_size", metrics.largest_cluster_size, null);
|
|
11439
|
+
insert.run(
|
|
11440
|
+
timestamp,
|
|
11441
|
+
"hub_scores_top10",
|
|
11442
|
+
metrics.hub_scores_top10.length,
|
|
11443
|
+
JSON.stringify(metrics.hub_scores_top10)
|
|
11444
|
+
);
|
|
11445
|
+
});
|
|
11446
|
+
transaction();
|
|
11447
|
+
}
|
|
11448
|
+
function getEmergingHubs(stateDb2, daysBack = 30) {
|
|
11449
|
+
const cutoff = Date.now() - daysBack * 24 * 60 * 60 * 1e3;
|
|
11450
|
+
const latestRow = stateDb2.db.prepare(
|
|
11451
|
+
`SELECT details FROM graph_snapshots
|
|
11452
|
+
WHERE metric = 'hub_scores_top10'
|
|
11453
|
+
ORDER BY timestamp DESC LIMIT 1`
|
|
11454
|
+
).get();
|
|
11455
|
+
const previousRow = stateDb2.db.prepare(
|
|
11456
|
+
`SELECT details FROM graph_snapshots
|
|
11457
|
+
WHERE metric = 'hub_scores_top10' AND timestamp >= ?
|
|
11458
|
+
ORDER BY timestamp ASC LIMIT 1`
|
|
11459
|
+
).get(cutoff);
|
|
11460
|
+
if (!latestRow?.details) return [];
|
|
11461
|
+
const currentHubs = JSON.parse(latestRow.details);
|
|
11462
|
+
const previousHubs = previousRow?.details ? JSON.parse(previousRow.details) : [];
|
|
11463
|
+
const previousMap = /* @__PURE__ */ new Map();
|
|
11464
|
+
for (const hub of previousHubs) {
|
|
11465
|
+
previousMap.set(hub.entity, hub.degree);
|
|
11466
|
+
}
|
|
11467
|
+
const emerging = currentHubs.map((hub) => {
|
|
11468
|
+
const prevDegree = previousMap.get(hub.entity) ?? 0;
|
|
11469
|
+
return {
|
|
11470
|
+
entity: hub.entity,
|
|
11471
|
+
current_degree: hub.degree,
|
|
11472
|
+
previous_degree: prevDegree,
|
|
11473
|
+
growth: hub.degree - prevDegree
|
|
11474
|
+
};
|
|
11475
|
+
});
|
|
11476
|
+
emerging.sort((a, b) => b.growth - a.growth);
|
|
11477
|
+
return emerging;
|
|
11478
|
+
}
|
|
11479
|
+
function compareGraphSnapshots(stateDb2, timestampBefore, timestampAfter) {
|
|
11480
|
+
const SCALAR_METRICS = ["avg_degree", "max_degree", "cluster_count", "largest_cluster_size"];
|
|
11481
|
+
function getSnapshotAt(ts) {
|
|
11482
|
+
const row = stateDb2.db.prepare(
|
|
11483
|
+
`SELECT DISTINCT timestamp FROM graph_snapshots WHERE timestamp <= ? ORDER BY timestamp DESC LIMIT 1`
|
|
11484
|
+
).get(ts);
|
|
11485
|
+
if (!row) return null;
|
|
11486
|
+
const rows = stateDb2.db.prepare(
|
|
11487
|
+
`SELECT metric, value, details FROM graph_snapshots WHERE timestamp = ?`
|
|
11488
|
+
).all(row.timestamp);
|
|
11489
|
+
return rows;
|
|
11490
|
+
}
|
|
11491
|
+
const beforeRows = getSnapshotAt(timestampBefore) ?? [];
|
|
11492
|
+
const afterRows = getSnapshotAt(timestampAfter) ?? [];
|
|
11493
|
+
const beforeMap = /* @__PURE__ */ new Map();
|
|
11494
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
11495
|
+
for (const r of beforeRows) beforeMap.set(r.metric, { value: r.value, details: r.details });
|
|
11496
|
+
for (const r of afterRows) afterMap.set(r.metric, { value: r.value, details: r.details });
|
|
11497
|
+
const metricChanges = SCALAR_METRICS.map((metric) => {
|
|
11498
|
+
const before = beforeMap.get(metric)?.value ?? 0;
|
|
11499
|
+
const after = afterMap.get(metric)?.value ?? 0;
|
|
11500
|
+
const delta = after - before;
|
|
11501
|
+
const deltaPercent = before !== 0 ? Math.round(delta / before * 1e4) / 100 : delta !== 0 ? 100 : 0;
|
|
11502
|
+
return { metric, before, after, delta, deltaPercent };
|
|
11503
|
+
});
|
|
11504
|
+
const beforeHubs = beforeMap.get("hub_scores_top10")?.details ? JSON.parse(beforeMap.get("hub_scores_top10").details) : [];
|
|
11505
|
+
const afterHubs = afterMap.get("hub_scores_top10")?.details ? JSON.parse(afterMap.get("hub_scores_top10").details) : [];
|
|
11506
|
+
const beforeHubMap = /* @__PURE__ */ new Map();
|
|
11507
|
+
for (const h of beforeHubs) beforeHubMap.set(h.entity, h.degree);
|
|
11508
|
+
const afterHubMap = /* @__PURE__ */ new Map();
|
|
11509
|
+
for (const h of afterHubs) afterHubMap.set(h.entity, h.degree);
|
|
11510
|
+
const allHubEntities = /* @__PURE__ */ new Set([...beforeHubMap.keys(), ...afterHubMap.keys()]);
|
|
11511
|
+
const hubScoreChanges = [];
|
|
11512
|
+
for (const entity of allHubEntities) {
|
|
11513
|
+
const before = beforeHubMap.get(entity) ?? 0;
|
|
11514
|
+
const after = afterHubMap.get(entity) ?? 0;
|
|
11515
|
+
if (before !== after) {
|
|
11516
|
+
hubScoreChanges.push({ entity, before, after, delta: after - before });
|
|
11517
|
+
}
|
|
11518
|
+
}
|
|
11519
|
+
hubScoreChanges.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
|
|
11520
|
+
return { metricChanges, hubScoreChanges };
|
|
11521
|
+
}
|
|
11522
|
+
function purgeOldSnapshots(stateDb2, retentionDays = 90) {
|
|
11523
|
+
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
11524
|
+
const result = stateDb2.db.prepare(
|
|
11525
|
+
"DELETE FROM graph_snapshots WHERE timestamp < ?"
|
|
11526
|
+
).run(cutoff);
|
|
11527
|
+
return result.changes;
|
|
11528
|
+
}
|
|
11529
|
+
|
|
11530
|
+
// src/core/read/watch/maintenance.ts
|
|
11531
|
+
var DEFAULT_INTERVAL_MS = 2 * 60 * 60 * 1e3;
|
|
11532
|
+
var MIN_INTERVAL_MS = 10 * 60 * 1e3;
|
|
11533
|
+
var JITTER_FACTOR = 0.15;
|
|
11534
|
+
var RECENT_REBUILD_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
11535
|
+
var IDLE_THRESHOLD_MS = 30 * 1e3;
|
|
11536
|
+
var STEP_TTLS = {
|
|
11537
|
+
entity_scan: 5 * 60 * 1e3,
|
|
11538
|
+
// 5 minutes
|
|
11539
|
+
hub_scores: 5 * 60 * 1e3,
|
|
11540
|
+
// 5 minutes
|
|
11541
|
+
recency: 60 * 60 * 1e3,
|
|
11542
|
+
// 1 hour
|
|
11543
|
+
cooccurrence: 60 * 60 * 1e3,
|
|
11544
|
+
// 1 hour
|
|
11545
|
+
edge_weights: 60 * 60 * 1e3,
|
|
11546
|
+
// 1 hour
|
|
11547
|
+
config_inference: 2 * 60 * 60 * 1e3
|
|
11548
|
+
// 2 hours (only runs during maintenance)
|
|
11549
|
+
};
|
|
11550
|
+
var timer = null;
|
|
11551
|
+
var config = null;
|
|
11552
|
+
var lastConfigInferenceAt = 0;
|
|
11553
|
+
function addJitter(interval) {
|
|
11554
|
+
const jitter = interval * JITTER_FACTOR * (2 * Math.random() - 1);
|
|
11555
|
+
return Math.max(MIN_INTERVAL_MS, interval + jitter);
|
|
11556
|
+
}
|
|
11557
|
+
function startMaintenanceTimer(cfg, intervalMs) {
|
|
11558
|
+
config = cfg;
|
|
11559
|
+
const baseInterval = Math.max(intervalMs ?? DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS);
|
|
11560
|
+
scheduleNext(baseInterval);
|
|
11561
|
+
serverLog("maintenance", `Timer started (interval ~${Math.round(baseInterval / 6e4)}min)`);
|
|
11562
|
+
}
|
|
11563
|
+
function stopMaintenanceTimer() {
|
|
11564
|
+
if (timer) {
|
|
11565
|
+
clearTimeout(timer);
|
|
11566
|
+
timer = null;
|
|
11567
|
+
}
|
|
11568
|
+
config = null;
|
|
11569
|
+
}
|
|
11570
|
+
function scheduleNext(baseInterval) {
|
|
11571
|
+
timer = setTimeout(() => {
|
|
11572
|
+
runMaintenance(baseInterval);
|
|
11573
|
+
}, addJitter(baseInterval));
|
|
11574
|
+
timer.unref();
|
|
11575
|
+
}
|
|
11576
|
+
async function runMaintenance(baseInterval) {
|
|
11577
|
+
const cfg = config;
|
|
11578
|
+
if (!cfg) return;
|
|
11579
|
+
const { ctx, sd } = cfg;
|
|
11580
|
+
if (ctx.pipelineActivity.busy) {
|
|
11581
|
+
serverLog("maintenance", "Skipped: pipeline busy");
|
|
11582
|
+
scheduleNext(baseInterval);
|
|
11583
|
+
return;
|
|
11584
|
+
}
|
|
11585
|
+
const lastFullRebuild = cfg.getLastFullRebuildAt();
|
|
11586
|
+
if (lastFullRebuild > 0 && Date.now() - lastFullRebuild < RECENT_REBUILD_THRESHOLD_MS) {
|
|
11587
|
+
serverLog("maintenance", `Skipped: full rebuild ${Math.round((Date.now() - lastFullRebuild) / 6e4)}min ago`);
|
|
11588
|
+
scheduleNext(baseInterval);
|
|
11589
|
+
return;
|
|
11590
|
+
}
|
|
11591
|
+
const lastRequest = cfg.getLastMcpRequestAt();
|
|
11592
|
+
if (lastRequest > 0 && Date.now() - lastRequest < IDLE_THRESHOLD_MS) {
|
|
11593
|
+
serverLog("maintenance", "Skipped: server not idle, retrying in 1min");
|
|
11594
|
+
timer = setTimeout(() => runMaintenance(baseInterval), 60 * 1e3);
|
|
11595
|
+
timer.unref();
|
|
11596
|
+
return;
|
|
11597
|
+
}
|
|
11598
|
+
const start = Date.now();
|
|
11599
|
+
const stepsRun = [];
|
|
11600
|
+
const tracker = createStepTracker();
|
|
11601
|
+
try {
|
|
11602
|
+
const now = Date.now();
|
|
11603
|
+
const entityAge = ctx.lastEntityScanAt > 0 ? now - ctx.lastEntityScanAt : Infinity;
|
|
11604
|
+
if (entityAge >= STEP_TTLS.entity_scan) {
|
|
11605
|
+
tracker.start("entity_scan", {});
|
|
11606
|
+
await cfg.updateEntitiesInStateDb(cfg.vp, sd);
|
|
11607
|
+
ctx.lastEntityScanAt = Date.now();
|
|
11608
|
+
const entities = sd ? getAllEntitiesFromDb2(sd) : [];
|
|
11609
|
+
tracker.end({ entity_count: entities.length });
|
|
11610
|
+
stepsRun.push("entity_scan");
|
|
11611
|
+
}
|
|
11612
|
+
const hubAge = ctx.lastHubScoreRebuildAt > 0 ? now - ctx.lastHubScoreRebuildAt : Infinity;
|
|
11613
|
+
if (hubAge >= STEP_TTLS.hub_scores) {
|
|
11614
|
+
tracker.start("hub_scores", {});
|
|
11615
|
+
const updated = await exportHubScores(cfg.getVaultIndex(), sd);
|
|
11616
|
+
ctx.lastHubScoreRebuildAt = Date.now();
|
|
11617
|
+
tracker.end({ updated: updated ?? 0 });
|
|
11618
|
+
stepsRun.push("hub_scores");
|
|
11619
|
+
}
|
|
11620
|
+
const cachedRecency = loadRecencyFromStateDb(sd ?? void 0);
|
|
11621
|
+
const recencyAge = cachedRecency ? now - (cachedRecency.lastUpdated ?? 0) : Infinity;
|
|
11622
|
+
if (recencyAge >= STEP_TTLS.recency) {
|
|
11623
|
+
tracker.start("recency", {});
|
|
11624
|
+
const entities = sd ? getAllEntitiesFromDb2(sd) : [];
|
|
11625
|
+
const entityInput = entities.map((e) => ({ name: e.name, path: e.path, aliases: e.aliases }));
|
|
11626
|
+
const recencyIndex2 = await buildRecencyIndex(cfg.vp, entityInput);
|
|
11627
|
+
saveRecencyToStateDb(recencyIndex2, sd ?? void 0);
|
|
11628
|
+
tracker.end({ entities: recencyIndex2.lastMentioned.size });
|
|
11629
|
+
stepsRun.push("recency");
|
|
11630
|
+
}
|
|
11631
|
+
const cooccurrenceAge = ctx.lastCooccurrenceRebuildAt > 0 ? now - ctx.lastCooccurrenceRebuildAt : Infinity;
|
|
11632
|
+
if (cooccurrenceAge >= STEP_TTLS.cooccurrence) {
|
|
11633
|
+
tracker.start("cooccurrence", {});
|
|
11634
|
+
const entities = sd ? getAllEntitiesFromDb2(sd) : [];
|
|
11635
|
+
const entityNames = entities.map((e) => e.name);
|
|
11636
|
+
const cooccurrenceIdx = await mineCooccurrences(cfg.vp, entityNames);
|
|
11637
|
+
setCooccurrenceIndex(cooccurrenceIdx);
|
|
11638
|
+
ctx.lastCooccurrenceRebuildAt = Date.now();
|
|
11639
|
+
ctx.cooccurrenceIndex = cooccurrenceIdx;
|
|
11640
|
+
if (sd) saveCooccurrenceToStateDb(sd, cooccurrenceIdx);
|
|
11641
|
+
tracker.end({ associations: cooccurrenceIdx._metadata.total_associations });
|
|
11642
|
+
stepsRun.push("cooccurrence");
|
|
11643
|
+
}
|
|
11644
|
+
const edgeWeightAge = ctx.lastEdgeWeightRebuildAt > 0 ? now - ctx.lastEdgeWeightRebuildAt : Infinity;
|
|
11645
|
+
if (sd && edgeWeightAge >= STEP_TTLS.edge_weights) {
|
|
11646
|
+
tracker.start("edge_weights", {});
|
|
11647
|
+
const result = recomputeEdgeWeights(sd);
|
|
11648
|
+
ctx.lastEdgeWeightRebuildAt = Date.now();
|
|
11649
|
+
tracker.end({ edges: result.edges_updated });
|
|
11650
|
+
stepsRun.push("edge_weights");
|
|
11651
|
+
}
|
|
11652
|
+
const configAge = lastConfigInferenceAt > 0 ? now - lastConfigInferenceAt : Infinity;
|
|
11653
|
+
if (sd && configAge >= STEP_TTLS.config_inference) {
|
|
11654
|
+
tracker.start("config_inference", {});
|
|
11655
|
+
const existing = loadConfig(sd);
|
|
11656
|
+
const inferred = inferConfig(cfg.getVaultIndex(), cfg.vp);
|
|
11657
|
+
saveConfig(sd, inferred, existing);
|
|
11658
|
+
cfg.updateFlywheelConfig(loadConfig(sd));
|
|
11659
|
+
lastConfigInferenceAt = Date.now();
|
|
11660
|
+
tracker.end({ inferred: true });
|
|
11661
|
+
stepsRun.push("config_inference");
|
|
11662
|
+
}
|
|
11663
|
+
if (sd && stepsRun.length > 0) {
|
|
11664
|
+
try {
|
|
11665
|
+
tracker.start("graph_snapshot", {});
|
|
11666
|
+
const graphMetrics = computeGraphMetrics(cfg.getVaultIndex());
|
|
11667
|
+
recordGraphSnapshot(sd, graphMetrics);
|
|
11668
|
+
tracker.end({ recorded: true });
|
|
11669
|
+
stepsRun.push("graph_snapshot");
|
|
11670
|
+
} catch (err) {
|
|
11671
|
+
tracker.end({ error: String(err) });
|
|
11672
|
+
}
|
|
11673
|
+
}
|
|
11674
|
+
if (sd && stepsRun.length > 0) {
|
|
11675
|
+
try {
|
|
11676
|
+
saveVaultIndexToCache(sd, cfg.getVaultIndex());
|
|
11677
|
+
ctx.lastIndexCacheSaveAt = Date.now();
|
|
11678
|
+
} catch {
|
|
11679
|
+
}
|
|
11680
|
+
}
|
|
11681
|
+
const duration = Date.now() - start;
|
|
11682
|
+
if (stepsRun.length > 0) {
|
|
11683
|
+
serverLog("maintenance", `Completed ${stepsRun.length} steps in ${duration}ms: ${stepsRun.join(", ")}`);
|
|
11684
|
+
if (sd) {
|
|
11685
|
+
recordIndexEvent(sd, {
|
|
11686
|
+
trigger: "maintenance",
|
|
11687
|
+
duration_ms: duration,
|
|
11688
|
+
note_count: cfg.getVaultIndex().notes.size,
|
|
11689
|
+
steps: tracker.steps
|
|
11690
|
+
});
|
|
11691
|
+
}
|
|
11692
|
+
} else {
|
|
11693
|
+
serverLog("maintenance", `All steps fresh, nothing to do (${duration}ms)`);
|
|
11694
|
+
}
|
|
11695
|
+
} catch (err) {
|
|
11696
|
+
const duration = Date.now() - start;
|
|
11697
|
+
serverLog("maintenance", `Failed after ${duration}ms: ${err instanceof Error ? err.message : err}`, "error");
|
|
11698
|
+
if (sd) {
|
|
11699
|
+
recordIndexEvent(sd, {
|
|
11700
|
+
trigger: "maintenance",
|
|
11701
|
+
duration_ms: duration,
|
|
11702
|
+
success: false,
|
|
11703
|
+
error: err instanceof Error ? err.message : String(err),
|
|
11704
|
+
steps: tracker.steps
|
|
11705
|
+
});
|
|
11706
|
+
}
|
|
11707
|
+
}
|
|
11708
|
+
scheduleNext(baseInterval);
|
|
11709
|
+
}
|
|
11710
|
+
|
|
11221
11711
|
// src/core/shared/metrics.ts
|
|
11222
11712
|
init_wikilinkFeedback();
|
|
11223
11713
|
var ALL_METRICS = [
|
|
@@ -11606,193 +12096,6 @@ function purgeOldInvocations(stateDb2, retentionDays = 90) {
|
|
|
11606
12096
|
return result.changes;
|
|
11607
12097
|
}
|
|
11608
12098
|
|
|
11609
|
-
// src/core/shared/graphSnapshots.ts
|
|
11610
|
-
function computeGraphMetrics(index) {
|
|
11611
|
-
const noteCount = index.notes.size;
|
|
11612
|
-
if (noteCount === 0) {
|
|
11613
|
-
return {
|
|
11614
|
-
avg_degree: 0,
|
|
11615
|
-
max_degree: 0,
|
|
11616
|
-
cluster_count: 0,
|
|
11617
|
-
largest_cluster_size: 0,
|
|
11618
|
-
hub_scores_top10: []
|
|
11619
|
-
};
|
|
11620
|
-
}
|
|
11621
|
-
const degreeMap = /* @__PURE__ */ new Map();
|
|
11622
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
11623
|
-
for (const [notePath, note] of index.notes) {
|
|
11624
|
-
if (!adjacency.has(notePath)) adjacency.set(notePath, /* @__PURE__ */ new Set());
|
|
11625
|
-
let degree = note.outlinks.length;
|
|
11626
|
-
for (const link of note.outlinks) {
|
|
11627
|
-
const targetLower = link.target.toLowerCase();
|
|
11628
|
-
const resolvedPath = index.entities.get(targetLower);
|
|
11629
|
-
if (resolvedPath && index.notes.has(resolvedPath)) {
|
|
11630
|
-
adjacency.get(notePath).add(resolvedPath);
|
|
11631
|
-
if (!adjacency.has(resolvedPath)) adjacency.set(resolvedPath, /* @__PURE__ */ new Set());
|
|
11632
|
-
adjacency.get(resolvedPath).add(notePath);
|
|
11633
|
-
}
|
|
11634
|
-
}
|
|
11635
|
-
degreeMap.set(notePath, degree);
|
|
11636
|
-
}
|
|
11637
|
-
for (const [target, backlinks] of index.backlinks) {
|
|
11638
|
-
const targetLower = target.toLowerCase();
|
|
11639
|
-
const resolvedPath = index.entities.get(targetLower);
|
|
11640
|
-
if (resolvedPath && degreeMap.has(resolvedPath)) {
|
|
11641
|
-
degreeMap.set(resolvedPath, degreeMap.get(resolvedPath) + backlinks.length);
|
|
11642
|
-
}
|
|
11643
|
-
}
|
|
11644
|
-
let totalDegree = 0;
|
|
11645
|
-
let maxDegree = 0;
|
|
11646
|
-
let maxDegreeNote = "";
|
|
11647
|
-
for (const [notePath, degree] of degreeMap) {
|
|
11648
|
-
totalDegree += degree;
|
|
11649
|
-
if (degree > maxDegree) {
|
|
11650
|
-
maxDegree = degree;
|
|
11651
|
-
maxDegreeNote = notePath;
|
|
11652
|
-
}
|
|
11653
|
-
}
|
|
11654
|
-
const avgDegree = noteCount > 0 ? Math.round(totalDegree / noteCount * 100) / 100 : 0;
|
|
11655
|
-
const visited = /* @__PURE__ */ new Set();
|
|
11656
|
-
const clusters = [];
|
|
11657
|
-
for (const notePath of index.notes.keys()) {
|
|
11658
|
-
if (visited.has(notePath)) continue;
|
|
11659
|
-
const queue = [notePath];
|
|
11660
|
-
visited.add(notePath);
|
|
11661
|
-
let clusterSize = 0;
|
|
11662
|
-
while (queue.length > 0) {
|
|
11663
|
-
const current = queue.shift();
|
|
11664
|
-
clusterSize++;
|
|
11665
|
-
const neighbors = adjacency.get(current);
|
|
11666
|
-
if (neighbors) {
|
|
11667
|
-
for (const neighbor of neighbors) {
|
|
11668
|
-
if (!visited.has(neighbor)) {
|
|
11669
|
-
visited.add(neighbor);
|
|
11670
|
-
queue.push(neighbor);
|
|
11671
|
-
}
|
|
11672
|
-
}
|
|
11673
|
-
}
|
|
11674
|
-
}
|
|
11675
|
-
clusters.push(clusterSize);
|
|
11676
|
-
}
|
|
11677
|
-
const clusterCount = clusters.length;
|
|
11678
|
-
const largestClusterSize = clusters.length > 0 ? Math.max(...clusters) : 0;
|
|
11679
|
-
const sorted = Array.from(degreeMap.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
11680
|
-
const hubScoresTop10 = sorted.map(([notePath, degree]) => {
|
|
11681
|
-
const note = index.notes.get(notePath);
|
|
11682
|
-
return {
|
|
11683
|
-
entity: note?.title ?? notePath,
|
|
11684
|
-
degree
|
|
11685
|
-
};
|
|
11686
|
-
});
|
|
11687
|
-
return {
|
|
11688
|
-
avg_degree: avgDegree,
|
|
11689
|
-
max_degree: maxDegree,
|
|
11690
|
-
cluster_count: clusterCount,
|
|
11691
|
-
largest_cluster_size: largestClusterSize,
|
|
11692
|
-
hub_scores_top10: hubScoresTop10
|
|
11693
|
-
};
|
|
11694
|
-
}
|
|
11695
|
-
function recordGraphSnapshot(stateDb2, metrics) {
|
|
11696
|
-
const timestamp = Date.now();
|
|
11697
|
-
const insert = stateDb2.db.prepare(
|
|
11698
|
-
"INSERT INTO graph_snapshots (timestamp, metric, value, details) VALUES (?, ?, ?, ?)"
|
|
11699
|
-
);
|
|
11700
|
-
const transaction = stateDb2.db.transaction(() => {
|
|
11701
|
-
insert.run(timestamp, "avg_degree", metrics.avg_degree, null);
|
|
11702
|
-
insert.run(timestamp, "max_degree", metrics.max_degree, null);
|
|
11703
|
-
insert.run(timestamp, "cluster_count", metrics.cluster_count, null);
|
|
11704
|
-
insert.run(timestamp, "largest_cluster_size", metrics.largest_cluster_size, null);
|
|
11705
|
-
insert.run(
|
|
11706
|
-
timestamp,
|
|
11707
|
-
"hub_scores_top10",
|
|
11708
|
-
metrics.hub_scores_top10.length,
|
|
11709
|
-
JSON.stringify(metrics.hub_scores_top10)
|
|
11710
|
-
);
|
|
11711
|
-
});
|
|
11712
|
-
transaction();
|
|
11713
|
-
}
|
|
11714
|
-
function getEmergingHubs(stateDb2, daysBack = 30) {
|
|
11715
|
-
const cutoff = Date.now() - daysBack * 24 * 60 * 60 * 1e3;
|
|
11716
|
-
const latestRow = stateDb2.db.prepare(
|
|
11717
|
-
`SELECT details FROM graph_snapshots
|
|
11718
|
-
WHERE metric = 'hub_scores_top10'
|
|
11719
|
-
ORDER BY timestamp DESC LIMIT 1`
|
|
11720
|
-
).get();
|
|
11721
|
-
const previousRow = stateDb2.db.prepare(
|
|
11722
|
-
`SELECT details FROM graph_snapshots
|
|
11723
|
-
WHERE metric = 'hub_scores_top10' AND timestamp >= ?
|
|
11724
|
-
ORDER BY timestamp ASC LIMIT 1`
|
|
11725
|
-
).get(cutoff);
|
|
11726
|
-
if (!latestRow?.details) return [];
|
|
11727
|
-
const currentHubs = JSON.parse(latestRow.details);
|
|
11728
|
-
const previousHubs = previousRow?.details ? JSON.parse(previousRow.details) : [];
|
|
11729
|
-
const previousMap = /* @__PURE__ */ new Map();
|
|
11730
|
-
for (const hub of previousHubs) {
|
|
11731
|
-
previousMap.set(hub.entity, hub.degree);
|
|
11732
|
-
}
|
|
11733
|
-
const emerging = currentHubs.map((hub) => {
|
|
11734
|
-
const prevDegree = previousMap.get(hub.entity) ?? 0;
|
|
11735
|
-
return {
|
|
11736
|
-
entity: hub.entity,
|
|
11737
|
-
current_degree: hub.degree,
|
|
11738
|
-
previous_degree: prevDegree,
|
|
11739
|
-
growth: hub.degree - prevDegree
|
|
11740
|
-
};
|
|
11741
|
-
});
|
|
11742
|
-
emerging.sort((a, b) => b.growth - a.growth);
|
|
11743
|
-
return emerging;
|
|
11744
|
-
}
|
|
11745
|
-
function compareGraphSnapshots(stateDb2, timestampBefore, timestampAfter) {
|
|
11746
|
-
const SCALAR_METRICS = ["avg_degree", "max_degree", "cluster_count", "largest_cluster_size"];
|
|
11747
|
-
function getSnapshotAt(ts) {
|
|
11748
|
-
const row = stateDb2.db.prepare(
|
|
11749
|
-
`SELECT DISTINCT timestamp FROM graph_snapshots WHERE timestamp <= ? ORDER BY timestamp DESC LIMIT 1`
|
|
11750
|
-
).get(ts);
|
|
11751
|
-
if (!row) return null;
|
|
11752
|
-
const rows = stateDb2.db.prepare(
|
|
11753
|
-
`SELECT metric, value, details FROM graph_snapshots WHERE timestamp = ?`
|
|
11754
|
-
).all(row.timestamp);
|
|
11755
|
-
return rows;
|
|
11756
|
-
}
|
|
11757
|
-
const beforeRows = getSnapshotAt(timestampBefore) ?? [];
|
|
11758
|
-
const afterRows = getSnapshotAt(timestampAfter) ?? [];
|
|
11759
|
-
const beforeMap = /* @__PURE__ */ new Map();
|
|
11760
|
-
const afterMap = /* @__PURE__ */ new Map();
|
|
11761
|
-
for (const r of beforeRows) beforeMap.set(r.metric, { value: r.value, details: r.details });
|
|
11762
|
-
for (const r of afterRows) afterMap.set(r.metric, { value: r.value, details: r.details });
|
|
11763
|
-
const metricChanges = SCALAR_METRICS.map((metric) => {
|
|
11764
|
-
const before = beforeMap.get(metric)?.value ?? 0;
|
|
11765
|
-
const after = afterMap.get(metric)?.value ?? 0;
|
|
11766
|
-
const delta = after - before;
|
|
11767
|
-
const deltaPercent = before !== 0 ? Math.round(delta / before * 1e4) / 100 : delta !== 0 ? 100 : 0;
|
|
11768
|
-
return { metric, before, after, delta, deltaPercent };
|
|
11769
|
-
});
|
|
11770
|
-
const beforeHubs = beforeMap.get("hub_scores_top10")?.details ? JSON.parse(beforeMap.get("hub_scores_top10").details) : [];
|
|
11771
|
-
const afterHubs = afterMap.get("hub_scores_top10")?.details ? JSON.parse(afterMap.get("hub_scores_top10").details) : [];
|
|
11772
|
-
const beforeHubMap = /* @__PURE__ */ new Map();
|
|
11773
|
-
for (const h of beforeHubs) beforeHubMap.set(h.entity, h.degree);
|
|
11774
|
-
const afterHubMap = /* @__PURE__ */ new Map();
|
|
11775
|
-
for (const h of afterHubs) afterHubMap.set(h.entity, h.degree);
|
|
11776
|
-
const allHubEntities = /* @__PURE__ */ new Set([...beforeHubMap.keys(), ...afterHubMap.keys()]);
|
|
11777
|
-
const hubScoreChanges = [];
|
|
11778
|
-
for (const entity of allHubEntities) {
|
|
11779
|
-
const before = beforeHubMap.get(entity) ?? 0;
|
|
11780
|
-
const after = afterHubMap.get(entity) ?? 0;
|
|
11781
|
-
if (before !== after) {
|
|
11782
|
-
hubScoreChanges.push({ entity, before, after, delta: after - before });
|
|
11783
|
-
}
|
|
11784
|
-
}
|
|
11785
|
-
hubScoreChanges.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
|
|
11786
|
-
return { metricChanges, hubScoreChanges };
|
|
11787
|
-
}
|
|
11788
|
-
function purgeOldSnapshots(stateDb2, retentionDays = 90) {
|
|
11789
|
-
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
11790
|
-
const result = stateDb2.db.prepare(
|
|
11791
|
-
"DELETE FROM graph_snapshots WHERE timestamp < ?"
|
|
11792
|
-
).run(cutoff);
|
|
11793
|
-
return result.changes;
|
|
11794
|
-
}
|
|
11795
|
-
|
|
11796
12099
|
// src/index.ts
|
|
11797
12100
|
init_serverLog();
|
|
11798
12101
|
init_wikilinkFeedback();
|
|
@@ -12673,8 +12976,8 @@ var DEFAULT_CONFIG2 = {
|
|
|
12673
12976
|
maxOutlinksPerHop: 10,
|
|
12674
12977
|
maxBackfill: 10
|
|
12675
12978
|
};
|
|
12676
|
-
function multiHopBackfill(primaryResults, index, stateDb2,
|
|
12677
|
-
const cfg = { ...DEFAULT_CONFIG2, ...
|
|
12979
|
+
function multiHopBackfill(primaryResults, index, stateDb2, config2 = {}) {
|
|
12980
|
+
const cfg = { ...DEFAULT_CONFIG2, ...config2 };
|
|
12678
12981
|
const seen = new Set(primaryResults.map((r) => r.path).filter(Boolean));
|
|
12679
12982
|
const candidates = [];
|
|
12680
12983
|
const hop1Results = [];
|
|
@@ -15490,7 +15793,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15490
15793
|
}
|
|
15491
15794
|
const indexBuilt = indexState2 === "ready" && index !== void 0 && index.notes !== void 0;
|
|
15492
15795
|
let lastIndexActivityAt;
|
|
15493
|
-
let
|
|
15796
|
+
let lastFullRebuildAt2;
|
|
15494
15797
|
let lastWatcherBatchAt;
|
|
15495
15798
|
let lastBuild;
|
|
15496
15799
|
let lastManual;
|
|
@@ -15500,13 +15803,13 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15500
15803
|
if (lastAny) lastIndexActivityAt = lastAny.timestamp;
|
|
15501
15804
|
lastBuild = getLastEventByTrigger(stateDb2, "startup_build") ?? void 0;
|
|
15502
15805
|
lastManual = getLastEventByTrigger(stateDb2, "manual_refresh") ?? void 0;
|
|
15503
|
-
|
|
15806
|
+
lastFullRebuildAt2 = Math.max(lastBuild?.timestamp ?? 0, lastManual?.timestamp ?? 0) || void 0;
|
|
15504
15807
|
const lastWatcher = getLastEventByTrigger(stateDb2, "watcher");
|
|
15505
15808
|
if (lastWatcher) lastWatcherBatchAt = lastWatcher.timestamp;
|
|
15506
15809
|
} catch {
|
|
15507
15810
|
}
|
|
15508
15811
|
}
|
|
15509
|
-
const freshnessTimestamp =
|
|
15812
|
+
const freshnessTimestamp = lastFullRebuildAt2 ?? (indexBuilt && index.builtAt ? index.builtAt.getTime() : void 0);
|
|
15510
15813
|
const indexAge = freshnessTimestamp ? Math.floor((Date.now() - freshnessTimestamp) / 1e3) : -1;
|
|
15511
15814
|
const indexStale = indexBuilt && indexAge > STALE_THRESHOLD_SECONDS;
|
|
15512
15815
|
if (indexState2 === "building") {
|
|
@@ -15553,8 +15856,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15553
15856
|
}
|
|
15554
15857
|
let configInfo;
|
|
15555
15858
|
if (isFull) {
|
|
15556
|
-
const
|
|
15557
|
-
configInfo = Object.keys(
|
|
15859
|
+
const config2 = getConfig2();
|
|
15860
|
+
configInfo = Object.keys(config2).length > 0 ? config2 : void 0;
|
|
15558
15861
|
}
|
|
15559
15862
|
let lastRebuild;
|
|
15560
15863
|
if (stateDb2) {
|
|
@@ -15683,15 +15986,15 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15683
15986
|
watcher_pending: getWatcherStatus2()?.pendingEvents,
|
|
15684
15987
|
last_index_activity_at: lastIndexActivityAt,
|
|
15685
15988
|
last_index_activity_ago_seconds: lastIndexActivityAt ? Math.floor((Date.now() - lastIndexActivityAt) / 1e3) : void 0,
|
|
15686
|
-
last_full_rebuild_at:
|
|
15989
|
+
last_full_rebuild_at: lastFullRebuildAt2,
|
|
15687
15990
|
last_watcher_batch_at: lastWatcherBatchAt,
|
|
15688
15991
|
pipeline_activity: pipelineActivity,
|
|
15689
15992
|
dead_link_count: isFull ? deadLinkCount : void 0,
|
|
15690
15993
|
top_dead_link_targets: isFull ? topDeadLinkTargets : void 0,
|
|
15691
15994
|
sweep: isFull ? getSweepResults() ?? void 0 : void 0,
|
|
15692
15995
|
proactive_linking: isFull && stateDb2 ? (() => {
|
|
15693
|
-
const
|
|
15694
|
-
const enabled =
|
|
15996
|
+
const config2 = getConfig2();
|
|
15997
|
+
const enabled = config2.proactive_linking !== false;
|
|
15695
15998
|
const queuePending = stateDb2.db.prepare(
|
|
15696
15999
|
`SELECT COUNT(*) as cnt FROM proactive_queue WHERE status = 'pending'`
|
|
15697
16000
|
).get();
|
|
@@ -16337,7 +16640,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
16337
16640
|
|
|
16338
16641
|
// src/tools/read/system.ts
|
|
16339
16642
|
import { z as z5 } from "zod";
|
|
16340
|
-
import { scanVaultEntities as
|
|
16643
|
+
import { scanVaultEntities as scanVaultEntities3, getEntityIndexFromDb as getEntityIndexFromDb2, getAllEntitiesFromDb as getAllEntitiesFromDb3 } from "@velvetmonkey/vault-core";
|
|
16341
16644
|
|
|
16342
16645
|
// src/core/read/aliasSuggestions.ts
|
|
16343
16646
|
import { STOPWORDS_EN as STOPWORDS_EN3 } from "@velvetmonkey/vault-core";
|
|
@@ -16454,11 +16757,11 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16454
16757
|
if (stateDb2) {
|
|
16455
16758
|
tracker.start("entity_sync", {});
|
|
16456
16759
|
try {
|
|
16457
|
-
const
|
|
16458
|
-
const excludeFolders =
|
|
16459
|
-
const entityIndex2 = await
|
|
16760
|
+
const config2 = loadConfig(stateDb2);
|
|
16761
|
+
const excludeFolders = config2.exclude_entity_folders?.length ? config2.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
|
|
16762
|
+
const entityIndex2 = await scanVaultEntities3(vaultPath2, {
|
|
16460
16763
|
excludeFolders,
|
|
16461
|
-
customCategories:
|
|
16764
|
+
customCategories: config2.custom_categories
|
|
16462
16765
|
});
|
|
16463
16766
|
stateDb2.replaceAllEntities(entityIndex2);
|
|
16464
16767
|
tracker.end({ entities: entityIndex2._metadata.total_entities });
|
|
@@ -16580,7 +16883,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16580
16883
|
if (stateDb2) {
|
|
16581
16884
|
tracker.start("recency", {});
|
|
16582
16885
|
try {
|
|
16583
|
-
const entities =
|
|
16886
|
+
const entities = getAllEntitiesFromDb3(stateDb2).map((e) => ({
|
|
16584
16887
|
name: e.name,
|
|
16585
16888
|
path: e.path,
|
|
16586
16889
|
aliases: e.aliases
|
|
@@ -16599,7 +16902,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16599
16902
|
if (stateDb2) {
|
|
16600
16903
|
tracker.start("cooccurrence", {});
|
|
16601
16904
|
try {
|
|
16602
|
-
const entityNames =
|
|
16905
|
+
const entityNames = getAllEntitiesFromDb3(stateDb2).map((e) => e.name);
|
|
16603
16906
|
const cooccurrenceIdx = await mineCooccurrences(vaultPath2, entityNames);
|
|
16604
16907
|
setCooccurrenceIndex(cooccurrenceIdx);
|
|
16605
16908
|
saveCooccurrenceToStateDb(stateDb2, cooccurrenceIdx);
|
|
@@ -16644,7 +16947,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16644
16947
|
if (stateDb2 && hasEntityEmbeddingsIndex()) {
|
|
16645
16948
|
tracker.start("entity_embeddings", {});
|
|
16646
16949
|
try {
|
|
16647
|
-
const entities =
|
|
16950
|
+
const entities = getAllEntitiesFromDb3(stateDb2);
|
|
16648
16951
|
if (entities.length > 0) {
|
|
16649
16952
|
const entityMap = new Map(entities.map((e) => [e.name, {
|
|
16650
16953
|
name: e.name,
|
|
@@ -17071,9 +17374,9 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17071
17374
|
const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
|
|
17072
17375
|
const index = getIndex();
|
|
17073
17376
|
const vaultPath2 = getVaultPath();
|
|
17074
|
-
const
|
|
17377
|
+
const config2 = getConfig2();
|
|
17075
17378
|
if (path39) {
|
|
17076
|
-
const result2 = await getTasksFromNote(index, path39, vaultPath2, getExcludeTags(
|
|
17379
|
+
const result2 = await getTasksFromNote(index, path39, vaultPath2, getExcludeTags(config2));
|
|
17077
17380
|
if (!result2) {
|
|
17078
17381
|
return {
|
|
17079
17382
|
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path39 }, null, 2) }]
|
|
@@ -17096,12 +17399,12 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17096
17399
|
};
|
|
17097
17400
|
}
|
|
17098
17401
|
if (isTaskCacheReady()) {
|
|
17099
|
-
refreshIfStale(vaultPath2, index, getExcludeTags(
|
|
17402
|
+
refreshIfStale(vaultPath2, index, getExcludeTags(config2));
|
|
17100
17403
|
if (has_due_date) {
|
|
17101
17404
|
const result3 = queryTasksFromCache({
|
|
17102
17405
|
status,
|
|
17103
17406
|
folder,
|
|
17104
|
-
excludeTags: getExcludeTags(
|
|
17407
|
+
excludeTags: getExcludeTags(config2),
|
|
17105
17408
|
has_due_date: true,
|
|
17106
17409
|
limit,
|
|
17107
17410
|
offset
|
|
@@ -17118,7 +17421,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17118
17421
|
status,
|
|
17119
17422
|
folder,
|
|
17120
17423
|
tag,
|
|
17121
|
-
excludeTags: getExcludeTags(
|
|
17424
|
+
excludeTags: getExcludeTags(config2),
|
|
17122
17425
|
limit,
|
|
17123
17426
|
offset
|
|
17124
17427
|
});
|
|
@@ -17137,7 +17440,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17137
17440
|
const allResults = await getTasksWithDueDates(index, vaultPath2, {
|
|
17138
17441
|
status,
|
|
17139
17442
|
folder,
|
|
17140
|
-
excludeTags: getExcludeTags(
|
|
17443
|
+
excludeTags: getExcludeTags(config2)
|
|
17141
17444
|
});
|
|
17142
17445
|
const paged2 = allResults.slice(offset, offset + limit);
|
|
17143
17446
|
return {
|
|
@@ -17153,7 +17456,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17153
17456
|
folder,
|
|
17154
17457
|
tag,
|
|
17155
17458
|
limit: limit + offset,
|
|
17156
|
-
excludeTags: getExcludeTags(
|
|
17459
|
+
excludeTags: getExcludeTags(config2)
|
|
17157
17460
|
});
|
|
17158
17461
|
const paged = result.tasks.slice(offset, offset + limit);
|
|
17159
17462
|
return {
|
|
@@ -17820,10 +18123,10 @@ function isTemplatePath(notePath) {
|
|
|
17820
18123
|
const folder = notePath.split("/")[0]?.toLowerCase() || "";
|
|
17821
18124
|
return folder === "templates" || folder === "template";
|
|
17822
18125
|
}
|
|
17823
|
-
function getExcludedPaths(index,
|
|
18126
|
+
function getExcludedPaths(index, config2) {
|
|
17824
18127
|
const excluded = /* @__PURE__ */ new Set();
|
|
17825
|
-
const excludeTags = new Set(getExcludeTags(
|
|
17826
|
-
const excludeEntities = new Set(getExcludeEntities(
|
|
18128
|
+
const excludeTags = new Set(getExcludeTags(config2).map((t) => t.toLowerCase()));
|
|
18129
|
+
const excludeEntities = new Set(getExcludeEntities(config2).map((e) => e.toLowerCase()));
|
|
17827
18130
|
if (excludeTags.size === 0 && excludeEntities.size === 0) return excluded;
|
|
17828
18131
|
for (const note of index.notes.values()) {
|
|
17829
18132
|
if (excludeTags.size > 0) {
|
|
@@ -17870,8 +18173,8 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb4
|
|
|
17870
18173
|
requireIndex();
|
|
17871
18174
|
const limit = Math.min(requestedLimit ?? 50, MAX_LIMIT);
|
|
17872
18175
|
const index = getIndex();
|
|
17873
|
-
const
|
|
17874
|
-
const excludedPaths = getExcludedPaths(index,
|
|
18176
|
+
const config2 = getConfig2?.() ?? {};
|
|
18177
|
+
const excludedPaths = getExcludedPaths(index, config2);
|
|
17875
18178
|
switch (analysis) {
|
|
17876
18179
|
case "orphans": {
|
|
17877
18180
|
const allOrphans = findOrphanNotes(index, folder).filter((o) => !isPeriodicNote(o.path) && !excludedPaths.has(o.path));
|
|
@@ -18039,7 +18342,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb4
|
|
|
18039
18342
|
return !note || !excludedPaths.has(note.path);
|
|
18040
18343
|
});
|
|
18041
18344
|
}
|
|
18042
|
-
const excludeEntities = new Set(getExcludeEntities(
|
|
18345
|
+
const excludeEntities = new Set(getExcludeEntities(config2).map((e) => e.toLowerCase()));
|
|
18043
18346
|
if (excludeEntities.size > 0) {
|
|
18044
18347
|
hubs = hubs.filter((hub) => !excludeEntities.has(hub.entity.toLowerCase()));
|
|
18045
18348
|
}
|
|
@@ -19678,14 +19981,14 @@ async function executeDeleteNote(options) {
|
|
|
19678
19981
|
}
|
|
19679
19982
|
|
|
19680
19983
|
// src/tools/write/mutations.ts
|
|
19681
|
-
async function createNoteFromTemplate(vaultPath2, notePath,
|
|
19984
|
+
async function createNoteFromTemplate(vaultPath2, notePath, config2) {
|
|
19682
19985
|
const validation = await validatePathSecure(vaultPath2, notePath);
|
|
19683
19986
|
if (!validation.valid) {
|
|
19684
19987
|
throw new Error(`Path blocked: ${validation.reason}`);
|
|
19685
19988
|
}
|
|
19686
19989
|
const fullPath = path25.join(vaultPath2, notePath);
|
|
19687
19990
|
await fs23.mkdir(path25.dirname(fullPath), { recursive: true });
|
|
19688
|
-
const templates =
|
|
19991
|
+
const templates = config2.templates || {};
|
|
19689
19992
|
const filename = path25.basename(notePath, ".md").toLowerCase();
|
|
19690
19993
|
let templatePath;
|
|
19691
19994
|
let periodicType;
|
|
@@ -19798,8 +20101,8 @@ function registerMutationTools(server2, getVaultPath, getConfig2 = () => ({})) {
|
|
|
19798
20101
|
try {
|
|
19799
20102
|
await fs23.access(fullPath);
|
|
19800
20103
|
} catch {
|
|
19801
|
-
const
|
|
19802
|
-
const result = await createNoteFromTemplate(vaultPath2, notePath,
|
|
20104
|
+
const config2 = getConfig2();
|
|
20105
|
+
const result = await createNoteFromTemplate(vaultPath2, notePath, config2);
|
|
19803
20106
|
noteCreated = result.created;
|
|
19804
20107
|
templateUsed = result.templateUsed;
|
|
19805
20108
|
}
|
|
@@ -23905,9 +24208,9 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb4) {
|
|
|
23905
24208
|
async ({ mode, key, value }) => {
|
|
23906
24209
|
switch (mode) {
|
|
23907
24210
|
case "get": {
|
|
23908
|
-
const
|
|
24211
|
+
const config2 = getConfig2();
|
|
23909
24212
|
return {
|
|
23910
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
24213
|
+
content: [{ type: "text", text: JSON.stringify(config2, null, 2) }]
|
|
23911
24214
|
};
|
|
23912
24215
|
}
|
|
23913
24216
|
case "set": {
|
|
@@ -23958,7 +24261,7 @@ init_wikilinkFeedback();
|
|
|
23958
24261
|
import { z as z28 } from "zod";
|
|
23959
24262
|
import * as fs32 from "fs/promises";
|
|
23960
24263
|
import * as path35 from "path";
|
|
23961
|
-
import { scanVaultEntities as
|
|
24264
|
+
import { scanVaultEntities as scanVaultEntities4, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
|
|
23962
24265
|
init_embeddings();
|
|
23963
24266
|
function hasSkipWikilinks(content) {
|
|
23964
24267
|
if (!content.startsWith("---")) return false;
|
|
@@ -24076,8 +24379,8 @@ async function executeRun(stateDb2, vaultPath2) {
|
|
|
24076
24379
|
if (entityCount === 0) {
|
|
24077
24380
|
const start = Date.now();
|
|
24078
24381
|
try {
|
|
24079
|
-
const
|
|
24080
|
-
const entityIndex2 = await
|
|
24382
|
+
const config2 = loadConfig(stateDb2);
|
|
24383
|
+
const entityIndex2 = await scanVaultEntities4(vaultPath2, { excludeFolders: EXCLUDE_FOLDERS, customCategories: config2.custom_categories });
|
|
24081
24384
|
stateDb2.replaceAllEntities(entityIndex2);
|
|
24082
24385
|
const newCount = entityIndex2._metadata.total_entities;
|
|
24083
24386
|
steps.push({
|
|
@@ -24658,7 +24961,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
24658
24961
|
// src/tools/read/semantic.ts
|
|
24659
24962
|
init_embeddings();
|
|
24660
24963
|
import { z as z31 } from "zod";
|
|
24661
|
-
import { getAllEntitiesFromDb as
|
|
24964
|
+
import { getAllEntitiesFromDb as getAllEntitiesFromDb4 } from "@velvetmonkey/vault-core";
|
|
24662
24965
|
function registerSemanticTools(server2, getVaultPath, getStateDb4) {
|
|
24663
24966
|
server2.registerTool(
|
|
24664
24967
|
"init_semantic",
|
|
@@ -24723,7 +25026,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb4) {
|
|
|
24723
25026
|
const embedded = progress.total - progress.skipped;
|
|
24724
25027
|
let entityEmbedded = 0;
|
|
24725
25028
|
try {
|
|
24726
|
-
const allEntities =
|
|
25029
|
+
const allEntities = getAllEntitiesFromDb4(stateDb2);
|
|
24727
25030
|
const entityMap = /* @__PURE__ */ new Map();
|
|
24728
25031
|
for (const e of allEntities) {
|
|
24729
25032
|
entityMap.set(e.name, {
|
|
@@ -24785,7 +25088,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb4) {
|
|
|
24785
25088
|
// src/tools/read/merges.ts
|
|
24786
25089
|
init_levenshtein();
|
|
24787
25090
|
import { z as z32 } from "zod";
|
|
24788
|
-
import { getAllEntitiesFromDb as
|
|
25091
|
+
import { getAllEntitiesFromDb as getAllEntitiesFromDb5, getDismissedMergePairs, recordMergeDismissal } from "@velvetmonkey/vault-core";
|
|
24789
25092
|
function normalizeName(name) {
|
|
24790
25093
|
return name.toLowerCase().replace(/[.\-_]/g, "").replace(/js$/, "").replace(/ts$/, "");
|
|
24791
25094
|
}
|
|
@@ -24803,7 +25106,7 @@ function registerMergeTools2(server2, getStateDb4) {
|
|
|
24803
25106
|
content: [{ type: "text", text: JSON.stringify({ suggestions: [], error: "StateDb not available" }) }]
|
|
24804
25107
|
};
|
|
24805
25108
|
}
|
|
24806
|
-
const entities =
|
|
25109
|
+
const entities = getAllEntitiesFromDb5(stateDb2);
|
|
24807
25110
|
if (entities.length === 0) {
|
|
24808
25111
|
return {
|
|
24809
25112
|
content: [{ type: "text", text: JSON.stringify({ suggestions: [] }) }]
|
|
@@ -26286,7 +26589,7 @@ function queryFlywheelAgeDays(stateDb2) {
|
|
|
26286
26589
|
if (!row?.first_ts) return 0;
|
|
26287
26590
|
return Math.floor((Date.now() - row.first_ts) / (24 * 60 * 60 * 1e3));
|
|
26288
26591
|
}
|
|
26289
|
-
function getCalibrationExport(stateDb2, metrics,
|
|
26592
|
+
function getCalibrationExport(stateDb2, metrics, config2, daysBack = 30, includeVaultId = true) {
|
|
26290
26593
|
const now = /* @__PURE__ */ new Date();
|
|
26291
26594
|
const start = new Date(now);
|
|
26292
26595
|
start.setDate(start.getDate() - daysBack + 1);
|
|
@@ -26309,8 +26612,8 @@ function getCalibrationExport(stateDb2, metrics, config, daysBack = 30, includeV
|
|
|
26309
26612
|
connected_ratio: round(metrics.connected_ratio),
|
|
26310
26613
|
semantic_enabled: hasEmbeddingsIndex(),
|
|
26311
26614
|
flywheel_age_days: queryFlywheelAgeDays(stateDb2),
|
|
26312
|
-
strictness_mode:
|
|
26313
|
-
adaptive_strictness:
|
|
26615
|
+
strictness_mode: config2.wikilink_strictness ?? "balanced",
|
|
26616
|
+
adaptive_strictness: config2.adaptive_strictness ?? true
|
|
26314
26617
|
},
|
|
26315
26618
|
entity_distribution: queryEntityDistribution(stateDb2),
|
|
26316
26619
|
funnel: queryFunnel2(stateDb2, startMs, startIso, endIso),
|
|
@@ -26344,11 +26647,11 @@ function registerCalibrationExportTools(server2, getIndex, getStateDb4, getConfi
|
|
|
26344
26647
|
}
|
|
26345
26648
|
const index = getIndex();
|
|
26346
26649
|
const metrics = computeMetrics(index, stateDb2);
|
|
26347
|
-
const
|
|
26650
|
+
const config2 = getConfig2();
|
|
26348
26651
|
const report = getCalibrationExport(
|
|
26349
26652
|
stateDb2,
|
|
26350
26653
|
metrics,
|
|
26351
|
-
|
|
26654
|
+
config2,
|
|
26352
26655
|
args.days_back ?? 30,
|
|
26353
26656
|
args.include_vault_id ?? true
|
|
26354
26657
|
);
|
|
@@ -26602,7 +26905,7 @@ function extractSearchMethod(result) {
|
|
|
26602
26905
|
}
|
|
26603
26906
|
return void 0;
|
|
26604
26907
|
}
|
|
26605
|
-
function applyToolGating(targetServer, categories, getDb4, registry, getVaultPath, vaultCallbacks, tierMode = "off", onTierStateChange, isFullToolset = false) {
|
|
26908
|
+
function applyToolGating(targetServer, categories, getDb4, registry, getVaultPath, vaultCallbacks, tierMode = "off", onTierStateChange, isFullToolset = false, onToolCall) {
|
|
26606
26909
|
let _registered = 0;
|
|
26607
26910
|
let _skipped = 0;
|
|
26608
26911
|
let tierOverride = "auto";
|
|
@@ -26695,6 +26998,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
26695
26998
|
}
|
|
26696
26999
|
function wrapWithTracking(toolName, handler) {
|
|
26697
27000
|
return async (...args) => {
|
|
27001
|
+
onToolCall?.();
|
|
26698
27002
|
const start = Date.now();
|
|
26699
27003
|
let success = true;
|
|
26700
27004
|
let notePaths;
|
|
@@ -27114,6 +27418,9 @@ var httpListener = null;
|
|
|
27114
27418
|
var watchdogTimer = null;
|
|
27115
27419
|
var serverReady = false;
|
|
27116
27420
|
var shutdownRequested = false;
|
|
27421
|
+
var lastMcpRequestAt = 0;
|
|
27422
|
+
var lastFullRebuildAt = 0;
|
|
27423
|
+
var deferredScheduler = null;
|
|
27117
27424
|
function getWatcherStatus() {
|
|
27118
27425
|
if (vaultRegistry) {
|
|
27119
27426
|
const name = globalThis.__flywheel_active_vault;
|
|
@@ -27147,8 +27454,8 @@ function handleTierStateChange(controller) {
|
|
|
27147
27454
|
syncRuntimeTierState(controller);
|
|
27148
27455
|
invalidateHttpPool();
|
|
27149
27456
|
}
|
|
27150
|
-
function getConfigToolTierOverride(
|
|
27151
|
-
return
|
|
27457
|
+
function getConfigToolTierOverride(config2) {
|
|
27458
|
+
return config2.tool_tier_override ?? "auto";
|
|
27152
27459
|
}
|
|
27153
27460
|
function buildRegistryContext() {
|
|
27154
27461
|
return {
|
|
@@ -27180,7 +27487,10 @@ function createConfiguredServer() {
|
|
|
27180
27487
|
buildVaultCallbacks(),
|
|
27181
27488
|
toolTierMode,
|
|
27182
27489
|
handleTierStateChange,
|
|
27183
|
-
toolConfig.isFullToolset
|
|
27490
|
+
toolConfig.isFullToolset,
|
|
27491
|
+
() => {
|
|
27492
|
+
lastMcpRequestAt = Date.now();
|
|
27493
|
+
}
|
|
27184
27494
|
);
|
|
27185
27495
|
registerAllTools(s, ctx, toolTierController);
|
|
27186
27496
|
toolTierController.setOverride(runtimeToolTierOverride);
|
|
@@ -27234,7 +27544,10 @@ var _gatingResult = applyToolGating(
|
|
|
27234
27544
|
buildVaultCallbacks(),
|
|
27235
27545
|
toolTierMode,
|
|
27236
27546
|
handleTierStateChange,
|
|
27237
|
-
toolConfig.isFullToolset
|
|
27547
|
+
toolConfig.isFullToolset,
|
|
27548
|
+
() => {
|
|
27549
|
+
lastMcpRequestAt = Date.now();
|
|
27550
|
+
}
|
|
27238
27551
|
);
|
|
27239
27552
|
registerAllTools(server, _registryCtx, _gatingResult);
|
|
27240
27553
|
_gatingResult.setOverride(runtimeToolTierOverride);
|
|
@@ -27357,17 +27670,17 @@ function updateVaultIndex(index) {
|
|
|
27357
27670
|
const ctx = getActiveVaultContext();
|
|
27358
27671
|
if (ctx) ctx.vaultIndex = index;
|
|
27359
27672
|
}
|
|
27360
|
-
function updateFlywheelConfig(
|
|
27361
|
-
flywheelConfig =
|
|
27362
|
-
setWikilinkConfig(
|
|
27673
|
+
function updateFlywheelConfig(config2) {
|
|
27674
|
+
flywheelConfig = config2;
|
|
27675
|
+
setWikilinkConfig(config2);
|
|
27363
27676
|
if (toolTierMode === "tiered" && primaryToolTierController) {
|
|
27364
|
-
primaryToolTierController.setOverride(getConfigToolTierOverride(
|
|
27677
|
+
primaryToolTierController.setOverride(getConfigToolTierOverride(config2));
|
|
27365
27678
|
syncRuntimeTierState(primaryToolTierController);
|
|
27366
27679
|
invalidateHttpPool();
|
|
27367
27680
|
}
|
|
27368
27681
|
const ctx = getActiveVaultContext();
|
|
27369
27682
|
if (ctx) {
|
|
27370
|
-
ctx.flywheelConfig =
|
|
27683
|
+
ctx.flywheelConfig = config2;
|
|
27371
27684
|
setActiveScope(buildVaultScope(ctx));
|
|
27372
27685
|
}
|
|
27373
27686
|
}
|
|
@@ -27422,6 +27735,7 @@ async function bootVault(ctx, startTime) {
|
|
|
27422
27735
|
note_count: cachedIndex.notes.size
|
|
27423
27736
|
});
|
|
27424
27737
|
}
|
|
27738
|
+
lastFullRebuildAt = Date.now();
|
|
27425
27739
|
await runPostIndexWork(ctx);
|
|
27426
27740
|
} else {
|
|
27427
27741
|
serverLog("index", `[${ctx.name}] Cache miss: building from scratch`);
|
|
@@ -27446,6 +27760,7 @@ async function bootVault(ctx, startTime) {
|
|
|
27446
27760
|
serverLog("index", `[${ctx.name}] Failed to save index cache: ${err instanceof Error ? err.message : err}`, "error");
|
|
27447
27761
|
}
|
|
27448
27762
|
}
|
|
27763
|
+
lastFullRebuildAt = Date.now();
|
|
27449
27764
|
await runPostIndexWork(ctx);
|
|
27450
27765
|
} catch (err) {
|
|
27451
27766
|
updateIndexState("error", err instanceof Error ? err : new Error(String(err)));
|
|
@@ -27666,11 +27981,11 @@ async function updateEntitiesInStateDb(vp, sd) {
|
|
|
27666
27981
|
const vault = vp ?? vaultPath;
|
|
27667
27982
|
if (!db4) return;
|
|
27668
27983
|
try {
|
|
27669
|
-
const
|
|
27670
|
-
const excludeFolders =
|
|
27671
|
-
const entityIndex2 = await
|
|
27984
|
+
const config2 = loadConfig(db4);
|
|
27985
|
+
const excludeFolders = config2.exclude_entity_folders?.length ? config2.exclude_entity_folders : DEFAULT_ENTITY_EXCLUDE_FOLDERS;
|
|
27986
|
+
const entityIndex2 = await scanVaultEntities5(vault, {
|
|
27672
27987
|
excludeFolders,
|
|
27673
|
-
customCategories:
|
|
27988
|
+
customCategories: config2.custom_categories
|
|
27674
27989
|
});
|
|
27675
27990
|
db4.replaceAllEntities(entityIndex2);
|
|
27676
27991
|
serverLog("index", `Updated ${entityIndex2._metadata.total_entities} entities in StateDb`);
|
|
@@ -27817,7 +28132,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27817
28132
|
serverLog("semantic", "Embeddings up-to-date, skipping build");
|
|
27818
28133
|
loadEntityEmbeddingsToMemory();
|
|
27819
28134
|
if (sd) {
|
|
27820
|
-
const entities =
|
|
28135
|
+
const entities = getAllEntitiesFromDb6(sd);
|
|
27821
28136
|
if (entities.length > 0) {
|
|
27822
28137
|
saveInferredCategories(classifyUncategorizedEntities(
|
|
27823
28138
|
entities.map((entity) => ({
|
|
@@ -27854,7 +28169,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27854
28169
|
}
|
|
27855
28170
|
});
|
|
27856
28171
|
if (sd) {
|
|
27857
|
-
const entities =
|
|
28172
|
+
const entities = getAllEntitiesFromDb6(sd);
|
|
27858
28173
|
if (entities.length > 0) {
|
|
27859
28174
|
const entityMap = new Map(entities.map((e) => [e.name, {
|
|
27860
28175
|
name: e.name,
|
|
@@ -27869,7 +28184,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27869
28184
|
activateVault(ctx);
|
|
27870
28185
|
loadEntityEmbeddingsToMemory();
|
|
27871
28186
|
if (sd) {
|
|
27872
|
-
const entities =
|
|
28187
|
+
const entities = getAllEntitiesFromDb6(sd);
|
|
27873
28188
|
if (entities.length > 0) {
|
|
27874
28189
|
saveInferredCategories(classifyUncategorizedEntities(
|
|
27875
28190
|
entities.map((entity) => ({
|
|
@@ -27907,7 +28222,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27907
28222
|
serverLog("semantic", "Skipping \u2014 FLYWHEEL_SKIP_EMBEDDINGS");
|
|
27908
28223
|
}
|
|
27909
28224
|
if (process.env.FLYWHEEL_WATCH !== "false") {
|
|
27910
|
-
const
|
|
28225
|
+
const config2 = parseWatcherConfig();
|
|
27911
28226
|
const lastContentHashes = /* @__PURE__ */ new Map();
|
|
27912
28227
|
if (sd) {
|
|
27913
28228
|
const persisted = loadContentHashes(sd);
|
|
@@ -27916,7 +28231,15 @@ async function runPostIndexWork(ctx) {
|
|
|
27916
28231
|
serverLog("watcher", `Loaded ${persisted.size} persisted content hashes`);
|
|
27917
28232
|
}
|
|
27918
28233
|
}
|
|
27919
|
-
serverLog("watcher", `File watcher enabled (debounce: ${
|
|
28234
|
+
serverLog("watcher", `File watcher enabled (debounce: ${config2.debounceMs}ms)`);
|
|
28235
|
+
deferredScheduler = new DeferredStepScheduler();
|
|
28236
|
+
deferredScheduler.setExecutor({
|
|
28237
|
+
ctx,
|
|
28238
|
+
vp,
|
|
28239
|
+
sd,
|
|
28240
|
+
getVaultIndex: () => vaultIndex,
|
|
28241
|
+
updateEntitiesInStateDb
|
|
28242
|
+
});
|
|
27920
28243
|
const handleBatch = async (batch) => {
|
|
27921
28244
|
const vaultPrefixes = /* @__PURE__ */ new Set([
|
|
27922
28245
|
normalizePath(vp),
|
|
@@ -28057,13 +28380,14 @@ async function runPostIndexWork(ctx) {
|
|
|
28057
28380
|
updateVaultIndex,
|
|
28058
28381
|
updateEntitiesInStateDb,
|
|
28059
28382
|
getVaultIndex: () => vaultIndex,
|
|
28060
|
-
buildVaultIndex
|
|
28383
|
+
buildVaultIndex,
|
|
28384
|
+
deferredScheduler: deferredScheduler ?? void 0
|
|
28061
28385
|
});
|
|
28062
28386
|
await runner.run();
|
|
28063
28387
|
};
|
|
28064
28388
|
const watcher = createVaultWatcher({
|
|
28065
28389
|
vaultPath: vp,
|
|
28066
|
-
config,
|
|
28390
|
+
config: config2,
|
|
28067
28391
|
onBatch: handleBatch,
|
|
28068
28392
|
onStateChange: (status) => {
|
|
28069
28393
|
if (status.state === "dirty") {
|
|
@@ -28104,6 +28428,18 @@ async function runPostIndexWork(ctx) {
|
|
|
28104
28428
|
if (sd) runPeriodicMaintenance(sd);
|
|
28105
28429
|
});
|
|
28106
28430
|
serverLog("server", "Sweep timer started (5 min interval)");
|
|
28431
|
+
const maintenanceIntervalMs = parseInt(process.env.FLYWHEEL_MAINTENANCE_INTERVAL_MINUTES ?? "120", 10) * 60 * 1e3;
|
|
28432
|
+
startMaintenanceTimer({
|
|
28433
|
+
ctx,
|
|
28434
|
+
vp,
|
|
28435
|
+
sd,
|
|
28436
|
+
getVaultIndex: () => ctx.vaultIndex,
|
|
28437
|
+
updateEntitiesInStateDb,
|
|
28438
|
+
updateFlywheelConfig,
|
|
28439
|
+
getLastMcpRequestAt: () => lastMcpRequestAt,
|
|
28440
|
+
getLastFullRebuildAt: () => lastFullRebuildAt
|
|
28441
|
+
}, maintenanceIntervalMs);
|
|
28442
|
+
serverLog("server", `Maintenance timer started (~${Math.round(maintenanceIntervalMs / 6e4)}min interval)`);
|
|
28107
28443
|
}
|
|
28108
28444
|
const postDuration = Date.now() - postStart;
|
|
28109
28445
|
serverLog("server", `Post-index work complete in ${postDuration}ms`);
|
|
@@ -28151,7 +28487,12 @@ function gracefulShutdown(signal) {
|
|
|
28151
28487
|
watcherInstance?.stop();
|
|
28152
28488
|
} catch {
|
|
28153
28489
|
}
|
|
28490
|
+
try {
|
|
28491
|
+
deferredScheduler?.cancelAll();
|
|
28492
|
+
} catch {
|
|
28493
|
+
}
|
|
28154
28494
|
stopSweepTimer();
|
|
28495
|
+
stopMaintenanceTimer();
|
|
28155
28496
|
flushLogs().catch(() => {
|
|
28156
28497
|
}).finally(() => process.exit(0));
|
|
28157
28498
|
setTimeout(() => process.exit(0), 2e3).unref();
|