@velvetmonkey/flywheel-memory 2.5.12 → 2.5.14
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 +554 -261
- package/dist/integrity-worker.js +49 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -344,7 +344,7 @@ function resolveWorkerPath() {
|
|
|
344
344
|
async function initEmbeddings() {
|
|
345
345
|
if (workerReady && worker) return;
|
|
346
346
|
if (workerInitPromise) return workerInitPromise;
|
|
347
|
-
workerInitPromise = new Promise((
|
|
347
|
+
workerInitPromise = new Promise((resolve4, reject) => {
|
|
348
348
|
try {
|
|
349
349
|
const workerPath = resolveWorkerPath();
|
|
350
350
|
console.error(`[Semantic] Spawning embedding worker: ${workerPath}`);
|
|
@@ -359,7 +359,7 @@ async function initEmbeddings() {
|
|
|
359
359
|
console.error(`[Semantic] Probed model ${activeModelConfig.id}: ${msg.dims} dims`);
|
|
360
360
|
}
|
|
361
361
|
console.error(`[Semantic] Worker ready (model: ${activeModelConfig.id}, dims: ${msg.dims})`);
|
|
362
|
-
|
|
362
|
+
resolve4();
|
|
363
363
|
break;
|
|
364
364
|
case "result": {
|
|
365
365
|
const pending = pendingEmbeds.get(msg.id);
|
|
@@ -435,8 +435,8 @@ async function embedText(text) {
|
|
|
435
435
|
throw new Error("Embedding worker not available");
|
|
436
436
|
}
|
|
437
437
|
const id = ++embedRequestId;
|
|
438
|
-
return new Promise((
|
|
439
|
-
pendingEmbeds.set(id, { resolve:
|
|
438
|
+
return new Promise((resolve4, reject) => {
|
|
439
|
+
pendingEmbeds.set(id, { resolve: resolve4, reject });
|
|
440
440
|
worker.postMessage({ type: "embed", id, text });
|
|
441
441
|
});
|
|
442
442
|
}
|
|
@@ -2696,7 +2696,7 @@ function isLockContentionError(error) {
|
|
|
2696
2696
|
return false;
|
|
2697
2697
|
}
|
|
2698
2698
|
function sleep(ms) {
|
|
2699
|
-
return new Promise((
|
|
2699
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2700
2700
|
}
|
|
2701
2701
|
function calculateDelay(attempt, config2) {
|
|
2702
2702
|
let delay = config2.baseDelayMs * Math.pow(2, attempt);
|
|
@@ -4304,10 +4304,26 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4304
4304
|
(token) => contentTokens.has(token) || contentStems.has(stem(token))
|
|
4305
4305
|
);
|
|
4306
4306
|
const strongCooccurrence = boost >= config2.minCooccurrenceGate;
|
|
4307
|
-
|
|
4307
|
+
let multiSeedOK = true;
|
|
4308
|
+
if (!hasContentOverlap && cooccurrenceIndex && config2.minContentMatch > 0) {
|
|
4309
|
+
let qualifyingSeedCount = 0;
|
|
4310
|
+
for (const seed of cooccurrenceSeeds) {
|
|
4311
|
+
const entityAssocs = cooccurrenceIndex.associations[seed];
|
|
4312
|
+
if (!entityAssocs) continue;
|
|
4313
|
+
const coocCount = entityAssocs.get(entityName) || 0;
|
|
4314
|
+
if (coocCount < (cooccurrenceIndex.minCount ?? 2)) continue;
|
|
4315
|
+
const dfEntity = cooccurrenceIndex.documentFrequency?.get(entityName) || 0;
|
|
4316
|
+
const dfSeed = cooccurrenceIndex.documentFrequency?.get(seed) || 0;
|
|
4317
|
+
if (dfEntity === 0 || dfSeed === 0) continue;
|
|
4318
|
+
const npmi = computeNpmi(coocCount, dfEntity, dfSeed, cooccurrenceIndex.totalNotesScanned ?? 1);
|
|
4319
|
+
if (npmi > 0) qualifyingSeedCount++;
|
|
4320
|
+
}
|
|
4321
|
+
multiSeedOK = qualifyingSeedCount >= 2;
|
|
4322
|
+
}
|
|
4323
|
+
if (!hasContentOverlap && !(strongCooccurrence && multiSeedOK)) {
|
|
4308
4324
|
continue;
|
|
4309
4325
|
}
|
|
4310
|
-
if (hasContentOverlap || strongCooccurrence) {
|
|
4326
|
+
if (hasContentOverlap || strongCooccurrence && multiSeedOK) {
|
|
4311
4327
|
entitiesWithAnyScoringPath.add(entityName);
|
|
4312
4328
|
}
|
|
4313
4329
|
const typeBoost = disabled.has("type_boost") ? 0 : getTypeBoost(category, getConfig()?.custom_categories, entityName);
|
|
@@ -4853,8 +4869,8 @@ var init_wikilinks = __esm({
|
|
|
4853
4869
|
minWordLength: 3,
|
|
4854
4870
|
minSuggestionScore: 10,
|
|
4855
4871
|
// Exact match (10) or two stem matches
|
|
4856
|
-
minMatchRatio: 0.
|
|
4857
|
-
//
|
|
4872
|
+
minMatchRatio: 0.6,
|
|
4873
|
+
// 60% of multi-word entity must match (blocks 1-of-2 token FPs)
|
|
4858
4874
|
requireMultipleMatches: false,
|
|
4859
4875
|
stemMatchBonus: 5,
|
|
4860
4876
|
// Standard bonus for stem matches
|
|
@@ -4863,8 +4879,10 @@ var init_wikilinks = __esm({
|
|
|
4863
4879
|
fuzzyMatchBonus: 4,
|
|
4864
4880
|
// Moderate fuzzy bonus
|
|
4865
4881
|
contentRelevanceFloor: 5,
|
|
4866
|
-
noRelevanceCap:
|
|
4867
|
-
|
|
4882
|
+
noRelevanceCap: 9,
|
|
4883
|
+
// Below minSuggestionScore — graph-only entities can't reach threshold
|
|
4884
|
+
minCooccurrenceGate: 6,
|
|
4885
|
+
// Stronger graph signal for graph-only admission
|
|
4868
4886
|
minContentMatch: 2
|
|
4869
4887
|
},
|
|
4870
4888
|
aggressive: {
|
|
@@ -5185,16 +5203,16 @@ var init_tool_embeddings_generated = __esm({
|
|
|
5185
5203
|
|
|
5186
5204
|
// src/core/write/path-security.ts
|
|
5187
5205
|
import fs20 from "fs/promises";
|
|
5188
|
-
import
|
|
5206
|
+
import path23 from "path";
|
|
5189
5207
|
function isSensitivePath(filePath) {
|
|
5190
5208
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
5191
5209
|
return SENSITIVE_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath));
|
|
5192
5210
|
}
|
|
5193
5211
|
function isWithinDirectory(child, parent, allowEqual = false) {
|
|
5194
|
-
const rel =
|
|
5212
|
+
const rel = path23.relative(path23.resolve(parent), path23.resolve(child));
|
|
5195
5213
|
if (rel === "") return allowEqual;
|
|
5196
|
-
const firstSeg = rel.split(
|
|
5197
|
-
return firstSeg !== ".." && !
|
|
5214
|
+
const firstSeg = rel.split(path23.sep)[0];
|
|
5215
|
+
return firstSeg !== ".." && !path23.isAbsolute(rel);
|
|
5198
5216
|
}
|
|
5199
5217
|
function validatePath(vaultPath2, notePath) {
|
|
5200
5218
|
if (notePath.startsWith("/")) {
|
|
@@ -5206,11 +5224,11 @@ function validatePath(vaultPath2, notePath) {
|
|
|
5206
5224
|
if (notePath.startsWith("\\")) {
|
|
5207
5225
|
return false;
|
|
5208
5226
|
}
|
|
5209
|
-
return isWithinDirectory(
|
|
5227
|
+
return isWithinDirectory(path23.resolve(vaultPath2, notePath), vaultPath2);
|
|
5210
5228
|
}
|
|
5211
5229
|
function sanitizeNotePath(notePath) {
|
|
5212
|
-
const dir =
|
|
5213
|
-
let filename =
|
|
5230
|
+
const dir = path23.dirname(notePath);
|
|
5231
|
+
let filename = path23.basename(notePath);
|
|
5214
5232
|
const ext = filename.endsWith(".md") ? ".md" : "";
|
|
5215
5233
|
let stem2 = ext ? filename.slice(0, -ext.length) : filename;
|
|
5216
5234
|
stem2 = stem2.replace(/\s+/g, "-");
|
|
@@ -5219,7 +5237,7 @@ function sanitizeNotePath(notePath) {
|
|
|
5219
5237
|
stem2 = stem2.replace(/-{2,}/g, "-");
|
|
5220
5238
|
stem2 = stem2.replace(/^-+|-+$/g, "");
|
|
5221
5239
|
filename = stem2 + (ext || ".md");
|
|
5222
|
-
return dir === "." ? filename :
|
|
5240
|
+
return dir === "." ? filename : path23.join(dir, filename).replace(/\\/g, "/");
|
|
5223
5241
|
}
|
|
5224
5242
|
async function validatePathSecure(vaultPath2, notePath) {
|
|
5225
5243
|
if (notePath.startsWith("/")) {
|
|
@@ -5240,14 +5258,14 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5240
5258
|
reason: "Absolute paths not allowed"
|
|
5241
5259
|
};
|
|
5242
5260
|
}
|
|
5243
|
-
const firstSeg =
|
|
5261
|
+
const firstSeg = path23.normalize(notePath).split(path23.sep).filter(Boolean)[0];
|
|
5244
5262
|
if (firstSeg === "..") {
|
|
5245
5263
|
return {
|
|
5246
5264
|
valid: false,
|
|
5247
5265
|
reason: "Path traversal not allowed"
|
|
5248
5266
|
};
|
|
5249
5267
|
}
|
|
5250
|
-
if (!isWithinDirectory(
|
|
5268
|
+
if (!isWithinDirectory(path23.resolve(vaultPath2, notePath), vaultPath2)) {
|
|
5251
5269
|
return {
|
|
5252
5270
|
valid: false,
|
|
5253
5271
|
reason: "Path traversal not allowed"
|
|
@@ -5260,7 +5278,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5260
5278
|
};
|
|
5261
5279
|
}
|
|
5262
5280
|
try {
|
|
5263
|
-
const fullPath =
|
|
5281
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
5264
5282
|
try {
|
|
5265
5283
|
await fs20.access(fullPath);
|
|
5266
5284
|
const realPath = await fs20.realpath(fullPath);
|
|
@@ -5271,7 +5289,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5271
5289
|
reason: "Symlink target is outside vault"
|
|
5272
5290
|
};
|
|
5273
5291
|
}
|
|
5274
|
-
const relativePath =
|
|
5292
|
+
const relativePath = path23.relative(realVaultPath, realPath);
|
|
5275
5293
|
if (isSensitivePath(relativePath)) {
|
|
5276
5294
|
return {
|
|
5277
5295
|
valid: false,
|
|
@@ -5279,7 +5297,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5279
5297
|
};
|
|
5280
5298
|
}
|
|
5281
5299
|
} catch {
|
|
5282
|
-
const parentDir =
|
|
5300
|
+
const parentDir = path23.dirname(fullPath);
|
|
5283
5301
|
try {
|
|
5284
5302
|
await fs20.access(parentDir);
|
|
5285
5303
|
const realParentPath = await fs20.realpath(parentDir);
|
|
@@ -6058,7 +6076,7 @@ var init_content_mutation = __esm({
|
|
|
6058
6076
|
|
|
6059
6077
|
// src/core/write/file-io.ts
|
|
6060
6078
|
import fs21 from "fs/promises";
|
|
6061
|
-
import
|
|
6079
|
+
import path24 from "path";
|
|
6062
6080
|
import matter5 from "gray-matter";
|
|
6063
6081
|
import { createHash as createHash2 } from "node:crypto";
|
|
6064
6082
|
function computeContentHash(rawContent) {
|
|
@@ -6069,7 +6087,7 @@ async function readVaultFile(vaultPath2, notePath) {
|
|
|
6069
6087
|
if (!validation.valid) {
|
|
6070
6088
|
throw new Error(`Invalid path: ${validation.reason}`);
|
|
6071
6089
|
}
|
|
6072
|
-
const fullPath =
|
|
6090
|
+
const fullPath = path24.join(vaultPath2, notePath);
|
|
6073
6091
|
const [rawContent, stat4] = await Promise.all([
|
|
6074
6092
|
fs21.readFile(fullPath, "utf-8"),
|
|
6075
6093
|
fs21.stat(fullPath)
|
|
@@ -6124,7 +6142,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
|
|
|
6124
6142
|
if (!validation.valid) {
|
|
6125
6143
|
throw new Error(`Invalid path: ${validation.reason}`);
|
|
6126
6144
|
}
|
|
6127
|
-
const fullPath =
|
|
6145
|
+
const fullPath = path24.join(vaultPath2, notePath);
|
|
6128
6146
|
if (expectedHash) {
|
|
6129
6147
|
const currentRaw = await fs21.readFile(fullPath, "utf-8");
|
|
6130
6148
|
const currentHash = computeContentHash(currentRaw);
|
|
@@ -6192,8 +6210,8 @@ function createContext(variables = {}) {
|
|
|
6192
6210
|
steps: {}
|
|
6193
6211
|
};
|
|
6194
6212
|
}
|
|
6195
|
-
function resolvePath(obj,
|
|
6196
|
-
const parts =
|
|
6213
|
+
function resolvePath(obj, path41) {
|
|
6214
|
+
const parts = path41.split(".");
|
|
6197
6215
|
let current = obj;
|
|
6198
6216
|
for (const part of parts) {
|
|
6199
6217
|
if (current === void 0 || current === null) {
|
|
@@ -6651,7 +6669,7 @@ __export(conditions_exports, {
|
|
|
6651
6669
|
shouldStepExecute: () => shouldStepExecute
|
|
6652
6670
|
});
|
|
6653
6671
|
import fs29 from "fs/promises";
|
|
6654
|
-
import
|
|
6672
|
+
import path32 from "path";
|
|
6655
6673
|
async function evaluateCondition(condition, vaultPath2, context) {
|
|
6656
6674
|
const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
|
|
6657
6675
|
const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
|
|
@@ -6704,7 +6722,7 @@ async function evaluateCondition(condition, vaultPath2, context) {
|
|
|
6704
6722
|
}
|
|
6705
6723
|
}
|
|
6706
6724
|
async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
6707
|
-
const fullPath =
|
|
6725
|
+
const fullPath = path32.join(vaultPath2, notePath);
|
|
6708
6726
|
try {
|
|
6709
6727
|
await fs29.access(fullPath);
|
|
6710
6728
|
return {
|
|
@@ -6719,7 +6737,7 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
|
6719
6737
|
}
|
|
6720
6738
|
}
|
|
6721
6739
|
async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
|
|
6722
|
-
const fullPath =
|
|
6740
|
+
const fullPath = path32.join(vaultPath2, notePath);
|
|
6723
6741
|
try {
|
|
6724
6742
|
await fs29.access(fullPath);
|
|
6725
6743
|
} catch {
|
|
@@ -6750,7 +6768,7 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
|
|
|
6750
6768
|
}
|
|
6751
6769
|
}
|
|
6752
6770
|
async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
|
|
6753
|
-
const fullPath =
|
|
6771
|
+
const fullPath = path32.join(vaultPath2, notePath);
|
|
6754
6772
|
try {
|
|
6755
6773
|
await fs29.access(fullPath);
|
|
6756
6774
|
} catch {
|
|
@@ -6781,7 +6799,7 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
|
|
|
6781
6799
|
}
|
|
6782
6800
|
}
|
|
6783
6801
|
async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
|
|
6784
|
-
const fullPath =
|
|
6802
|
+
const fullPath = path32.join(vaultPath2, notePath);
|
|
6785
6803
|
try {
|
|
6786
6804
|
await fs29.access(fullPath);
|
|
6787
6805
|
} catch {
|
|
@@ -6925,10 +6943,10 @@ var init_taskHelpers = __esm({
|
|
|
6925
6943
|
});
|
|
6926
6944
|
|
|
6927
6945
|
// src/index.ts
|
|
6928
|
-
import * as
|
|
6929
|
-
import { readFileSync as readFileSync6, realpathSync, existsSync as
|
|
6930
|
-
import { fileURLToPath as
|
|
6931
|
-
import { dirname as
|
|
6946
|
+
import * as path40 from "path";
|
|
6947
|
+
import { readFileSync as readFileSync6, realpathSync, existsSync as existsSync4 } from "fs";
|
|
6948
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6949
|
+
import { dirname as dirname8, join as join21 } from "path";
|
|
6932
6950
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6933
6951
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6934
6952
|
import { performance as performance2 } from "node:perf_hooks";
|
|
@@ -7165,8 +7183,8 @@ function updateIndexProgress(parsed, total) {
|
|
|
7165
7183
|
function normalizeTarget(target) {
|
|
7166
7184
|
return target.toLowerCase().replace(/\.md$/, "");
|
|
7167
7185
|
}
|
|
7168
|
-
function normalizeNotePath(
|
|
7169
|
-
return
|
|
7186
|
+
function normalizeNotePath(path41) {
|
|
7187
|
+
return path41.toLowerCase().replace(/\.md$/, "");
|
|
7170
7188
|
}
|
|
7171
7189
|
async function buildVaultIndex(vaultPath2, options = {}) {
|
|
7172
7190
|
const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
|
|
@@ -7214,7 +7232,7 @@ async function buildVaultIndexInternal(vaultPath2, startTime, onProgress) {
|
|
|
7214
7232
|
console.error(`Parsed ${parsedCount}/${files.length} files (${elapsed}s)`);
|
|
7215
7233
|
onProgress?.(parsedCount, files.length);
|
|
7216
7234
|
}
|
|
7217
|
-
await new Promise((
|
|
7235
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
7218
7236
|
}
|
|
7219
7237
|
if (parseErrors.length > 0) {
|
|
7220
7238
|
const msg = `Failed to parse ${parseErrors.length} file(s):`;
|
|
@@ -7340,7 +7358,7 @@ function findSimilarEntity(index, target) {
|
|
|
7340
7358
|
}
|
|
7341
7359
|
const maxDist = normalizedLen <= 10 ? 1 : 2;
|
|
7342
7360
|
let bestMatch;
|
|
7343
|
-
for (const [entity,
|
|
7361
|
+
for (const [entity, path41] of index.entities) {
|
|
7344
7362
|
const lenDiff = Math.abs(entity.length - normalizedLen);
|
|
7345
7363
|
if (lenDiff > maxDist) {
|
|
7346
7364
|
continue;
|
|
@@ -7348,7 +7366,7 @@ function findSimilarEntity(index, target) {
|
|
|
7348
7366
|
const dist = levenshteinDistance(normalized, entity);
|
|
7349
7367
|
if (dist > 0 && dist <= maxDist) {
|
|
7350
7368
|
if (!bestMatch || dist < bestMatch.distance) {
|
|
7351
|
-
bestMatch = { path:
|
|
7369
|
+
bestMatch = { path: path41, entity, distance: dist };
|
|
7352
7370
|
if (dist === 1) {
|
|
7353
7371
|
return bestMatch;
|
|
7354
7372
|
}
|
|
@@ -7907,30 +7925,30 @@ var EventQueue = class {
|
|
|
7907
7925
|
* Add a new event to the queue
|
|
7908
7926
|
*/
|
|
7909
7927
|
push(type, rawPath) {
|
|
7910
|
-
const
|
|
7928
|
+
const path41 = normalizePath(rawPath);
|
|
7911
7929
|
const now = Date.now();
|
|
7912
7930
|
const event = {
|
|
7913
7931
|
type,
|
|
7914
|
-
path:
|
|
7932
|
+
path: path41,
|
|
7915
7933
|
timestamp: now
|
|
7916
7934
|
};
|
|
7917
|
-
let pending = this.pending.get(
|
|
7935
|
+
let pending = this.pending.get(path41);
|
|
7918
7936
|
if (!pending) {
|
|
7919
7937
|
pending = {
|
|
7920
7938
|
events: [],
|
|
7921
7939
|
timer: null,
|
|
7922
7940
|
lastEvent: now
|
|
7923
7941
|
};
|
|
7924
|
-
this.pending.set(
|
|
7942
|
+
this.pending.set(path41, pending);
|
|
7925
7943
|
}
|
|
7926
7944
|
pending.events.push(event);
|
|
7927
7945
|
pending.lastEvent = now;
|
|
7928
|
-
console.error(`[flywheel] QUEUE: pushed ${type} for ${
|
|
7946
|
+
console.error(`[flywheel] QUEUE: pushed ${type} for ${path41}, pending=${this.pending.size}`);
|
|
7929
7947
|
if (pending.timer) {
|
|
7930
7948
|
clearTimeout(pending.timer);
|
|
7931
7949
|
}
|
|
7932
7950
|
pending.timer = setTimeout(() => {
|
|
7933
|
-
this.flushPath(
|
|
7951
|
+
this.flushPath(path41);
|
|
7934
7952
|
}, this.config.debounceMs);
|
|
7935
7953
|
if (this.pending.size >= this.config.batchSize) {
|
|
7936
7954
|
this.flush();
|
|
@@ -7951,10 +7969,10 @@ var EventQueue = class {
|
|
|
7951
7969
|
/**
|
|
7952
7970
|
* Flush a single path's events
|
|
7953
7971
|
*/
|
|
7954
|
-
flushPath(
|
|
7955
|
-
const pending = this.pending.get(
|
|
7972
|
+
flushPath(path41) {
|
|
7973
|
+
const pending = this.pending.get(path41);
|
|
7956
7974
|
if (!pending || pending.events.length === 0) return;
|
|
7957
|
-
console.error(`[flywheel] QUEUE: flushing ${
|
|
7975
|
+
console.error(`[flywheel] QUEUE: flushing ${path41}, events=${pending.events.length}`);
|
|
7958
7976
|
if (pending.timer) {
|
|
7959
7977
|
clearTimeout(pending.timer);
|
|
7960
7978
|
pending.timer = null;
|
|
@@ -7963,7 +7981,7 @@ var EventQueue = class {
|
|
|
7963
7981
|
if (coalescedType) {
|
|
7964
7982
|
const coalesced = {
|
|
7965
7983
|
type: coalescedType,
|
|
7966
|
-
path:
|
|
7984
|
+
path: path41,
|
|
7967
7985
|
originalEvents: [...pending.events]
|
|
7968
7986
|
};
|
|
7969
7987
|
this.onBatch({
|
|
@@ -7972,7 +7990,7 @@ var EventQueue = class {
|
|
|
7972
7990
|
timestamp: Date.now()
|
|
7973
7991
|
});
|
|
7974
7992
|
}
|
|
7975
|
-
this.pending.delete(
|
|
7993
|
+
this.pending.delete(path41);
|
|
7976
7994
|
}
|
|
7977
7995
|
/**
|
|
7978
7996
|
* Flush all pending events
|
|
@@ -7984,7 +8002,7 @@ var EventQueue = class {
|
|
|
7984
8002
|
}
|
|
7985
8003
|
if (this.pending.size === 0) return;
|
|
7986
8004
|
const events = [];
|
|
7987
|
-
for (const [
|
|
8005
|
+
for (const [path41, pending] of this.pending) {
|
|
7988
8006
|
if (pending.timer) {
|
|
7989
8007
|
clearTimeout(pending.timer);
|
|
7990
8008
|
}
|
|
@@ -7992,7 +8010,7 @@ var EventQueue = class {
|
|
|
7992
8010
|
if (coalescedType) {
|
|
7993
8011
|
events.push({
|
|
7994
8012
|
type: coalescedType,
|
|
7995
|
-
path:
|
|
8013
|
+
path: path41,
|
|
7996
8014
|
originalEvents: [...pending.events]
|
|
7997
8015
|
});
|
|
7998
8016
|
}
|
|
@@ -8292,7 +8310,7 @@ async function processBatch(index, vaultPath2, batch, options = {}) {
|
|
|
8292
8310
|
}
|
|
8293
8311
|
onProgress?.(processed, total);
|
|
8294
8312
|
if (processed % YIELD_INTERVAL === 0 && processed < total) {
|
|
8295
|
-
await new Promise((
|
|
8313
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
8296
8314
|
}
|
|
8297
8315
|
}
|
|
8298
8316
|
const durationMs = Date.now() - startTime;
|
|
@@ -8384,31 +8402,31 @@ function createVaultWatcher(options) {
|
|
|
8384
8402
|
usePolling: config2.usePolling,
|
|
8385
8403
|
interval: config2.usePolling ? config2.pollInterval : void 0
|
|
8386
8404
|
});
|
|
8387
|
-
watcher.on("add", (
|
|
8388
|
-
console.error(`[flywheel] RAW EVENT: add ${
|
|
8389
|
-
if (shouldWatch(
|
|
8390
|
-
console.error(`[flywheel] ACCEPTED: add ${
|
|
8391
|
-
eventQueue.push("add",
|
|
8405
|
+
watcher.on("add", (path41) => {
|
|
8406
|
+
console.error(`[flywheel] RAW EVENT: add ${path41}`);
|
|
8407
|
+
if (shouldWatch(path41, vaultPath2)) {
|
|
8408
|
+
console.error(`[flywheel] ACCEPTED: add ${path41}`);
|
|
8409
|
+
eventQueue.push("add", path41);
|
|
8392
8410
|
} else {
|
|
8393
|
-
console.error(`[flywheel] FILTERED: add ${
|
|
8411
|
+
console.error(`[flywheel] FILTERED: add ${path41}`);
|
|
8394
8412
|
}
|
|
8395
8413
|
});
|
|
8396
|
-
watcher.on("change", (
|
|
8397
|
-
console.error(`[flywheel] RAW EVENT: change ${
|
|
8398
|
-
if (shouldWatch(
|
|
8399
|
-
console.error(`[flywheel] ACCEPTED: change ${
|
|
8400
|
-
eventQueue.push("change",
|
|
8414
|
+
watcher.on("change", (path41) => {
|
|
8415
|
+
console.error(`[flywheel] RAW EVENT: change ${path41}`);
|
|
8416
|
+
if (shouldWatch(path41, vaultPath2)) {
|
|
8417
|
+
console.error(`[flywheel] ACCEPTED: change ${path41}`);
|
|
8418
|
+
eventQueue.push("change", path41);
|
|
8401
8419
|
} else {
|
|
8402
|
-
console.error(`[flywheel] FILTERED: change ${
|
|
8420
|
+
console.error(`[flywheel] FILTERED: change ${path41}`);
|
|
8403
8421
|
}
|
|
8404
8422
|
});
|
|
8405
|
-
watcher.on("unlink", (
|
|
8406
|
-
console.error(`[flywheel] RAW EVENT: unlink ${
|
|
8407
|
-
if (shouldWatch(
|
|
8408
|
-
console.error(`[flywheel] ACCEPTED: unlink ${
|
|
8409
|
-
eventQueue.push("unlink",
|
|
8423
|
+
watcher.on("unlink", (path41) => {
|
|
8424
|
+
console.error(`[flywheel] RAW EVENT: unlink ${path41}`);
|
|
8425
|
+
if (shouldWatch(path41, vaultPath2)) {
|
|
8426
|
+
console.error(`[flywheel] ACCEPTED: unlink ${path41}`);
|
|
8427
|
+
eventQueue.push("unlink", path41);
|
|
8410
8428
|
} else {
|
|
8411
|
-
console.error(`[flywheel] FILTERED: unlink ${
|
|
8429
|
+
console.error(`[flywheel] FILTERED: unlink ${path41}`);
|
|
8412
8430
|
}
|
|
8413
8431
|
});
|
|
8414
8432
|
watcher.on("ready", () => {
|
|
@@ -8448,8 +8466,6 @@ import {
|
|
|
8448
8466
|
getProtectedZones,
|
|
8449
8467
|
rangeOverlapsProtectedZone,
|
|
8450
8468
|
detectImplicitEntities as detectImplicitEntities2,
|
|
8451
|
-
checkDbIntegrity,
|
|
8452
|
-
safeBackupAsync,
|
|
8453
8469
|
recordEntityMention as recordEntityMention2
|
|
8454
8470
|
} from "@velvetmonkey/vault-core";
|
|
8455
8471
|
init_serverLog();
|
|
@@ -9740,6 +9756,17 @@ var PipelineRunner = class {
|
|
|
9740
9756
|
suggestionResults = [];
|
|
9741
9757
|
async run() {
|
|
9742
9758
|
const { p, tracker } = this;
|
|
9759
|
+
if (p.ctx.integrityState === "failed") {
|
|
9760
|
+
serverLog("watcher", `Skipping batch for ${p.ctx.name}: StateDb integrity failed`, "warn");
|
|
9761
|
+
this.activity.busy = false;
|
|
9762
|
+
this.activity.current_step = null;
|
|
9763
|
+
this.activity.last_completed_at = Date.now();
|
|
9764
|
+
this.activity.last_completed_trigger = "watcher";
|
|
9765
|
+
this.activity.last_completed_duration_ms = 0;
|
|
9766
|
+
this.activity.last_completed_files = p.events.length;
|
|
9767
|
+
this.activity.last_completed_steps = [];
|
|
9768
|
+
return;
|
|
9769
|
+
}
|
|
9743
9770
|
this.activity.busy = true;
|
|
9744
9771
|
this.activity.trigger = "watcher";
|
|
9745
9772
|
this.activity.started_at = this.batchStart;
|
|
@@ -10625,21 +10652,15 @@ var PipelineRunner = class {
|
|
|
10625
10652
|
async integrityCheck() {
|
|
10626
10653
|
const { p } = this;
|
|
10627
10654
|
if (!p.sd) return { skipped: true, reason: "no statedb" };
|
|
10628
|
-
const
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
}
|
|
10634
|
-
const result = checkDbIntegrity(p.sd.db);
|
|
10635
|
-
p.sd.setMetadataValue.run("last_integrity_check", String(Date.now()));
|
|
10636
|
-
if (result.ok) {
|
|
10637
|
-
await safeBackupAsync(p.sd.db, p.sd.dbPath);
|
|
10638
|
-
return { integrity: "ok", backed_up: true };
|
|
10639
|
-
} else {
|
|
10655
|
+
const result = await p.runIntegrityCheck(p.ctx, "watcher");
|
|
10656
|
+
if (result.status === "healthy") {
|
|
10657
|
+
return { integrity: "ok", backed_up: result.backupCreated };
|
|
10658
|
+
}
|
|
10659
|
+
if (result.status === "failed") {
|
|
10640
10660
|
serverLog("watcher", `Integrity check FAILED: ${result.detail}`, "error");
|
|
10641
10661
|
return { integrity: "failed", detail: result.detail };
|
|
10642
10662
|
}
|
|
10663
|
+
return { skipped: true, reason: result.detail ?? "integrity runner unavailable" };
|
|
10643
10664
|
}
|
|
10644
10665
|
// ── Maintenance: periodic incremental vacuum ─────────────────────
|
|
10645
10666
|
async maintenance() {
|
|
@@ -10999,8 +11020,77 @@ function getToolSelectionReport(stateDb2, daysBack = 7) {
|
|
|
10999
11020
|
};
|
|
11000
11021
|
}
|
|
11001
11022
|
|
|
11023
|
+
// src/core/read/integrity.ts
|
|
11024
|
+
import * as path16 from "node:path";
|
|
11025
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
11026
|
+
import { Worker as Worker2 } from "node:worker_threads";
|
|
11027
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11028
|
+
var INTEGRITY_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
11029
|
+
var INTEGRITY_CHECK_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
11030
|
+
var INTEGRITY_BACKUP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
11031
|
+
var INTEGRITY_METADATA_KEYS = {
|
|
11032
|
+
checkedAt: "last_integrity_check",
|
|
11033
|
+
status: "last_integrity_status",
|
|
11034
|
+
durationMs: "last_integrity_duration_ms",
|
|
11035
|
+
detail: "last_integrity_detail"
|
|
11036
|
+
};
|
|
11037
|
+
function resolveWorkerSpec() {
|
|
11038
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
11039
|
+
const thisDir = path16.dirname(thisFile);
|
|
11040
|
+
const prodPath = path16.join(thisDir, "integrity-worker.js");
|
|
11041
|
+
if (existsSync3(prodPath)) return { filename: prodPath };
|
|
11042
|
+
const distPath = path16.resolve(thisDir, "..", "..", "..", "dist", "integrity-worker.js");
|
|
11043
|
+
if (existsSync3(distPath)) return { filename: distPath };
|
|
11044
|
+
const srcPath = path16.join(thisDir, "integrity-worker.ts");
|
|
11045
|
+
return { filename: srcPath, execArgv: ["--import", "tsx"] };
|
|
11046
|
+
}
|
|
11047
|
+
async function runIntegrityWorker(message, timeoutMs = INTEGRITY_CHECK_TIMEOUT_MS) {
|
|
11048
|
+
const workerSpec = resolveWorkerSpec();
|
|
11049
|
+
return new Promise((resolve4) => {
|
|
11050
|
+
const worker2 = new Worker2(workerSpec.filename, {
|
|
11051
|
+
execArgv: workerSpec.execArgv
|
|
11052
|
+
});
|
|
11053
|
+
let settled = false;
|
|
11054
|
+
const finish = (result) => {
|
|
11055
|
+
if (settled) return;
|
|
11056
|
+
settled = true;
|
|
11057
|
+
clearTimeout(timer2);
|
|
11058
|
+
void worker2.terminate().catch(() => {
|
|
11059
|
+
});
|
|
11060
|
+
resolve4(result);
|
|
11061
|
+
};
|
|
11062
|
+
const timer2 = setTimeout(() => {
|
|
11063
|
+
finish({
|
|
11064
|
+
status: "error",
|
|
11065
|
+
detail: `Integrity worker timed out after ${timeoutMs}ms`,
|
|
11066
|
+
durationMs: timeoutMs,
|
|
11067
|
+
backupCreated: false
|
|
11068
|
+
});
|
|
11069
|
+
}, timeoutMs);
|
|
11070
|
+
worker2.once("message", (result) => finish(result));
|
|
11071
|
+
worker2.once("error", (err) => {
|
|
11072
|
+
finish({
|
|
11073
|
+
status: "error",
|
|
11074
|
+
detail: err.message,
|
|
11075
|
+
durationMs: 0,
|
|
11076
|
+
backupCreated: false
|
|
11077
|
+
});
|
|
11078
|
+
});
|
|
11079
|
+
worker2.once("exit", (code) => {
|
|
11080
|
+
if (settled || code === 0) return;
|
|
11081
|
+
finish({
|
|
11082
|
+
status: "error",
|
|
11083
|
+
detail: `Integrity worker exited with code ${code}`,
|
|
11084
|
+
durationMs: 0,
|
|
11085
|
+
backupCreated: false
|
|
11086
|
+
});
|
|
11087
|
+
});
|
|
11088
|
+
worker2.postMessage(message);
|
|
11089
|
+
});
|
|
11090
|
+
}
|
|
11091
|
+
|
|
11002
11092
|
// src/index.ts
|
|
11003
|
-
import { openStateDb, scanVaultEntities as scanVaultEntities5, getAllEntitiesFromDb as getAllEntitiesFromDb6, loadContentHashes, saveContentHashBatch, renameContentHash
|
|
11093
|
+
import { openStateDb, scanVaultEntities as scanVaultEntities5, getAllEntitiesFromDb as getAllEntitiesFromDb6, loadContentHashes, saveContentHashBatch, renameContentHash } from "@velvetmonkey/vault-core";
|
|
11004
11094
|
|
|
11005
11095
|
// src/core/write/memory.ts
|
|
11006
11096
|
init_wikilinkFeedback();
|
|
@@ -12074,8 +12164,8 @@ function getNoteAccessFrequency(stateDb2, daysBack = 30) {
|
|
|
12074
12164
|
}
|
|
12075
12165
|
}
|
|
12076
12166
|
}
|
|
12077
|
-
return Array.from(noteMap.entries()).map(([
|
|
12078
|
-
path:
|
|
12167
|
+
return Array.from(noteMap.entries()).map(([path41, stats]) => ({
|
|
12168
|
+
path: path41,
|
|
12079
12169
|
access_count: stats.access_count,
|
|
12080
12170
|
last_accessed: stats.last_accessed,
|
|
12081
12171
|
tools_used: Array.from(stats.tools)
|
|
@@ -12762,10 +12852,10 @@ Use "flywheel_config" to inspect runtime configuration and set "tool_tier_overri
|
|
|
12762
12852
|
}
|
|
12763
12853
|
|
|
12764
12854
|
// src/tool-registry.ts
|
|
12765
|
-
import * as
|
|
12766
|
-
import { dirname as
|
|
12855
|
+
import * as path39 from "path";
|
|
12856
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
12767
12857
|
import { statSync as statSync6, readFileSync as readFileSync5 } from "fs";
|
|
12768
|
-
import { fileURLToPath as
|
|
12858
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12769
12859
|
import { z as z39 } from "zod";
|
|
12770
12860
|
import { CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
12771
12861
|
import { getSessionId } from "@velvetmonkey/vault-core";
|
|
@@ -13114,13 +13204,13 @@ function multiHopBackfill(primaryResults, index, stateDb2, config2 = {}) {
|
|
|
13114
13204
|
candidates.sort((a, b) => b.score - a.score);
|
|
13115
13205
|
return candidates.slice(0, cfg.maxBackfill).map((c) => c.result);
|
|
13116
13206
|
}
|
|
13117
|
-
function scoreCandidate(
|
|
13118
|
-
const note = index.notes.get(
|
|
13207
|
+
function scoreCandidate(path41, index, stateDb2) {
|
|
13208
|
+
const note = index.notes.get(path41);
|
|
13119
13209
|
const decay = recencyDecay(note?.modified);
|
|
13120
13210
|
let hubScore = 1;
|
|
13121
13211
|
if (stateDb2) {
|
|
13122
13212
|
try {
|
|
13123
|
-
const title = note?.title ??
|
|
13213
|
+
const title = note?.title ?? path41.replace(/\.md$/, "").split("/").pop() ?? "";
|
|
13124
13214
|
const entity = getEntityByName3(stateDb2, title);
|
|
13125
13215
|
if (entity) hubScore = entity.hubScore ?? 1;
|
|
13126
13216
|
} catch {
|
|
@@ -13357,7 +13447,7 @@ init_stemmer();
|
|
|
13357
13447
|
|
|
13358
13448
|
// src/tools/read/structure.ts
|
|
13359
13449
|
import * as fs12 from "fs";
|
|
13360
|
-
import * as
|
|
13450
|
+
import * as path17 from "path";
|
|
13361
13451
|
var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
|
|
13362
13452
|
function extractHeadings2(content) {
|
|
13363
13453
|
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
@@ -13411,7 +13501,7 @@ function buildSections(headings, totalLines) {
|
|
|
13411
13501
|
async function getNoteStructure(index, notePath, vaultPath2) {
|
|
13412
13502
|
const note = index.notes.get(notePath);
|
|
13413
13503
|
if (!note) return null;
|
|
13414
|
-
const absolutePath =
|
|
13504
|
+
const absolutePath = path17.join(vaultPath2, notePath);
|
|
13415
13505
|
let content;
|
|
13416
13506
|
try {
|
|
13417
13507
|
content = await fs12.promises.readFile(absolutePath, "utf-8");
|
|
@@ -13435,7 +13525,7 @@ async function getNoteStructure(index, notePath, vaultPath2) {
|
|
|
13435
13525
|
async function getSectionContent(index, notePath, headingText, vaultPath2, includeSubheadings = true) {
|
|
13436
13526
|
const note = index.notes.get(notePath);
|
|
13437
13527
|
if (!note) return null;
|
|
13438
|
-
const absolutePath =
|
|
13528
|
+
const absolutePath = path17.join(vaultPath2, notePath);
|
|
13439
13529
|
let content;
|
|
13440
13530
|
try {
|
|
13441
13531
|
content = await fs12.promises.readFile(absolutePath, "utf-8");
|
|
@@ -13478,7 +13568,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
|
|
|
13478
13568
|
const results = [];
|
|
13479
13569
|
for (const note of index.notes.values()) {
|
|
13480
13570
|
if (folder && !note.path.startsWith(folder)) continue;
|
|
13481
|
-
const absolutePath =
|
|
13571
|
+
const absolutePath = path17.join(vaultPath2, note.path);
|
|
13482
13572
|
let content;
|
|
13483
13573
|
try {
|
|
13484
13574
|
content = await fs12.promises.readFile(absolutePath, "utf-8");
|
|
@@ -13580,11 +13670,11 @@ function applyEntityBridging(results, stateDb2, maxBridgesPerResult = 5) {
|
|
|
13580
13670
|
const linkMap = /* @__PURE__ */ new Map();
|
|
13581
13671
|
try {
|
|
13582
13672
|
const paths = results.map((r) => r.path).filter(Boolean);
|
|
13583
|
-
for (const
|
|
13673
|
+
for (const path41 of paths) {
|
|
13584
13674
|
const rows = stateDb2.db.prepare(
|
|
13585
13675
|
"SELECT target FROM note_links WHERE note_path = ?"
|
|
13586
|
-
).all(
|
|
13587
|
-
linkMap.set(
|
|
13676
|
+
).all(path41);
|
|
13677
|
+
linkMap.set(path41, new Set(rows.map((r) => r.target)));
|
|
13588
13678
|
}
|
|
13589
13679
|
} catch {
|
|
13590
13680
|
return;
|
|
@@ -14137,7 +14227,7 @@ init_vault_scope();
|
|
|
14137
14227
|
|
|
14138
14228
|
// src/tools/read/graph.ts
|
|
14139
14229
|
import * as fs13 from "fs";
|
|
14140
|
-
import * as
|
|
14230
|
+
import * as path18 from "path";
|
|
14141
14231
|
import { z as z2 } from "zod";
|
|
14142
14232
|
|
|
14143
14233
|
// src/tools/read/graphAdvanced.ts
|
|
@@ -14571,7 +14661,7 @@ function detectCycles(index, maxLength = 10, limit = 20) {
|
|
|
14571
14661
|
// src/tools/read/graph.ts
|
|
14572
14662
|
async function getContext(vaultPath2, sourcePath, line, contextLines = 1) {
|
|
14573
14663
|
try {
|
|
14574
|
-
const fullPath =
|
|
14664
|
+
const fullPath = path18.join(vaultPath2, sourcePath);
|
|
14575
14665
|
const content = await fs13.promises.readFile(fullPath, "utf-8");
|
|
14576
14666
|
const allLines = content.split("\n");
|
|
14577
14667
|
let fmLines = 0;
|
|
@@ -15116,14 +15206,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb4 = ()
|
|
|
15116
15206
|
};
|
|
15117
15207
|
function findSimilarEntity2(target, entities) {
|
|
15118
15208
|
const targetLower = target.toLowerCase();
|
|
15119
|
-
for (const [name,
|
|
15209
|
+
for (const [name, path41] of entities) {
|
|
15120
15210
|
if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
|
|
15121
|
-
return
|
|
15211
|
+
return path41;
|
|
15122
15212
|
}
|
|
15123
15213
|
}
|
|
15124
|
-
for (const [name,
|
|
15214
|
+
for (const [name, path41] of entities) {
|
|
15125
15215
|
if (name.includes(targetLower) || targetLower.includes(name)) {
|
|
15126
|
-
return
|
|
15216
|
+
return path41;
|
|
15127
15217
|
}
|
|
15128
15218
|
}
|
|
15129
15219
|
return void 0;
|
|
@@ -15666,7 +15756,17 @@ function getProactiveLinkingOneLiner(stateDb2, daysBack = 1) {
|
|
|
15666
15756
|
init_wikilinkFeedback();
|
|
15667
15757
|
init_embeddings();
|
|
15668
15758
|
var STALE_THRESHOLD_SECONDS = 300;
|
|
15669
|
-
function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () => ({}), getStateDb4 = () => null, getWatcherStatus2 = () => null, getVersion = () => "unknown", getPipelineActivityState = () => null) {
|
|
15759
|
+
function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () => ({}), getStateDb4 = () => null, getWatcherStatus2 = () => null, getVersion = () => "unknown", getPipelineActivityState = () => null, getVaultRuntimeState = () => ({
|
|
15760
|
+
bootState: "booting",
|
|
15761
|
+
integrityState: "unknown",
|
|
15762
|
+
integrityCheckInProgress: false,
|
|
15763
|
+
integrityStartedAt: null,
|
|
15764
|
+
integritySource: null,
|
|
15765
|
+
lastIntegrityCheckedAt: null,
|
|
15766
|
+
lastIntegrityDurationMs: null,
|
|
15767
|
+
lastIntegrityDetail: null,
|
|
15768
|
+
lastBackupAt: null
|
|
15769
|
+
})) {
|
|
15670
15770
|
const IndexProgressSchema = z4.object({
|
|
15671
15771
|
parsed: z4.coerce.number().describe("Number of files parsed so far"),
|
|
15672
15772
|
total: z4.coerce.number().describe("Total number of files to parse")
|
|
@@ -15819,18 +15919,14 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
15819
15919
|
}
|
|
15820
15920
|
let dbIntegrityFailed = false;
|
|
15821
15921
|
const stateDb2 = getStateDb4();
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
} catch (err) {
|
|
15831
|
-
dbIntegrityFailed = true;
|
|
15832
|
-
recommendations.push(`Database integrity check error: ${err instanceof Error ? err.message : err}`);
|
|
15833
|
-
}
|
|
15922
|
+
const runtimeState = getVaultRuntimeState();
|
|
15923
|
+
if (runtimeState.integrityState === "failed") {
|
|
15924
|
+
dbIntegrityFailed = true;
|
|
15925
|
+
recommendations.push(`Database integrity check failed: ${runtimeState.lastIntegrityDetail ?? "unknown integrity failure"}`);
|
|
15926
|
+
} else if (runtimeState.integrityState === "error") {
|
|
15927
|
+
recommendations.push(`Database integrity check error: ${runtimeState.lastIntegrityDetail ?? "integrity runner error"}`);
|
|
15928
|
+
} else if (runtimeState.integrityCheckInProgress) {
|
|
15929
|
+
recommendations.push("Database integrity check is still running.");
|
|
15834
15930
|
}
|
|
15835
15931
|
const indexBuilt = indexState2 === "ready" && index !== void 0 && index.notes !== void 0;
|
|
15836
15932
|
let lastIndexActivityAt;
|
|
@@ -16024,6 +16120,14 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
16024
16120
|
tasks_ready: isTaskCacheReady(),
|
|
16025
16121
|
tasks_building: isTaskCacheBuilding(),
|
|
16026
16122
|
watcher_state: getWatcherStatus2()?.state,
|
|
16123
|
+
boot_state: runtimeState.bootState,
|
|
16124
|
+
integrity_state: runtimeState.integrityState,
|
|
16125
|
+
integrity_check_in_progress: runtimeState.integrityCheckInProgress,
|
|
16126
|
+
integrity_started_at: runtimeState.integrityStartedAt,
|
|
16127
|
+
integrity_source: runtimeState.integritySource,
|
|
16128
|
+
integrity_last_checked_at: runtimeState.lastIntegrityCheckedAt,
|
|
16129
|
+
integrity_duration_ms: runtimeState.lastIntegrityDurationMs,
|
|
16130
|
+
integrity_detail: runtimeState.lastIntegrityDetail,
|
|
16027
16131
|
watcher_pending: getWatcherStatus2()?.pendingEvents,
|
|
16028
16132
|
last_index_activity_at: lastIndexActivityAt,
|
|
16029
16133
|
last_index_activity_ago_seconds: lastIndexActivityAt ? Math.floor((Date.now() - lastIndexActivityAt) / 1e3) : void 0,
|
|
@@ -16075,6 +16179,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
16075
16179
|
async ({ detail = false }) => {
|
|
16076
16180
|
const activity = getPipelineActivityState();
|
|
16077
16181
|
const now = Date.now();
|
|
16182
|
+
const runtimeState = getVaultRuntimeState();
|
|
16078
16183
|
const output = {
|
|
16079
16184
|
busy: activity?.busy ?? false,
|
|
16080
16185
|
trigger: activity?.trigger ?? null,
|
|
@@ -16083,6 +16188,9 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
16083
16188
|
current_step: activity?.current_step ?? null,
|
|
16084
16189
|
progress: activity && activity.busy && activity.total_steps > 0 ? `${activity.completed_steps}/${activity.total_steps} steps` : null,
|
|
16085
16190
|
pending_events: activity?.pending_events ?? 0,
|
|
16191
|
+
boot_state: runtimeState.bootState,
|
|
16192
|
+
integrity_state: runtimeState.integrityState,
|
|
16193
|
+
integrity_check_in_progress: runtimeState.integrityCheckInProgress,
|
|
16086
16194
|
last_completed: activity?.last_completed_at ? {
|
|
16087
16195
|
at: activity.last_completed_at,
|
|
16088
16196
|
ago_seconds: Math.floor((now - activity.last_completed_at) / 1e3),
|
|
@@ -16143,8 +16251,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
16143
16251
|
daily_counts: z4.record(z4.number())
|
|
16144
16252
|
}).describe("Activity summary for the last 7 days")
|
|
16145
16253
|
};
|
|
16146
|
-
function isPeriodicNote3(
|
|
16147
|
-
const filename =
|
|
16254
|
+
function isPeriodicNote3(path41) {
|
|
16255
|
+
const filename = path41.split("/").pop() || "";
|
|
16148
16256
|
const nameWithoutExt = filename.replace(/\.md$/, "");
|
|
16149
16257
|
const patterns = [
|
|
16150
16258
|
/^\d{4}-\d{2}-\d{2}$/,
|
|
@@ -16159,7 +16267,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
16159
16267
|
// YYYY (yearly)
|
|
16160
16268
|
];
|
|
16161
16269
|
const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
|
|
16162
|
-
const folder =
|
|
16270
|
+
const folder = path41.split("/")[0]?.toLowerCase() || "";
|
|
16163
16271
|
return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
|
|
16164
16272
|
}
|
|
16165
16273
|
async function runVaultStats() {
|
|
@@ -17266,13 +17374,13 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17266
17374
|
max_content_chars: z6.number().default(2e4).describe("Max total chars of section content to include. Sections are truncated at paragraph boundaries.")
|
|
17267
17375
|
}
|
|
17268
17376
|
},
|
|
17269
|
-
async ({ path:
|
|
17377
|
+
async ({ path: path41, include_content, max_content_chars }) => {
|
|
17270
17378
|
const index = getIndex();
|
|
17271
17379
|
const vaultPath2 = getVaultPath();
|
|
17272
|
-
const result = await getNoteStructure(index,
|
|
17380
|
+
const result = await getNoteStructure(index, path41, vaultPath2);
|
|
17273
17381
|
if (!result) {
|
|
17274
17382
|
return {
|
|
17275
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
17383
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path41 }, null, 2) }]
|
|
17276
17384
|
};
|
|
17277
17385
|
}
|
|
17278
17386
|
let totalChars = 0;
|
|
@@ -17283,7 +17391,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17283
17391
|
truncated = true;
|
|
17284
17392
|
break;
|
|
17285
17393
|
}
|
|
17286
|
-
const sectionResult = await getSectionContent(index,
|
|
17394
|
+
const sectionResult = await getSectionContent(index, path41, section.heading.text, vaultPath2, true);
|
|
17287
17395
|
if (sectionResult) {
|
|
17288
17396
|
let content = sectionResult.content;
|
|
17289
17397
|
const remaining = max_content_chars - totalChars;
|
|
@@ -17298,13 +17406,13 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17298
17406
|
}
|
|
17299
17407
|
}
|
|
17300
17408
|
}
|
|
17301
|
-
const note = index.notes.get(
|
|
17409
|
+
const note = index.notes.get(path41);
|
|
17302
17410
|
const enriched = { ...result };
|
|
17303
17411
|
if (note) {
|
|
17304
17412
|
enriched.frontmatter = note.frontmatter;
|
|
17305
17413
|
enriched.tags = note.tags;
|
|
17306
17414
|
enriched.aliases = note.aliases;
|
|
17307
|
-
const normalizedPath =
|
|
17415
|
+
const normalizedPath = path41.toLowerCase().replace(/\.md$/, "");
|
|
17308
17416
|
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
17309
17417
|
enriched.backlink_count = backlinks.length;
|
|
17310
17418
|
enriched.outlink_count = note.outlinks.length;
|
|
@@ -17342,15 +17450,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17342
17450
|
max_content_chars: z6.number().default(1e4).describe("Max chars of section content. Truncated at paragraph boundaries.")
|
|
17343
17451
|
}
|
|
17344
17452
|
},
|
|
17345
|
-
async ({ path:
|
|
17453
|
+
async ({ path: path41, heading, include_subheadings, max_content_chars }) => {
|
|
17346
17454
|
const index = getIndex();
|
|
17347
17455
|
const vaultPath2 = getVaultPath();
|
|
17348
|
-
const result = await getSectionContent(index,
|
|
17456
|
+
const result = await getSectionContent(index, path41, heading, vaultPath2, include_subheadings);
|
|
17349
17457
|
if (!result) {
|
|
17350
17458
|
return {
|
|
17351
17459
|
content: [{ type: "text", text: JSON.stringify({
|
|
17352
17460
|
error: "Section not found",
|
|
17353
|
-
path:
|
|
17461
|
+
path: path41,
|
|
17354
17462
|
heading
|
|
17355
17463
|
}, null, 2) }]
|
|
17356
17464
|
};
|
|
@@ -17411,16 +17519,16 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17411
17519
|
offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
|
|
17412
17520
|
}
|
|
17413
17521
|
},
|
|
17414
|
-
async ({ path:
|
|
17522
|
+
async ({ path: path41, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
|
|
17415
17523
|
const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
|
|
17416
17524
|
const index = getIndex();
|
|
17417
17525
|
const vaultPath2 = getVaultPath();
|
|
17418
17526
|
const config2 = getConfig2();
|
|
17419
|
-
if (
|
|
17420
|
-
const result2 = await getTasksFromNote(index,
|
|
17527
|
+
if (path41) {
|
|
17528
|
+
const result2 = await getTasksFromNote(index, path41, vaultPath2, getExcludeTags(config2));
|
|
17421
17529
|
if (!result2) {
|
|
17422
17530
|
return {
|
|
17423
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
17531
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path41 }, null, 2) }]
|
|
17424
17532
|
};
|
|
17425
17533
|
}
|
|
17426
17534
|
let filtered = result2;
|
|
@@ -17430,7 +17538,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17430
17538
|
const paged2 = filtered.slice(offset, offset + limit);
|
|
17431
17539
|
return {
|
|
17432
17540
|
content: [{ type: "text", text: JSON.stringify({
|
|
17433
|
-
path:
|
|
17541
|
+
path: path41,
|
|
17434
17542
|
total_count: filtered.length,
|
|
17435
17543
|
returned_count: paged2.length,
|
|
17436
17544
|
open: result2.filter((t) => t.status === "open").length,
|
|
@@ -17586,7 +17694,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
17586
17694
|
// src/tools/read/migrations.ts
|
|
17587
17695
|
import { z as z7 } from "zod";
|
|
17588
17696
|
import * as fs15 from "fs/promises";
|
|
17589
|
-
import * as
|
|
17697
|
+
import * as path19 from "path";
|
|
17590
17698
|
import matter2 from "gray-matter";
|
|
17591
17699
|
function getNotesInFolder(index, folder) {
|
|
17592
17700
|
const notes = [];
|
|
@@ -17599,7 +17707,7 @@ function getNotesInFolder(index, folder) {
|
|
|
17599
17707
|
return notes;
|
|
17600
17708
|
}
|
|
17601
17709
|
async function readFileContent(notePath, vaultPath2) {
|
|
17602
|
-
const fullPath =
|
|
17710
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
17603
17711
|
try {
|
|
17604
17712
|
return await fs15.readFile(fullPath, "utf-8");
|
|
17605
17713
|
} catch {
|
|
@@ -17607,7 +17715,7 @@ async function readFileContent(notePath, vaultPath2) {
|
|
|
17607
17715
|
}
|
|
17608
17716
|
}
|
|
17609
17717
|
async function writeFileContent(notePath, vaultPath2, content) {
|
|
17610
|
-
const fullPath =
|
|
17718
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
17611
17719
|
try {
|
|
17612
17720
|
await fs15.writeFile(fullPath, content, "utf-8");
|
|
17613
17721
|
return true;
|
|
@@ -17788,7 +17896,7 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
|
|
|
17788
17896
|
|
|
17789
17897
|
// src/tools/read/graphAnalysis.ts
|
|
17790
17898
|
import fs16 from "node:fs";
|
|
17791
|
-
import
|
|
17899
|
+
import path20 from "node:path";
|
|
17792
17900
|
import { z as z8 } from "zod";
|
|
17793
17901
|
|
|
17794
17902
|
// src/tools/read/schema.ts
|
|
@@ -18314,7 +18422,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb4
|
|
|
18314
18422
|
const scored = allNotes.map((note) => {
|
|
18315
18423
|
let wordCount = 0;
|
|
18316
18424
|
try {
|
|
18317
|
-
const content = fs16.readFileSync(
|
|
18425
|
+
const content = fs16.readFileSync(path20.join(vaultPath2, note.path), "utf-8");
|
|
18318
18426
|
const body = content.replace(/^---[\s\S]*?---\n?/, "");
|
|
18319
18427
|
wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
|
|
18320
18428
|
} catch {
|
|
@@ -18945,12 +19053,12 @@ import { z as z11 } from "zod";
|
|
|
18945
19053
|
|
|
18946
19054
|
// src/tools/read/bidirectional.ts
|
|
18947
19055
|
import * as fs17 from "fs/promises";
|
|
18948
|
-
import * as
|
|
19056
|
+
import * as path21 from "path";
|
|
18949
19057
|
import matter3 from "gray-matter";
|
|
18950
19058
|
var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
|
|
18951
19059
|
var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
18952
19060
|
async function readFileContent2(notePath, vaultPath2) {
|
|
18953
|
-
const fullPath =
|
|
19061
|
+
const fullPath = path21.join(vaultPath2, notePath);
|
|
18954
19062
|
try {
|
|
18955
19063
|
return await fs17.readFile(fullPath, "utf-8");
|
|
18956
19064
|
} catch {
|
|
@@ -19129,10 +19237,10 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
|
|
|
19129
19237
|
|
|
19130
19238
|
// src/tools/read/computed.ts
|
|
19131
19239
|
import * as fs18 from "fs/promises";
|
|
19132
|
-
import * as
|
|
19240
|
+
import * as path22 from "path";
|
|
19133
19241
|
import matter4 from "gray-matter";
|
|
19134
19242
|
async function readFileContent3(notePath, vaultPath2) {
|
|
19135
|
-
const fullPath =
|
|
19243
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
19136
19244
|
try {
|
|
19137
19245
|
return await fs18.readFile(fullPath, "utf-8");
|
|
19138
19246
|
} catch {
|
|
@@ -19140,7 +19248,7 @@ async function readFileContent3(notePath, vaultPath2) {
|
|
|
19140
19248
|
}
|
|
19141
19249
|
}
|
|
19142
19250
|
async function getFileStats(notePath, vaultPath2) {
|
|
19143
|
-
const fullPath =
|
|
19251
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
19144
19252
|
try {
|
|
19145
19253
|
const stats = await fs18.stat(fullPath);
|
|
19146
19254
|
return {
|
|
@@ -19413,7 +19521,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
19413
19521
|
init_writer();
|
|
19414
19522
|
import { z as z12 } from "zod";
|
|
19415
19523
|
import fs24 from "fs/promises";
|
|
19416
|
-
import
|
|
19524
|
+
import path27 from "path";
|
|
19417
19525
|
|
|
19418
19526
|
// src/core/write/validator.ts
|
|
19419
19527
|
var TIMESTAMP_PATTERN = /^\*\*\d{2}:\d{2}\*\*/;
|
|
@@ -19632,16 +19740,16 @@ init_writer();
|
|
|
19632
19740
|
init_wikilinks();
|
|
19633
19741
|
init_wikilinkFeedback();
|
|
19634
19742
|
import fs23 from "fs/promises";
|
|
19635
|
-
import
|
|
19743
|
+
import path26 from "path";
|
|
19636
19744
|
|
|
19637
19745
|
// src/core/write/policy/policyPaths.ts
|
|
19638
19746
|
import fs22 from "fs/promises";
|
|
19639
|
-
import
|
|
19747
|
+
import path25 from "path";
|
|
19640
19748
|
function getPoliciesDir(vaultPath2) {
|
|
19641
|
-
return
|
|
19749
|
+
return path25.join(vaultPath2, ".flywheel", "policies");
|
|
19642
19750
|
}
|
|
19643
19751
|
function getLegacyPoliciesDir(vaultPath2) {
|
|
19644
|
-
return
|
|
19752
|
+
return path25.join(vaultPath2, ".claude", "policies");
|
|
19645
19753
|
}
|
|
19646
19754
|
async function ensurePoliciesDir(vaultPath2) {
|
|
19647
19755
|
const dir = getPoliciesDir(vaultPath2);
|
|
@@ -19658,14 +19766,14 @@ async function migratePoliciesIfNeeded(vaultPath2) {
|
|
|
19658
19766
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
19659
19767
|
if (yamlFiles.length === 0) {
|
|
19660
19768
|
await tryRmdir(legacyDir);
|
|
19661
|
-
await tryRmdir(
|
|
19769
|
+
await tryRmdir(path25.join(vaultPath2, ".claude"));
|
|
19662
19770
|
return;
|
|
19663
19771
|
}
|
|
19664
19772
|
await ensurePoliciesDir(vaultPath2);
|
|
19665
19773
|
const destDir = getPoliciesDir(vaultPath2);
|
|
19666
19774
|
for (const file of yamlFiles) {
|
|
19667
|
-
const src =
|
|
19668
|
-
const dest =
|
|
19775
|
+
const src = path25.join(legacyDir, file);
|
|
19776
|
+
const dest = path25.join(destDir, file);
|
|
19669
19777
|
try {
|
|
19670
19778
|
await fs22.access(dest);
|
|
19671
19779
|
} catch {
|
|
@@ -19674,7 +19782,7 @@ async function migratePoliciesIfNeeded(vaultPath2) {
|
|
|
19674
19782
|
await fs22.unlink(src);
|
|
19675
19783
|
}
|
|
19676
19784
|
await tryRmdir(legacyDir);
|
|
19677
|
-
await tryRmdir(
|
|
19785
|
+
await tryRmdir(path25.join(vaultPath2, ".claude"));
|
|
19678
19786
|
}
|
|
19679
19787
|
async function tryRmdir(dir) {
|
|
19680
19788
|
try {
|
|
@@ -19687,7 +19795,7 @@ async function tryRmdir(dir) {
|
|
|
19687
19795
|
}
|
|
19688
19796
|
var migrationCache = /* @__PURE__ */ new Map();
|
|
19689
19797
|
async function ensureMigrated(vaultPath2) {
|
|
19690
|
-
const key =
|
|
19798
|
+
const key = path25.resolve(vaultPath2);
|
|
19691
19799
|
if (!migrationCache.has(key)) {
|
|
19692
19800
|
migrationCache.set(key, migratePoliciesIfNeeded(vaultPath2));
|
|
19693
19801
|
}
|
|
@@ -19754,7 +19862,7 @@ async function getPolicyHint(vaultPath2) {
|
|
|
19754
19862
|
return "";
|
|
19755
19863
|
}
|
|
19756
19864
|
async function ensureFileExists(vaultPath2, notePath) {
|
|
19757
|
-
const fullPath =
|
|
19865
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
19758
19866
|
try {
|
|
19759
19867
|
await fs23.access(fullPath);
|
|
19760
19868
|
return null;
|
|
@@ -19959,7 +20067,7 @@ async function executeCreateNote(options) {
|
|
|
19959
20067
|
if (!pathCheck.valid) {
|
|
19960
20068
|
return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
|
|
19961
20069
|
}
|
|
19962
|
-
const fullPath =
|
|
20070
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
19963
20071
|
let fileExists = false;
|
|
19964
20072
|
try {
|
|
19965
20073
|
await fs23.access(fullPath);
|
|
@@ -19969,7 +20077,7 @@ async function executeCreateNote(options) {
|
|
|
19969
20077
|
if (fileExists && !overwrite) {
|
|
19970
20078
|
return { success: false, result: errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`), filesWritten: [] };
|
|
19971
20079
|
}
|
|
19972
|
-
await fs23.mkdir(
|
|
20080
|
+
await fs23.mkdir(path26.dirname(fullPath), { recursive: true });
|
|
19973
20081
|
const { maybeApplyWikilinks: maybeApplyWikilinks2 } = await Promise.resolve().then(() => (init_wikilinks(), wikilinks_exports));
|
|
19974
20082
|
const { content: processedContent } = maybeApplyWikilinks2(content, skipWikilinks ?? false, notePath);
|
|
19975
20083
|
let finalFrontmatter = frontmatter;
|
|
@@ -20003,7 +20111,7 @@ async function executeDeleteNote(options) {
|
|
|
20003
20111
|
if (!pathCheck.valid) {
|
|
20004
20112
|
return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
|
|
20005
20113
|
}
|
|
20006
|
-
const fullPath =
|
|
20114
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
20007
20115
|
try {
|
|
20008
20116
|
await fs23.access(fullPath);
|
|
20009
20117
|
} catch {
|
|
@@ -20027,10 +20135,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config2) {
|
|
|
20027
20135
|
if (!validation.valid) {
|
|
20028
20136
|
throw new Error(`Path blocked: ${validation.reason}`);
|
|
20029
20137
|
}
|
|
20030
|
-
const fullPath =
|
|
20031
|
-
await fs24.mkdir(
|
|
20138
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
20139
|
+
await fs24.mkdir(path27.dirname(fullPath), { recursive: true });
|
|
20032
20140
|
const templates = config2.templates || {};
|
|
20033
|
-
const filename =
|
|
20141
|
+
const filename = path27.basename(notePath, ".md").toLowerCase();
|
|
20034
20142
|
let templatePath;
|
|
20035
20143
|
let periodicType;
|
|
20036
20144
|
const dailyPattern = /^\d{4}-\d{2}-\d{2}/;
|
|
@@ -20063,7 +20171,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config2) {
|
|
|
20063
20171
|
];
|
|
20064
20172
|
for (const candidate of candidates) {
|
|
20065
20173
|
try {
|
|
20066
|
-
await fs24.access(
|
|
20174
|
+
await fs24.access(path27.join(vaultPath2, candidate));
|
|
20067
20175
|
templatePath = candidate;
|
|
20068
20176
|
console.error(`[Flywheel] Template not in config but found at ${candidate} \u2014 using it`);
|
|
20069
20177
|
break;
|
|
@@ -20074,11 +20182,11 @@ async function createNoteFromTemplate(vaultPath2, notePath, config2) {
|
|
|
20074
20182
|
let templateContent;
|
|
20075
20183
|
if (templatePath) {
|
|
20076
20184
|
try {
|
|
20077
|
-
const absTemplatePath =
|
|
20185
|
+
const absTemplatePath = path27.join(vaultPath2, templatePath);
|
|
20078
20186
|
templateContent = await fs24.readFile(absTemplatePath, "utf-8");
|
|
20079
20187
|
} catch {
|
|
20080
20188
|
console.error(`[Flywheel] Template at ${templatePath} not readable, using minimal fallback`);
|
|
20081
|
-
const title =
|
|
20189
|
+
const title = path27.basename(notePath, ".md");
|
|
20082
20190
|
templateContent = `---
|
|
20083
20191
|
---
|
|
20084
20192
|
|
|
@@ -20090,7 +20198,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config2) {
|
|
|
20090
20198
|
if (periodicType) {
|
|
20091
20199
|
console.error(`[Flywheel] No ${periodicType} template found in config or vault \u2014 using minimal fallback`);
|
|
20092
20200
|
}
|
|
20093
|
-
const title =
|
|
20201
|
+
const title = path27.basename(notePath, ".md");
|
|
20094
20202
|
templateContent = `---
|
|
20095
20203
|
---
|
|
20096
20204
|
|
|
@@ -20099,7 +20207,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config2) {
|
|
|
20099
20207
|
}
|
|
20100
20208
|
const now = /* @__PURE__ */ new Date();
|
|
20101
20209
|
const dateStr = now.toISOString().split("T")[0];
|
|
20102
|
-
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g,
|
|
20210
|
+
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path27.basename(notePath, ".md"));
|
|
20103
20211
|
const matter9 = (await import("gray-matter")).default;
|
|
20104
20212
|
const parsed = matter9(templateContent);
|
|
20105
20213
|
if (!parsed.data.date) {
|
|
@@ -20138,7 +20246,7 @@ function registerMutationTools(server2, getVaultPath, getConfig2 = () => ({})) {
|
|
|
20138
20246
|
let noteCreated = false;
|
|
20139
20247
|
let templateUsed;
|
|
20140
20248
|
if (create_if_missing && !dry_run) {
|
|
20141
|
-
const fullPath =
|
|
20249
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
20142
20250
|
try {
|
|
20143
20251
|
await fs24.access(fullPath);
|
|
20144
20252
|
} catch {
|
|
@@ -20149,7 +20257,7 @@ function registerMutationTools(server2, getVaultPath, getConfig2 = () => ({})) {
|
|
|
20149
20257
|
}
|
|
20150
20258
|
}
|
|
20151
20259
|
if (create_if_missing && dry_run) {
|
|
20152
|
-
const fullPath =
|
|
20260
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
20153
20261
|
try {
|
|
20154
20262
|
await fs24.access(fullPath);
|
|
20155
20263
|
} catch {
|
|
@@ -20636,7 +20744,7 @@ init_writer();
|
|
|
20636
20744
|
init_wikilinks();
|
|
20637
20745
|
import { z as z15 } from "zod";
|
|
20638
20746
|
import fs25 from "fs/promises";
|
|
20639
|
-
import
|
|
20747
|
+
import path28 from "path";
|
|
20640
20748
|
function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
20641
20749
|
server2.tool(
|
|
20642
20750
|
"vault_create_note",
|
|
@@ -20662,23 +20770,23 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
|
20662
20770
|
if (!validatePath(vaultPath2, notePath)) {
|
|
20663
20771
|
return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
|
|
20664
20772
|
}
|
|
20665
|
-
const fullPath =
|
|
20773
|
+
const fullPath = path28.join(vaultPath2, notePath);
|
|
20666
20774
|
const existsCheck = await ensureFileExists(vaultPath2, notePath);
|
|
20667
20775
|
if (existsCheck === null && !overwrite) {
|
|
20668
20776
|
return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
|
|
20669
20777
|
}
|
|
20670
|
-
const dir =
|
|
20778
|
+
const dir = path28.dirname(fullPath);
|
|
20671
20779
|
await fs25.mkdir(dir, { recursive: true });
|
|
20672
20780
|
let effectiveContent = content;
|
|
20673
20781
|
let effectiveFrontmatter = frontmatter;
|
|
20674
20782
|
if (template) {
|
|
20675
|
-
const templatePath =
|
|
20783
|
+
const templatePath = path28.join(vaultPath2, template);
|
|
20676
20784
|
try {
|
|
20677
20785
|
const raw = await fs25.readFile(templatePath, "utf-8");
|
|
20678
20786
|
const matter9 = (await import("gray-matter")).default;
|
|
20679
20787
|
const parsed = matter9(raw);
|
|
20680
20788
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
20681
|
-
const title =
|
|
20789
|
+
const title = path28.basename(notePath, ".md");
|
|
20682
20790
|
let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
|
|
20683
20791
|
if (content) {
|
|
20684
20792
|
templateContent = templateContent.trimEnd() + "\n\n" + content;
|
|
@@ -20697,7 +20805,7 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
|
20697
20805
|
effectiveFrontmatter.created = now.toISOString();
|
|
20698
20806
|
}
|
|
20699
20807
|
const warnings = [];
|
|
20700
|
-
const noteName =
|
|
20808
|
+
const noteName = path28.basename(notePath, ".md");
|
|
20701
20809
|
const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
|
|
20702
20810
|
const preflight = await checkPreflightSimilarity(noteName);
|
|
20703
20811
|
if (preflight.existingEntity) {
|
|
@@ -20838,7 +20946,7 @@ ${sources}`;
|
|
|
20838
20946
|
}
|
|
20839
20947
|
return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
|
|
20840
20948
|
}
|
|
20841
|
-
const fullPath =
|
|
20949
|
+
const fullPath = path28.join(vaultPath2, notePath);
|
|
20842
20950
|
await fs25.unlink(fullPath);
|
|
20843
20951
|
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
|
|
20844
20952
|
const message = backlinkWarning ? `Deleted note: ${notePath}
|
|
@@ -20860,7 +20968,7 @@ init_git();
|
|
|
20860
20968
|
init_wikilinks();
|
|
20861
20969
|
import { z as z16 } from "zod";
|
|
20862
20970
|
import fs26 from "fs/promises";
|
|
20863
|
-
import
|
|
20971
|
+
import path29 from "path";
|
|
20864
20972
|
import matter6 from "gray-matter";
|
|
20865
20973
|
function escapeRegex(str) {
|
|
20866
20974
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -20879,7 +20987,7 @@ function extractWikilinks2(content) {
|
|
|
20879
20987
|
return wikilinks;
|
|
20880
20988
|
}
|
|
20881
20989
|
function getTitleFromPath(filePath) {
|
|
20882
|
-
return
|
|
20990
|
+
return path29.basename(filePath, ".md");
|
|
20883
20991
|
}
|
|
20884
20992
|
async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
20885
20993
|
const results = [];
|
|
@@ -20888,7 +20996,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
20888
20996
|
const files = [];
|
|
20889
20997
|
const entries = await fs26.readdir(dir, { withFileTypes: true });
|
|
20890
20998
|
for (const entry of entries) {
|
|
20891
|
-
const fullPath =
|
|
20999
|
+
const fullPath = path29.join(dir, entry.name);
|
|
20892
21000
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
20893
21001
|
files.push(...await scanDir(fullPath));
|
|
20894
21002
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -20899,7 +21007,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
20899
21007
|
}
|
|
20900
21008
|
const allFiles = await scanDir(vaultPath2);
|
|
20901
21009
|
for (const filePath of allFiles) {
|
|
20902
|
-
const relativePath =
|
|
21010
|
+
const relativePath = path29.relative(vaultPath2, filePath);
|
|
20903
21011
|
const content = await fs26.readFile(filePath, "utf-8");
|
|
20904
21012
|
const wikilinks = extractWikilinks2(content);
|
|
20905
21013
|
const matchingLinks = [];
|
|
@@ -20986,8 +21094,8 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
20986
21094
|
};
|
|
20987
21095
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
20988
21096
|
}
|
|
20989
|
-
const oldFullPath =
|
|
20990
|
-
const newFullPath =
|
|
21097
|
+
const oldFullPath = path29.join(vaultPath2, oldPath);
|
|
21098
|
+
const newFullPath = path29.join(vaultPath2, newPath);
|
|
20991
21099
|
try {
|
|
20992
21100
|
await fs26.access(oldFullPath);
|
|
20993
21101
|
} catch {
|
|
@@ -21068,7 +21176,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
21068
21176
|
};
|
|
21069
21177
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
21070
21178
|
}
|
|
21071
|
-
const destDir =
|
|
21179
|
+
const destDir = path29.dirname(newFullPath);
|
|
21072
21180
|
await fs26.mkdir(destDir, { recursive: true });
|
|
21073
21181
|
await fs26.rename(oldFullPath, newFullPath);
|
|
21074
21182
|
let gitCommit;
|
|
@@ -21144,10 +21252,10 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
21144
21252
|
if (sanitizedTitle !== newTitle) {
|
|
21145
21253
|
console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
|
|
21146
21254
|
}
|
|
21147
|
-
const fullPath =
|
|
21148
|
-
const dir =
|
|
21149
|
-
const newPath = dir === "." ? `${sanitizedTitle}.md` :
|
|
21150
|
-
const newFullPath =
|
|
21255
|
+
const fullPath = path29.join(vaultPath2, notePath);
|
|
21256
|
+
const dir = path29.dirname(notePath);
|
|
21257
|
+
const newPath = dir === "." ? `${sanitizedTitle}.md` : path29.join(dir, `${sanitizedTitle}.md`);
|
|
21258
|
+
const newFullPath = path29.join(vaultPath2, newPath);
|
|
21151
21259
|
try {
|
|
21152
21260
|
await fs26.access(fullPath);
|
|
21153
21261
|
} catch {
|
|
@@ -21279,7 +21387,7 @@ init_writer();
|
|
|
21279
21387
|
init_wikilinks();
|
|
21280
21388
|
import { z as z17 } from "zod";
|
|
21281
21389
|
import fs27 from "fs/promises";
|
|
21282
|
-
import
|
|
21390
|
+
import path30 from "path";
|
|
21283
21391
|
function registerMergeTools(server2, getVaultPath) {
|
|
21284
21392
|
server2.tool(
|
|
21285
21393
|
"merge_entities",
|
|
@@ -21576,14 +21684,14 @@ async function findSourceNote(vaultPath2, sourceName, excludePath) {
|
|
|
21576
21684
|
}
|
|
21577
21685
|
for (const entry of entries) {
|
|
21578
21686
|
if (entry.name.startsWith(".")) continue;
|
|
21579
|
-
const fullPath =
|
|
21687
|
+
const fullPath = path30.join(dir, entry.name);
|
|
21580
21688
|
if (entry.isDirectory()) {
|
|
21581
21689
|
const found = await scanDir(fullPath);
|
|
21582
21690
|
if (found) return found;
|
|
21583
21691
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
21584
|
-
const basename5 =
|
|
21692
|
+
const basename5 = path30.basename(entry.name, ".md");
|
|
21585
21693
|
if (basename5.toLowerCase() === targetLower) {
|
|
21586
|
-
const relative2 =
|
|
21694
|
+
const relative2 = path30.relative(vaultPath2, fullPath).replace(/\\/g, "/");
|
|
21587
21695
|
if (relative2 !== excludePath) return relative2;
|
|
21588
21696
|
}
|
|
21589
21697
|
}
|
|
@@ -21704,7 +21812,7 @@ Message: ${undoResult.undoneCommit.message}` : void 0
|
|
|
21704
21812
|
}
|
|
21705
21813
|
|
|
21706
21814
|
// src/tools/write/policy.ts
|
|
21707
|
-
import * as
|
|
21815
|
+
import * as path35 from "path";
|
|
21708
21816
|
import { z as z20 } from "zod";
|
|
21709
21817
|
|
|
21710
21818
|
// src/core/write/policy/index.ts
|
|
@@ -21714,7 +21822,7 @@ init_schema();
|
|
|
21714
21822
|
// src/core/write/policy/parser.ts
|
|
21715
21823
|
init_schema();
|
|
21716
21824
|
import fs28 from "fs/promises";
|
|
21717
|
-
import
|
|
21825
|
+
import path31 from "path";
|
|
21718
21826
|
import matter7 from "gray-matter";
|
|
21719
21827
|
function parseYaml(content) {
|
|
21720
21828
|
const parsed = matter7(`---
|
|
@@ -21765,12 +21873,12 @@ async function loadPolicyFile(filePath) {
|
|
|
21765
21873
|
async function loadPolicy(vaultPath2, policyName) {
|
|
21766
21874
|
await ensureMigrated(vaultPath2);
|
|
21767
21875
|
const policiesDir = getPoliciesDir(vaultPath2);
|
|
21768
|
-
const policyPath =
|
|
21876
|
+
const policyPath = path31.join(policiesDir, `${policyName}.yaml`);
|
|
21769
21877
|
try {
|
|
21770
21878
|
await fs28.access(policyPath);
|
|
21771
21879
|
return loadPolicyFile(policyPath);
|
|
21772
21880
|
} catch {
|
|
21773
|
-
const ymlPath =
|
|
21881
|
+
const ymlPath = path31.join(policiesDir, `${policyName}.yml`);
|
|
21774
21882
|
try {
|
|
21775
21883
|
await fs28.access(ymlPath);
|
|
21776
21884
|
return loadPolicyFile(ymlPath);
|
|
@@ -21913,7 +22021,7 @@ init_writer();
|
|
|
21913
22021
|
init_git();
|
|
21914
22022
|
init_wikilinks();
|
|
21915
22023
|
import fs30 from "fs/promises";
|
|
21916
|
-
import
|
|
22024
|
+
import path33 from "path";
|
|
21917
22025
|
init_constants2();
|
|
21918
22026
|
async function executeStep(step, vaultPath2, context, conditionResults, searchFn) {
|
|
21919
22027
|
const { execute, reason } = shouldStepExecute(step.when, conditionResults);
|
|
@@ -22104,12 +22212,12 @@ async function executeCreateNote2(params, vaultPath2, context) {
|
|
|
22104
22212
|
let frontmatter = params.frontmatter || {};
|
|
22105
22213
|
if (params.template) {
|
|
22106
22214
|
try {
|
|
22107
|
-
const templatePath =
|
|
22215
|
+
const templatePath = path33.join(vaultPath2, String(params.template));
|
|
22108
22216
|
const raw = await fs30.readFile(templatePath, "utf-8");
|
|
22109
22217
|
const matter9 = (await import("gray-matter")).default;
|
|
22110
22218
|
const parsed = matter9(raw);
|
|
22111
22219
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
22112
|
-
const title =
|
|
22220
|
+
const title = path33.basename(notePath, ".md");
|
|
22113
22221
|
let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
|
|
22114
22222
|
if (content) {
|
|
22115
22223
|
templateContent = templateContent.trimEnd() + "\n\n" + content;
|
|
@@ -22142,7 +22250,7 @@ async function executeToggleTask(params, vaultPath2) {
|
|
|
22142
22250
|
const notePath = String(params.path || "");
|
|
22143
22251
|
const task = String(params.task || "");
|
|
22144
22252
|
const section = params.section ? String(params.section) : void 0;
|
|
22145
|
-
const fullPath =
|
|
22253
|
+
const fullPath = path33.join(vaultPath2, notePath);
|
|
22146
22254
|
try {
|
|
22147
22255
|
await fs30.access(fullPath);
|
|
22148
22256
|
} catch {
|
|
@@ -22425,7 +22533,7 @@ async function rollbackChanges(vaultPath2, originalContents, filesModified) {
|
|
|
22425
22533
|
const pathCheck = await validatePathSecure(vaultPath2, filePath);
|
|
22426
22534
|
if (!pathCheck.valid) continue;
|
|
22427
22535
|
const original = originalContents.get(filePath);
|
|
22428
|
-
const fullPath =
|
|
22536
|
+
const fullPath = path33.join(vaultPath2, filePath);
|
|
22429
22537
|
if (original === null) {
|
|
22430
22538
|
try {
|
|
22431
22539
|
await fs30.unlink(fullPath);
|
|
@@ -22480,7 +22588,7 @@ async function previewPolicy(policy, vaultPath2, variables) {
|
|
|
22480
22588
|
|
|
22481
22589
|
// src/core/write/policy/storage.ts
|
|
22482
22590
|
import fs31 from "fs/promises";
|
|
22483
|
-
import
|
|
22591
|
+
import path34 from "path";
|
|
22484
22592
|
async function listPolicies(vaultPath2) {
|
|
22485
22593
|
await ensureMigrated(vaultPath2);
|
|
22486
22594
|
const dir = getPoliciesDir(vaultPath2);
|
|
@@ -22491,7 +22599,7 @@ async function listPolicies(vaultPath2) {
|
|
|
22491
22599
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
|
22492
22600
|
continue;
|
|
22493
22601
|
}
|
|
22494
|
-
const filePath =
|
|
22602
|
+
const filePath = path34.join(dir, file);
|
|
22495
22603
|
const stat4 = await fs31.stat(filePath);
|
|
22496
22604
|
const content = await fs31.readFile(filePath, "utf-8");
|
|
22497
22605
|
const metadata = extractPolicyMetadata(content);
|
|
@@ -22517,7 +22625,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
|
|
|
22517
22625
|
const dir = getPoliciesDir(vaultPath2);
|
|
22518
22626
|
await ensurePoliciesDir(vaultPath2);
|
|
22519
22627
|
const filename = `${policyName}.yaml`;
|
|
22520
|
-
const filePath =
|
|
22628
|
+
const filePath = path34.join(dir, filename);
|
|
22521
22629
|
if (!overwrite) {
|
|
22522
22630
|
try {
|
|
22523
22631
|
await fs31.access(filePath);
|
|
@@ -22631,7 +22739,7 @@ function registerPolicyTools(server2, getVaultPath, getSearchFn) {
|
|
|
22631
22739
|
const policies = await listPolicies(vaultPath2);
|
|
22632
22740
|
const response = {
|
|
22633
22741
|
success: true,
|
|
22634
|
-
vault:
|
|
22742
|
+
vault: path35.basename(vaultPath2),
|
|
22635
22743
|
vault_path: vaultPath2,
|
|
22636
22744
|
count: policies.length,
|
|
22637
22745
|
policies: policies.map((p) => ({
|
|
@@ -23065,7 +23173,7 @@ import { z as z21 } from "zod";
|
|
|
23065
23173
|
|
|
23066
23174
|
// src/core/write/tagRename.ts
|
|
23067
23175
|
import * as fs32 from "fs/promises";
|
|
23068
|
-
import * as
|
|
23176
|
+
import * as path36 from "path";
|
|
23069
23177
|
import matter8 from "gray-matter";
|
|
23070
23178
|
import { getProtectedZones as getProtectedZones2 } from "@velvetmonkey/vault-core";
|
|
23071
23179
|
function getNotesInFolder3(index, folder) {
|
|
@@ -23171,7 +23279,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
|
|
|
23171
23279
|
const previews = [];
|
|
23172
23280
|
let totalChanges = 0;
|
|
23173
23281
|
for (const note of affectedNotes) {
|
|
23174
|
-
const fullPath =
|
|
23282
|
+
const fullPath = path36.join(vaultPath2, note.path);
|
|
23175
23283
|
let fileContent;
|
|
23176
23284
|
try {
|
|
23177
23285
|
fileContent = await fs32.readFile(fullPath, "utf-8");
|
|
@@ -24301,7 +24409,7 @@ init_wikilinks();
|
|
|
24301
24409
|
init_wikilinkFeedback();
|
|
24302
24410
|
import { z as z28 } from "zod";
|
|
24303
24411
|
import * as fs33 from "fs/promises";
|
|
24304
|
-
import * as
|
|
24412
|
+
import * as path37 from "path";
|
|
24305
24413
|
import { scanVaultEntities as scanVaultEntities4, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
|
|
24306
24414
|
init_embeddings();
|
|
24307
24415
|
function hasSkipWikilinks(content) {
|
|
@@ -24317,13 +24425,13 @@ async function collectMarkdownFiles(dirPath, basePath, excludeFolders) {
|
|
|
24317
24425
|
const entries = await fs33.readdir(dirPath, { withFileTypes: true });
|
|
24318
24426
|
for (const entry of entries) {
|
|
24319
24427
|
if (entry.name.startsWith(".")) continue;
|
|
24320
|
-
const fullPath =
|
|
24428
|
+
const fullPath = path37.join(dirPath, entry.name);
|
|
24321
24429
|
if (entry.isDirectory()) {
|
|
24322
24430
|
if (excludeFolders.some((f) => entry.name.toLowerCase() === f.toLowerCase())) continue;
|
|
24323
24431
|
const sub = await collectMarkdownFiles(fullPath, basePath, excludeFolders);
|
|
24324
24432
|
results.push(...sub);
|
|
24325
24433
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
24326
|
-
results.push(
|
|
24434
|
+
results.push(path37.relative(basePath, fullPath));
|
|
24327
24435
|
}
|
|
24328
24436
|
}
|
|
24329
24437
|
} catch {
|
|
@@ -24353,7 +24461,7 @@ var EXCLUDE_FOLDERS = [
|
|
|
24353
24461
|
];
|
|
24354
24462
|
function buildStatusReport(stateDb2, vaultPath2) {
|
|
24355
24463
|
const recommendations = [];
|
|
24356
|
-
const dbPath =
|
|
24464
|
+
const dbPath = path37.join(vaultPath2, ".flywheel", "state.db");
|
|
24357
24465
|
const statedbExists = stateDb2 !== null;
|
|
24358
24466
|
if (!statedbExists) {
|
|
24359
24467
|
recommendations.push("StateDb not initialized \u2014 server needs restart");
|
|
@@ -24480,7 +24588,7 @@ async function executeRun(stateDb2, vaultPath2) {
|
|
|
24480
24588
|
const allFiles = await collectMarkdownFiles(vaultPath2, vaultPath2, EXCLUDE_FOLDERS);
|
|
24481
24589
|
let eligible = 0;
|
|
24482
24590
|
for (const relativePath of allFiles) {
|
|
24483
|
-
const fullPath =
|
|
24591
|
+
const fullPath = path37.join(vaultPath2, relativePath);
|
|
24484
24592
|
let content;
|
|
24485
24593
|
try {
|
|
24486
24594
|
content = await fs33.readFile(fullPath, "utf-8");
|
|
@@ -24538,7 +24646,7 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
|
|
|
24538
24646
|
const eligible = [];
|
|
24539
24647
|
let notesSkipped = 0;
|
|
24540
24648
|
for (const relativePath of allFiles) {
|
|
24541
|
-
const fullPath =
|
|
24649
|
+
const fullPath = path37.join(vaultPath2, relativePath);
|
|
24542
24650
|
let content;
|
|
24543
24651
|
try {
|
|
24544
24652
|
content = await fs33.readFile(fullPath, "utf-8");
|
|
@@ -24568,7 +24676,7 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
|
|
|
24568
24676
|
match_count: result.linksAdded
|
|
24569
24677
|
});
|
|
24570
24678
|
if (!dryRun) {
|
|
24571
|
-
const fullPath =
|
|
24679
|
+
const fullPath = path37.join(vaultPath2, relativePath);
|
|
24572
24680
|
await fs33.writeFile(fullPath, result.content, "utf-8");
|
|
24573
24681
|
notesModified++;
|
|
24574
24682
|
if (stateDb2) {
|
|
@@ -24747,7 +24855,7 @@ import { z as z30 } from "zod";
|
|
|
24747
24855
|
|
|
24748
24856
|
// src/core/read/similarity.ts
|
|
24749
24857
|
import * as fs34 from "fs";
|
|
24750
|
-
import * as
|
|
24858
|
+
import * as path38 from "path";
|
|
24751
24859
|
init_embeddings();
|
|
24752
24860
|
|
|
24753
24861
|
// src/core/read/mmr.ts
|
|
@@ -24817,7 +24925,7 @@ function extractKeyTerms(content, maxTerms = 15) {
|
|
|
24817
24925
|
}
|
|
24818
24926
|
function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
|
|
24819
24927
|
const limit = options.limit ?? 10;
|
|
24820
|
-
const absPath =
|
|
24928
|
+
const absPath = path38.join(vaultPath2, sourcePath);
|
|
24821
24929
|
let content;
|
|
24822
24930
|
try {
|
|
24823
24931
|
content = fs34.readFileSync(absPath, "utf-8");
|
|
@@ -24959,7 +25067,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
24959
25067
|
diversity: z30.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
|
|
24960
25068
|
}
|
|
24961
25069
|
},
|
|
24962
|
-
async ({ path:
|
|
25070
|
+
async ({ path: path41, limit, diversity }) => {
|
|
24963
25071
|
const index = getIndex();
|
|
24964
25072
|
const vaultPath2 = getVaultPath();
|
|
24965
25073
|
const stateDb2 = getStateDb4();
|
|
@@ -24968,10 +25076,10 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
24968
25076
|
content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
|
|
24969
25077
|
};
|
|
24970
25078
|
}
|
|
24971
|
-
if (!index.notes.has(
|
|
25079
|
+
if (!index.notes.has(path41)) {
|
|
24972
25080
|
return {
|
|
24973
25081
|
content: [{ type: "text", text: JSON.stringify({
|
|
24974
|
-
error: `Note not found: ${
|
|
25082
|
+
error: `Note not found: ${path41}`,
|
|
24975
25083
|
hint: "Use the full relative path including .md extension"
|
|
24976
25084
|
}, null, 2) }]
|
|
24977
25085
|
};
|
|
@@ -24983,12 +25091,12 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb4) {
|
|
|
24983
25091
|
};
|
|
24984
25092
|
const useHybrid = hasEmbeddingsIndex();
|
|
24985
25093
|
const method = useHybrid ? "hybrid" : "bm25";
|
|
24986
|
-
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index,
|
|
25094
|
+
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path41, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path41, opts);
|
|
24987
25095
|
return {
|
|
24988
25096
|
content: [{
|
|
24989
25097
|
type: "text",
|
|
24990
25098
|
text: JSON.stringify({
|
|
24991
|
-
source:
|
|
25099
|
+
source: path41,
|
|
24992
25100
|
method,
|
|
24993
25101
|
count: results.length,
|
|
24994
25102
|
similar: results
|
|
@@ -26862,9 +26970,9 @@ function registerVaultResources(server2, getIndex) {
|
|
|
26862
26970
|
}
|
|
26863
26971
|
|
|
26864
26972
|
// src/tool-registry.ts
|
|
26865
|
-
var __trFilename =
|
|
26866
|
-
var __trDirname =
|
|
26867
|
-
var trPkg = JSON.parse(readFileSync5(
|
|
26973
|
+
var __trFilename = fileURLToPath3(import.meta.url);
|
|
26974
|
+
var __trDirname = dirname6(__trFilename);
|
|
26975
|
+
var trPkg = JSON.parse(readFileSync5(join19(__trDirname, "../package.json"), "utf-8"));
|
|
26868
26976
|
var ACTIVATION_PATTERNS = [
|
|
26869
26977
|
{
|
|
26870
26978
|
category: "graph",
|
|
@@ -26902,6 +27010,34 @@ var ACTIVATION_PATTERNS = [
|
|
|
26902
27010
|
patterns: [/\b(delete note|move note|rename note|merge entities|merge notes?)\b/i]
|
|
26903
27011
|
}
|
|
26904
27012
|
];
|
|
27013
|
+
var MUTATING_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
27014
|
+
"vault_add_to_section",
|
|
27015
|
+
"vault_remove_from_section",
|
|
27016
|
+
"vault_replace_in_section",
|
|
27017
|
+
"vault_add_task",
|
|
27018
|
+
"vault_toggle_task",
|
|
27019
|
+
"vault_update_frontmatter",
|
|
27020
|
+
"vault_create_note",
|
|
27021
|
+
"vault_delete_note",
|
|
27022
|
+
"vault_move_note",
|
|
27023
|
+
"vault_rename_note",
|
|
27024
|
+
"merge_entities",
|
|
27025
|
+
"absorb_as_alias",
|
|
27026
|
+
"vault_undo_last_mutation",
|
|
27027
|
+
"policy",
|
|
27028
|
+
"rename_tag",
|
|
27029
|
+
"wikilink_feedback",
|
|
27030
|
+
"tool_selection_feedback",
|
|
27031
|
+
"vault_record_correction",
|
|
27032
|
+
"vault_resolve_correction",
|
|
27033
|
+
"memory",
|
|
27034
|
+
"flywheel_config",
|
|
27035
|
+
"vault_init",
|
|
27036
|
+
"rename_field",
|
|
27037
|
+
"migrate_field_values",
|
|
27038
|
+
"refresh_index",
|
|
27039
|
+
"init_semantic"
|
|
27040
|
+
]);
|
|
26905
27041
|
function getPatternSignals(raw) {
|
|
26906
27042
|
if (!raw) return [];
|
|
26907
27043
|
return ACTIVATION_PATTERNS.filter(({ patterns }) => patterns.some((pattern) => pattern.test(raw))).map(({ category, tier }) => ({ category, tier }));
|
|
@@ -27094,7 +27230,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
27094
27230
|
let totalBytes = 0;
|
|
27095
27231
|
for (const p of notePaths) {
|
|
27096
27232
|
try {
|
|
27097
|
-
totalBytes += statSync6(
|
|
27233
|
+
totalBytes += statSync6(path39.join(vp, p)).size;
|
|
27098
27234
|
} catch {
|
|
27099
27235
|
}
|
|
27100
27236
|
}
|
|
@@ -27127,6 +27263,26 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
27127
27263
|
};
|
|
27128
27264
|
}
|
|
27129
27265
|
const isMultiVault = registry?.isMultiVault ?? false;
|
|
27266
|
+
function getTargetVaultContext(params) {
|
|
27267
|
+
if (!registry) return null;
|
|
27268
|
+
if (isMultiVault) {
|
|
27269
|
+
const vaultName = typeof params?.vault === "string" ? params.vault : void 0;
|
|
27270
|
+
return registry.getContext(vaultName);
|
|
27271
|
+
}
|
|
27272
|
+
return registry.getContext();
|
|
27273
|
+
}
|
|
27274
|
+
function wrapWithIntegrityGate(toolName, handler) {
|
|
27275
|
+
if (!MUTATING_TOOL_NAMES.has(toolName)) return handler;
|
|
27276
|
+
return async (...args) => {
|
|
27277
|
+
const params = args[0] && typeof args[0] === "object" ? args[0] : void 0;
|
|
27278
|
+
const vaultCtx = getTargetVaultContext(params);
|
|
27279
|
+
const integrityState = vaultCtx?.integrityState ?? getActiveScopeOrNull()?.integrityState;
|
|
27280
|
+
if (integrityState === "failed") {
|
|
27281
|
+
throw new Error("StateDb integrity failed; write operations are disabled until recovery/restart.");
|
|
27282
|
+
}
|
|
27283
|
+
return handler(...args);
|
|
27284
|
+
};
|
|
27285
|
+
}
|
|
27130
27286
|
function wrapWithVaultActivation(toolName, handler) {
|
|
27131
27287
|
if (!isMultiVault || !registry || !vaultCallbacks) return handler;
|
|
27132
27288
|
return async (...args) => {
|
|
@@ -27242,6 +27398,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
27242
27398
|
if (args.length > 0 && typeof args[args.length - 1] === "function") {
|
|
27243
27399
|
let handler = args[args.length - 1];
|
|
27244
27400
|
handler = wrapWithVaultActivation(name, handler);
|
|
27401
|
+
handler = wrapWithIntegrityGate(name, handler);
|
|
27245
27402
|
args[args.length - 1] = wrapWithTracking(name, handler);
|
|
27246
27403
|
}
|
|
27247
27404
|
const registered = origTool(name, ...args);
|
|
@@ -27256,6 +27413,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
27256
27413
|
if (args.length > 0 && typeof args[args.length - 1] === "function") {
|
|
27257
27414
|
let handler = args[args.length - 1];
|
|
27258
27415
|
handler = wrapWithVaultActivation(name, handler);
|
|
27416
|
+
handler = wrapWithIntegrityGate(name, handler);
|
|
27259
27417
|
args[args.length - 1] = wrapWithTracking(name, handler);
|
|
27260
27418
|
}
|
|
27261
27419
|
const registered = origRegisterTool(name, ...args);
|
|
@@ -27349,7 +27507,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
27349
27507
|
}
|
|
27350
27508
|
function registerAllTools(targetServer, ctx, controller) {
|
|
27351
27509
|
const { getVaultPath: gvp, getVaultIndex: gvi, getStateDb: gsd, getFlywheelConfig: gcf } = ctx;
|
|
27352
|
-
registerHealthTools(targetServer, gvi, gvp, gcf, gsd, ctx.getWatcherStatus, () => trPkg.version, ctx.getPipelineActivity);
|
|
27510
|
+
registerHealthTools(targetServer, gvi, gvp, gcf, gsd, ctx.getWatcherStatus, () => trPkg.version, ctx.getPipelineActivity, ctx.getVaultRuntimeState);
|
|
27353
27511
|
registerSystemTools(
|
|
27354
27512
|
targetServer,
|
|
27355
27513
|
gvi,
|
|
@@ -27445,9 +27603,9 @@ function registerAllTools(targetServer, ctx, controller) {
|
|
|
27445
27603
|
}
|
|
27446
27604
|
|
|
27447
27605
|
// src/index.ts
|
|
27448
|
-
var __filename2 =
|
|
27449
|
-
var __dirname =
|
|
27450
|
-
var pkg = JSON.parse(readFileSync6(
|
|
27606
|
+
var __filename2 = fileURLToPath4(import.meta.url);
|
|
27607
|
+
var __dirname = dirname8(__filename2);
|
|
27608
|
+
var pkg = JSON.parse(readFileSync6(join21(__dirname, "../package.json"), "utf-8"));
|
|
27451
27609
|
var vaultPath;
|
|
27452
27610
|
var resolvedVaultPath;
|
|
27453
27611
|
var vaultIndex;
|
|
@@ -27463,6 +27621,7 @@ var lastMcpRequestAt = 0;
|
|
|
27463
27621
|
var lastFullRebuildAt = 0;
|
|
27464
27622
|
var startupScanFiles = null;
|
|
27465
27623
|
var deferredScheduler = null;
|
|
27624
|
+
var integrityRuns = /* @__PURE__ */ new Map();
|
|
27466
27625
|
function getWatcherStatus() {
|
|
27467
27626
|
if (vaultRegistry) {
|
|
27468
27627
|
const name = globalThis.__flywheel_active_vault;
|
|
@@ -27507,6 +27666,20 @@ function buildRegistryContext() {
|
|
|
27507
27666
|
getFlywheelConfig: () => getActiveScopeOrNull()?.flywheelConfig ?? flywheelConfig,
|
|
27508
27667
|
getWatcherStatus,
|
|
27509
27668
|
getPipelineActivity: () => getActiveScopeOrNull()?.pipelineActivity ?? null,
|
|
27669
|
+
getVaultRuntimeState: () => {
|
|
27670
|
+
const scope = getActiveScopeOrNull();
|
|
27671
|
+
return {
|
|
27672
|
+
bootState: scope?.bootState ?? "booting",
|
|
27673
|
+
integrityState: scope?.integrityState ?? "unknown",
|
|
27674
|
+
integrityCheckInProgress: scope?.integrityCheckInProgress ?? false,
|
|
27675
|
+
integrityStartedAt: scope?.integrityStartedAt ?? null,
|
|
27676
|
+
integritySource: scope?.integritySource ?? null,
|
|
27677
|
+
lastIntegrityCheckedAt: scope?.lastIntegrityCheckedAt ?? null,
|
|
27678
|
+
lastIntegrityDurationMs: scope?.lastIntegrityDurationMs ?? null,
|
|
27679
|
+
lastIntegrityDetail: scope?.lastIntegrityDetail ?? null,
|
|
27680
|
+
lastBackupAt: scope?.lastBackupAt ?? null
|
|
27681
|
+
};
|
|
27682
|
+
},
|
|
27510
27683
|
updateVaultIndex,
|
|
27511
27684
|
updateFlywheelConfig
|
|
27512
27685
|
};
|
|
@@ -27608,6 +27781,110 @@ function loadVaultCooccurrence(ctx) {
|
|
|
27608
27781
|
serverLog("index", `[${ctx.name}] Co-occurrence: loaded from cache (${Object.keys(cachedCooc.index.associations).length} entities, ${cachedCooc.index._metadata.total_associations} associations)`);
|
|
27609
27782
|
}
|
|
27610
27783
|
}
|
|
27784
|
+
function hydrateIntegrityMetadata(ctx) {
|
|
27785
|
+
if (!ctx.stateDb) return;
|
|
27786
|
+
const checkedAtRow = ctx.stateDb.getMetadataValue.get(INTEGRITY_METADATA_KEYS.checkedAt);
|
|
27787
|
+
const statusRow = ctx.stateDb.getMetadataValue.get(INTEGRITY_METADATA_KEYS.status);
|
|
27788
|
+
const durationRow = ctx.stateDb.getMetadataValue.get(INTEGRITY_METADATA_KEYS.durationMs);
|
|
27789
|
+
const detailRow = ctx.stateDb.getMetadataValue.get(INTEGRITY_METADATA_KEYS.detail);
|
|
27790
|
+
ctx.lastIntegrityCheckedAt = checkedAtRow ? parseInt(checkedAtRow.value, 10) || null : null;
|
|
27791
|
+
ctx.lastIntegrityDurationMs = durationRow ? parseInt(durationRow.value, 10) || null : null;
|
|
27792
|
+
ctx.lastIntegrityDetail = detailRow?.value ? detailRow.value : null;
|
|
27793
|
+
const status = statusRow?.value;
|
|
27794
|
+
if (status === "healthy" || status === "failed" || status === "error") {
|
|
27795
|
+
ctx.integrityState = status;
|
|
27796
|
+
}
|
|
27797
|
+
}
|
|
27798
|
+
function setBootState(ctx, state2) {
|
|
27799
|
+
ctx.bootState = state2;
|
|
27800
|
+
if (globalThis.__flywheel_active_vault === ctx.name) {
|
|
27801
|
+
setActiveScope(buildVaultScope(ctx));
|
|
27802
|
+
}
|
|
27803
|
+
}
|
|
27804
|
+
function setIntegrityState(ctx, state2, detail = ctx.lastIntegrityDetail, durationMs = ctx.lastIntegrityDurationMs) {
|
|
27805
|
+
ctx.integrityState = state2;
|
|
27806
|
+
ctx.lastIntegrityDetail = detail;
|
|
27807
|
+
ctx.lastIntegrityDurationMs = durationMs;
|
|
27808
|
+
if (state2 === "failed") {
|
|
27809
|
+
ctx.bootState = "degraded";
|
|
27810
|
+
}
|
|
27811
|
+
if (globalThis.__flywheel_active_vault === ctx.name) {
|
|
27812
|
+
setActiveScope(buildVaultScope(ctx));
|
|
27813
|
+
}
|
|
27814
|
+
}
|
|
27815
|
+
function persistIntegrityMetadata(ctx) {
|
|
27816
|
+
if (!ctx.stateDb || ctx.lastIntegrityCheckedAt == null) return;
|
|
27817
|
+
ctx.stateDb.setMetadataValue.run(INTEGRITY_METADATA_KEYS.checkedAt, String(ctx.lastIntegrityCheckedAt));
|
|
27818
|
+
ctx.stateDb.setMetadataValue.run(INTEGRITY_METADATA_KEYS.status, ctx.integrityState);
|
|
27819
|
+
if (ctx.lastIntegrityDurationMs != null) {
|
|
27820
|
+
ctx.stateDb.setMetadataValue.run(INTEGRITY_METADATA_KEYS.durationMs, String(ctx.lastIntegrityDurationMs));
|
|
27821
|
+
}
|
|
27822
|
+
if (ctx.lastIntegrityDetail) {
|
|
27823
|
+
ctx.stateDb.setMetadataValue.run(INTEGRITY_METADATA_KEYS.detail, ctx.lastIntegrityDetail);
|
|
27824
|
+
} else {
|
|
27825
|
+
ctx.stateDb.setMetadataValue.run(INTEGRITY_METADATA_KEYS.detail, "");
|
|
27826
|
+
}
|
|
27827
|
+
}
|
|
27828
|
+
function shouldRunBackup(ctx) {
|
|
27829
|
+
if (ctx.lastBackupAt == null) return true;
|
|
27830
|
+
return Date.now() - ctx.lastBackupAt >= INTEGRITY_BACKUP_INTERVAL_MS;
|
|
27831
|
+
}
|
|
27832
|
+
async function runIntegrityCheck(ctx, source, options = {}) {
|
|
27833
|
+
if (!ctx.stateDb) {
|
|
27834
|
+
return { status: "error", detail: "StateDb not available", durationMs: 0, backupCreated: false };
|
|
27835
|
+
}
|
|
27836
|
+
if (!options.force && ctx.integrityState === "healthy" && ctx.lastIntegrityCheckedAt != null) {
|
|
27837
|
+
if (Date.now() - ctx.lastIntegrityCheckedAt < INTEGRITY_CHECK_INTERVAL_MS) {
|
|
27838
|
+
return {
|
|
27839
|
+
status: "healthy",
|
|
27840
|
+
detail: ctx.lastIntegrityDetail,
|
|
27841
|
+
durationMs: ctx.lastIntegrityDurationMs ?? 0,
|
|
27842
|
+
backupCreated: false
|
|
27843
|
+
};
|
|
27844
|
+
}
|
|
27845
|
+
}
|
|
27846
|
+
const existing = integrityRuns.get(ctx.name);
|
|
27847
|
+
if (existing) return existing;
|
|
27848
|
+
ctx.integrityCheckInProgress = true;
|
|
27849
|
+
ctx.integrityStartedAt = Date.now();
|
|
27850
|
+
ctx.integritySource = source;
|
|
27851
|
+
setIntegrityState(ctx, "checking", ctx.lastIntegrityDetail, ctx.lastIntegrityDurationMs);
|
|
27852
|
+
serverLog("statedb", `[${ctx.name}] Integrity check started (${source})`);
|
|
27853
|
+
const promise = runIntegrityWorker({
|
|
27854
|
+
dbPath: ctx.stateDb.dbPath,
|
|
27855
|
+
runBackup: shouldRunBackup(ctx),
|
|
27856
|
+
busyTimeoutMs: 5e3
|
|
27857
|
+
}).then((result) => {
|
|
27858
|
+
ctx.integrityCheckInProgress = false;
|
|
27859
|
+
ctx.integrityStartedAt = null;
|
|
27860
|
+
ctx.integritySource = source;
|
|
27861
|
+
ctx.lastIntegrityCheckedAt = Date.now();
|
|
27862
|
+
ctx.lastIntegrityDurationMs = result.durationMs;
|
|
27863
|
+
ctx.lastIntegrityDetail = result.detail;
|
|
27864
|
+
if (result.backupCreated) {
|
|
27865
|
+
ctx.lastBackupAt = Date.now();
|
|
27866
|
+
}
|
|
27867
|
+
if (result.status === "healthy") {
|
|
27868
|
+
setIntegrityState(ctx, "healthy", result.detail, result.durationMs);
|
|
27869
|
+
serverLog("statedb", `[${ctx.name}] Integrity check passed in ${result.durationMs}ms`);
|
|
27870
|
+
} else if (result.status === "failed") {
|
|
27871
|
+
setIntegrityState(ctx, "failed", result.detail, result.durationMs);
|
|
27872
|
+
serverLog("statedb", `[${ctx.name}] Integrity check failed: ${result.detail}`, "error");
|
|
27873
|
+
} else {
|
|
27874
|
+
setIntegrityState(ctx, "error", result.detail, result.durationMs);
|
|
27875
|
+
serverLog("statedb", `[${ctx.name}] Integrity check error: ${result.detail}`, "warn");
|
|
27876
|
+
}
|
|
27877
|
+
persistIntegrityMetadata(ctx);
|
|
27878
|
+
return result;
|
|
27879
|
+
}).finally(() => {
|
|
27880
|
+
integrityRuns.delete(ctx.name);
|
|
27881
|
+
if (globalThis.__flywheel_active_vault === ctx.name) {
|
|
27882
|
+
setActiveScope(buildVaultScope(ctx));
|
|
27883
|
+
}
|
|
27884
|
+
});
|
|
27885
|
+
integrityRuns.set(ctx.name, promise);
|
|
27886
|
+
return promise;
|
|
27887
|
+
}
|
|
27611
27888
|
async function initializeVault(name, vaultPathArg) {
|
|
27612
27889
|
const ctx = {
|
|
27613
27890
|
name,
|
|
@@ -27625,11 +27902,21 @@ async function initializeVault(name, vaultPathArg) {
|
|
|
27625
27902
|
lastEntityScanAt: 0,
|
|
27626
27903
|
lastHubScoreRebuildAt: 0,
|
|
27627
27904
|
lastIndexCacheSaveAt: 0,
|
|
27628
|
-
pipelineActivity: createEmptyPipelineActivity()
|
|
27905
|
+
pipelineActivity: createEmptyPipelineActivity(),
|
|
27906
|
+
bootState: "booting",
|
|
27907
|
+
integrityState: "unknown",
|
|
27908
|
+
integrityCheckInProgress: false,
|
|
27909
|
+
integrityStartedAt: null,
|
|
27910
|
+
integritySource: null,
|
|
27911
|
+
lastIntegrityCheckedAt: null,
|
|
27912
|
+
lastIntegrityDurationMs: null,
|
|
27913
|
+
lastIntegrityDetail: null,
|
|
27914
|
+
lastBackupAt: null
|
|
27629
27915
|
};
|
|
27630
27916
|
try {
|
|
27631
27917
|
ctx.stateDb = openStateDb(vaultPathArg);
|
|
27632
27918
|
serverLog("statedb", `[${name}] StateDb initialized`);
|
|
27919
|
+
hydrateIntegrityMetadata(ctx);
|
|
27633
27920
|
const vaultInitRow = ctx.stateDb.getMetadataValue.get("vault_init_last_run_at");
|
|
27634
27921
|
if (!vaultInitRow) {
|
|
27635
27922
|
serverLog("server", `[${name}] Vault not initialized \u2014 call vault_init to enrich legacy notes`);
|
|
@@ -27653,7 +27940,16 @@ function buildVaultScope(ctx) {
|
|
|
27653
27940
|
indexError: ctx.indexError,
|
|
27654
27941
|
embeddingsBuilding: ctx.embeddingsBuilding,
|
|
27655
27942
|
entityEmbeddingsMap: getEntityEmbeddingsMap(),
|
|
27656
|
-
pipelineActivity: ctx.pipelineActivity
|
|
27943
|
+
pipelineActivity: ctx.pipelineActivity,
|
|
27944
|
+
bootState: ctx.bootState,
|
|
27945
|
+
integrityState: ctx.integrityState,
|
|
27946
|
+
integrityCheckInProgress: ctx.integrityCheckInProgress,
|
|
27947
|
+
integrityStartedAt: ctx.integrityStartedAt,
|
|
27948
|
+
integritySource: ctx.integritySource,
|
|
27949
|
+
lastIntegrityCheckedAt: ctx.lastIntegrityCheckedAt,
|
|
27950
|
+
lastIntegrityDurationMs: ctx.lastIntegrityDurationMs,
|
|
27951
|
+
lastIntegrityDetail: ctx.lastIntegrityDetail,
|
|
27952
|
+
lastBackupAt: ctx.lastBackupAt
|
|
27657
27953
|
};
|
|
27658
27954
|
}
|
|
27659
27955
|
function activateVault(ctx, skipEmbeddingLoad = false) {
|
|
@@ -27807,6 +28103,9 @@ async function bootVault(ctx, startTime) {
|
|
|
27807
28103
|
serverLog("index", `[${ctx.name}] Failed to build vault index: ${err instanceof Error ? err.message : err}`, "error");
|
|
27808
28104
|
}
|
|
27809
28105
|
}
|
|
28106
|
+
if (ctx.bootState !== "degraded") {
|
|
28107
|
+
setBootState(ctx, "ready");
|
|
28108
|
+
}
|
|
27810
28109
|
}
|
|
27811
28110
|
async function main() {
|
|
27812
28111
|
const vaultConfigs = parseVaultConfig();
|
|
@@ -27816,7 +28115,7 @@ async function main() {
|
|
|
27816
28115
|
} catch {
|
|
27817
28116
|
resolvedVaultPath = vaultPath.replace(/\\/g, "/");
|
|
27818
28117
|
}
|
|
27819
|
-
if (!
|
|
28118
|
+
if (!existsSync4(resolvedVaultPath)) {
|
|
27820
28119
|
console.error(`[flywheel] Fatal: vault path does not exist: ${resolvedVaultPath}`);
|
|
27821
28120
|
console.error(`[flywheel] Set PROJECT_PATH or VAULT_PATH to a valid Obsidian vault directory.`);
|
|
27822
28121
|
process.exit(1);
|
|
@@ -27831,14 +28130,17 @@ async function main() {
|
|
|
27831
28130
|
vaultRegistry.addContext(primaryCtx2);
|
|
27832
28131
|
stateDb = primaryCtx2.stateDb;
|
|
27833
28132
|
activateVault(primaryCtx2, true);
|
|
28133
|
+
serverLog("server", `[${primaryCtx2.name}] stateDb_open=${Date.now() - startTime}ms`);
|
|
27834
28134
|
} else {
|
|
27835
28135
|
vaultRegistry = new VaultRegistry("default");
|
|
27836
28136
|
const ctx = await initializeVault("default", vaultPath);
|
|
27837
28137
|
vaultRegistry.addContext(ctx);
|
|
27838
28138
|
stateDb = ctx.stateDb;
|
|
27839
28139
|
activateVault(ctx, true);
|
|
28140
|
+
serverLog("server", `[${ctx.name}] stateDb_open=${Date.now() - startTime}ms`);
|
|
27840
28141
|
}
|
|
27841
28142
|
await initToolRouting();
|
|
28143
|
+
serverLog("server", `tool_routing=${Date.now() - startTime}ms`);
|
|
27842
28144
|
if (stateDb) {
|
|
27843
28145
|
try {
|
|
27844
28146
|
const vaultName = vaultRegistry?.primaryName ?? "default";
|
|
@@ -27930,29 +28232,16 @@ async function main() {
|
|
|
27930
28232
|
});
|
|
27931
28233
|
}
|
|
27932
28234
|
const primaryCtx = vaultRegistry.getContext();
|
|
27933
|
-
|
|
27934
|
-
|
|
27935
|
-
|
|
27936
|
-
|
|
27937
|
-
|
|
27938
|
-
});
|
|
27939
|
-
} else {
|
|
27940
|
-
serverLog("statedb", `[${primaryCtx.name}] Integrity check failed: ${integrity.detail} \u2014 recreating`, "error");
|
|
27941
|
-
const dbPath = primaryCtx.stateDb.dbPath;
|
|
27942
|
-
preserveCorruptedDb(dbPath);
|
|
27943
|
-
primaryCtx.stateDb.close();
|
|
27944
|
-
deleteStateDbFiles(dbPath);
|
|
27945
|
-
primaryCtx.stateDb = openStateDb(primaryCtx.vaultPath);
|
|
27946
|
-
attemptSalvage(primaryCtx.stateDb.db, dbPath);
|
|
27947
|
-
stateDb = primaryCtx.stateDb;
|
|
27948
|
-
activateVault(primaryCtx, true);
|
|
27949
|
-
}
|
|
27950
|
-
serverLog("statedb", `[${primaryCtx.name}] Integrity check passed in ${Date.now() - startTime}ms`);
|
|
27951
|
-
}
|
|
28235
|
+
setBootState(primaryCtx, "transport_connected");
|
|
28236
|
+
serverLog("server", `[${primaryCtx.name}] transport_connect=${Date.now() - startTime}ms`);
|
|
28237
|
+
serverLog("server", `[${primaryCtx.name}] integrity_check_started=${Date.now() - startTime}ms`);
|
|
28238
|
+
void runIntegrityCheck(primaryCtx, "startup");
|
|
28239
|
+
setBootState(primaryCtx, "booting");
|
|
27952
28240
|
loadVaultCooccurrence(primaryCtx);
|
|
27953
28241
|
activateVault(primaryCtx);
|
|
27954
28242
|
await bootVault(primaryCtx, startTime);
|
|
27955
28243
|
activateVault(primaryCtx);
|
|
28244
|
+
serverLog("server", `[${primaryCtx.name}] boot_complete=${Date.now() - startTime}ms`);
|
|
27956
28245
|
serverReady = true;
|
|
27957
28246
|
const watchdogInterval = parseInt(process.env.FLYWHEEL_WATCHDOG_INTERVAL ?? "0", 10);
|
|
27958
28247
|
if (watchdogInterval > 0 && (transportMode === "http" || transportMode === "both")) {
|
|
@@ -28014,6 +28303,9 @@ async function main() {
|
|
|
28014
28303
|
const ctx = await initializeVault(vc.name, vc.path);
|
|
28015
28304
|
vaultRegistry.addContext(ctx);
|
|
28016
28305
|
invalidateHttpPool();
|
|
28306
|
+
setBootState(ctx, "transport_connected");
|
|
28307
|
+
void runIntegrityCheck(ctx, "startup");
|
|
28308
|
+
setBootState(ctx, "booting");
|
|
28017
28309
|
loadVaultCooccurrence(ctx);
|
|
28018
28310
|
activateVault(ctx);
|
|
28019
28311
|
await bootVault(ctx, startTime);
|
|
@@ -28229,7 +28521,7 @@ async function runPostIndexWork(ctx) {
|
|
|
28229
28521
|
if (attempt < MAX_BUILD_RETRIES) {
|
|
28230
28522
|
const delay = 1e4;
|
|
28231
28523
|
serverLog("semantic", `Build failed (attempt ${attempt}/${MAX_BUILD_RETRIES}): ${msg}. Retrying in ${delay / 1e3}s...`, "error");
|
|
28232
|
-
await new Promise((
|
|
28524
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
28233
28525
|
return attemptBuild(attempt + 1);
|
|
28234
28526
|
}
|
|
28235
28527
|
serverLog("semantic", `Embeddings build failed after ${MAX_BUILD_RETRIES} attempts: ${msg}`, "error");
|
|
@@ -28285,8 +28577,8 @@ async function runPostIndexWork(ctx) {
|
|
|
28285
28577
|
}
|
|
28286
28578
|
} catch {
|
|
28287
28579
|
try {
|
|
28288
|
-
const dir =
|
|
28289
|
-
const base =
|
|
28580
|
+
const dir = path40.dirname(rawPath);
|
|
28581
|
+
const base = path40.basename(rawPath);
|
|
28290
28582
|
const resolvedDir = realpathSync(dir).replace(/\\/g, "/");
|
|
28291
28583
|
for (const prefix of vaultPrefixes) {
|
|
28292
28584
|
if (resolvedDir.startsWith(prefix + "/") || resolvedDir === prefix) {
|
|
@@ -28318,7 +28610,7 @@ async function runPostIndexWork(ctx) {
|
|
|
28318
28610
|
continue;
|
|
28319
28611
|
}
|
|
28320
28612
|
try {
|
|
28321
|
-
const content = await fs35.readFile(
|
|
28613
|
+
const content = await fs35.readFile(path40.join(vp, event.path), "utf-8");
|
|
28322
28614
|
const hash = createHash4("sha256").update(content).digest("hex").slice(0, 16);
|
|
28323
28615
|
if (lastContentHashes.get(event.path) === hash) {
|
|
28324
28616
|
serverLog("watcher", `Hash unchanged, skipping: ${event.path}`);
|
|
@@ -28405,7 +28697,8 @@ async function runPostIndexWork(ctx) {
|
|
|
28405
28697
|
updateEntitiesInStateDb,
|
|
28406
28698
|
getVaultIndex: () => vaultIndex,
|
|
28407
28699
|
buildVaultIndex,
|
|
28408
|
-
deferredScheduler: deferredScheduler ?? void 0
|
|
28700
|
+
deferredScheduler: deferredScheduler ?? void 0,
|
|
28701
|
+
runIntegrityCheck
|
|
28409
28702
|
});
|
|
28410
28703
|
await runner.run();
|
|
28411
28704
|
};
|