@velvetmonkey/flywheel-memory 2.0.35 → 2.0.36
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 +438 -196
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ var init_constants = __esm({
|
|
|
59
59
|
|
|
60
60
|
// src/core/write/writer.ts
|
|
61
61
|
import fs18 from "fs/promises";
|
|
62
|
-
import
|
|
62
|
+
import path19 from "path";
|
|
63
63
|
import matter5 from "gray-matter";
|
|
64
64
|
function isSensitivePath(filePath) {
|
|
65
65
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
@@ -386,8 +386,8 @@ function validatePath(vaultPath2, notePath) {
|
|
|
386
386
|
if (notePath.startsWith("\\")) {
|
|
387
387
|
return false;
|
|
388
388
|
}
|
|
389
|
-
const resolvedVault =
|
|
390
|
-
const resolvedNote =
|
|
389
|
+
const resolvedVault = path19.resolve(vaultPath2);
|
|
390
|
+
const resolvedNote = path19.resolve(vaultPath2, notePath);
|
|
391
391
|
return resolvedNote.startsWith(resolvedVault);
|
|
392
392
|
}
|
|
393
393
|
async function validatePathSecure(vaultPath2, notePath) {
|
|
@@ -415,8 +415,8 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
415
415
|
reason: "Path traversal not allowed"
|
|
416
416
|
};
|
|
417
417
|
}
|
|
418
|
-
const resolvedVault =
|
|
419
|
-
const resolvedNote =
|
|
418
|
+
const resolvedVault = path19.resolve(vaultPath2);
|
|
419
|
+
const resolvedNote = path19.resolve(vaultPath2, notePath);
|
|
420
420
|
if (!resolvedNote.startsWith(resolvedVault)) {
|
|
421
421
|
return {
|
|
422
422
|
valid: false,
|
|
@@ -430,7 +430,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
430
430
|
};
|
|
431
431
|
}
|
|
432
432
|
try {
|
|
433
|
-
const fullPath =
|
|
433
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
434
434
|
try {
|
|
435
435
|
await fs18.access(fullPath);
|
|
436
436
|
const realPath = await fs18.realpath(fullPath);
|
|
@@ -441,7 +441,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
441
441
|
reason: "Symlink target is outside vault"
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
|
-
const relativePath =
|
|
444
|
+
const relativePath = path19.relative(realVaultPath, realPath);
|
|
445
445
|
if (isSensitivePath(relativePath)) {
|
|
446
446
|
return {
|
|
447
447
|
valid: false,
|
|
@@ -449,7 +449,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
449
449
|
};
|
|
450
450
|
}
|
|
451
451
|
} catch {
|
|
452
|
-
const parentDir =
|
|
452
|
+
const parentDir = path19.dirname(fullPath);
|
|
453
453
|
try {
|
|
454
454
|
await fs18.access(parentDir);
|
|
455
455
|
const realParentPath = await fs18.realpath(parentDir);
|
|
@@ -475,7 +475,7 @@ async function readVaultFile(vaultPath2, notePath) {
|
|
|
475
475
|
if (!validatePath(vaultPath2, notePath)) {
|
|
476
476
|
throw new Error("Invalid path: path traversal not allowed");
|
|
477
477
|
}
|
|
478
|
-
const fullPath =
|
|
478
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
479
479
|
const [rawContent, stat3] = await Promise.all([
|
|
480
480
|
fs18.readFile(fullPath, "utf-8"),
|
|
481
481
|
fs18.stat(fullPath)
|
|
@@ -528,7 +528,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
|
|
|
528
528
|
if (!validation.valid) {
|
|
529
529
|
throw new Error(`Invalid path: ${validation.reason}`);
|
|
530
530
|
}
|
|
531
|
-
const fullPath =
|
|
531
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
532
532
|
let output = matter5.stringify(content, frontmatter);
|
|
533
533
|
output = normalizeTrailingNewline(output);
|
|
534
534
|
output = convertLineEndings(output, lineEnding);
|
|
@@ -836,8 +836,8 @@ function createContext(variables = {}) {
|
|
|
836
836
|
steps: {}
|
|
837
837
|
};
|
|
838
838
|
}
|
|
839
|
-
function resolvePath(obj,
|
|
840
|
-
const parts =
|
|
839
|
+
function resolvePath(obj, path31) {
|
|
840
|
+
const parts = path31.split(".");
|
|
841
841
|
let current = obj;
|
|
842
842
|
for (const part of parts) {
|
|
843
843
|
if (current === void 0 || current === null) {
|
|
@@ -1279,7 +1279,7 @@ __export(conditions_exports, {
|
|
|
1279
1279
|
shouldStepExecute: () => shouldStepExecute
|
|
1280
1280
|
});
|
|
1281
1281
|
import fs25 from "fs/promises";
|
|
1282
|
-
import
|
|
1282
|
+
import path25 from "path";
|
|
1283
1283
|
async function evaluateCondition(condition, vaultPath2, context) {
|
|
1284
1284
|
const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
|
|
1285
1285
|
const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
|
|
@@ -1332,7 +1332,7 @@ async function evaluateCondition(condition, vaultPath2, context) {
|
|
|
1332
1332
|
}
|
|
1333
1333
|
}
|
|
1334
1334
|
async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
1335
|
-
const fullPath =
|
|
1335
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
1336
1336
|
try {
|
|
1337
1337
|
await fs25.access(fullPath);
|
|
1338
1338
|
return {
|
|
@@ -1347,7 +1347,7 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
|
1347
1347
|
}
|
|
1348
1348
|
}
|
|
1349
1349
|
async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
|
|
1350
|
-
const fullPath =
|
|
1350
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
1351
1351
|
try {
|
|
1352
1352
|
await fs25.access(fullPath);
|
|
1353
1353
|
} catch {
|
|
@@ -1378,7 +1378,7 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
|
|
|
1378
1378
|
}
|
|
1379
1379
|
}
|
|
1380
1380
|
async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
|
|
1381
|
-
const fullPath =
|
|
1381
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
1382
1382
|
try {
|
|
1383
1383
|
await fs25.access(fullPath);
|
|
1384
1384
|
} catch {
|
|
@@ -1409,7 +1409,7 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
|
|
|
1409
1409
|
}
|
|
1410
1410
|
}
|
|
1411
1411
|
async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
|
|
1412
|
-
const fullPath =
|
|
1412
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
1413
1413
|
try {
|
|
1414
1414
|
await fs25.access(fullPath);
|
|
1415
1415
|
} catch {
|
|
@@ -1552,7 +1552,7 @@ var init_taskHelpers = __esm({
|
|
|
1552
1552
|
});
|
|
1553
1553
|
|
|
1554
1554
|
// src/index.ts
|
|
1555
|
-
import * as
|
|
1555
|
+
import * as path30 from "path";
|
|
1556
1556
|
import { readFileSync as readFileSync4, realpathSync } from "fs";
|
|
1557
1557
|
import { fileURLToPath } from "url";
|
|
1558
1558
|
import { dirname as dirname4, join as join16 } from "path";
|
|
@@ -2216,8 +2216,8 @@ function updateIndexProgress(parsed, total) {
|
|
|
2216
2216
|
function normalizeTarget(target) {
|
|
2217
2217
|
return target.toLowerCase().replace(/\.md$/, "");
|
|
2218
2218
|
}
|
|
2219
|
-
function normalizeNotePath(
|
|
2220
|
-
return
|
|
2219
|
+
function normalizeNotePath(path31) {
|
|
2220
|
+
return path31.toLowerCase().replace(/\.md$/, "");
|
|
2221
2221
|
}
|
|
2222
2222
|
async function buildVaultIndex(vaultPath2, options = {}) {
|
|
2223
2223
|
const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
|
|
@@ -2386,7 +2386,7 @@ function findSimilarEntity(index, target) {
|
|
|
2386
2386
|
}
|
|
2387
2387
|
const maxDist = normalizedLen <= 10 ? 1 : 2;
|
|
2388
2388
|
let bestMatch;
|
|
2389
|
-
for (const [entity,
|
|
2389
|
+
for (const [entity, path31] of index.entities) {
|
|
2390
2390
|
const lenDiff = Math.abs(entity.length - normalizedLen);
|
|
2391
2391
|
if (lenDiff > maxDist) {
|
|
2392
2392
|
continue;
|
|
@@ -2394,7 +2394,7 @@ function findSimilarEntity(index, target) {
|
|
|
2394
2394
|
const dist = levenshteinDistance(normalized, entity);
|
|
2395
2395
|
if (dist > 0 && dist <= maxDist) {
|
|
2396
2396
|
if (!bestMatch || dist < bestMatch.distance) {
|
|
2397
|
-
bestMatch = { path:
|
|
2397
|
+
bestMatch = { path: path31, entity, distance: dist };
|
|
2398
2398
|
if (dist === 1) {
|
|
2399
2399
|
return bestMatch;
|
|
2400
2400
|
}
|
|
@@ -2864,30 +2864,30 @@ var EventQueue = class {
|
|
|
2864
2864
|
* Add a new event to the queue
|
|
2865
2865
|
*/
|
|
2866
2866
|
push(type, rawPath) {
|
|
2867
|
-
const
|
|
2867
|
+
const path31 = normalizePath(rawPath);
|
|
2868
2868
|
const now = Date.now();
|
|
2869
2869
|
const event = {
|
|
2870
2870
|
type,
|
|
2871
|
-
path:
|
|
2871
|
+
path: path31,
|
|
2872
2872
|
timestamp: now
|
|
2873
2873
|
};
|
|
2874
|
-
let pending = this.pending.get(
|
|
2874
|
+
let pending = this.pending.get(path31);
|
|
2875
2875
|
if (!pending) {
|
|
2876
2876
|
pending = {
|
|
2877
2877
|
events: [],
|
|
2878
2878
|
timer: null,
|
|
2879
2879
|
lastEvent: now
|
|
2880
2880
|
};
|
|
2881
|
-
this.pending.set(
|
|
2881
|
+
this.pending.set(path31, pending);
|
|
2882
2882
|
}
|
|
2883
2883
|
pending.events.push(event);
|
|
2884
2884
|
pending.lastEvent = now;
|
|
2885
|
-
console.error(`[flywheel] QUEUE: pushed ${type} for ${
|
|
2885
|
+
console.error(`[flywheel] QUEUE: pushed ${type} for ${path31}, pending=${this.pending.size}`);
|
|
2886
2886
|
if (pending.timer) {
|
|
2887
2887
|
clearTimeout(pending.timer);
|
|
2888
2888
|
}
|
|
2889
2889
|
pending.timer = setTimeout(() => {
|
|
2890
|
-
this.flushPath(
|
|
2890
|
+
this.flushPath(path31);
|
|
2891
2891
|
}, this.config.debounceMs);
|
|
2892
2892
|
if (this.pending.size >= this.config.batchSize) {
|
|
2893
2893
|
this.flush();
|
|
@@ -2908,10 +2908,10 @@ var EventQueue = class {
|
|
|
2908
2908
|
/**
|
|
2909
2909
|
* Flush a single path's events
|
|
2910
2910
|
*/
|
|
2911
|
-
flushPath(
|
|
2912
|
-
const pending = this.pending.get(
|
|
2911
|
+
flushPath(path31) {
|
|
2912
|
+
const pending = this.pending.get(path31);
|
|
2913
2913
|
if (!pending || pending.events.length === 0) return;
|
|
2914
|
-
console.error(`[flywheel] QUEUE: flushing ${
|
|
2914
|
+
console.error(`[flywheel] QUEUE: flushing ${path31}, events=${pending.events.length}`);
|
|
2915
2915
|
if (pending.timer) {
|
|
2916
2916
|
clearTimeout(pending.timer);
|
|
2917
2917
|
pending.timer = null;
|
|
@@ -2920,7 +2920,7 @@ var EventQueue = class {
|
|
|
2920
2920
|
if (coalescedType) {
|
|
2921
2921
|
const coalesced = {
|
|
2922
2922
|
type: coalescedType,
|
|
2923
|
-
path:
|
|
2923
|
+
path: path31,
|
|
2924
2924
|
originalEvents: [...pending.events]
|
|
2925
2925
|
};
|
|
2926
2926
|
this.onBatch({
|
|
@@ -2928,7 +2928,7 @@ var EventQueue = class {
|
|
|
2928
2928
|
timestamp: Date.now()
|
|
2929
2929
|
});
|
|
2930
2930
|
}
|
|
2931
|
-
this.pending.delete(
|
|
2931
|
+
this.pending.delete(path31);
|
|
2932
2932
|
}
|
|
2933
2933
|
/**
|
|
2934
2934
|
* Flush all pending events
|
|
@@ -2940,7 +2940,7 @@ var EventQueue = class {
|
|
|
2940
2940
|
}
|
|
2941
2941
|
if (this.pending.size === 0) return;
|
|
2942
2942
|
const events = [];
|
|
2943
|
-
for (const [
|
|
2943
|
+
for (const [path31, pending] of this.pending) {
|
|
2944
2944
|
if (pending.timer) {
|
|
2945
2945
|
clearTimeout(pending.timer);
|
|
2946
2946
|
}
|
|
@@ -2948,7 +2948,7 @@ var EventQueue = class {
|
|
|
2948
2948
|
if (coalescedType) {
|
|
2949
2949
|
events.push({
|
|
2950
2950
|
type: coalescedType,
|
|
2951
|
-
path:
|
|
2951
|
+
path: path31,
|
|
2952
2952
|
originalEvents: [...pending.events]
|
|
2953
2953
|
});
|
|
2954
2954
|
}
|
|
@@ -3023,6 +3023,208 @@ function parseWatcherConfig() {
|
|
|
3023
3023
|
};
|
|
3024
3024
|
}
|
|
3025
3025
|
|
|
3026
|
+
// src/core/read/watch/incrementalIndex.ts
|
|
3027
|
+
import path6 from "path";
|
|
3028
|
+
function normalizeTarget2(target) {
|
|
3029
|
+
return target.toLowerCase().replace(/\.md$/, "");
|
|
3030
|
+
}
|
|
3031
|
+
function normalizeNotePath2(notePath) {
|
|
3032
|
+
return notePath.toLowerCase().replace(/\.md$/, "");
|
|
3033
|
+
}
|
|
3034
|
+
function removeNoteFromIndex(index, notePath) {
|
|
3035
|
+
const note = index.notes.get(notePath);
|
|
3036
|
+
if (!note) {
|
|
3037
|
+
return false;
|
|
3038
|
+
}
|
|
3039
|
+
index.notes.delete(notePath);
|
|
3040
|
+
const normalizedTitle = normalizeTarget2(note.title);
|
|
3041
|
+
const normalizedPath = normalizeNotePath2(notePath);
|
|
3042
|
+
if (index.entities.get(normalizedTitle) === notePath) {
|
|
3043
|
+
index.entities.delete(normalizedTitle);
|
|
3044
|
+
}
|
|
3045
|
+
if (index.entities.get(normalizedPath) === notePath) {
|
|
3046
|
+
index.entities.delete(normalizedPath);
|
|
3047
|
+
}
|
|
3048
|
+
for (const alias of note.aliases) {
|
|
3049
|
+
const normalizedAlias = normalizeTarget2(alias);
|
|
3050
|
+
if (index.entities.get(normalizedAlias) === notePath) {
|
|
3051
|
+
index.entities.delete(normalizedAlias);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
for (const tag of note.tags) {
|
|
3055
|
+
const tagPaths = index.tags.get(tag);
|
|
3056
|
+
if (tagPaths) {
|
|
3057
|
+
tagPaths.delete(notePath);
|
|
3058
|
+
if (tagPaths.size === 0) {
|
|
3059
|
+
index.tags.delete(tag);
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
for (const link of note.outlinks) {
|
|
3064
|
+
const normalizedTarget = normalizeTarget2(link.target);
|
|
3065
|
+
const targetPath = index.entities.get(normalizedTarget);
|
|
3066
|
+
const key = targetPath ? normalizeNotePath2(targetPath) : normalizedTarget;
|
|
3067
|
+
const backlinks = index.backlinks.get(key);
|
|
3068
|
+
if (backlinks) {
|
|
3069
|
+
const filtered = backlinks.filter((bl) => bl.source !== notePath);
|
|
3070
|
+
if (filtered.length === 0) {
|
|
3071
|
+
index.backlinks.delete(key);
|
|
3072
|
+
} else {
|
|
3073
|
+
index.backlinks.set(key, filtered);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
return true;
|
|
3078
|
+
}
|
|
3079
|
+
function addNoteToIndex(index, note) {
|
|
3080
|
+
index.notes.set(note.path, note);
|
|
3081
|
+
const normalizedTitle = normalizeTarget2(note.title);
|
|
3082
|
+
const normalizedPath = normalizeNotePath2(note.path);
|
|
3083
|
+
if (!index.entities.has(normalizedTitle)) {
|
|
3084
|
+
index.entities.set(normalizedTitle, note.path);
|
|
3085
|
+
}
|
|
3086
|
+
index.entities.set(normalizedPath, note.path);
|
|
3087
|
+
for (const alias of note.aliases) {
|
|
3088
|
+
const normalizedAlias = normalizeTarget2(alias);
|
|
3089
|
+
if (!index.entities.has(normalizedAlias)) {
|
|
3090
|
+
index.entities.set(normalizedAlias, note.path);
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
for (const tag of note.tags) {
|
|
3094
|
+
if (!index.tags.has(tag)) {
|
|
3095
|
+
index.tags.set(tag, /* @__PURE__ */ new Set());
|
|
3096
|
+
}
|
|
3097
|
+
index.tags.get(tag).add(note.path);
|
|
3098
|
+
}
|
|
3099
|
+
for (const link of note.outlinks) {
|
|
3100
|
+
const normalizedTarget = normalizeTarget2(link.target);
|
|
3101
|
+
const targetPath = index.entities.get(normalizedTarget);
|
|
3102
|
+
const key = targetPath ? normalizeNotePath2(targetPath) : normalizedTarget;
|
|
3103
|
+
if (!index.backlinks.has(key)) {
|
|
3104
|
+
index.backlinks.set(key, []);
|
|
3105
|
+
}
|
|
3106
|
+
index.backlinks.get(key).push({
|
|
3107
|
+
source: note.path,
|
|
3108
|
+
line: link.line
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
async function upsertNote(index, vaultPath2, notePath) {
|
|
3113
|
+
try {
|
|
3114
|
+
const existed = index.notes.has(notePath);
|
|
3115
|
+
if (existed) {
|
|
3116
|
+
removeNoteFromIndex(index, notePath);
|
|
3117
|
+
}
|
|
3118
|
+
const fullPath = path6.join(vaultPath2, notePath);
|
|
3119
|
+
const fs31 = await import("fs/promises");
|
|
3120
|
+
const stats = await fs31.stat(fullPath);
|
|
3121
|
+
const vaultFile = {
|
|
3122
|
+
path: notePath,
|
|
3123
|
+
absolutePath: fullPath,
|
|
3124
|
+
modified: stats.mtime
|
|
3125
|
+
};
|
|
3126
|
+
const note = await parseNote(vaultFile);
|
|
3127
|
+
addNoteToIndex(index, note);
|
|
3128
|
+
return {
|
|
3129
|
+
success: true,
|
|
3130
|
+
action: existed ? "updated" : "added",
|
|
3131
|
+
path: notePath
|
|
3132
|
+
};
|
|
3133
|
+
} catch (error) {
|
|
3134
|
+
return {
|
|
3135
|
+
success: false,
|
|
3136
|
+
action: "unchanged",
|
|
3137
|
+
path: notePath,
|
|
3138
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function deleteNote(index, notePath) {
|
|
3143
|
+
const removed = removeNoteFromIndex(index, notePath);
|
|
3144
|
+
return {
|
|
3145
|
+
success: removed,
|
|
3146
|
+
action: removed ? "removed" : "unchanged",
|
|
3147
|
+
path: notePath
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
// src/core/read/watch/batchProcessor.ts
|
|
3152
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
3153
|
+
var YIELD_INTERVAL = 10;
|
|
3154
|
+
async function processBatch(index, vaultPath2, batch, options = {}) {
|
|
3155
|
+
const { concurrency = DEFAULT_CONCURRENCY, onProgress, onError } = options;
|
|
3156
|
+
const startTime = Date.now();
|
|
3157
|
+
const results = [];
|
|
3158
|
+
let successful = 0;
|
|
3159
|
+
let failed = 0;
|
|
3160
|
+
let processed = 0;
|
|
3161
|
+
const events = batch.events;
|
|
3162
|
+
const total = events.length;
|
|
3163
|
+
if (total === 0) {
|
|
3164
|
+
return {
|
|
3165
|
+
total: 0,
|
|
3166
|
+
successful: 0,
|
|
3167
|
+
failed: 0,
|
|
3168
|
+
results: [],
|
|
3169
|
+
durationMs: 0
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
console.error(`[flywheel] Processing ${total} file events`);
|
|
3173
|
+
for (let i = 0; i < events.length; i += concurrency) {
|
|
3174
|
+
const chunk = events.slice(i, i + concurrency);
|
|
3175
|
+
const chunkResults = await Promise.allSettled(
|
|
3176
|
+
chunk.map(async (event) => {
|
|
3177
|
+
const relativePath = getRelativePath(vaultPath2, event.path);
|
|
3178
|
+
if (event.type === "delete") {
|
|
3179
|
+
return deleteNote(index, relativePath);
|
|
3180
|
+
} else {
|
|
3181
|
+
return upsertNote(index, vaultPath2, relativePath);
|
|
3182
|
+
}
|
|
3183
|
+
})
|
|
3184
|
+
);
|
|
3185
|
+
for (let j = 0; j < chunkResults.length; j++) {
|
|
3186
|
+
const result = chunkResults[j];
|
|
3187
|
+
processed++;
|
|
3188
|
+
if (result.status === "fulfilled") {
|
|
3189
|
+
results.push(result.value);
|
|
3190
|
+
if (result.value.success) {
|
|
3191
|
+
successful++;
|
|
3192
|
+
} else {
|
|
3193
|
+
failed++;
|
|
3194
|
+
if (result.value.error && onError) {
|
|
3195
|
+
onError(result.value.path, result.value.error);
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
} else {
|
|
3199
|
+
failed++;
|
|
3200
|
+
const event = chunk[j];
|
|
3201
|
+
const relativePath = getRelativePath(vaultPath2, event.path);
|
|
3202
|
+
const error = result.reason instanceof Error ? result.reason : new Error(String(result.reason));
|
|
3203
|
+
results.push({
|
|
3204
|
+
success: false,
|
|
3205
|
+
action: "unchanged",
|
|
3206
|
+
path: relativePath,
|
|
3207
|
+
error
|
|
3208
|
+
});
|
|
3209
|
+
onError?.(relativePath, error);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
onProgress?.(processed, total);
|
|
3213
|
+
if (processed % YIELD_INTERVAL === 0 && processed < total) {
|
|
3214
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
const durationMs = Date.now() - startTime;
|
|
3218
|
+
console.error(`[flywheel] Processed ${successful}/${total} files in ${durationMs}ms`);
|
|
3219
|
+
return {
|
|
3220
|
+
total,
|
|
3221
|
+
successful,
|
|
3222
|
+
failed,
|
|
3223
|
+
results,
|
|
3224
|
+
durationMs
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3026
3228
|
// src/core/read/watch/index.ts
|
|
3027
3229
|
function createVaultWatcher(options) {
|
|
3028
3230
|
const { vaultPath: vaultPath2, onBatch, onStateChange, onError } = options;
|
|
@@ -3097,31 +3299,31 @@ function createVaultWatcher(options) {
|
|
|
3097
3299
|
usePolling: config.usePolling,
|
|
3098
3300
|
interval: config.usePolling ? config.pollInterval : void 0
|
|
3099
3301
|
});
|
|
3100
|
-
watcher.on("add", (
|
|
3101
|
-
console.error(`[flywheel] RAW EVENT: add ${
|
|
3102
|
-
if (shouldWatch(
|
|
3103
|
-
console.error(`[flywheel] ACCEPTED: add ${
|
|
3104
|
-
eventQueue.push("add",
|
|
3302
|
+
watcher.on("add", (path31) => {
|
|
3303
|
+
console.error(`[flywheel] RAW EVENT: add ${path31}`);
|
|
3304
|
+
if (shouldWatch(path31, vaultPath2)) {
|
|
3305
|
+
console.error(`[flywheel] ACCEPTED: add ${path31}`);
|
|
3306
|
+
eventQueue.push("add", path31);
|
|
3105
3307
|
} else {
|
|
3106
|
-
console.error(`[flywheel] FILTERED: add ${
|
|
3308
|
+
console.error(`[flywheel] FILTERED: add ${path31}`);
|
|
3107
3309
|
}
|
|
3108
3310
|
});
|
|
3109
|
-
watcher.on("change", (
|
|
3110
|
-
console.error(`[flywheel] RAW EVENT: change ${
|
|
3111
|
-
if (shouldWatch(
|
|
3112
|
-
console.error(`[flywheel] ACCEPTED: change ${
|
|
3113
|
-
eventQueue.push("change",
|
|
3311
|
+
watcher.on("change", (path31) => {
|
|
3312
|
+
console.error(`[flywheel] RAW EVENT: change ${path31}`);
|
|
3313
|
+
if (shouldWatch(path31, vaultPath2)) {
|
|
3314
|
+
console.error(`[flywheel] ACCEPTED: change ${path31}`);
|
|
3315
|
+
eventQueue.push("change", path31);
|
|
3114
3316
|
} else {
|
|
3115
|
-
console.error(`[flywheel] FILTERED: change ${
|
|
3317
|
+
console.error(`[flywheel] FILTERED: change ${path31}`);
|
|
3116
3318
|
}
|
|
3117
3319
|
});
|
|
3118
|
-
watcher.on("unlink", (
|
|
3119
|
-
console.error(`[flywheel] RAW EVENT: unlink ${
|
|
3120
|
-
if (shouldWatch(
|
|
3121
|
-
console.error(`[flywheel] ACCEPTED: unlink ${
|
|
3122
|
-
eventQueue.push("unlink",
|
|
3320
|
+
watcher.on("unlink", (path31) => {
|
|
3321
|
+
console.error(`[flywheel] RAW EVENT: unlink ${path31}`);
|
|
3322
|
+
if (shouldWatch(path31, vaultPath2)) {
|
|
3323
|
+
console.error(`[flywheel] ACCEPTED: unlink ${path31}`);
|
|
3324
|
+
eventQueue.push("unlink", path31);
|
|
3123
3325
|
} else {
|
|
3124
|
-
console.error(`[flywheel] FILTERED: unlink ${
|
|
3326
|
+
console.error(`[flywheel] FILTERED: unlink ${path31}`);
|
|
3125
3327
|
}
|
|
3126
3328
|
});
|
|
3127
3329
|
watcher.on("ready", () => {
|
|
@@ -3725,7 +3927,7 @@ function getExtendedDashboardData(stateDb2) {
|
|
|
3725
3927
|
|
|
3726
3928
|
// src/core/write/git.ts
|
|
3727
3929
|
import { simpleGit, CheckRepoActions } from "simple-git";
|
|
3728
|
-
import
|
|
3930
|
+
import path7 from "path";
|
|
3729
3931
|
import fs6 from "fs/promises";
|
|
3730
3932
|
import {
|
|
3731
3933
|
setWriteState,
|
|
@@ -3779,7 +3981,7 @@ function clearLastMutationCommit() {
|
|
|
3779
3981
|
}
|
|
3780
3982
|
}
|
|
3781
3983
|
async function checkGitLock(vaultPath2) {
|
|
3782
|
-
const lockPath =
|
|
3984
|
+
const lockPath = path7.join(vaultPath2, ".git/index.lock");
|
|
3783
3985
|
try {
|
|
3784
3986
|
const stat3 = await fs6.stat(lockPath);
|
|
3785
3987
|
const ageMs = Date.now() - stat3.mtimeMs;
|
|
@@ -3802,7 +4004,7 @@ async function isGitRepo(vaultPath2) {
|
|
|
3802
4004
|
}
|
|
3803
4005
|
}
|
|
3804
4006
|
async function checkLockFile(vaultPath2) {
|
|
3805
|
-
const lockPath =
|
|
4007
|
+
const lockPath = path7.join(vaultPath2, ".git/index.lock");
|
|
3806
4008
|
try {
|
|
3807
4009
|
const stat3 = await fs6.stat(lockPath);
|
|
3808
4010
|
const ageMs = Date.now() - stat3.mtimeMs;
|
|
@@ -3852,7 +4054,7 @@ async function commitChange(vaultPath2, filePath, messagePrefix, retryConfig = D
|
|
|
3852
4054
|
}
|
|
3853
4055
|
}
|
|
3854
4056
|
await git.add(filePath);
|
|
3855
|
-
const fileName =
|
|
4057
|
+
const fileName = path7.basename(filePath);
|
|
3856
4058
|
const commitMessage = `${messagePrefix} Update ${fileName}`;
|
|
3857
4059
|
const result = await git.commit(commitMessage);
|
|
3858
4060
|
if (result.commit) {
|
|
@@ -4046,7 +4248,7 @@ function setHintsStateDb(stateDb2) {
|
|
|
4046
4248
|
|
|
4047
4249
|
// src/core/shared/recency.ts
|
|
4048
4250
|
import { readdir, readFile, stat } from "fs/promises";
|
|
4049
|
-
import
|
|
4251
|
+
import path8 from "path";
|
|
4050
4252
|
import {
|
|
4051
4253
|
getEntityName,
|
|
4052
4254
|
recordEntityMention,
|
|
@@ -4068,9 +4270,9 @@ async function* walkMarkdownFiles(dir, baseDir) {
|
|
|
4068
4270
|
try {
|
|
4069
4271
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
4070
4272
|
for (const entry of entries) {
|
|
4071
|
-
const fullPath =
|
|
4072
|
-
const relativePath =
|
|
4073
|
-
const topFolder = relativePath.split(
|
|
4273
|
+
const fullPath = path8.join(dir, entry.name);
|
|
4274
|
+
const relativePath = path8.relative(baseDir, fullPath);
|
|
4275
|
+
const topFolder = relativePath.split(path8.sep)[0];
|
|
4074
4276
|
if (EXCLUDED_FOLDERS.has(topFolder)) {
|
|
4075
4277
|
continue;
|
|
4076
4278
|
}
|
|
@@ -4950,7 +5152,7 @@ function tokenize(text) {
|
|
|
4950
5152
|
|
|
4951
5153
|
// src/core/shared/cooccurrence.ts
|
|
4952
5154
|
import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
4953
|
-
import
|
|
5155
|
+
import path9 from "path";
|
|
4954
5156
|
var DEFAULT_MIN_COOCCURRENCE = 2;
|
|
4955
5157
|
var EXCLUDED_FOLDERS2 = /* @__PURE__ */ new Set([
|
|
4956
5158
|
"templates",
|
|
@@ -4984,9 +5186,9 @@ async function* walkMarkdownFiles2(dir, baseDir) {
|
|
|
4984
5186
|
try {
|
|
4985
5187
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
4986
5188
|
for (const entry of entries) {
|
|
4987
|
-
const fullPath =
|
|
4988
|
-
const relativePath =
|
|
4989
|
-
const topFolder = relativePath.split(
|
|
5189
|
+
const fullPath = path9.join(dir, entry.name);
|
|
5190
|
+
const relativePath = path9.relative(baseDir, fullPath);
|
|
5191
|
+
const topFolder = relativePath.split(path9.sep)[0];
|
|
4990
5192
|
if (EXCLUDED_FOLDERS2.has(topFolder)) {
|
|
4991
5193
|
continue;
|
|
4992
5194
|
}
|
|
@@ -5249,9 +5451,11 @@ function processWikilinks(content, notePath) {
|
|
|
5249
5451
|
const resolved = resolveAliasWikilinks(content, sortedEntities, {
|
|
5250
5452
|
caseInsensitive: true
|
|
5251
5453
|
});
|
|
5454
|
+
const step1LinkedEntities = new Set(resolved.linkedEntities.map((e) => e.toLowerCase()));
|
|
5252
5455
|
const result = applyWikilinks(resolved.content, sortedEntities, {
|
|
5253
5456
|
firstOccurrenceOnly: true,
|
|
5254
|
-
caseInsensitive: true
|
|
5457
|
+
caseInsensitive: true,
|
|
5458
|
+
alreadyLinked: step1LinkedEntities
|
|
5255
5459
|
});
|
|
5256
5460
|
const implicitEnabled = moduleConfig?.implicit_detection !== false;
|
|
5257
5461
|
const validPatterns = new Set(ALL_IMPLICIT_PATTERNS);
|
|
@@ -6298,11 +6502,11 @@ function countFTS5Mentions(term) {
|
|
|
6298
6502
|
}
|
|
6299
6503
|
|
|
6300
6504
|
// src/core/read/taskCache.ts
|
|
6301
|
-
import * as
|
|
6505
|
+
import * as path11 from "path";
|
|
6302
6506
|
|
|
6303
6507
|
// src/tools/read/tasks.ts
|
|
6304
6508
|
import * as fs8 from "fs";
|
|
6305
|
-
import * as
|
|
6509
|
+
import * as path10 from "path";
|
|
6306
6510
|
var TASK_REGEX = /^(\s*)- \[([ xX\-])\]\s+(.+)$/;
|
|
6307
6511
|
var TAG_REGEX2 = /#([a-zA-Z][a-zA-Z0-9_/-]*)/g;
|
|
6308
6512
|
var DATE_REGEX = /\b(\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4})\b/;
|
|
@@ -6371,7 +6575,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
|
|
|
6371
6575
|
const allTasks = [];
|
|
6372
6576
|
for (const note of index.notes.values()) {
|
|
6373
6577
|
if (folder && !note.path.startsWith(folder)) continue;
|
|
6374
|
-
const absolutePath =
|
|
6578
|
+
const absolutePath = path10.join(vaultPath2, note.path);
|
|
6375
6579
|
const tasks = await extractTasksFromNote(note.path, absolutePath);
|
|
6376
6580
|
allTasks.push(...tasks);
|
|
6377
6581
|
}
|
|
@@ -6415,7 +6619,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
|
|
|
6415
6619
|
async function getTasksFromNote(index, notePath, vaultPath2, excludeTags = []) {
|
|
6416
6620
|
const note = index.notes.get(notePath);
|
|
6417
6621
|
if (!note) return null;
|
|
6418
|
-
const absolutePath =
|
|
6622
|
+
const absolutePath = path10.join(vaultPath2, notePath);
|
|
6419
6623
|
let tasks = await extractTasksFromNote(notePath, absolutePath);
|
|
6420
6624
|
if (excludeTags.length > 0) {
|
|
6421
6625
|
tasks = tasks.filter(
|
|
@@ -6507,7 +6711,7 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
|
|
|
6507
6711
|
}
|
|
6508
6712
|
const allRows = [];
|
|
6509
6713
|
for (const notePath of notePaths) {
|
|
6510
|
-
const absolutePath =
|
|
6714
|
+
const absolutePath = path11.join(vaultPath2, notePath);
|
|
6511
6715
|
const tasks = await extractTasksFromNote(notePath, absolutePath);
|
|
6512
6716
|
for (const task of tasks) {
|
|
6513
6717
|
if (excludeTags?.length && excludeTags.some((t) => task.tags.includes(t))) {
|
|
@@ -6549,7 +6753,7 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
|
|
|
6549
6753
|
async function updateTaskCacheForFile(vaultPath2, relativePath) {
|
|
6550
6754
|
if (!db3) return;
|
|
6551
6755
|
db3.prepare("DELETE FROM tasks WHERE path = ?").run(relativePath);
|
|
6552
|
-
const absolutePath =
|
|
6756
|
+
const absolutePath = path11.join(vaultPath2, relativePath);
|
|
6553
6757
|
const tasks = await extractTasksFromNote(relativePath, absolutePath);
|
|
6554
6758
|
if (tasks.length > 0) {
|
|
6555
6759
|
const insertStmt = db3.prepare(`
|
|
@@ -6689,7 +6893,7 @@ import { openStateDb, scanVaultEntities as scanVaultEntities3, getSessionId, get
|
|
|
6689
6893
|
|
|
6690
6894
|
// src/tools/read/graph.ts
|
|
6691
6895
|
import * as fs9 from "fs";
|
|
6692
|
-
import * as
|
|
6896
|
+
import * as path12 from "path";
|
|
6693
6897
|
import { z } from "zod";
|
|
6694
6898
|
|
|
6695
6899
|
// src/core/read/constants.ts
|
|
@@ -6973,7 +7177,7 @@ function requireIndex() {
|
|
|
6973
7177
|
// src/tools/read/graph.ts
|
|
6974
7178
|
async function getContext(vaultPath2, sourcePath, line, contextLines = 1) {
|
|
6975
7179
|
try {
|
|
6976
|
-
const fullPath =
|
|
7180
|
+
const fullPath = path12.join(vaultPath2, sourcePath);
|
|
6977
7181
|
const content = await fs9.promises.readFile(fullPath, "utf-8");
|
|
6978
7182
|
const allLines = content.split("\n");
|
|
6979
7183
|
let fmLines = 0;
|
|
@@ -7287,14 +7491,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
|
|
|
7287
7491
|
};
|
|
7288
7492
|
function findSimilarEntity2(target, entities) {
|
|
7289
7493
|
const targetLower = target.toLowerCase();
|
|
7290
|
-
for (const [name,
|
|
7494
|
+
for (const [name, path31] of entities) {
|
|
7291
7495
|
if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
|
|
7292
|
-
return
|
|
7496
|
+
return path31;
|
|
7293
7497
|
}
|
|
7294
7498
|
}
|
|
7295
|
-
for (const [name,
|
|
7499
|
+
for (const [name, path31] of entities) {
|
|
7296
7500
|
if (name.includes(targetLower) || targetLower.includes(name)) {
|
|
7297
|
-
return
|
|
7501
|
+
return path31;
|
|
7298
7502
|
}
|
|
7299
7503
|
}
|
|
7300
7504
|
return void 0;
|
|
@@ -8271,8 +8475,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
8271
8475
|
daily_counts: z3.record(z3.number())
|
|
8272
8476
|
}).describe("Activity summary for the last 7 days")
|
|
8273
8477
|
};
|
|
8274
|
-
function isPeriodicNote2(
|
|
8275
|
-
const filename =
|
|
8478
|
+
function isPeriodicNote2(path31) {
|
|
8479
|
+
const filename = path31.split("/").pop() || "";
|
|
8276
8480
|
const nameWithoutExt = filename.replace(/\.md$/, "");
|
|
8277
8481
|
const patterns = [
|
|
8278
8482
|
/^\d{4}-\d{2}-\d{2}$/,
|
|
@@ -8287,7 +8491,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
8287
8491
|
// YYYY (yearly)
|
|
8288
8492
|
];
|
|
8289
8493
|
const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
|
|
8290
|
-
const folder =
|
|
8494
|
+
const folder = path31.split("/")[0]?.toLowerCase() || "";
|
|
8291
8495
|
return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
|
|
8292
8496
|
}
|
|
8293
8497
|
server2.registerTool(
|
|
@@ -8695,7 +8899,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
8695
8899
|
|
|
8696
8900
|
// src/tools/read/system.ts
|
|
8697
8901
|
import * as fs11 from "fs";
|
|
8698
|
-
import * as
|
|
8902
|
+
import * as path13 from "path";
|
|
8699
8903
|
import { z as z5 } from "zod";
|
|
8700
8904
|
import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
|
|
8701
8905
|
|
|
@@ -8995,7 +9199,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
8995
9199
|
continue;
|
|
8996
9200
|
}
|
|
8997
9201
|
try {
|
|
8998
|
-
const fullPath =
|
|
9202
|
+
const fullPath = path13.join(vaultPath2, note.path);
|
|
8999
9203
|
const content = await fs11.promises.readFile(fullPath, "utf-8");
|
|
9000
9204
|
const lines = content.split("\n");
|
|
9001
9205
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -9111,7 +9315,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
9111
9315
|
let wordCount;
|
|
9112
9316
|
if (include_word_count) {
|
|
9113
9317
|
try {
|
|
9114
|
-
const fullPath =
|
|
9318
|
+
const fullPath = path13.join(vaultPath2, resolvedPath);
|
|
9115
9319
|
const content = await fs11.promises.readFile(fullPath, "utf-8");
|
|
9116
9320
|
wordCount = content.split(/\s+/).filter((w) => w.length > 0).length;
|
|
9117
9321
|
} catch {
|
|
@@ -9342,7 +9546,7 @@ import { z as z6 } from "zod";
|
|
|
9342
9546
|
|
|
9343
9547
|
// src/tools/read/structure.ts
|
|
9344
9548
|
import * as fs12 from "fs";
|
|
9345
|
-
import * as
|
|
9549
|
+
import * as path14 from "path";
|
|
9346
9550
|
var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
|
|
9347
9551
|
function extractHeadings(content) {
|
|
9348
9552
|
const lines = content.split("\n");
|
|
@@ -9396,7 +9600,7 @@ function buildSections(headings, totalLines) {
|
|
|
9396
9600
|
async function getNoteStructure(index, notePath, vaultPath2) {
|
|
9397
9601
|
const note = index.notes.get(notePath);
|
|
9398
9602
|
if (!note) return null;
|
|
9399
|
-
const absolutePath =
|
|
9603
|
+
const absolutePath = path14.join(vaultPath2, notePath);
|
|
9400
9604
|
let content;
|
|
9401
9605
|
try {
|
|
9402
9606
|
content = await fs12.promises.readFile(absolutePath, "utf-8");
|
|
@@ -9419,7 +9623,7 @@ async function getNoteStructure(index, notePath, vaultPath2) {
|
|
|
9419
9623
|
async function getSectionContent(index, notePath, headingText, vaultPath2, includeSubheadings = true) {
|
|
9420
9624
|
const note = index.notes.get(notePath);
|
|
9421
9625
|
if (!note) return null;
|
|
9422
|
-
const absolutePath =
|
|
9626
|
+
const absolutePath = path14.join(vaultPath2, notePath);
|
|
9423
9627
|
let content;
|
|
9424
9628
|
try {
|
|
9425
9629
|
content = await fs12.promises.readFile(absolutePath, "utf-8");
|
|
@@ -9461,7 +9665,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
|
|
|
9461
9665
|
const results = [];
|
|
9462
9666
|
for (const note of index.notes.values()) {
|
|
9463
9667
|
if (folder && !note.path.startsWith(folder)) continue;
|
|
9464
|
-
const absolutePath =
|
|
9668
|
+
const absolutePath = path14.join(vaultPath2, note.path);
|
|
9465
9669
|
let content;
|
|
9466
9670
|
try {
|
|
9467
9671
|
content = await fs12.promises.readFile(absolutePath, "utf-8");
|
|
@@ -9495,18 +9699,18 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
9495
9699
|
include_content: z6.boolean().default(false).describe("Include the text content under each top-level section")
|
|
9496
9700
|
}
|
|
9497
9701
|
},
|
|
9498
|
-
async ({ path:
|
|
9702
|
+
async ({ path: path31, include_content }) => {
|
|
9499
9703
|
const index = getIndex();
|
|
9500
9704
|
const vaultPath2 = getVaultPath();
|
|
9501
|
-
const result = await getNoteStructure(index,
|
|
9705
|
+
const result = await getNoteStructure(index, path31, vaultPath2);
|
|
9502
9706
|
if (!result) {
|
|
9503
9707
|
return {
|
|
9504
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
9708
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path31 }, null, 2) }]
|
|
9505
9709
|
};
|
|
9506
9710
|
}
|
|
9507
9711
|
if (include_content) {
|
|
9508
9712
|
for (const section of result.sections) {
|
|
9509
|
-
const sectionResult = await getSectionContent(index,
|
|
9713
|
+
const sectionResult = await getSectionContent(index, path31, section.heading.text, vaultPath2, true);
|
|
9510
9714
|
if (sectionResult) {
|
|
9511
9715
|
section.content = sectionResult.content;
|
|
9512
9716
|
}
|
|
@@ -9528,15 +9732,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
9528
9732
|
include_subheadings: z6.boolean().default(true).describe("Include content under subheadings")
|
|
9529
9733
|
}
|
|
9530
9734
|
},
|
|
9531
|
-
async ({ path:
|
|
9735
|
+
async ({ path: path31, heading, include_subheadings }) => {
|
|
9532
9736
|
const index = getIndex();
|
|
9533
9737
|
const vaultPath2 = getVaultPath();
|
|
9534
|
-
const result = await getSectionContent(index,
|
|
9738
|
+
const result = await getSectionContent(index, path31, heading, vaultPath2, include_subheadings);
|
|
9535
9739
|
if (!result) {
|
|
9536
9740
|
return {
|
|
9537
9741
|
content: [{ type: "text", text: JSON.stringify({
|
|
9538
9742
|
error: "Section not found",
|
|
9539
|
-
path:
|
|
9743
|
+
path: path31,
|
|
9540
9744
|
heading
|
|
9541
9745
|
}, null, 2) }]
|
|
9542
9746
|
};
|
|
@@ -9590,16 +9794,16 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
9590
9794
|
offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
|
|
9591
9795
|
}
|
|
9592
9796
|
},
|
|
9593
|
-
async ({ path:
|
|
9797
|
+
async ({ path: path31, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
|
|
9594
9798
|
const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
|
|
9595
9799
|
const index = getIndex();
|
|
9596
9800
|
const vaultPath2 = getVaultPath();
|
|
9597
9801
|
const config = getConfig();
|
|
9598
|
-
if (
|
|
9599
|
-
const result2 = await getTasksFromNote(index,
|
|
9802
|
+
if (path31) {
|
|
9803
|
+
const result2 = await getTasksFromNote(index, path31, vaultPath2, config.exclude_task_tags || []);
|
|
9600
9804
|
if (!result2) {
|
|
9601
9805
|
return {
|
|
9602
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
9806
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path31 }, null, 2) }]
|
|
9603
9807
|
};
|
|
9604
9808
|
}
|
|
9605
9809
|
let filtered = result2;
|
|
@@ -9609,7 +9813,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
9609
9813
|
const paged2 = filtered.slice(offset, offset + limit);
|
|
9610
9814
|
return {
|
|
9611
9815
|
content: [{ type: "text", text: JSON.stringify({
|
|
9612
|
-
path:
|
|
9816
|
+
path: path31,
|
|
9613
9817
|
total_count: filtered.length,
|
|
9614
9818
|
returned_count: paged2.length,
|
|
9615
9819
|
open: result2.filter((t) => t.status === "open").length,
|
|
@@ -9765,7 +9969,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
9765
9969
|
// src/tools/read/migrations.ts
|
|
9766
9970
|
import { z as z7 } from "zod";
|
|
9767
9971
|
import * as fs13 from "fs/promises";
|
|
9768
|
-
import * as
|
|
9972
|
+
import * as path15 from "path";
|
|
9769
9973
|
import matter2 from "gray-matter";
|
|
9770
9974
|
function getNotesInFolder(index, folder) {
|
|
9771
9975
|
const notes = [];
|
|
@@ -9778,7 +9982,7 @@ function getNotesInFolder(index, folder) {
|
|
|
9778
9982
|
return notes;
|
|
9779
9983
|
}
|
|
9780
9984
|
async function readFileContent(notePath, vaultPath2) {
|
|
9781
|
-
const fullPath =
|
|
9985
|
+
const fullPath = path15.join(vaultPath2, notePath);
|
|
9782
9986
|
try {
|
|
9783
9987
|
return await fs13.readFile(fullPath, "utf-8");
|
|
9784
9988
|
} catch {
|
|
@@ -9786,7 +9990,7 @@ async function readFileContent(notePath, vaultPath2) {
|
|
|
9786
9990
|
}
|
|
9787
9991
|
}
|
|
9788
9992
|
async function writeFileContent(notePath, vaultPath2, content) {
|
|
9789
|
-
const fullPath =
|
|
9993
|
+
const fullPath = path15.join(vaultPath2, notePath);
|
|
9790
9994
|
try {
|
|
9791
9995
|
await fs13.writeFile(fullPath, content, "utf-8");
|
|
9792
9996
|
return true;
|
|
@@ -9967,7 +10171,7 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
|
|
|
9967
10171
|
|
|
9968
10172
|
// src/tools/read/graphAnalysis.ts
|
|
9969
10173
|
import fs14 from "node:fs";
|
|
9970
|
-
import
|
|
10174
|
+
import path16 from "node:path";
|
|
9971
10175
|
import { z as z8 } from "zod";
|
|
9972
10176
|
|
|
9973
10177
|
// src/tools/read/schema.ts
|
|
@@ -10729,7 +10933,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb,
|
|
|
10729
10933
|
const scored = allNotes.map((note) => {
|
|
10730
10934
|
let wordCount = 0;
|
|
10731
10935
|
try {
|
|
10732
|
-
const content = fs14.readFileSync(
|
|
10936
|
+
const content = fs14.readFileSync(path16.join(vaultPath2, note.path), "utf-8");
|
|
10733
10937
|
const body = content.replace(/^---[\s\S]*?---\n?/, "");
|
|
10734
10938
|
wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
|
|
10735
10939
|
} catch {
|
|
@@ -11309,12 +11513,12 @@ import { z as z10 } from "zod";
|
|
|
11309
11513
|
|
|
11310
11514
|
// src/tools/read/bidirectional.ts
|
|
11311
11515
|
import * as fs15 from "fs/promises";
|
|
11312
|
-
import * as
|
|
11516
|
+
import * as path17 from "path";
|
|
11313
11517
|
import matter3 from "gray-matter";
|
|
11314
11518
|
var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
|
|
11315
11519
|
var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
11316
11520
|
async function readFileContent2(notePath, vaultPath2) {
|
|
11317
|
-
const fullPath =
|
|
11521
|
+
const fullPath = path17.join(vaultPath2, notePath);
|
|
11318
11522
|
try {
|
|
11319
11523
|
return await fs15.readFile(fullPath, "utf-8");
|
|
11320
11524
|
} catch {
|
|
@@ -11493,10 +11697,10 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
|
|
|
11493
11697
|
|
|
11494
11698
|
// src/tools/read/computed.ts
|
|
11495
11699
|
import * as fs16 from "fs/promises";
|
|
11496
|
-
import * as
|
|
11700
|
+
import * as path18 from "path";
|
|
11497
11701
|
import matter4 from "gray-matter";
|
|
11498
11702
|
async function readFileContent3(notePath, vaultPath2) {
|
|
11499
|
-
const fullPath =
|
|
11703
|
+
const fullPath = path18.join(vaultPath2, notePath);
|
|
11500
11704
|
try {
|
|
11501
11705
|
return await fs16.readFile(fullPath, "utf-8");
|
|
11502
11706
|
} catch {
|
|
@@ -11504,7 +11708,7 @@ async function readFileContent3(notePath, vaultPath2) {
|
|
|
11504
11708
|
}
|
|
11505
11709
|
}
|
|
11506
11710
|
async function getFileStats(notePath, vaultPath2) {
|
|
11507
|
-
const fullPath =
|
|
11711
|
+
const fullPath = path18.join(vaultPath2, notePath);
|
|
11508
11712
|
try {
|
|
11509
11713
|
const stats = await fs16.stat(fullPath);
|
|
11510
11714
|
return {
|
|
@@ -11775,7 +11979,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
11775
11979
|
init_writer();
|
|
11776
11980
|
import { z as z11 } from "zod";
|
|
11777
11981
|
import fs20 from "fs/promises";
|
|
11778
|
-
import
|
|
11982
|
+
import path21 from "path";
|
|
11779
11983
|
|
|
11780
11984
|
// src/core/write/validator.ts
|
|
11781
11985
|
var TIMESTAMP_PATTERN = /^\*\*\d{2}:\d{2}\*\*/;
|
|
@@ -11978,7 +12182,7 @@ function runValidationPipeline(content, format, options = {}) {
|
|
|
11978
12182
|
// src/core/write/mutation-helpers.ts
|
|
11979
12183
|
init_writer();
|
|
11980
12184
|
import fs19 from "fs/promises";
|
|
11981
|
-
import
|
|
12185
|
+
import path20 from "path";
|
|
11982
12186
|
init_constants();
|
|
11983
12187
|
init_writer();
|
|
11984
12188
|
function formatMcpResult(result) {
|
|
@@ -12027,7 +12231,7 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
|
|
|
12027
12231
|
return info;
|
|
12028
12232
|
}
|
|
12029
12233
|
async function ensureFileExists(vaultPath2, notePath) {
|
|
12030
|
-
const fullPath =
|
|
12234
|
+
const fullPath = path20.join(vaultPath2, notePath);
|
|
12031
12235
|
try {
|
|
12032
12236
|
await fs19.access(fullPath);
|
|
12033
12237
|
return null;
|
|
@@ -12086,7 +12290,7 @@ async function withVaultFile(options, operation) {
|
|
|
12086
12290
|
if ("error" in result) {
|
|
12087
12291
|
return formatMcpResult(result.error);
|
|
12088
12292
|
}
|
|
12089
|
-
const fullPath =
|
|
12293
|
+
const fullPath = path20.join(vaultPath2, notePath);
|
|
12090
12294
|
const statBefore = await fs19.stat(fullPath);
|
|
12091
12295
|
if (statBefore.mtimeMs !== result.mtimeMs) {
|
|
12092
12296
|
console.warn(`[withVaultFile] External modification detected on ${notePath}, re-reading and retrying`);
|
|
@@ -12149,10 +12353,10 @@ async function withVaultFrontmatter(options, operation) {
|
|
|
12149
12353
|
|
|
12150
12354
|
// src/tools/write/mutations.ts
|
|
12151
12355
|
async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
12152
|
-
const fullPath =
|
|
12153
|
-
await fs20.mkdir(
|
|
12356
|
+
const fullPath = path21.join(vaultPath2, notePath);
|
|
12357
|
+
await fs20.mkdir(path21.dirname(fullPath), { recursive: true });
|
|
12154
12358
|
const templates = config.templates || {};
|
|
12155
|
-
const filename =
|
|
12359
|
+
const filename = path21.basename(notePath, ".md").toLowerCase();
|
|
12156
12360
|
let templatePath;
|
|
12157
12361
|
const dailyPattern = /^\d{4}-\d{2}-\d{2}/;
|
|
12158
12362
|
const weeklyPattern = /^\d{4}-W\d{2}/;
|
|
@@ -12173,10 +12377,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
12173
12377
|
let templateContent;
|
|
12174
12378
|
if (templatePath) {
|
|
12175
12379
|
try {
|
|
12176
|
-
const absTemplatePath =
|
|
12380
|
+
const absTemplatePath = path21.join(vaultPath2, templatePath);
|
|
12177
12381
|
templateContent = await fs20.readFile(absTemplatePath, "utf-8");
|
|
12178
12382
|
} catch {
|
|
12179
|
-
const title =
|
|
12383
|
+
const title = path21.basename(notePath, ".md");
|
|
12180
12384
|
templateContent = `---
|
|
12181
12385
|
---
|
|
12182
12386
|
|
|
@@ -12185,7 +12389,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
12185
12389
|
templatePath = void 0;
|
|
12186
12390
|
}
|
|
12187
12391
|
} else {
|
|
12188
|
-
const title =
|
|
12392
|
+
const title = path21.basename(notePath, ".md");
|
|
12189
12393
|
templateContent = `---
|
|
12190
12394
|
---
|
|
12191
12395
|
|
|
@@ -12194,7 +12398,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
12194
12398
|
}
|
|
12195
12399
|
const now = /* @__PURE__ */ new Date();
|
|
12196
12400
|
const dateStr = now.toISOString().split("T")[0];
|
|
12197
|
-
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g,
|
|
12401
|
+
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path21.basename(notePath, ".md"));
|
|
12198
12402
|
const matter9 = (await import("gray-matter")).default;
|
|
12199
12403
|
const parsed = matter9(templateContent);
|
|
12200
12404
|
if (!parsed.data.date) {
|
|
@@ -12234,7 +12438,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
12234
12438
|
let noteCreated = false;
|
|
12235
12439
|
let templateUsed;
|
|
12236
12440
|
if (create_if_missing) {
|
|
12237
|
-
const fullPath =
|
|
12441
|
+
const fullPath = path21.join(vaultPath2, notePath);
|
|
12238
12442
|
try {
|
|
12239
12443
|
await fs20.access(fullPath);
|
|
12240
12444
|
} catch {
|
|
@@ -12693,7 +12897,7 @@ Example: vault_update_frontmatter({ path: "projects/alpha.md", frontmatter: { st
|
|
|
12693
12897
|
init_writer();
|
|
12694
12898
|
import { z as z14 } from "zod";
|
|
12695
12899
|
import fs21 from "fs/promises";
|
|
12696
|
-
import
|
|
12900
|
+
import path22 from "path";
|
|
12697
12901
|
function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
12698
12902
|
server2.tool(
|
|
12699
12903
|
"vault_create_note",
|
|
@@ -12716,23 +12920,23 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
12716
12920
|
if (!validatePath(vaultPath2, notePath)) {
|
|
12717
12921
|
return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
|
|
12718
12922
|
}
|
|
12719
|
-
const fullPath =
|
|
12923
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
12720
12924
|
const existsCheck = await ensureFileExists(vaultPath2, notePath);
|
|
12721
12925
|
if (existsCheck === null && !overwrite) {
|
|
12722
12926
|
return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
|
|
12723
12927
|
}
|
|
12724
|
-
const dir =
|
|
12928
|
+
const dir = path22.dirname(fullPath);
|
|
12725
12929
|
await fs21.mkdir(dir, { recursive: true });
|
|
12726
12930
|
let effectiveContent = content;
|
|
12727
12931
|
let effectiveFrontmatter = frontmatter;
|
|
12728
12932
|
if (template) {
|
|
12729
|
-
const templatePath =
|
|
12933
|
+
const templatePath = path22.join(vaultPath2, template);
|
|
12730
12934
|
try {
|
|
12731
12935
|
const raw = await fs21.readFile(templatePath, "utf-8");
|
|
12732
12936
|
const matter9 = (await import("gray-matter")).default;
|
|
12733
12937
|
const parsed = matter9(raw);
|
|
12734
12938
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
12735
|
-
const title =
|
|
12939
|
+
const title = path22.basename(notePath, ".md");
|
|
12736
12940
|
let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
|
|
12737
12941
|
if (content) {
|
|
12738
12942
|
templateContent = templateContent.trimEnd() + "\n\n" + content;
|
|
@@ -12751,7 +12955,7 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
12751
12955
|
effectiveFrontmatter.created = now.toISOString();
|
|
12752
12956
|
}
|
|
12753
12957
|
const warnings = [];
|
|
12754
|
-
const noteName =
|
|
12958
|
+
const noteName = path22.basename(notePath, ".md");
|
|
12755
12959
|
const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
|
|
12756
12960
|
const preflight = await checkPreflightSimilarity(noteName);
|
|
12757
12961
|
if (preflight.existingEntity) {
|
|
@@ -12868,7 +13072,7 @@ ${sources}`;
|
|
|
12868
13072
|
}
|
|
12869
13073
|
return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
|
|
12870
13074
|
}
|
|
12871
|
-
const fullPath =
|
|
13075
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
12872
13076
|
await fs21.unlink(fullPath);
|
|
12873
13077
|
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
|
|
12874
13078
|
const message = backlinkWarning ? `Deleted note: ${notePath}
|
|
@@ -12888,7 +13092,7 @@ Warning: ${backlinkWarning}` : `Deleted note: ${notePath}`;
|
|
|
12888
13092
|
init_writer();
|
|
12889
13093
|
import { z as z15 } from "zod";
|
|
12890
13094
|
import fs22 from "fs/promises";
|
|
12891
|
-
import
|
|
13095
|
+
import path23 from "path";
|
|
12892
13096
|
import matter6 from "gray-matter";
|
|
12893
13097
|
function escapeRegex(str) {
|
|
12894
13098
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -12907,7 +13111,7 @@ function extractWikilinks2(content) {
|
|
|
12907
13111
|
return wikilinks;
|
|
12908
13112
|
}
|
|
12909
13113
|
function getTitleFromPath(filePath) {
|
|
12910
|
-
return
|
|
13114
|
+
return path23.basename(filePath, ".md");
|
|
12911
13115
|
}
|
|
12912
13116
|
async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
12913
13117
|
const results = [];
|
|
@@ -12916,7 +13120,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
12916
13120
|
const files = [];
|
|
12917
13121
|
const entries = await fs22.readdir(dir, { withFileTypes: true });
|
|
12918
13122
|
for (const entry of entries) {
|
|
12919
|
-
const fullPath =
|
|
13123
|
+
const fullPath = path23.join(dir, entry.name);
|
|
12920
13124
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
12921
13125
|
files.push(...await scanDir(fullPath));
|
|
12922
13126
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -12927,7 +13131,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
12927
13131
|
}
|
|
12928
13132
|
const allFiles = await scanDir(vaultPath2);
|
|
12929
13133
|
for (const filePath of allFiles) {
|
|
12930
|
-
const relativePath =
|
|
13134
|
+
const relativePath = path23.relative(vaultPath2, filePath);
|
|
12931
13135
|
const content = await fs22.readFile(filePath, "utf-8");
|
|
12932
13136
|
const wikilinks = extractWikilinks2(content);
|
|
12933
13137
|
const matchingLinks = [];
|
|
@@ -12947,7 +13151,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
12947
13151
|
return results;
|
|
12948
13152
|
}
|
|
12949
13153
|
async function updateBacklinksInFile(vaultPath2, filePath, oldTitles, newTitle) {
|
|
12950
|
-
const fullPath =
|
|
13154
|
+
const fullPath = path23.join(vaultPath2, filePath);
|
|
12951
13155
|
const raw = await fs22.readFile(fullPath, "utf-8");
|
|
12952
13156
|
const parsed = matter6(raw);
|
|
12953
13157
|
let content = parsed.content;
|
|
@@ -13014,8 +13218,8 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
13014
13218
|
};
|
|
13015
13219
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
13016
13220
|
}
|
|
13017
|
-
const oldFullPath =
|
|
13018
|
-
const newFullPath =
|
|
13221
|
+
const oldFullPath = path23.join(vaultPath2, oldPath);
|
|
13222
|
+
const newFullPath = path23.join(vaultPath2, newPath);
|
|
13019
13223
|
try {
|
|
13020
13224
|
await fs22.access(oldFullPath);
|
|
13021
13225
|
} catch {
|
|
@@ -13065,7 +13269,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
13065
13269
|
}
|
|
13066
13270
|
}
|
|
13067
13271
|
}
|
|
13068
|
-
const destDir =
|
|
13272
|
+
const destDir = path23.dirname(newFullPath);
|
|
13069
13273
|
await fs22.mkdir(destDir, { recursive: true });
|
|
13070
13274
|
await fs22.rename(oldFullPath, newFullPath);
|
|
13071
13275
|
let gitCommit;
|
|
@@ -13151,10 +13355,10 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
13151
13355
|
if (sanitizedTitle !== newTitle) {
|
|
13152
13356
|
console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
|
|
13153
13357
|
}
|
|
13154
|
-
const fullPath =
|
|
13155
|
-
const dir =
|
|
13156
|
-
const newPath = dir === "." ? `${sanitizedTitle}.md` :
|
|
13157
|
-
const newFullPath =
|
|
13358
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
13359
|
+
const dir = path23.dirname(notePath);
|
|
13360
|
+
const newPath = dir === "." ? `${sanitizedTitle}.md` : path23.join(dir, `${sanitizedTitle}.md`);
|
|
13361
|
+
const newFullPath = path23.join(vaultPath2, newPath);
|
|
13158
13362
|
try {
|
|
13159
13363
|
await fs22.access(fullPath);
|
|
13160
13364
|
} catch {
|
|
@@ -13511,7 +13715,7 @@ init_schema();
|
|
|
13511
13715
|
// src/core/write/policy/parser.ts
|
|
13512
13716
|
init_schema();
|
|
13513
13717
|
import fs24 from "fs/promises";
|
|
13514
|
-
import
|
|
13718
|
+
import path24 from "path";
|
|
13515
13719
|
import matter7 from "gray-matter";
|
|
13516
13720
|
function parseYaml(content) {
|
|
13517
13721
|
const parsed = matter7(`---
|
|
@@ -13560,13 +13764,13 @@ async function loadPolicyFile(filePath) {
|
|
|
13560
13764
|
}
|
|
13561
13765
|
}
|
|
13562
13766
|
async function loadPolicy(vaultPath2, policyName) {
|
|
13563
|
-
const policiesDir =
|
|
13564
|
-
const policyPath =
|
|
13767
|
+
const policiesDir = path24.join(vaultPath2, ".claude", "policies");
|
|
13768
|
+
const policyPath = path24.join(policiesDir, `${policyName}.yaml`);
|
|
13565
13769
|
try {
|
|
13566
13770
|
await fs24.access(policyPath);
|
|
13567
13771
|
return loadPolicyFile(policyPath);
|
|
13568
13772
|
} catch {
|
|
13569
|
-
const ymlPath =
|
|
13773
|
+
const ymlPath = path24.join(policiesDir, `${policyName}.yml`);
|
|
13570
13774
|
try {
|
|
13571
13775
|
await fs24.access(ymlPath);
|
|
13572
13776
|
return loadPolicyFile(ymlPath);
|
|
@@ -13707,7 +13911,7 @@ init_conditions();
|
|
|
13707
13911
|
init_schema();
|
|
13708
13912
|
init_writer();
|
|
13709
13913
|
import fs26 from "fs/promises";
|
|
13710
|
-
import
|
|
13914
|
+
import path26 from "path";
|
|
13711
13915
|
init_constants();
|
|
13712
13916
|
async function executeStep(step, vaultPath2, context, conditionResults) {
|
|
13713
13917
|
const { execute, reason } = shouldStepExecute(step.when, conditionResults);
|
|
@@ -13781,7 +13985,7 @@ async function executeAddToSection(params, vaultPath2, context) {
|
|
|
13781
13985
|
const preserveListNesting = params.preserveListNesting !== false;
|
|
13782
13986
|
const suggestOutgoingLinks = params.suggestOutgoingLinks !== false;
|
|
13783
13987
|
const maxSuggestions = Number(params.maxSuggestions) || 3;
|
|
13784
|
-
const fullPath =
|
|
13988
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
13785
13989
|
try {
|
|
13786
13990
|
await fs26.access(fullPath);
|
|
13787
13991
|
} catch {
|
|
@@ -13821,7 +14025,7 @@ async function executeRemoveFromSection(params, vaultPath2) {
|
|
|
13821
14025
|
const pattern = String(params.pattern || "");
|
|
13822
14026
|
const mode = params.mode || "first";
|
|
13823
14027
|
const useRegex = Boolean(params.useRegex);
|
|
13824
|
-
const fullPath =
|
|
14028
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
13825
14029
|
try {
|
|
13826
14030
|
await fs26.access(fullPath);
|
|
13827
14031
|
} catch {
|
|
@@ -13852,7 +14056,7 @@ async function executeReplaceInSection(params, vaultPath2, context) {
|
|
|
13852
14056
|
const mode = params.mode || "first";
|
|
13853
14057
|
const useRegex = Boolean(params.useRegex);
|
|
13854
14058
|
const skipWikilinks = Boolean(params.skipWikilinks);
|
|
13855
|
-
const fullPath =
|
|
14059
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
13856
14060
|
try {
|
|
13857
14061
|
await fs26.access(fullPath);
|
|
13858
14062
|
} catch {
|
|
@@ -13895,7 +14099,7 @@ async function executeCreateNote(params, vaultPath2, context) {
|
|
|
13895
14099
|
if (!validatePath(vaultPath2, notePath)) {
|
|
13896
14100
|
return { success: false, message: "Invalid path: path traversal not allowed", path: notePath };
|
|
13897
14101
|
}
|
|
13898
|
-
const fullPath =
|
|
14102
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
13899
14103
|
try {
|
|
13900
14104
|
await fs26.access(fullPath);
|
|
13901
14105
|
if (!overwrite) {
|
|
@@ -13903,7 +14107,7 @@ async function executeCreateNote(params, vaultPath2, context) {
|
|
|
13903
14107
|
}
|
|
13904
14108
|
} catch {
|
|
13905
14109
|
}
|
|
13906
|
-
const dir =
|
|
14110
|
+
const dir = path26.dirname(fullPath);
|
|
13907
14111
|
await fs26.mkdir(dir, { recursive: true });
|
|
13908
14112
|
const { content: processedContent } = maybeApplyWikilinks(content, skipWikilinks, notePath);
|
|
13909
14113
|
await writeVaultFile(vaultPath2, notePath, processedContent, frontmatter);
|
|
@@ -13923,7 +14127,7 @@ async function executeDeleteNote(params, vaultPath2) {
|
|
|
13923
14127
|
if (!validatePath(vaultPath2, notePath)) {
|
|
13924
14128
|
return { success: false, message: "Invalid path: path traversal not allowed", path: notePath };
|
|
13925
14129
|
}
|
|
13926
|
-
const fullPath =
|
|
14130
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
13927
14131
|
try {
|
|
13928
14132
|
await fs26.access(fullPath);
|
|
13929
14133
|
} catch {
|
|
@@ -13940,7 +14144,7 @@ async function executeToggleTask(params, vaultPath2) {
|
|
|
13940
14144
|
const notePath = String(params.path || "");
|
|
13941
14145
|
const task = String(params.task || "");
|
|
13942
14146
|
const section = params.section ? String(params.section) : void 0;
|
|
13943
|
-
const fullPath =
|
|
14147
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
13944
14148
|
try {
|
|
13945
14149
|
await fs26.access(fullPath);
|
|
13946
14150
|
} catch {
|
|
@@ -13983,7 +14187,7 @@ async function executeAddTask(params, vaultPath2, context) {
|
|
|
13983
14187
|
const completed = Boolean(params.completed);
|
|
13984
14188
|
const skipWikilinks = Boolean(params.skipWikilinks);
|
|
13985
14189
|
const preserveListNesting = params.preserveListNesting !== false;
|
|
13986
|
-
const fullPath =
|
|
14190
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
13987
14191
|
try {
|
|
13988
14192
|
await fs26.access(fullPath);
|
|
13989
14193
|
} catch {
|
|
@@ -14020,7 +14224,7 @@ async function executeAddTask(params, vaultPath2, context) {
|
|
|
14020
14224
|
async function executeUpdateFrontmatter(params, vaultPath2) {
|
|
14021
14225
|
const notePath = String(params.path || "");
|
|
14022
14226
|
const updates = params.frontmatter || {};
|
|
14023
|
-
const fullPath =
|
|
14227
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
14024
14228
|
try {
|
|
14025
14229
|
await fs26.access(fullPath);
|
|
14026
14230
|
} catch {
|
|
@@ -14042,7 +14246,7 @@ async function executeAddFrontmatterField(params, vaultPath2) {
|
|
|
14042
14246
|
const notePath = String(params.path || "");
|
|
14043
14247
|
const key = String(params.key || "");
|
|
14044
14248
|
const value = params.value;
|
|
14045
|
-
const fullPath =
|
|
14249
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
14046
14250
|
try {
|
|
14047
14251
|
await fs26.access(fullPath);
|
|
14048
14252
|
} catch {
|
|
@@ -14205,7 +14409,7 @@ async function executePolicy(policy, vaultPath2, variables, commit = false) {
|
|
|
14205
14409
|
async function rollbackChanges(vaultPath2, originalContents, filesModified) {
|
|
14206
14410
|
for (const filePath of filesModified) {
|
|
14207
14411
|
const original = originalContents.get(filePath);
|
|
14208
|
-
const fullPath =
|
|
14412
|
+
const fullPath = path26.join(vaultPath2, filePath);
|
|
14209
14413
|
if (original === null) {
|
|
14210
14414
|
try {
|
|
14211
14415
|
await fs26.unlink(fullPath);
|
|
@@ -14260,9 +14464,9 @@ async function previewPolicy(policy, vaultPath2, variables) {
|
|
|
14260
14464
|
|
|
14261
14465
|
// src/core/write/policy/storage.ts
|
|
14262
14466
|
import fs27 from "fs/promises";
|
|
14263
|
-
import
|
|
14467
|
+
import path27 from "path";
|
|
14264
14468
|
function getPoliciesDir(vaultPath2) {
|
|
14265
|
-
return
|
|
14469
|
+
return path27.join(vaultPath2, ".claude", "policies");
|
|
14266
14470
|
}
|
|
14267
14471
|
async function ensurePoliciesDir(vaultPath2) {
|
|
14268
14472
|
const dir = getPoliciesDir(vaultPath2);
|
|
@@ -14277,7 +14481,7 @@ async function listPolicies(vaultPath2) {
|
|
|
14277
14481
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
|
14278
14482
|
continue;
|
|
14279
14483
|
}
|
|
14280
|
-
const filePath =
|
|
14484
|
+
const filePath = path27.join(dir, file);
|
|
14281
14485
|
const stat3 = await fs27.stat(filePath);
|
|
14282
14486
|
const content = await fs27.readFile(filePath, "utf-8");
|
|
14283
14487
|
const metadata = extractPolicyMetadata(content);
|
|
@@ -14302,7 +14506,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
|
|
|
14302
14506
|
const dir = getPoliciesDir(vaultPath2);
|
|
14303
14507
|
await ensurePoliciesDir(vaultPath2);
|
|
14304
14508
|
const filename = `${policyName}.yaml`;
|
|
14305
|
-
const filePath =
|
|
14509
|
+
const filePath = path27.join(dir, filename);
|
|
14306
14510
|
if (!overwrite) {
|
|
14307
14511
|
try {
|
|
14308
14512
|
await fs27.access(filePath);
|
|
@@ -14846,7 +15050,7 @@ import { z as z20 } from "zod";
|
|
|
14846
15050
|
|
|
14847
15051
|
// src/core/write/tagRename.ts
|
|
14848
15052
|
import * as fs28 from "fs/promises";
|
|
14849
|
-
import * as
|
|
15053
|
+
import * as path28 from "path";
|
|
14850
15054
|
import matter8 from "gray-matter";
|
|
14851
15055
|
import { getProtectedZones } from "@velvetmonkey/vault-core";
|
|
14852
15056
|
function getNotesInFolder3(index, folder) {
|
|
@@ -14952,7 +15156,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
|
|
|
14952
15156
|
const previews = [];
|
|
14953
15157
|
let totalChanges = 0;
|
|
14954
15158
|
for (const note of affectedNotes) {
|
|
14955
|
-
const fullPath =
|
|
15159
|
+
const fullPath = path28.join(vaultPath2, note.path);
|
|
14956
15160
|
let fileContent;
|
|
14957
15161
|
try {
|
|
14958
15162
|
fileContent = await fs28.readFile(fullPath, "utf-8");
|
|
@@ -15590,8 +15794,8 @@ function getNoteAccessFrequency(stateDb2, daysBack = 30) {
|
|
|
15590
15794
|
}
|
|
15591
15795
|
}
|
|
15592
15796
|
}
|
|
15593
|
-
return Array.from(noteMap.entries()).map(([
|
|
15594
|
-
path:
|
|
15797
|
+
return Array.from(noteMap.entries()).map(([path31, stats]) => ({
|
|
15798
|
+
path: path31,
|
|
15595
15799
|
access_count: stats.access_count,
|
|
15596
15800
|
last_accessed: stats.last_accessed,
|
|
15597
15801
|
tools_used: Array.from(stats.tools)
|
|
@@ -15744,7 +15948,7 @@ import { z as z25 } from "zod";
|
|
|
15744
15948
|
|
|
15745
15949
|
// src/core/read/similarity.ts
|
|
15746
15950
|
import * as fs29 from "fs";
|
|
15747
|
-
import * as
|
|
15951
|
+
import * as path29 from "path";
|
|
15748
15952
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
15749
15953
|
"the",
|
|
15750
15954
|
"be",
|
|
@@ -15881,7 +16085,7 @@ function extractKeyTerms(content, maxTerms = 15) {
|
|
|
15881
16085
|
}
|
|
15882
16086
|
function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
|
|
15883
16087
|
const limit = options.limit ?? 10;
|
|
15884
|
-
const absPath =
|
|
16088
|
+
const absPath = path29.join(vaultPath2, sourcePath);
|
|
15885
16089
|
let content;
|
|
15886
16090
|
try {
|
|
15887
16091
|
content = fs29.readFileSync(absPath, "utf-8");
|
|
@@ -16009,7 +16213,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
16009
16213
|
exclude_linked: z25.boolean().optional().describe("Exclude notes already linked to/from the source note (default: true)")
|
|
16010
16214
|
}
|
|
16011
16215
|
},
|
|
16012
|
-
async ({ path:
|
|
16216
|
+
async ({ path: path31, limit, exclude_linked }) => {
|
|
16013
16217
|
const index = getIndex();
|
|
16014
16218
|
const vaultPath2 = getVaultPath();
|
|
16015
16219
|
const stateDb2 = getStateDb();
|
|
@@ -16018,10 +16222,10 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
16018
16222
|
content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
|
|
16019
16223
|
};
|
|
16020
16224
|
}
|
|
16021
|
-
if (!index.notes.has(
|
|
16225
|
+
if (!index.notes.has(path31)) {
|
|
16022
16226
|
return {
|
|
16023
16227
|
content: [{ type: "text", text: JSON.stringify({
|
|
16024
|
-
error: `Note not found: ${
|
|
16228
|
+
error: `Note not found: ${path31}`,
|
|
16025
16229
|
hint: "Use the full relative path including .md extension"
|
|
16026
16230
|
}, null, 2) }]
|
|
16027
16231
|
};
|
|
@@ -16032,12 +16236,12 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
16032
16236
|
};
|
|
16033
16237
|
const useHybrid = hasEmbeddingsIndex();
|
|
16034
16238
|
const method = useHybrid ? "hybrid" : "bm25";
|
|
16035
|
-
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index,
|
|
16239
|
+
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path31, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path31, opts);
|
|
16036
16240
|
return {
|
|
16037
16241
|
content: [{
|
|
16038
16242
|
type: "text",
|
|
16039
16243
|
text: JSON.stringify({
|
|
16040
|
-
source:
|
|
16244
|
+
source: path31,
|
|
16041
16245
|
method,
|
|
16042
16246
|
exclude_linked: exclude_linked ?? true,
|
|
16043
16247
|
count: results.length,
|
|
@@ -16275,6 +16479,7 @@ function registerMergeTools2(server2, getStateDb) {
|
|
|
16275
16479
|
|
|
16276
16480
|
// src/index.ts
|
|
16277
16481
|
import * as fs30 from "node:fs/promises";
|
|
16482
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
16278
16483
|
|
|
16279
16484
|
// src/resources/vault.ts
|
|
16280
16485
|
function registerVaultResources(server2, getIndex) {
|
|
@@ -16885,6 +17090,7 @@ async function runPostIndexWork(index) {
|
|
|
16885
17090
|
}
|
|
16886
17091
|
if (process.env.FLYWHEEL_WATCH !== "false") {
|
|
16887
17092
|
const config = parseWatcherConfig();
|
|
17093
|
+
const lastContentHashes = /* @__PURE__ */ new Map();
|
|
16888
17094
|
serverLog("watcher", `File watcher enabled (debounce: ${config.debounceMs}ms)`);
|
|
16889
17095
|
const watcher = createVaultWatcher({
|
|
16890
17096
|
vaultPath,
|
|
@@ -16916,8 +17122,8 @@ async function runPostIndexWork(index) {
|
|
|
16916
17122
|
}
|
|
16917
17123
|
} catch {
|
|
16918
17124
|
try {
|
|
16919
|
-
const dir =
|
|
16920
|
-
const base =
|
|
17125
|
+
const dir = path30.dirname(event.path);
|
|
17126
|
+
const base = path30.basename(event.path);
|
|
16921
17127
|
const resolvedDir = realpathSync(dir).replace(/\\/g, "/");
|
|
16922
17128
|
for (const prefix of vaultPrefixes) {
|
|
16923
17129
|
if (resolvedDir.startsWith(prefix + "/") || resolvedDir === prefix) {
|
|
@@ -16932,16 +17138,52 @@ async function runPostIndexWork(index) {
|
|
|
16932
17138
|
}
|
|
16933
17139
|
}
|
|
16934
17140
|
}
|
|
16935
|
-
|
|
17141
|
+
const filteredEvents = [];
|
|
17142
|
+
for (const event of batch.events) {
|
|
17143
|
+
if (event.type === "delete") {
|
|
17144
|
+
filteredEvents.push(event);
|
|
17145
|
+
lastContentHashes.delete(event.path);
|
|
17146
|
+
continue;
|
|
17147
|
+
}
|
|
17148
|
+
try {
|
|
17149
|
+
const content = await fs30.readFile(path30.join(vaultPath, event.path), "utf-8");
|
|
17150
|
+
const hash = createHash2("md5").update(content).digest("hex");
|
|
17151
|
+
if (lastContentHashes.get(event.path) === hash) {
|
|
17152
|
+
serverLog("watcher", `Hash unchanged, skipping: ${event.path}`);
|
|
17153
|
+
continue;
|
|
17154
|
+
}
|
|
17155
|
+
lastContentHashes.set(event.path, hash);
|
|
17156
|
+
filteredEvents.push(event);
|
|
17157
|
+
} catch {
|
|
17158
|
+
filteredEvents.push(event);
|
|
17159
|
+
}
|
|
17160
|
+
}
|
|
17161
|
+
if (filteredEvents.length === 0) {
|
|
17162
|
+
serverLog("watcher", "All files unchanged (hash gate), skipping batch");
|
|
17163
|
+
return;
|
|
17164
|
+
}
|
|
17165
|
+
serverLog("watcher", `Processing ${filteredEvents.length} file changes`);
|
|
16936
17166
|
const batchStart = Date.now();
|
|
16937
|
-
const changedPaths =
|
|
17167
|
+
const changedPaths = filteredEvents.map((e) => e.path);
|
|
16938
17168
|
const tracker = createStepTracker();
|
|
16939
17169
|
try {
|
|
16940
|
-
tracker.start("index_rebuild", { files_changed:
|
|
16941
|
-
|
|
17170
|
+
tracker.start("index_rebuild", { files_changed: filteredEvents.length, changed_paths: changedPaths });
|
|
17171
|
+
if (!vaultIndex) {
|
|
17172
|
+
vaultIndex = await buildVaultIndex(vaultPath);
|
|
17173
|
+
serverLog("watcher", `Index rebuilt (full): ${vaultIndex.notes.size} notes, ${vaultIndex.entities.size} entities`);
|
|
17174
|
+
} else {
|
|
17175
|
+
const absoluteBatch = {
|
|
17176
|
+
...batch,
|
|
17177
|
+
events: filteredEvents.map((e) => ({
|
|
17178
|
+
...e,
|
|
17179
|
+
path: path30.join(vaultPath, e.path)
|
|
17180
|
+
}))
|
|
17181
|
+
};
|
|
17182
|
+
const batchResult = await processBatch(vaultIndex, vaultPath, absoluteBatch);
|
|
17183
|
+
serverLog("watcher", `Incremental: ${batchResult.successful}/${batchResult.total} files in ${batchResult.durationMs}ms`);
|
|
17184
|
+
}
|
|
16942
17185
|
setIndexState("ready");
|
|
16943
17186
|
tracker.end({ note_count: vaultIndex.notes.size, entity_count: vaultIndex.entities.size, tag_count: vaultIndex.tags.size });
|
|
16944
|
-
serverLog("watcher", `Index rebuilt: ${vaultIndex.notes.size} notes, ${vaultIndex.entities.size} entities`);
|
|
16945
17187
|
const hubBefore = /* @__PURE__ */ new Map();
|
|
16946
17188
|
if (stateDb) {
|
|
16947
17189
|
const rows = stateDb.db.prepare("SELECT name, hub_score FROM entities").all();
|
|
@@ -16985,16 +17227,16 @@ async function runPostIndexWork(index) {
|
|
|
16985
17227
|
serverLog("watcher", `Recency: failed: ${e}`);
|
|
16986
17228
|
}
|
|
16987
17229
|
if (hasEmbeddingsIndex()) {
|
|
16988
|
-
tracker.start("note_embeddings", { files:
|
|
17230
|
+
tracker.start("note_embeddings", { files: filteredEvents.length });
|
|
16989
17231
|
let embUpdated = 0;
|
|
16990
17232
|
let embRemoved = 0;
|
|
16991
|
-
for (const event of
|
|
17233
|
+
for (const event of filteredEvents) {
|
|
16992
17234
|
try {
|
|
16993
17235
|
if (event.type === "delete") {
|
|
16994
17236
|
removeEmbedding(event.path);
|
|
16995
17237
|
embRemoved++;
|
|
16996
17238
|
} else if (event.path.endsWith(".md")) {
|
|
16997
|
-
const absPath =
|
|
17239
|
+
const absPath = path30.join(vaultPath, event.path);
|
|
16998
17240
|
await updateEmbedding(event.path, absPath);
|
|
16999
17241
|
embUpdated++;
|
|
17000
17242
|
}
|
|
@@ -17007,12 +17249,12 @@ async function runPostIndexWork(index) {
|
|
|
17007
17249
|
tracker.skip("note_embeddings", "not built");
|
|
17008
17250
|
}
|
|
17009
17251
|
if (hasEntityEmbeddingsIndex() && stateDb) {
|
|
17010
|
-
tracker.start("entity_embeddings", { files:
|
|
17252
|
+
tracker.start("entity_embeddings", { files: filteredEvents.length });
|
|
17011
17253
|
let entEmbUpdated = 0;
|
|
17012
17254
|
const entEmbNames = [];
|
|
17013
17255
|
try {
|
|
17014
17256
|
const allEntities = getAllEntitiesFromDb3(stateDb);
|
|
17015
|
-
for (const event of
|
|
17257
|
+
for (const event of filteredEvents) {
|
|
17016
17258
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
17017
17259
|
const matching = allEntities.filter((e) => e.path === event.path);
|
|
17018
17260
|
for (const entity of matching) {
|
|
@@ -17046,10 +17288,10 @@ async function runPostIndexWork(index) {
|
|
|
17046
17288
|
} else {
|
|
17047
17289
|
tracker.skip("index_cache", "no stateDb");
|
|
17048
17290
|
}
|
|
17049
|
-
tracker.start("task_cache", { files:
|
|
17291
|
+
tracker.start("task_cache", { files: filteredEvents.length });
|
|
17050
17292
|
let taskUpdated = 0;
|
|
17051
17293
|
let taskRemoved = 0;
|
|
17052
|
-
for (const event of
|
|
17294
|
+
for (const event of filteredEvents) {
|
|
17053
17295
|
try {
|
|
17054
17296
|
if (event.type === "delete") {
|
|
17055
17297
|
removeTaskCacheForFile(event.path);
|
|
@@ -17063,11 +17305,11 @@ async function runPostIndexWork(index) {
|
|
|
17063
17305
|
}
|
|
17064
17306
|
tracker.end({ updated: taskUpdated, removed: taskRemoved });
|
|
17065
17307
|
serverLog("watcher", `Task cache: ${taskUpdated} updated, ${taskRemoved} removed`);
|
|
17066
|
-
tracker.start("forward_links", { files:
|
|
17308
|
+
tracker.start("forward_links", { files: filteredEvents.length });
|
|
17067
17309
|
const forwardLinkResults = [];
|
|
17068
17310
|
let totalResolved = 0;
|
|
17069
17311
|
let totalDead = 0;
|
|
17070
|
-
for (const event of
|
|
17312
|
+
for (const event of filteredEvents) {
|
|
17071
17313
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
17072
17314
|
try {
|
|
17073
17315
|
const links = getForwardLinksForNote(vaultIndex, event.path);
|
|
@@ -17095,10 +17337,10 @@ async function runPostIndexWork(index) {
|
|
|
17095
17337
|
links: forwardLinkResults
|
|
17096
17338
|
});
|
|
17097
17339
|
serverLog("watcher", `Forward links: ${totalResolved} resolved, ${totalDead} dead`);
|
|
17098
|
-
tracker.start("wikilink_check", { files:
|
|
17340
|
+
tracker.start("wikilink_check", { files: filteredEvents.length });
|
|
17099
17341
|
const trackedLinks = [];
|
|
17100
17342
|
if (stateDb) {
|
|
17101
|
-
for (const event of
|
|
17343
|
+
for (const event of filteredEvents) {
|
|
17102
17344
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
17103
17345
|
try {
|
|
17104
17346
|
const apps = getTrackedApplications(stateDb, event.path);
|
|
@@ -17109,13 +17351,13 @@ async function runPostIndexWork(index) {
|
|
|
17109
17351
|
}
|
|
17110
17352
|
tracker.end({ tracked: trackedLinks });
|
|
17111
17353
|
serverLog("watcher", `Wikilink check: ${trackedLinks.reduce((s, t) => s + t.entities.length, 0)} tracked links in ${trackedLinks.length} files`);
|
|
17112
|
-
tracker.start("implicit_feedback", { files:
|
|
17354
|
+
tracker.start("implicit_feedback", { files: filteredEvents.length });
|
|
17113
17355
|
const feedbackResults = [];
|
|
17114
17356
|
if (stateDb) {
|
|
17115
|
-
for (const event of
|
|
17357
|
+
for (const event of filteredEvents) {
|
|
17116
17358
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
17117
17359
|
try {
|
|
17118
|
-
const content = await fs30.readFile(
|
|
17360
|
+
const content = await fs30.readFile(path30.join(vaultPath, event.path), "utf-8");
|
|
17119
17361
|
const removed = processImplicitFeedback(stateDb, event.path, content);
|
|
17120
17362
|
for (const entity of removed) feedbackResults.push({ entity, file: event.path });
|
|
17121
17363
|
} catch {
|
|
@@ -17132,12 +17374,12 @@ async function runPostIndexWork(index) {
|
|
|
17132
17374
|
trigger: "watcher",
|
|
17133
17375
|
duration_ms: duration,
|
|
17134
17376
|
note_count: vaultIndex.notes.size,
|
|
17135
|
-
files_changed:
|
|
17377
|
+
files_changed: filteredEvents.length,
|
|
17136
17378
|
changed_paths: changedPaths,
|
|
17137
17379
|
steps: tracker.steps
|
|
17138
17380
|
});
|
|
17139
17381
|
}
|
|
17140
|
-
serverLog("watcher", `Batch complete: ${
|
|
17382
|
+
serverLog("watcher", `Batch complete: ${filteredEvents.length} files, ${duration}ms, ${tracker.steps.length} steps`);
|
|
17141
17383
|
} catch (err) {
|
|
17142
17384
|
setIndexState("error");
|
|
17143
17385
|
setIndexError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -17147,7 +17389,7 @@ async function runPostIndexWork(index) {
|
|
|
17147
17389
|
trigger: "watcher",
|
|
17148
17390
|
duration_ms: duration,
|
|
17149
17391
|
success: false,
|
|
17150
|
-
files_changed:
|
|
17392
|
+
files_changed: filteredEvents.length,
|
|
17151
17393
|
changed_paths: changedPaths,
|
|
17152
17394
|
error: err instanceof Error ? err.message : String(err),
|
|
17153
17395
|
steps: tracker.steps
|