@velvetmonkey/flywheel-memory 2.5.9 → 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 +642 -312
- package/package.json +3 -3
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();
|
|
@@ -7375,21 +7375,21 @@ var DEFAULT_CONFIG = {
|
|
|
7375
7375
|
implicit_detection: true,
|
|
7376
7376
|
adaptive_strictness: true
|
|
7377
7377
|
};
|
|
7378
|
-
function migrateExcludeConfig(
|
|
7378
|
+
function migrateExcludeConfig(config2) {
|
|
7379
7379
|
const oldTags = [
|
|
7380
|
-
...
|
|
7381
|
-
...
|
|
7380
|
+
...config2.exclude_task_tags ?? [],
|
|
7381
|
+
...config2.exclude_analysis_tags ?? []
|
|
7382
7382
|
];
|
|
7383
|
-
const oldEntities =
|
|
7384
|
-
if (oldTags.length === 0 && oldEntities.length === 0) return
|
|
7383
|
+
const oldEntities = config2.exclude_entities ?? [];
|
|
7384
|
+
if (oldTags.length === 0 && oldEntities.length === 0) return config2;
|
|
7385
7385
|
const normalizedTags = oldTags.map((t) => t.startsWith("#") ? t : `#${t}`);
|
|
7386
7386
|
const merged = /* @__PURE__ */ new Set([
|
|
7387
|
-
...
|
|
7387
|
+
...config2.exclude ?? [],
|
|
7388
7388
|
...normalizedTags,
|
|
7389
7389
|
...oldEntities
|
|
7390
7390
|
]);
|
|
7391
7391
|
return {
|
|
7392
|
-
...
|
|
7392
|
+
...config2,
|
|
7393
7393
|
exclude: Array.from(merged),
|
|
7394
7394
|
// Clear deprecated fields
|
|
7395
7395
|
exclude_task_tags: void 0,
|
|
@@ -7488,11 +7488,11 @@ function inferConfig(index, vaultPath2) {
|
|
|
7488
7488
|
}
|
|
7489
7489
|
return inferred;
|
|
7490
7490
|
}
|
|
7491
|
-
function getExcludeTags(
|
|
7492
|
-
return (
|
|
7491
|
+
function getExcludeTags(config2) {
|
|
7492
|
+
return (config2.exclude ?? []).filter((e) => e.startsWith("#")).map((e) => e.slice(1));
|
|
7493
7493
|
}
|
|
7494
|
-
function getExcludeEntities(
|
|
7495
|
-
return (
|
|
7494
|
+
function getExcludeEntities(config2) {
|
|
7495
|
+
return (config2.exclude ?? []).filter((e) => !e.startsWith("#"));
|
|
7496
7496
|
}
|
|
7497
7497
|
var TEMPLATE_PATTERNS = {
|
|
7498
7498
|
daily: /^daily[\s._-]*(note|template)?\.md$/i,
|
|
@@ -7812,8 +7812,8 @@ var EventQueue = class {
|
|
|
7812
7812
|
config;
|
|
7813
7813
|
flushTimer = null;
|
|
7814
7814
|
onBatch;
|
|
7815
|
-
constructor(
|
|
7816
|
-
this.config =
|
|
7815
|
+
constructor(config2, onBatch) {
|
|
7816
|
+
this.config = config2;
|
|
7817
7817
|
this.onBatch = onBatch;
|
|
7818
7818
|
}
|
|
7819
7819
|
/**
|
|
@@ -8226,7 +8226,7 @@ init_serverLog();
|
|
|
8226
8226
|
// src/core/read/watch/index.ts
|
|
8227
8227
|
function createVaultWatcher(options) {
|
|
8228
8228
|
const { vaultPath: vaultPath2, onBatch, onStateChange, onError } = options;
|
|
8229
|
-
const
|
|
8229
|
+
const config2 = {
|
|
8230
8230
|
...DEFAULT_WATCHER_CONFIG,
|
|
8231
8231
|
...parseWatcherConfig(),
|
|
8232
8232
|
...options.config
|
|
@@ -8271,7 +8271,7 @@ function createVaultWatcher(options) {
|
|
|
8271
8271
|
}
|
|
8272
8272
|
}
|
|
8273
8273
|
};
|
|
8274
|
-
const eventQueue = new EventQueue(
|
|
8274
|
+
const eventQueue = new EventQueue(config2, processBatch2);
|
|
8275
8275
|
const instance = {
|
|
8276
8276
|
get status() {
|
|
8277
8277
|
return getStatus();
|
|
@@ -8284,8 +8284,8 @@ function createVaultWatcher(options) {
|
|
|
8284
8284
|
console.error("[flywheel] Watcher already started");
|
|
8285
8285
|
return;
|
|
8286
8286
|
}
|
|
8287
|
-
console.error(`[flywheel] Starting file watcher (debounce: ${
|
|
8288
|
-
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}`);
|
|
8289
8289
|
watcher = chokidar.watch(vaultPath2, {
|
|
8290
8290
|
ignored: createIgnoreFunction(vaultPath2),
|
|
8291
8291
|
persistent: true,
|
|
@@ -8294,8 +8294,8 @@ function createVaultWatcher(options) {
|
|
|
8294
8294
|
stabilityThreshold: 300,
|
|
8295
8295
|
pollInterval: 100
|
|
8296
8296
|
},
|
|
8297
|
-
usePolling:
|
|
8298
|
-
interval:
|
|
8297
|
+
usePolling: config2.usePolling,
|
|
8298
|
+
interval: config2.usePolling ? config2.pollInterval : void 0
|
|
8299
8299
|
});
|
|
8300
8300
|
watcher.on("add", (path39) => {
|
|
8301
8301
|
console.error(`[flywheel] RAW EVENT: add ${path39}`);
|
|
@@ -9479,6 +9479,104 @@ function refreshIfStale(vaultPath2, index, excludeTags) {
|
|
|
9479
9479
|
init_wikilinkFeedback();
|
|
9480
9480
|
init_corrections();
|
|
9481
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
|
+
};
|
|
9482
9580
|
var PIPELINE_TOTAL_STEPS = 22;
|
|
9483
9581
|
function createEmptyPipelineActivity() {
|
|
9484
9582
|
return {
|
|
@@ -9706,6 +9804,7 @@ var PipelineRunner = class {
|
|
|
9706
9804
|
tracker.skip("entity_scan", `cache valid (${Math.round(entityScanAgeMs / 1e3)}s old)`);
|
|
9707
9805
|
this.entitiesBefore = p.sd ? getAllEntitiesFromDb(p.sd) : [];
|
|
9708
9806
|
this.entitiesAfter = this.entitiesBefore;
|
|
9807
|
+
p.deferredScheduler?.schedule("entity_scan", 5 * 60 * 1e3 - entityScanAgeMs);
|
|
9709
9808
|
serverLog("watcher", `Entity scan: throttled (${Math.round(entityScanAgeMs / 1e3)}s old)`);
|
|
9710
9809
|
return;
|
|
9711
9810
|
}
|
|
@@ -9751,6 +9850,7 @@ var PipelineRunner = class {
|
|
|
9751
9850
|
const { p } = this;
|
|
9752
9851
|
const hubAgeMs = p.ctx.lastHubScoreRebuildAt > 0 ? Date.now() - p.ctx.lastHubScoreRebuildAt : Infinity;
|
|
9753
9852
|
if (hubAgeMs < 5 * 60 * 1e3) {
|
|
9853
|
+
p.deferredScheduler?.schedule("hub_scores", 5 * 60 * 1e3 - hubAgeMs);
|
|
9754
9854
|
serverLog("watcher", `Hub scores: throttled (${Math.round(hubAgeMs / 1e3)}s old)`);
|
|
9755
9855
|
return { skipped: true, age_ms: hubAgeMs };
|
|
9756
9856
|
}
|
|
@@ -9780,6 +9880,7 @@ var PipelineRunner = class {
|
|
|
9780
9880
|
serverLog("watcher", `Recency: rebuilt ${recencyIndex2.lastMentioned.size} entities`);
|
|
9781
9881
|
return { rebuilt: true, entities: recencyIndex2.lastMentioned.size };
|
|
9782
9882
|
}
|
|
9883
|
+
p.deferredScheduler?.schedule("recency", 60 * 60 * 1e3 - cacheAgeMs);
|
|
9783
9884
|
serverLog("watcher", `Recency: cache valid (${Math.round(cacheAgeMs / 1e3)}s old)`);
|
|
9784
9885
|
return { rebuilt: false, cached_age_ms: cacheAgeMs };
|
|
9785
9886
|
}
|
|
@@ -9799,6 +9900,7 @@ var PipelineRunner = class {
|
|
|
9799
9900
|
serverLog("watcher", `Co-occurrence: rebuilt ${cooccurrenceIdx._metadata.total_associations} associations`);
|
|
9800
9901
|
return { rebuilt: true, associations: cooccurrenceIdx._metadata.total_associations };
|
|
9801
9902
|
}
|
|
9903
|
+
p.deferredScheduler?.schedule("cooccurrence", 60 * 60 * 1e3 - cooccurrenceAgeMs);
|
|
9802
9904
|
serverLog("watcher", `Co-occurrence: cache valid (${Math.round(cooccurrenceAgeMs / 1e3)}s old)`);
|
|
9803
9905
|
return { rebuilt: false, age_ms: cooccurrenceAgeMs };
|
|
9804
9906
|
}
|
|
@@ -9821,6 +9923,7 @@ var PipelineRunner = class {
|
|
|
9821
9923
|
top_changes: result.top_changes
|
|
9822
9924
|
};
|
|
9823
9925
|
}
|
|
9926
|
+
p.deferredScheduler?.schedule("edge_weights", 60 * 60 * 1e3 - edgeWeightAgeMs);
|
|
9824
9927
|
serverLog("watcher", `Edge weights: cache valid (${Math.round(edgeWeightAgeMs / 1e3)}s old)`);
|
|
9825
9928
|
return { rebuilt: false, age_ms: edgeWeightAgeMs };
|
|
9826
9929
|
}
|
|
@@ -10810,7 +10913,7 @@ function getToolSelectionReport(stateDb2, daysBack = 7) {
|
|
|
10810
10913
|
}
|
|
10811
10914
|
|
|
10812
10915
|
// src/index.ts
|
|
10813
|
-
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";
|
|
10814
10917
|
|
|
10815
10918
|
// src/core/write/memory.ts
|
|
10816
10919
|
init_wikilinkFeedback();
|
|
@@ -11229,6 +11332,382 @@ function getSweepResults() {
|
|
|
11229
11332
|
return cachedResults;
|
|
11230
11333
|
}
|
|
11231
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
|
+
|
|
11232
11711
|
// src/core/shared/metrics.ts
|
|
11233
11712
|
init_wikilinkFeedback();
|
|
11234
11713
|
var ALL_METRICS = [
|
|
@@ -11617,193 +12096,6 @@ function purgeOldInvocations(stateDb2, retentionDays = 90) {
|
|
|
11617
12096
|
return result.changes;
|
|
11618
12097
|
}
|
|
11619
12098
|
|
|
11620
|
-
// src/core/shared/graphSnapshots.ts
|
|
11621
|
-
function computeGraphMetrics(index) {
|
|
11622
|
-
const noteCount = index.notes.size;
|
|
11623
|
-
if (noteCount === 0) {
|
|
11624
|
-
return {
|
|
11625
|
-
avg_degree: 0,
|
|
11626
|
-
max_degree: 0,
|
|
11627
|
-
cluster_count: 0,
|
|
11628
|
-
largest_cluster_size: 0,
|
|
11629
|
-
hub_scores_top10: []
|
|
11630
|
-
};
|
|
11631
|
-
}
|
|
11632
|
-
const degreeMap = /* @__PURE__ */ new Map();
|
|
11633
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
11634
|
-
for (const [notePath, note] of index.notes) {
|
|
11635
|
-
if (!adjacency.has(notePath)) adjacency.set(notePath, /* @__PURE__ */ new Set());
|
|
11636
|
-
let degree = note.outlinks.length;
|
|
11637
|
-
for (const link of note.outlinks) {
|
|
11638
|
-
const targetLower = link.target.toLowerCase();
|
|
11639
|
-
const resolvedPath = index.entities.get(targetLower);
|
|
11640
|
-
if (resolvedPath && index.notes.has(resolvedPath)) {
|
|
11641
|
-
adjacency.get(notePath).add(resolvedPath);
|
|
11642
|
-
if (!adjacency.has(resolvedPath)) adjacency.set(resolvedPath, /* @__PURE__ */ new Set());
|
|
11643
|
-
adjacency.get(resolvedPath).add(notePath);
|
|
11644
|
-
}
|
|
11645
|
-
}
|
|
11646
|
-
degreeMap.set(notePath, degree);
|
|
11647
|
-
}
|
|
11648
|
-
for (const [target, backlinks] of index.backlinks) {
|
|
11649
|
-
const targetLower = target.toLowerCase();
|
|
11650
|
-
const resolvedPath = index.entities.get(targetLower);
|
|
11651
|
-
if (resolvedPath && degreeMap.has(resolvedPath)) {
|
|
11652
|
-
degreeMap.set(resolvedPath, degreeMap.get(resolvedPath) + backlinks.length);
|
|
11653
|
-
}
|
|
11654
|
-
}
|
|
11655
|
-
let totalDegree = 0;
|
|
11656
|
-
let maxDegree = 0;
|
|
11657
|
-
let maxDegreeNote = "";
|
|
11658
|
-
for (const [notePath, degree] of degreeMap) {
|
|
11659
|
-
totalDegree += degree;
|
|
11660
|
-
if (degree > maxDegree) {
|
|
11661
|
-
maxDegree = degree;
|
|
11662
|
-
maxDegreeNote = notePath;
|
|
11663
|
-
}
|
|
11664
|
-
}
|
|
11665
|
-
const avgDegree = noteCount > 0 ? Math.round(totalDegree / noteCount * 100) / 100 : 0;
|
|
11666
|
-
const visited = /* @__PURE__ */ new Set();
|
|
11667
|
-
const clusters = [];
|
|
11668
|
-
for (const notePath of index.notes.keys()) {
|
|
11669
|
-
if (visited.has(notePath)) continue;
|
|
11670
|
-
const queue = [notePath];
|
|
11671
|
-
visited.add(notePath);
|
|
11672
|
-
let clusterSize = 0;
|
|
11673
|
-
while (queue.length > 0) {
|
|
11674
|
-
const current = queue.shift();
|
|
11675
|
-
clusterSize++;
|
|
11676
|
-
const neighbors = adjacency.get(current);
|
|
11677
|
-
if (neighbors) {
|
|
11678
|
-
for (const neighbor of neighbors) {
|
|
11679
|
-
if (!visited.has(neighbor)) {
|
|
11680
|
-
visited.add(neighbor);
|
|
11681
|
-
queue.push(neighbor);
|
|
11682
|
-
}
|
|
11683
|
-
}
|
|
11684
|
-
}
|
|
11685
|
-
}
|
|
11686
|
-
clusters.push(clusterSize);
|
|
11687
|
-
}
|
|
11688
|
-
const clusterCount = clusters.length;
|
|
11689
|
-
const largestClusterSize = clusters.length > 0 ? Math.max(...clusters) : 0;
|
|
11690
|
-
const sorted = Array.from(degreeMap.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
11691
|
-
const hubScoresTop10 = sorted.map(([notePath, degree]) => {
|
|
11692
|
-
const note = index.notes.get(notePath);
|
|
11693
|
-
return {
|
|
11694
|
-
entity: note?.title ?? notePath,
|
|
11695
|
-
degree
|
|
11696
|
-
};
|
|
11697
|
-
});
|
|
11698
|
-
return {
|
|
11699
|
-
avg_degree: avgDegree,
|
|
11700
|
-
max_degree: maxDegree,
|
|
11701
|
-
cluster_count: clusterCount,
|
|
11702
|
-
largest_cluster_size: largestClusterSize,
|
|
11703
|
-
hub_scores_top10: hubScoresTop10
|
|
11704
|
-
};
|
|
11705
|
-
}
|
|
11706
|
-
function recordGraphSnapshot(stateDb2, metrics) {
|
|
11707
|
-
const timestamp = Date.now();
|
|
11708
|
-
const insert = stateDb2.db.prepare(
|
|
11709
|
-
"INSERT INTO graph_snapshots (timestamp, metric, value, details) VALUES (?, ?, ?, ?)"
|
|
11710
|
-
);
|
|
11711
|
-
const transaction = stateDb2.db.transaction(() => {
|
|
11712
|
-
insert.run(timestamp, "avg_degree", metrics.avg_degree, null);
|
|
11713
|
-
insert.run(timestamp, "max_degree", metrics.max_degree, null);
|
|
11714
|
-
insert.run(timestamp, "cluster_count", metrics.cluster_count, null);
|
|
11715
|
-
insert.run(timestamp, "largest_cluster_size", metrics.largest_cluster_size, null);
|
|
11716
|
-
insert.run(
|
|
11717
|
-
timestamp,
|
|
11718
|
-
"hub_scores_top10",
|
|
11719
|
-
metrics.hub_scores_top10.length,
|
|
11720
|
-
JSON.stringify(metrics.hub_scores_top10)
|
|
11721
|
-
);
|
|
11722
|
-
});
|
|
11723
|
-
transaction();
|
|
11724
|
-
}
|
|
11725
|
-
function getEmergingHubs(stateDb2, daysBack = 30) {
|
|
11726
|
-
const cutoff = Date.now() - daysBack * 24 * 60 * 60 * 1e3;
|
|
11727
|
-
const latestRow = stateDb2.db.prepare(
|
|
11728
|
-
`SELECT details FROM graph_snapshots
|
|
11729
|
-
WHERE metric = 'hub_scores_top10'
|
|
11730
|
-
ORDER BY timestamp DESC LIMIT 1`
|
|
11731
|
-
).get();
|
|
11732
|
-
const previousRow = stateDb2.db.prepare(
|
|
11733
|
-
`SELECT details FROM graph_snapshots
|
|
11734
|
-
WHERE metric = 'hub_scores_top10' AND timestamp >= ?
|
|
11735
|
-
ORDER BY timestamp ASC LIMIT 1`
|
|
11736
|
-
).get(cutoff);
|
|
11737
|
-
if (!latestRow?.details) return [];
|
|
11738
|
-
const currentHubs = JSON.parse(latestRow.details);
|
|
11739
|
-
const previousHubs = previousRow?.details ? JSON.parse(previousRow.details) : [];
|
|
11740
|
-
const previousMap = /* @__PURE__ */ new Map();
|
|
11741
|
-
for (const hub of previousHubs) {
|
|
11742
|
-
previousMap.set(hub.entity, hub.degree);
|
|
11743
|
-
}
|
|
11744
|
-
const emerging = currentHubs.map((hub) => {
|
|
11745
|
-
const prevDegree = previousMap.get(hub.entity) ?? 0;
|
|
11746
|
-
return {
|
|
11747
|
-
entity: hub.entity,
|
|
11748
|
-
current_degree: hub.degree,
|
|
11749
|
-
previous_degree: prevDegree,
|
|
11750
|
-
growth: hub.degree - prevDegree
|
|
11751
|
-
};
|
|
11752
|
-
});
|
|
11753
|
-
emerging.sort((a, b) => b.growth - a.growth);
|
|
11754
|
-
return emerging;
|
|
11755
|
-
}
|
|
11756
|
-
function compareGraphSnapshots(stateDb2, timestampBefore, timestampAfter) {
|
|
11757
|
-
const SCALAR_METRICS = ["avg_degree", "max_degree", "cluster_count", "largest_cluster_size"];
|
|
11758
|
-
function getSnapshotAt(ts) {
|
|
11759
|
-
const row = stateDb2.db.prepare(
|
|
11760
|
-
`SELECT DISTINCT timestamp FROM graph_snapshots WHERE timestamp <= ? ORDER BY timestamp DESC LIMIT 1`
|
|
11761
|
-
).get(ts);
|
|
11762
|
-
if (!row) return null;
|
|
11763
|
-
const rows = stateDb2.db.prepare(
|
|
11764
|
-
`SELECT metric, value, details FROM graph_snapshots WHERE timestamp = ?`
|
|
11765
|
-
).all(row.timestamp);
|
|
11766
|
-
return rows;
|
|
11767
|
-
}
|
|
11768
|
-
const beforeRows = getSnapshotAt(timestampBefore) ?? [];
|
|
11769
|
-
const afterRows = getSnapshotAt(timestampAfter) ?? [];
|
|
11770
|
-
const beforeMap = /* @__PURE__ */ new Map();
|
|
11771
|
-
const afterMap = /* @__PURE__ */ new Map();
|
|
11772
|
-
for (const r of beforeRows) beforeMap.set(r.metric, { value: r.value, details: r.details });
|
|
11773
|
-
for (const r of afterRows) afterMap.set(r.metric, { value: r.value, details: r.details });
|
|
11774
|
-
const metricChanges = SCALAR_METRICS.map((metric) => {
|
|
11775
|
-
const before = beforeMap.get(metric)?.value ?? 0;
|
|
11776
|
-
const after = afterMap.get(metric)?.value ?? 0;
|
|
11777
|
-
const delta = after - before;
|
|
11778
|
-
const deltaPercent = before !== 0 ? Math.round(delta / before * 1e4) / 100 : delta !== 0 ? 100 : 0;
|
|
11779
|
-
return { metric, before, after, delta, deltaPercent };
|
|
11780
|
-
});
|
|
11781
|
-
const beforeHubs = beforeMap.get("hub_scores_top10")?.details ? JSON.parse(beforeMap.get("hub_scores_top10").details) : [];
|
|
11782
|
-
const afterHubs = afterMap.get("hub_scores_top10")?.details ? JSON.parse(afterMap.get("hub_scores_top10").details) : [];
|
|
11783
|
-
const beforeHubMap = /* @__PURE__ */ new Map();
|
|
11784
|
-
for (const h of beforeHubs) beforeHubMap.set(h.entity, h.degree);
|
|
11785
|
-
const afterHubMap = /* @__PURE__ */ new Map();
|
|
11786
|
-
for (const h of afterHubs) afterHubMap.set(h.entity, h.degree);
|
|
11787
|
-
const allHubEntities = /* @__PURE__ */ new Set([...beforeHubMap.keys(), ...afterHubMap.keys()]);
|
|
11788
|
-
const hubScoreChanges = [];
|
|
11789
|
-
for (const entity of allHubEntities) {
|
|
11790
|
-
const before = beforeHubMap.get(entity) ?? 0;
|
|
11791
|
-
const after = afterHubMap.get(entity) ?? 0;
|
|
11792
|
-
if (before !== after) {
|
|
11793
|
-
hubScoreChanges.push({ entity, before, after, delta: after - before });
|
|
11794
|
-
}
|
|
11795
|
-
}
|
|
11796
|
-
hubScoreChanges.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
|
|
11797
|
-
return { metricChanges, hubScoreChanges };
|
|
11798
|
-
}
|
|
11799
|
-
function purgeOldSnapshots(stateDb2, retentionDays = 90) {
|
|
11800
|
-
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
11801
|
-
const result = stateDb2.db.prepare(
|
|
11802
|
-
"DELETE FROM graph_snapshots WHERE timestamp < ?"
|
|
11803
|
-
).run(cutoff);
|
|
11804
|
-
return result.changes;
|
|
11805
|
-
}
|
|
11806
|
-
|
|
11807
12099
|
// src/index.ts
|
|
11808
12100
|
init_serverLog();
|
|
11809
12101
|
init_wikilinkFeedback();
|
|
@@ -12684,8 +12976,8 @@ var DEFAULT_CONFIG2 = {
|
|
|
12684
12976
|
maxOutlinksPerHop: 10,
|
|
12685
12977
|
maxBackfill: 10
|
|
12686
12978
|
};
|
|
12687
|
-
function multiHopBackfill(primaryResults, index, stateDb2,
|
|
12688
|
-
const cfg = { ...DEFAULT_CONFIG2, ...
|
|
12979
|
+
function multiHopBackfill(primaryResults, index, stateDb2, config2 = {}) {
|
|
12980
|
+
const cfg = { ...DEFAULT_CONFIG2, ...config2 };
|
|
12689
12981
|
const seen = new Set(primaryResults.map((r) => r.path).filter(Boolean));
|
|
12690
12982
|
const candidates = [];
|
|
12691
12983
|
const hop1Results = [];
|
|
@@ -15501,7 +15793,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15501
15793
|
}
|
|
15502
15794
|
const indexBuilt = indexState2 === "ready" && index !== void 0 && index.notes !== void 0;
|
|
15503
15795
|
let lastIndexActivityAt;
|
|
15504
|
-
let
|
|
15796
|
+
let lastFullRebuildAt2;
|
|
15505
15797
|
let lastWatcherBatchAt;
|
|
15506
15798
|
let lastBuild;
|
|
15507
15799
|
let lastManual;
|
|
@@ -15511,13 +15803,13 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15511
15803
|
if (lastAny) lastIndexActivityAt = lastAny.timestamp;
|
|
15512
15804
|
lastBuild = getLastEventByTrigger(stateDb2, "startup_build") ?? void 0;
|
|
15513
15805
|
lastManual = getLastEventByTrigger(stateDb2, "manual_refresh") ?? void 0;
|
|
15514
|
-
|
|
15806
|
+
lastFullRebuildAt2 = Math.max(lastBuild?.timestamp ?? 0, lastManual?.timestamp ?? 0) || void 0;
|
|
15515
15807
|
const lastWatcher = getLastEventByTrigger(stateDb2, "watcher");
|
|
15516
15808
|
if (lastWatcher) lastWatcherBatchAt = lastWatcher.timestamp;
|
|
15517
15809
|
} catch {
|
|
15518
15810
|
}
|
|
15519
15811
|
}
|
|
15520
|
-
const freshnessTimestamp =
|
|
15812
|
+
const freshnessTimestamp = lastFullRebuildAt2 ?? (indexBuilt && index.builtAt ? index.builtAt.getTime() : void 0);
|
|
15521
15813
|
const indexAge = freshnessTimestamp ? Math.floor((Date.now() - freshnessTimestamp) / 1e3) : -1;
|
|
15522
15814
|
const indexStale = indexBuilt && indexAge > STALE_THRESHOLD_SECONDS;
|
|
15523
15815
|
if (indexState2 === "building") {
|
|
@@ -15564,8 +15856,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15564
15856
|
}
|
|
15565
15857
|
let configInfo;
|
|
15566
15858
|
if (isFull) {
|
|
15567
|
-
const
|
|
15568
|
-
configInfo = Object.keys(
|
|
15859
|
+
const config2 = getConfig2();
|
|
15860
|
+
configInfo = Object.keys(config2).length > 0 ? config2 : void 0;
|
|
15569
15861
|
}
|
|
15570
15862
|
let lastRebuild;
|
|
15571
15863
|
if (stateDb2) {
|
|
@@ -15694,15 +15986,15 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15694
15986
|
watcher_pending: getWatcherStatus2()?.pendingEvents,
|
|
15695
15987
|
last_index_activity_at: lastIndexActivityAt,
|
|
15696
15988
|
last_index_activity_ago_seconds: lastIndexActivityAt ? Math.floor((Date.now() - lastIndexActivityAt) / 1e3) : void 0,
|
|
15697
|
-
last_full_rebuild_at:
|
|
15989
|
+
last_full_rebuild_at: lastFullRebuildAt2,
|
|
15698
15990
|
last_watcher_batch_at: lastWatcherBatchAt,
|
|
15699
15991
|
pipeline_activity: pipelineActivity,
|
|
15700
15992
|
dead_link_count: isFull ? deadLinkCount : void 0,
|
|
15701
15993
|
top_dead_link_targets: isFull ? topDeadLinkTargets : void 0,
|
|
15702
15994
|
sweep: isFull ? getSweepResults() ?? void 0 : void 0,
|
|
15703
15995
|
proactive_linking: isFull && stateDb2 ? (() => {
|
|
15704
|
-
const
|
|
15705
|
-
const enabled =
|
|
15996
|
+
const config2 = getConfig2();
|
|
15997
|
+
const enabled = config2.proactive_linking !== false;
|
|
15706
15998
|
const queuePending = stateDb2.db.prepare(
|
|
15707
15999
|
`SELECT COUNT(*) as cnt FROM proactive_queue WHERE status = 'pending'`
|
|
15708
16000
|
).get();
|
|
@@ -16348,7 +16640,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
16348
16640
|
|
|
16349
16641
|
// src/tools/read/system.ts
|
|
16350
16642
|
import { z as z5 } from "zod";
|
|
16351
|
-
import { scanVaultEntities as
|
|
16643
|
+
import { scanVaultEntities as scanVaultEntities3, getEntityIndexFromDb as getEntityIndexFromDb2, getAllEntitiesFromDb as getAllEntitiesFromDb3 } from "@velvetmonkey/vault-core";
|
|
16352
16644
|
|
|
16353
16645
|
// src/core/read/aliasSuggestions.ts
|
|
16354
16646
|
import { STOPWORDS_EN as STOPWORDS_EN3 } from "@velvetmonkey/vault-core";
|
|
@@ -16465,11 +16757,11 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16465
16757
|
if (stateDb2) {
|
|
16466
16758
|
tracker.start("entity_sync", {});
|
|
16467
16759
|
try {
|
|
16468
|
-
const
|
|
16469
|
-
const excludeFolders =
|
|
16470
|
-
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, {
|
|
16471
16763
|
excludeFolders,
|
|
16472
|
-
customCategories:
|
|
16764
|
+
customCategories: config2.custom_categories
|
|
16473
16765
|
});
|
|
16474
16766
|
stateDb2.replaceAllEntities(entityIndex2);
|
|
16475
16767
|
tracker.end({ entities: entityIndex2._metadata.total_entities });
|
|
@@ -16591,7 +16883,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16591
16883
|
if (stateDb2) {
|
|
16592
16884
|
tracker.start("recency", {});
|
|
16593
16885
|
try {
|
|
16594
|
-
const entities =
|
|
16886
|
+
const entities = getAllEntitiesFromDb3(stateDb2).map((e) => ({
|
|
16595
16887
|
name: e.name,
|
|
16596
16888
|
path: e.path,
|
|
16597
16889
|
aliases: e.aliases
|
|
@@ -16610,7 +16902,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16610
16902
|
if (stateDb2) {
|
|
16611
16903
|
tracker.start("cooccurrence", {});
|
|
16612
16904
|
try {
|
|
16613
|
-
const entityNames =
|
|
16905
|
+
const entityNames = getAllEntitiesFromDb3(stateDb2).map((e) => e.name);
|
|
16614
16906
|
const cooccurrenceIdx = await mineCooccurrences(vaultPath2, entityNames);
|
|
16615
16907
|
setCooccurrenceIndex(cooccurrenceIdx);
|
|
16616
16908
|
saveCooccurrenceToStateDb(stateDb2, cooccurrenceIdx);
|
|
@@ -16655,7 +16947,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
16655
16947
|
if (stateDb2 && hasEntityEmbeddingsIndex()) {
|
|
16656
16948
|
tracker.start("entity_embeddings", {});
|
|
16657
16949
|
try {
|
|
16658
|
-
const entities =
|
|
16950
|
+
const entities = getAllEntitiesFromDb3(stateDb2);
|
|
16659
16951
|
if (entities.length > 0) {
|
|
16660
16952
|
const entityMap = new Map(entities.map((e) => [e.name, {
|
|
16661
16953
|
name: e.name,
|
|
@@ -17082,9 +17374,9 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17082
17374
|
const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
|
|
17083
17375
|
const index = getIndex();
|
|
17084
17376
|
const vaultPath2 = getVaultPath();
|
|
17085
|
-
const
|
|
17377
|
+
const config2 = getConfig2();
|
|
17086
17378
|
if (path39) {
|
|
17087
|
-
const result2 = await getTasksFromNote(index, path39, vaultPath2, getExcludeTags(
|
|
17379
|
+
const result2 = await getTasksFromNote(index, path39, vaultPath2, getExcludeTags(config2));
|
|
17088
17380
|
if (!result2) {
|
|
17089
17381
|
return {
|
|
17090
17382
|
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path39 }, null, 2) }]
|
|
@@ -17107,12 +17399,12 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17107
17399
|
};
|
|
17108
17400
|
}
|
|
17109
17401
|
if (isTaskCacheReady()) {
|
|
17110
|
-
refreshIfStale(vaultPath2, index, getExcludeTags(
|
|
17402
|
+
refreshIfStale(vaultPath2, index, getExcludeTags(config2));
|
|
17111
17403
|
if (has_due_date) {
|
|
17112
17404
|
const result3 = queryTasksFromCache({
|
|
17113
17405
|
status,
|
|
17114
17406
|
folder,
|
|
17115
|
-
excludeTags: getExcludeTags(
|
|
17407
|
+
excludeTags: getExcludeTags(config2),
|
|
17116
17408
|
has_due_date: true,
|
|
17117
17409
|
limit,
|
|
17118
17410
|
offset
|
|
@@ -17129,7 +17421,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17129
17421
|
status,
|
|
17130
17422
|
folder,
|
|
17131
17423
|
tag,
|
|
17132
|
-
excludeTags: getExcludeTags(
|
|
17424
|
+
excludeTags: getExcludeTags(config2),
|
|
17133
17425
|
limit,
|
|
17134
17426
|
offset
|
|
17135
17427
|
});
|
|
@@ -17148,7 +17440,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17148
17440
|
const allResults = await getTasksWithDueDates(index, vaultPath2, {
|
|
17149
17441
|
status,
|
|
17150
17442
|
folder,
|
|
17151
|
-
excludeTags: getExcludeTags(
|
|
17443
|
+
excludeTags: getExcludeTags(config2)
|
|
17152
17444
|
});
|
|
17153
17445
|
const paged2 = allResults.slice(offset, offset + limit);
|
|
17154
17446
|
return {
|
|
@@ -17164,7 +17456,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17164
17456
|
folder,
|
|
17165
17457
|
tag,
|
|
17166
17458
|
limit: limit + offset,
|
|
17167
|
-
excludeTags: getExcludeTags(
|
|
17459
|
+
excludeTags: getExcludeTags(config2)
|
|
17168
17460
|
});
|
|
17169
17461
|
const paged = result.tasks.slice(offset, offset + limit);
|
|
17170
17462
|
return {
|
|
@@ -17831,10 +18123,10 @@ function isTemplatePath(notePath) {
|
|
|
17831
18123
|
const folder = notePath.split("/")[0]?.toLowerCase() || "";
|
|
17832
18124
|
return folder === "templates" || folder === "template";
|
|
17833
18125
|
}
|
|
17834
|
-
function getExcludedPaths(index,
|
|
18126
|
+
function getExcludedPaths(index, config2) {
|
|
17835
18127
|
const excluded = /* @__PURE__ */ new Set();
|
|
17836
|
-
const excludeTags = new Set(getExcludeTags(
|
|
17837
|
-
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()));
|
|
17838
18130
|
if (excludeTags.size === 0 && excludeEntities.size === 0) return excluded;
|
|
17839
18131
|
for (const note of index.notes.values()) {
|
|
17840
18132
|
if (excludeTags.size > 0) {
|
|
@@ -17881,8 +18173,8 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb4
|
|
|
17881
18173
|
requireIndex();
|
|
17882
18174
|
const limit = Math.min(requestedLimit ?? 50, MAX_LIMIT);
|
|
17883
18175
|
const index = getIndex();
|
|
17884
|
-
const
|
|
17885
|
-
const excludedPaths = getExcludedPaths(index,
|
|
18176
|
+
const config2 = getConfig2?.() ?? {};
|
|
18177
|
+
const excludedPaths = getExcludedPaths(index, config2);
|
|
17886
18178
|
switch (analysis) {
|
|
17887
18179
|
case "orphans": {
|
|
17888
18180
|
const allOrphans = findOrphanNotes(index, folder).filter((o) => !isPeriodicNote(o.path) && !excludedPaths.has(o.path));
|
|
@@ -18050,7 +18342,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb4
|
|
|
18050
18342
|
return !note || !excludedPaths.has(note.path);
|
|
18051
18343
|
});
|
|
18052
18344
|
}
|
|
18053
|
-
const excludeEntities = new Set(getExcludeEntities(
|
|
18345
|
+
const excludeEntities = new Set(getExcludeEntities(config2).map((e) => e.toLowerCase()));
|
|
18054
18346
|
if (excludeEntities.size > 0) {
|
|
18055
18347
|
hubs = hubs.filter((hub) => !excludeEntities.has(hub.entity.toLowerCase()));
|
|
18056
18348
|
}
|
|
@@ -19689,14 +19981,14 @@ async function executeDeleteNote(options) {
|
|
|
19689
19981
|
}
|
|
19690
19982
|
|
|
19691
19983
|
// src/tools/write/mutations.ts
|
|
19692
|
-
async function createNoteFromTemplate(vaultPath2, notePath,
|
|
19984
|
+
async function createNoteFromTemplate(vaultPath2, notePath, config2) {
|
|
19693
19985
|
const validation = await validatePathSecure(vaultPath2, notePath);
|
|
19694
19986
|
if (!validation.valid) {
|
|
19695
19987
|
throw new Error(`Path blocked: ${validation.reason}`);
|
|
19696
19988
|
}
|
|
19697
19989
|
const fullPath = path25.join(vaultPath2, notePath);
|
|
19698
19990
|
await fs23.mkdir(path25.dirname(fullPath), { recursive: true });
|
|
19699
|
-
const templates =
|
|
19991
|
+
const templates = config2.templates || {};
|
|
19700
19992
|
const filename = path25.basename(notePath, ".md").toLowerCase();
|
|
19701
19993
|
let templatePath;
|
|
19702
19994
|
let periodicType;
|
|
@@ -19809,8 +20101,8 @@ function registerMutationTools(server2, getVaultPath, getConfig2 = () => ({})) {
|
|
|
19809
20101
|
try {
|
|
19810
20102
|
await fs23.access(fullPath);
|
|
19811
20103
|
} catch {
|
|
19812
|
-
const
|
|
19813
|
-
const result = await createNoteFromTemplate(vaultPath2, notePath,
|
|
20104
|
+
const config2 = getConfig2();
|
|
20105
|
+
const result = await createNoteFromTemplate(vaultPath2, notePath, config2);
|
|
19814
20106
|
noteCreated = result.created;
|
|
19815
20107
|
templateUsed = result.templateUsed;
|
|
19816
20108
|
}
|
|
@@ -23916,9 +24208,9 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb4) {
|
|
|
23916
24208
|
async ({ mode, key, value }) => {
|
|
23917
24209
|
switch (mode) {
|
|
23918
24210
|
case "get": {
|
|
23919
|
-
const
|
|
24211
|
+
const config2 = getConfig2();
|
|
23920
24212
|
return {
|
|
23921
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
24213
|
+
content: [{ type: "text", text: JSON.stringify(config2, null, 2) }]
|
|
23922
24214
|
};
|
|
23923
24215
|
}
|
|
23924
24216
|
case "set": {
|
|
@@ -23969,7 +24261,7 @@ init_wikilinkFeedback();
|
|
|
23969
24261
|
import { z as z28 } from "zod";
|
|
23970
24262
|
import * as fs32 from "fs/promises";
|
|
23971
24263
|
import * as path35 from "path";
|
|
23972
|
-
import { scanVaultEntities as
|
|
24264
|
+
import { scanVaultEntities as scanVaultEntities4, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
|
|
23973
24265
|
init_embeddings();
|
|
23974
24266
|
function hasSkipWikilinks(content) {
|
|
23975
24267
|
if (!content.startsWith("---")) return false;
|
|
@@ -24087,8 +24379,8 @@ async function executeRun(stateDb2, vaultPath2) {
|
|
|
24087
24379
|
if (entityCount === 0) {
|
|
24088
24380
|
const start = Date.now();
|
|
24089
24381
|
try {
|
|
24090
|
-
const
|
|
24091
|
-
const entityIndex2 = await
|
|
24382
|
+
const config2 = loadConfig(stateDb2);
|
|
24383
|
+
const entityIndex2 = await scanVaultEntities4(vaultPath2, { excludeFolders: EXCLUDE_FOLDERS, customCategories: config2.custom_categories });
|
|
24092
24384
|
stateDb2.replaceAllEntities(entityIndex2);
|
|
24093
24385
|
const newCount = entityIndex2._metadata.total_entities;
|
|
24094
24386
|
steps.push({
|
|
@@ -24669,7 +24961,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
24669
24961
|
// src/tools/read/semantic.ts
|
|
24670
24962
|
init_embeddings();
|
|
24671
24963
|
import { z as z31 } from "zod";
|
|
24672
|
-
import { getAllEntitiesFromDb as
|
|
24964
|
+
import { getAllEntitiesFromDb as getAllEntitiesFromDb4 } from "@velvetmonkey/vault-core";
|
|
24673
24965
|
function registerSemanticTools(server2, getVaultPath, getStateDb4) {
|
|
24674
24966
|
server2.registerTool(
|
|
24675
24967
|
"init_semantic",
|
|
@@ -24734,7 +25026,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb4) {
|
|
|
24734
25026
|
const embedded = progress.total - progress.skipped;
|
|
24735
25027
|
let entityEmbedded = 0;
|
|
24736
25028
|
try {
|
|
24737
|
-
const allEntities =
|
|
25029
|
+
const allEntities = getAllEntitiesFromDb4(stateDb2);
|
|
24738
25030
|
const entityMap = /* @__PURE__ */ new Map();
|
|
24739
25031
|
for (const e of allEntities) {
|
|
24740
25032
|
entityMap.set(e.name, {
|
|
@@ -24796,7 +25088,7 @@ function registerSemanticTools(server2, getVaultPath, getStateDb4) {
|
|
|
24796
25088
|
// src/tools/read/merges.ts
|
|
24797
25089
|
init_levenshtein();
|
|
24798
25090
|
import { z as z32 } from "zod";
|
|
24799
|
-
import { getAllEntitiesFromDb as
|
|
25091
|
+
import { getAllEntitiesFromDb as getAllEntitiesFromDb5, getDismissedMergePairs, recordMergeDismissal } from "@velvetmonkey/vault-core";
|
|
24800
25092
|
function normalizeName(name) {
|
|
24801
25093
|
return name.toLowerCase().replace(/[.\-_]/g, "").replace(/js$/, "").replace(/ts$/, "");
|
|
24802
25094
|
}
|
|
@@ -24814,7 +25106,7 @@ function registerMergeTools2(server2, getStateDb4) {
|
|
|
24814
25106
|
content: [{ type: "text", text: JSON.stringify({ suggestions: [], error: "StateDb not available" }) }]
|
|
24815
25107
|
};
|
|
24816
25108
|
}
|
|
24817
|
-
const entities =
|
|
25109
|
+
const entities = getAllEntitiesFromDb5(stateDb2);
|
|
24818
25110
|
if (entities.length === 0) {
|
|
24819
25111
|
return {
|
|
24820
25112
|
content: [{ type: "text", text: JSON.stringify({ suggestions: [] }) }]
|
|
@@ -26297,7 +26589,7 @@ function queryFlywheelAgeDays(stateDb2) {
|
|
|
26297
26589
|
if (!row?.first_ts) return 0;
|
|
26298
26590
|
return Math.floor((Date.now() - row.first_ts) / (24 * 60 * 60 * 1e3));
|
|
26299
26591
|
}
|
|
26300
|
-
function getCalibrationExport(stateDb2, metrics,
|
|
26592
|
+
function getCalibrationExport(stateDb2, metrics, config2, daysBack = 30, includeVaultId = true) {
|
|
26301
26593
|
const now = /* @__PURE__ */ new Date();
|
|
26302
26594
|
const start = new Date(now);
|
|
26303
26595
|
start.setDate(start.getDate() - daysBack + 1);
|
|
@@ -26320,8 +26612,8 @@ function getCalibrationExport(stateDb2, metrics, config, daysBack = 30, includeV
|
|
|
26320
26612
|
connected_ratio: round(metrics.connected_ratio),
|
|
26321
26613
|
semantic_enabled: hasEmbeddingsIndex(),
|
|
26322
26614
|
flywheel_age_days: queryFlywheelAgeDays(stateDb2),
|
|
26323
|
-
strictness_mode:
|
|
26324
|
-
adaptive_strictness:
|
|
26615
|
+
strictness_mode: config2.wikilink_strictness ?? "balanced",
|
|
26616
|
+
adaptive_strictness: config2.adaptive_strictness ?? true
|
|
26325
26617
|
},
|
|
26326
26618
|
entity_distribution: queryEntityDistribution(stateDb2),
|
|
26327
26619
|
funnel: queryFunnel2(stateDb2, startMs, startIso, endIso),
|
|
@@ -26355,11 +26647,11 @@ function registerCalibrationExportTools(server2, getIndex, getStateDb4, getConfi
|
|
|
26355
26647
|
}
|
|
26356
26648
|
const index = getIndex();
|
|
26357
26649
|
const metrics = computeMetrics(index, stateDb2);
|
|
26358
|
-
const
|
|
26650
|
+
const config2 = getConfig2();
|
|
26359
26651
|
const report = getCalibrationExport(
|
|
26360
26652
|
stateDb2,
|
|
26361
26653
|
metrics,
|
|
26362
|
-
|
|
26654
|
+
config2,
|
|
26363
26655
|
args.days_back ?? 30,
|
|
26364
26656
|
args.include_vault_id ?? true
|
|
26365
26657
|
);
|
|
@@ -26613,7 +26905,7 @@ function extractSearchMethod(result) {
|
|
|
26613
26905
|
}
|
|
26614
26906
|
return void 0;
|
|
26615
26907
|
}
|
|
26616
|
-
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) {
|
|
26617
26909
|
let _registered = 0;
|
|
26618
26910
|
let _skipped = 0;
|
|
26619
26911
|
let tierOverride = "auto";
|
|
@@ -26706,6 +26998,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
26706
26998
|
}
|
|
26707
26999
|
function wrapWithTracking(toolName, handler) {
|
|
26708
27000
|
return async (...args) => {
|
|
27001
|
+
onToolCall?.();
|
|
26709
27002
|
const start = Date.now();
|
|
26710
27003
|
let success = true;
|
|
26711
27004
|
let notePaths;
|
|
@@ -27125,6 +27418,9 @@ var httpListener = null;
|
|
|
27125
27418
|
var watchdogTimer = null;
|
|
27126
27419
|
var serverReady = false;
|
|
27127
27420
|
var shutdownRequested = false;
|
|
27421
|
+
var lastMcpRequestAt = 0;
|
|
27422
|
+
var lastFullRebuildAt = 0;
|
|
27423
|
+
var deferredScheduler = null;
|
|
27128
27424
|
function getWatcherStatus() {
|
|
27129
27425
|
if (vaultRegistry) {
|
|
27130
27426
|
const name = globalThis.__flywheel_active_vault;
|
|
@@ -27158,8 +27454,8 @@ function handleTierStateChange(controller) {
|
|
|
27158
27454
|
syncRuntimeTierState(controller);
|
|
27159
27455
|
invalidateHttpPool();
|
|
27160
27456
|
}
|
|
27161
|
-
function getConfigToolTierOverride(
|
|
27162
|
-
return
|
|
27457
|
+
function getConfigToolTierOverride(config2) {
|
|
27458
|
+
return config2.tool_tier_override ?? "auto";
|
|
27163
27459
|
}
|
|
27164
27460
|
function buildRegistryContext() {
|
|
27165
27461
|
return {
|
|
@@ -27191,7 +27487,10 @@ function createConfiguredServer() {
|
|
|
27191
27487
|
buildVaultCallbacks(),
|
|
27192
27488
|
toolTierMode,
|
|
27193
27489
|
handleTierStateChange,
|
|
27194
|
-
toolConfig.isFullToolset
|
|
27490
|
+
toolConfig.isFullToolset,
|
|
27491
|
+
() => {
|
|
27492
|
+
lastMcpRequestAt = Date.now();
|
|
27493
|
+
}
|
|
27195
27494
|
);
|
|
27196
27495
|
registerAllTools(s, ctx, toolTierController);
|
|
27197
27496
|
toolTierController.setOverride(runtimeToolTierOverride);
|
|
@@ -27245,7 +27544,10 @@ var _gatingResult = applyToolGating(
|
|
|
27245
27544
|
buildVaultCallbacks(),
|
|
27246
27545
|
toolTierMode,
|
|
27247
27546
|
handleTierStateChange,
|
|
27248
|
-
toolConfig.isFullToolset
|
|
27547
|
+
toolConfig.isFullToolset,
|
|
27548
|
+
() => {
|
|
27549
|
+
lastMcpRequestAt = Date.now();
|
|
27550
|
+
}
|
|
27249
27551
|
);
|
|
27250
27552
|
registerAllTools(server, _registryCtx, _gatingResult);
|
|
27251
27553
|
_gatingResult.setOverride(runtimeToolTierOverride);
|
|
@@ -27368,17 +27670,17 @@ function updateVaultIndex(index) {
|
|
|
27368
27670
|
const ctx = getActiveVaultContext();
|
|
27369
27671
|
if (ctx) ctx.vaultIndex = index;
|
|
27370
27672
|
}
|
|
27371
|
-
function updateFlywheelConfig(
|
|
27372
|
-
flywheelConfig =
|
|
27373
|
-
setWikilinkConfig(
|
|
27673
|
+
function updateFlywheelConfig(config2) {
|
|
27674
|
+
flywheelConfig = config2;
|
|
27675
|
+
setWikilinkConfig(config2);
|
|
27374
27676
|
if (toolTierMode === "tiered" && primaryToolTierController) {
|
|
27375
|
-
primaryToolTierController.setOverride(getConfigToolTierOverride(
|
|
27677
|
+
primaryToolTierController.setOverride(getConfigToolTierOverride(config2));
|
|
27376
27678
|
syncRuntimeTierState(primaryToolTierController);
|
|
27377
27679
|
invalidateHttpPool();
|
|
27378
27680
|
}
|
|
27379
27681
|
const ctx = getActiveVaultContext();
|
|
27380
27682
|
if (ctx) {
|
|
27381
|
-
ctx.flywheelConfig =
|
|
27683
|
+
ctx.flywheelConfig = config2;
|
|
27382
27684
|
setActiveScope(buildVaultScope(ctx));
|
|
27383
27685
|
}
|
|
27384
27686
|
}
|
|
@@ -27433,6 +27735,7 @@ async function bootVault(ctx, startTime) {
|
|
|
27433
27735
|
note_count: cachedIndex.notes.size
|
|
27434
27736
|
});
|
|
27435
27737
|
}
|
|
27738
|
+
lastFullRebuildAt = Date.now();
|
|
27436
27739
|
await runPostIndexWork(ctx);
|
|
27437
27740
|
} else {
|
|
27438
27741
|
serverLog("index", `[${ctx.name}] Cache miss: building from scratch`);
|
|
@@ -27457,6 +27760,7 @@ async function bootVault(ctx, startTime) {
|
|
|
27457
27760
|
serverLog("index", `[${ctx.name}] Failed to save index cache: ${err instanceof Error ? err.message : err}`, "error");
|
|
27458
27761
|
}
|
|
27459
27762
|
}
|
|
27763
|
+
lastFullRebuildAt = Date.now();
|
|
27460
27764
|
await runPostIndexWork(ctx);
|
|
27461
27765
|
} catch (err) {
|
|
27462
27766
|
updateIndexState("error", err instanceof Error ? err : new Error(String(err)));
|
|
@@ -27677,11 +27981,11 @@ async function updateEntitiesInStateDb(vp, sd) {
|
|
|
27677
27981
|
const vault = vp ?? vaultPath;
|
|
27678
27982
|
if (!db4) return;
|
|
27679
27983
|
try {
|
|
27680
|
-
const
|
|
27681
|
-
const excludeFolders =
|
|
27682
|
-
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, {
|
|
27683
27987
|
excludeFolders,
|
|
27684
|
-
customCategories:
|
|
27988
|
+
customCategories: config2.custom_categories
|
|
27685
27989
|
});
|
|
27686
27990
|
db4.replaceAllEntities(entityIndex2);
|
|
27687
27991
|
serverLog("index", `Updated ${entityIndex2._metadata.total_entities} entities in StateDb`);
|
|
@@ -27828,7 +28132,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27828
28132
|
serverLog("semantic", "Embeddings up-to-date, skipping build");
|
|
27829
28133
|
loadEntityEmbeddingsToMemory();
|
|
27830
28134
|
if (sd) {
|
|
27831
|
-
const entities =
|
|
28135
|
+
const entities = getAllEntitiesFromDb6(sd);
|
|
27832
28136
|
if (entities.length > 0) {
|
|
27833
28137
|
saveInferredCategories(classifyUncategorizedEntities(
|
|
27834
28138
|
entities.map((entity) => ({
|
|
@@ -27865,7 +28169,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27865
28169
|
}
|
|
27866
28170
|
});
|
|
27867
28171
|
if (sd) {
|
|
27868
|
-
const entities =
|
|
28172
|
+
const entities = getAllEntitiesFromDb6(sd);
|
|
27869
28173
|
if (entities.length > 0) {
|
|
27870
28174
|
const entityMap = new Map(entities.map((e) => [e.name, {
|
|
27871
28175
|
name: e.name,
|
|
@@ -27880,7 +28184,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27880
28184
|
activateVault(ctx);
|
|
27881
28185
|
loadEntityEmbeddingsToMemory();
|
|
27882
28186
|
if (sd) {
|
|
27883
|
-
const entities =
|
|
28187
|
+
const entities = getAllEntitiesFromDb6(sd);
|
|
27884
28188
|
if (entities.length > 0) {
|
|
27885
28189
|
saveInferredCategories(classifyUncategorizedEntities(
|
|
27886
28190
|
entities.map((entity) => ({
|
|
@@ -27918,7 +28222,7 @@ async function runPostIndexWork(ctx) {
|
|
|
27918
28222
|
serverLog("semantic", "Skipping \u2014 FLYWHEEL_SKIP_EMBEDDINGS");
|
|
27919
28223
|
}
|
|
27920
28224
|
if (process.env.FLYWHEEL_WATCH !== "false") {
|
|
27921
|
-
const
|
|
28225
|
+
const config2 = parseWatcherConfig();
|
|
27922
28226
|
const lastContentHashes = /* @__PURE__ */ new Map();
|
|
27923
28227
|
if (sd) {
|
|
27924
28228
|
const persisted = loadContentHashes(sd);
|
|
@@ -27927,7 +28231,15 @@ async function runPostIndexWork(ctx) {
|
|
|
27927
28231
|
serverLog("watcher", `Loaded ${persisted.size} persisted content hashes`);
|
|
27928
28232
|
}
|
|
27929
28233
|
}
|
|
27930
|
-
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
|
+
});
|
|
27931
28243
|
const handleBatch = async (batch) => {
|
|
27932
28244
|
const vaultPrefixes = /* @__PURE__ */ new Set([
|
|
27933
28245
|
normalizePath(vp),
|
|
@@ -28068,13 +28380,14 @@ async function runPostIndexWork(ctx) {
|
|
|
28068
28380
|
updateVaultIndex,
|
|
28069
28381
|
updateEntitiesInStateDb,
|
|
28070
28382
|
getVaultIndex: () => vaultIndex,
|
|
28071
|
-
buildVaultIndex
|
|
28383
|
+
buildVaultIndex,
|
|
28384
|
+
deferredScheduler: deferredScheduler ?? void 0
|
|
28072
28385
|
});
|
|
28073
28386
|
await runner.run();
|
|
28074
28387
|
};
|
|
28075
28388
|
const watcher = createVaultWatcher({
|
|
28076
28389
|
vaultPath: vp,
|
|
28077
|
-
config,
|
|
28390
|
+
config: config2,
|
|
28078
28391
|
onBatch: handleBatch,
|
|
28079
28392
|
onStateChange: (status) => {
|
|
28080
28393
|
if (status.state === "dirty") {
|
|
@@ -28115,6 +28428,18 @@ async function runPostIndexWork(ctx) {
|
|
|
28115
28428
|
if (sd) runPeriodicMaintenance(sd);
|
|
28116
28429
|
});
|
|
28117
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)`);
|
|
28118
28443
|
}
|
|
28119
28444
|
const postDuration = Date.now() - postStart;
|
|
28120
28445
|
serverLog("server", `Post-index work complete in ${postDuration}ms`);
|
|
@@ -28162,7 +28487,12 @@ function gracefulShutdown(signal) {
|
|
|
28162
28487
|
watcherInstance?.stop();
|
|
28163
28488
|
} catch {
|
|
28164
28489
|
}
|
|
28490
|
+
try {
|
|
28491
|
+
deferredScheduler?.cancelAll();
|
|
28492
|
+
} catch {
|
|
28493
|
+
}
|
|
28165
28494
|
stopSweepTimer();
|
|
28495
|
+
stopMaintenanceTimer();
|
|
28166
28496
|
flushLogs().catch(() => {
|
|
28167
28497
|
}).finally(() => process.exit(0));
|
|
28168
28498
|
setTimeout(() => process.exit(0), 2e3).unref();
|