@velvetmonkey/flywheel-memory 2.0.139 → 2.0.140
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 +592 -511
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4595,6 +4595,8 @@ __export(proactiveQueue_exports, {
|
|
|
4595
4595
|
enqueueProactiveSuggestions: () => enqueueProactiveSuggestions,
|
|
4596
4596
|
expireStaleEntries: () => expireStaleEntries
|
|
4597
4597
|
});
|
|
4598
|
+
import * as path12 from "path";
|
|
4599
|
+
import { statSync as statSync3 } from "fs";
|
|
4598
4600
|
function enqueueProactiveSuggestions(stateDb2, entries) {
|
|
4599
4601
|
if (entries.length === 0) return 0;
|
|
4600
4602
|
const now = Date.now();
|
|
@@ -4620,7 +4622,7 @@ function enqueueProactiveSuggestions(stateDb2, entries) {
|
|
|
4620
4622
|
}
|
|
4621
4623
|
return enqueued;
|
|
4622
4624
|
}
|
|
4623
|
-
async function drainProactiveQueue(stateDb2, vaultPath2,
|
|
4625
|
+
async function drainProactiveQueue(stateDb2, vaultPath2, config, applyFn) {
|
|
4624
4626
|
const result = {
|
|
4625
4627
|
applied: [],
|
|
4626
4628
|
expired: 0,
|
|
@@ -4652,7 +4654,14 @@ async function drainProactiveQueue(stateDb2, vaultPath2, currentBatchPaths, conf
|
|
|
4652
4654
|
`SELECT COUNT(*) as cnt FROM wikilink_applications WHERE note_path = ? AND applied_at >= ?`
|
|
4653
4655
|
);
|
|
4654
4656
|
for (const [filePath, suggestions] of byFile) {
|
|
4655
|
-
|
|
4657
|
+
const fullPath = path12.join(vaultPath2, filePath);
|
|
4658
|
+
try {
|
|
4659
|
+
const mtime = statSync3(fullPath).mtimeMs;
|
|
4660
|
+
if (Date.now() - mtime < MTIME_GUARD_MS) {
|
|
4661
|
+
result.skippedActiveEdit += suggestions.length;
|
|
4662
|
+
continue;
|
|
4663
|
+
}
|
|
4664
|
+
} catch {
|
|
4656
4665
|
result.skippedActiveEdit += suggestions.length;
|
|
4657
4666
|
continue;
|
|
4658
4667
|
}
|
|
@@ -4698,12 +4707,13 @@ function expireStaleEntries(stateDb2) {
|
|
|
4698
4707
|
).run(Date.now());
|
|
4699
4708
|
return result.changes;
|
|
4700
4709
|
}
|
|
4701
|
-
var QUEUE_TTL_MS;
|
|
4710
|
+
var QUEUE_TTL_MS, MTIME_GUARD_MS;
|
|
4702
4711
|
var init_proactiveQueue = __esm({
|
|
4703
4712
|
"src/core/write/proactiveQueue.ts"() {
|
|
4704
4713
|
"use strict";
|
|
4705
4714
|
init_serverLog();
|
|
4706
|
-
QUEUE_TTL_MS = 60 * 60 * 1e3;
|
|
4715
|
+
QUEUE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
4716
|
+
MTIME_GUARD_MS = 6e4;
|
|
4707
4717
|
}
|
|
4708
4718
|
});
|
|
4709
4719
|
|
|
@@ -4722,7 +4732,7 @@ var init_constants = __esm({
|
|
|
4722
4732
|
|
|
4723
4733
|
// src/core/write/writer.ts
|
|
4724
4734
|
import fs21 from "fs/promises";
|
|
4725
|
-
import
|
|
4735
|
+
import path23 from "path";
|
|
4726
4736
|
import matter5 from "gray-matter";
|
|
4727
4737
|
import { createHash as createHash2 } from "node:crypto";
|
|
4728
4738
|
function isSensitivePath(filePath) {
|
|
@@ -5050,13 +5060,13 @@ function validatePath(vaultPath2, notePath) {
|
|
|
5050
5060
|
if (notePath.startsWith("\\")) {
|
|
5051
5061
|
return false;
|
|
5052
5062
|
}
|
|
5053
|
-
const resolvedVault =
|
|
5054
|
-
const resolvedNote =
|
|
5063
|
+
const resolvedVault = path23.resolve(vaultPath2);
|
|
5064
|
+
const resolvedNote = path23.resolve(vaultPath2, notePath);
|
|
5055
5065
|
return resolvedNote.startsWith(resolvedVault);
|
|
5056
5066
|
}
|
|
5057
5067
|
function sanitizeNotePath(notePath) {
|
|
5058
|
-
const dir =
|
|
5059
|
-
let filename =
|
|
5068
|
+
const dir = path23.dirname(notePath);
|
|
5069
|
+
let filename = path23.basename(notePath);
|
|
5060
5070
|
const ext = filename.endsWith(".md") ? ".md" : "";
|
|
5061
5071
|
let stem2 = ext ? filename.slice(0, -ext.length) : filename;
|
|
5062
5072
|
stem2 = stem2.replace(/\s+/g, "-");
|
|
@@ -5065,7 +5075,7 @@ function sanitizeNotePath(notePath) {
|
|
|
5065
5075
|
stem2 = stem2.replace(/-{2,}/g, "-");
|
|
5066
5076
|
stem2 = stem2.replace(/^-+|-+$/g, "");
|
|
5067
5077
|
filename = stem2 + (ext || ".md");
|
|
5068
|
-
return dir === "." ? filename :
|
|
5078
|
+
return dir === "." ? filename : path23.join(dir, filename).replace(/\\/g, "/");
|
|
5069
5079
|
}
|
|
5070
5080
|
async function validatePathSecure(vaultPath2, notePath) {
|
|
5071
5081
|
if (notePath.startsWith("/")) {
|
|
@@ -5092,8 +5102,8 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5092
5102
|
reason: "Path traversal not allowed"
|
|
5093
5103
|
};
|
|
5094
5104
|
}
|
|
5095
|
-
const resolvedVault =
|
|
5096
|
-
const resolvedNote =
|
|
5105
|
+
const resolvedVault = path23.resolve(vaultPath2);
|
|
5106
|
+
const resolvedNote = path23.resolve(vaultPath2, notePath);
|
|
5097
5107
|
if (!resolvedNote.startsWith(resolvedVault)) {
|
|
5098
5108
|
return {
|
|
5099
5109
|
valid: false,
|
|
@@ -5107,7 +5117,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5107
5117
|
};
|
|
5108
5118
|
}
|
|
5109
5119
|
try {
|
|
5110
|
-
const fullPath =
|
|
5120
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
5111
5121
|
try {
|
|
5112
5122
|
await fs21.access(fullPath);
|
|
5113
5123
|
const realPath = await fs21.realpath(fullPath);
|
|
@@ -5118,7 +5128,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5118
5128
|
reason: "Symlink target is outside vault"
|
|
5119
5129
|
};
|
|
5120
5130
|
}
|
|
5121
|
-
const relativePath =
|
|
5131
|
+
const relativePath = path23.relative(realVaultPath, realPath);
|
|
5122
5132
|
if (isSensitivePath(relativePath)) {
|
|
5123
5133
|
return {
|
|
5124
5134
|
valid: false,
|
|
@@ -5126,7 +5136,7 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
5126
5136
|
};
|
|
5127
5137
|
}
|
|
5128
5138
|
} catch {
|
|
5129
|
-
const parentDir =
|
|
5139
|
+
const parentDir = path23.dirname(fullPath);
|
|
5130
5140
|
try {
|
|
5131
5141
|
await fs21.access(parentDir);
|
|
5132
5142
|
const realParentPath = await fs21.realpath(parentDir);
|
|
@@ -5155,7 +5165,7 @@ async function readVaultFile(vaultPath2, notePath) {
|
|
|
5155
5165
|
if (!validatePath(vaultPath2, notePath)) {
|
|
5156
5166
|
throw new Error("Invalid path: path traversal not allowed");
|
|
5157
5167
|
}
|
|
5158
|
-
const fullPath =
|
|
5168
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
5159
5169
|
const [rawContent, stat5] = await Promise.all([
|
|
5160
5170
|
fs21.readFile(fullPath, "utf-8"),
|
|
5161
5171
|
fs21.stat(fullPath)
|
|
@@ -5210,7 +5220,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
|
|
|
5210
5220
|
if (!validation.valid) {
|
|
5211
5221
|
throw new Error(`Invalid path: ${validation.reason}`);
|
|
5212
5222
|
}
|
|
5213
|
-
const fullPath =
|
|
5223
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
5214
5224
|
if (expectedHash) {
|
|
5215
5225
|
const currentRaw = await fs21.readFile(fullPath, "utf-8");
|
|
5216
5226
|
const currentHash = computeContentHash(currentRaw);
|
|
@@ -5610,8 +5620,8 @@ function createContext(variables = {}) {
|
|
|
5610
5620
|
steps: {}
|
|
5611
5621
|
};
|
|
5612
5622
|
}
|
|
5613
|
-
function resolvePath(obj,
|
|
5614
|
-
const parts =
|
|
5623
|
+
function resolvePath(obj, path37) {
|
|
5624
|
+
const parts = path37.split(".");
|
|
5615
5625
|
let current = obj;
|
|
5616
5626
|
for (const part of parts) {
|
|
5617
5627
|
if (current === void 0 || current === null) {
|
|
@@ -6069,7 +6079,7 @@ __export(conditions_exports, {
|
|
|
6069
6079
|
shouldStepExecute: () => shouldStepExecute
|
|
6070
6080
|
});
|
|
6071
6081
|
import fs28 from "fs/promises";
|
|
6072
|
-
import
|
|
6082
|
+
import path29 from "path";
|
|
6073
6083
|
async function evaluateCondition(condition, vaultPath2, context) {
|
|
6074
6084
|
const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
|
|
6075
6085
|
const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
|
|
@@ -6122,7 +6132,7 @@ async function evaluateCondition(condition, vaultPath2, context) {
|
|
|
6122
6132
|
}
|
|
6123
6133
|
}
|
|
6124
6134
|
async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
6125
|
-
const fullPath =
|
|
6135
|
+
const fullPath = path29.join(vaultPath2, notePath);
|
|
6126
6136
|
try {
|
|
6127
6137
|
await fs28.access(fullPath);
|
|
6128
6138
|
return {
|
|
@@ -6137,7 +6147,7 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
|
6137
6147
|
}
|
|
6138
6148
|
}
|
|
6139
6149
|
async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
|
|
6140
|
-
const fullPath =
|
|
6150
|
+
const fullPath = path29.join(vaultPath2, notePath);
|
|
6141
6151
|
try {
|
|
6142
6152
|
await fs28.access(fullPath);
|
|
6143
6153
|
} catch {
|
|
@@ -6168,7 +6178,7 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
|
|
|
6168
6178
|
}
|
|
6169
6179
|
}
|
|
6170
6180
|
async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
|
|
6171
|
-
const fullPath =
|
|
6181
|
+
const fullPath = path29.join(vaultPath2, notePath);
|
|
6172
6182
|
try {
|
|
6173
6183
|
await fs28.access(fullPath);
|
|
6174
6184
|
} catch {
|
|
@@ -6199,7 +6209,7 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
|
|
|
6199
6209
|
}
|
|
6200
6210
|
}
|
|
6201
6211
|
async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
|
|
6202
|
-
const fullPath =
|
|
6212
|
+
const fullPath = path29.join(vaultPath2, notePath);
|
|
6203
6213
|
try {
|
|
6204
6214
|
await fs28.access(fullPath);
|
|
6205
6215
|
} catch {
|
|
@@ -6343,10 +6353,10 @@ var init_taskHelpers = __esm({
|
|
|
6343
6353
|
});
|
|
6344
6354
|
|
|
6345
6355
|
// src/index.ts
|
|
6346
|
-
import * as
|
|
6356
|
+
import * as path36 from "path";
|
|
6347
6357
|
import { readFileSync as readFileSync6, realpathSync, existsSync as existsSync3 } from "fs";
|
|
6348
6358
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6349
|
-
import { dirname as dirname7, join as
|
|
6359
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
6350
6360
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6351
6361
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6352
6362
|
|
|
@@ -6581,8 +6591,8 @@ function updateIndexProgress(parsed, total) {
|
|
|
6581
6591
|
function normalizeTarget(target) {
|
|
6582
6592
|
return target.toLowerCase().replace(/\.md$/, "");
|
|
6583
6593
|
}
|
|
6584
|
-
function normalizeNotePath(
|
|
6585
|
-
return
|
|
6594
|
+
function normalizeNotePath(path37) {
|
|
6595
|
+
return path37.toLowerCase().replace(/\.md$/, "");
|
|
6586
6596
|
}
|
|
6587
6597
|
async function buildVaultIndex(vaultPath2, options = {}) {
|
|
6588
6598
|
const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
|
|
@@ -6751,7 +6761,7 @@ function findSimilarEntity(index, target) {
|
|
|
6751
6761
|
}
|
|
6752
6762
|
const maxDist = normalizedLen <= 10 ? 1 : 2;
|
|
6753
6763
|
let bestMatch;
|
|
6754
|
-
for (const [entity,
|
|
6764
|
+
for (const [entity, path37] of index.entities) {
|
|
6755
6765
|
const lenDiff = Math.abs(entity.length - normalizedLen);
|
|
6756
6766
|
if (lenDiff > maxDist) {
|
|
6757
6767
|
continue;
|
|
@@ -6759,7 +6769,7 @@ function findSimilarEntity(index, target) {
|
|
|
6759
6769
|
const dist = levenshteinDistance(normalized, entity);
|
|
6760
6770
|
if (dist > 0 && dist <= maxDist) {
|
|
6761
6771
|
if (!bestMatch || dist < bestMatch.distance) {
|
|
6762
|
-
bestMatch = { path:
|
|
6772
|
+
bestMatch = { path: path37, entity, distance: dist };
|
|
6763
6773
|
if (dist === 1) {
|
|
6764
6774
|
return bestMatch;
|
|
6765
6775
|
}
|
|
@@ -7290,30 +7300,30 @@ var EventQueue = class {
|
|
|
7290
7300
|
* Add a new event to the queue
|
|
7291
7301
|
*/
|
|
7292
7302
|
push(type, rawPath) {
|
|
7293
|
-
const
|
|
7303
|
+
const path37 = normalizePath(rawPath);
|
|
7294
7304
|
const now = Date.now();
|
|
7295
7305
|
const event = {
|
|
7296
7306
|
type,
|
|
7297
|
-
path:
|
|
7307
|
+
path: path37,
|
|
7298
7308
|
timestamp: now
|
|
7299
7309
|
};
|
|
7300
|
-
let pending = this.pending.get(
|
|
7310
|
+
let pending = this.pending.get(path37);
|
|
7301
7311
|
if (!pending) {
|
|
7302
7312
|
pending = {
|
|
7303
7313
|
events: [],
|
|
7304
7314
|
timer: null,
|
|
7305
7315
|
lastEvent: now
|
|
7306
7316
|
};
|
|
7307
|
-
this.pending.set(
|
|
7317
|
+
this.pending.set(path37, pending);
|
|
7308
7318
|
}
|
|
7309
7319
|
pending.events.push(event);
|
|
7310
7320
|
pending.lastEvent = now;
|
|
7311
|
-
console.error(`[flywheel] QUEUE: pushed ${type} for ${
|
|
7321
|
+
console.error(`[flywheel] QUEUE: pushed ${type} for ${path37}, pending=${this.pending.size}`);
|
|
7312
7322
|
if (pending.timer) {
|
|
7313
7323
|
clearTimeout(pending.timer);
|
|
7314
7324
|
}
|
|
7315
7325
|
pending.timer = setTimeout(() => {
|
|
7316
|
-
this.flushPath(
|
|
7326
|
+
this.flushPath(path37);
|
|
7317
7327
|
}, this.config.debounceMs);
|
|
7318
7328
|
if (this.pending.size >= this.config.batchSize) {
|
|
7319
7329
|
this.flush();
|
|
@@ -7334,10 +7344,10 @@ var EventQueue = class {
|
|
|
7334
7344
|
/**
|
|
7335
7345
|
* Flush a single path's events
|
|
7336
7346
|
*/
|
|
7337
|
-
flushPath(
|
|
7338
|
-
const pending = this.pending.get(
|
|
7347
|
+
flushPath(path37) {
|
|
7348
|
+
const pending = this.pending.get(path37);
|
|
7339
7349
|
if (!pending || pending.events.length === 0) return;
|
|
7340
|
-
console.error(`[flywheel] QUEUE: flushing ${
|
|
7350
|
+
console.error(`[flywheel] QUEUE: flushing ${path37}, events=${pending.events.length}`);
|
|
7341
7351
|
if (pending.timer) {
|
|
7342
7352
|
clearTimeout(pending.timer);
|
|
7343
7353
|
pending.timer = null;
|
|
@@ -7346,7 +7356,7 @@ var EventQueue = class {
|
|
|
7346
7356
|
if (coalescedType) {
|
|
7347
7357
|
const coalesced = {
|
|
7348
7358
|
type: coalescedType,
|
|
7349
|
-
path:
|
|
7359
|
+
path: path37,
|
|
7350
7360
|
originalEvents: [...pending.events]
|
|
7351
7361
|
};
|
|
7352
7362
|
this.onBatch({
|
|
@@ -7355,7 +7365,7 @@ var EventQueue = class {
|
|
|
7355
7365
|
timestamp: Date.now()
|
|
7356
7366
|
});
|
|
7357
7367
|
}
|
|
7358
|
-
this.pending.delete(
|
|
7368
|
+
this.pending.delete(path37);
|
|
7359
7369
|
}
|
|
7360
7370
|
/**
|
|
7361
7371
|
* Flush all pending events
|
|
@@ -7367,7 +7377,7 @@ var EventQueue = class {
|
|
|
7367
7377
|
}
|
|
7368
7378
|
if (this.pending.size === 0) return;
|
|
7369
7379
|
const events = [];
|
|
7370
|
-
for (const [
|
|
7380
|
+
for (const [path37, pending] of this.pending) {
|
|
7371
7381
|
if (pending.timer) {
|
|
7372
7382
|
clearTimeout(pending.timer);
|
|
7373
7383
|
}
|
|
@@ -7375,7 +7385,7 @@ var EventQueue = class {
|
|
|
7375
7385
|
if (coalescedType) {
|
|
7376
7386
|
events.push({
|
|
7377
7387
|
type: coalescedType,
|
|
7378
|
-
path:
|
|
7388
|
+
path: path37,
|
|
7379
7389
|
originalEvents: [...pending.events]
|
|
7380
7390
|
});
|
|
7381
7391
|
}
|
|
@@ -7761,31 +7771,31 @@ function createVaultWatcher(options) {
|
|
|
7761
7771
|
usePolling: config.usePolling,
|
|
7762
7772
|
interval: config.usePolling ? config.pollInterval : void 0
|
|
7763
7773
|
});
|
|
7764
|
-
watcher.on("add", (
|
|
7765
|
-
console.error(`[flywheel] RAW EVENT: add ${
|
|
7766
|
-
if (shouldWatch(
|
|
7767
|
-
console.error(`[flywheel] ACCEPTED: add ${
|
|
7768
|
-
eventQueue.push("add",
|
|
7774
|
+
watcher.on("add", (path37) => {
|
|
7775
|
+
console.error(`[flywheel] RAW EVENT: add ${path37}`);
|
|
7776
|
+
if (shouldWatch(path37, vaultPath2)) {
|
|
7777
|
+
console.error(`[flywheel] ACCEPTED: add ${path37}`);
|
|
7778
|
+
eventQueue.push("add", path37);
|
|
7769
7779
|
} else {
|
|
7770
|
-
console.error(`[flywheel] FILTERED: add ${
|
|
7780
|
+
console.error(`[flywheel] FILTERED: add ${path37}`);
|
|
7771
7781
|
}
|
|
7772
7782
|
});
|
|
7773
|
-
watcher.on("change", (
|
|
7774
|
-
console.error(`[flywheel] RAW EVENT: change ${
|
|
7775
|
-
if (shouldWatch(
|
|
7776
|
-
console.error(`[flywheel] ACCEPTED: change ${
|
|
7777
|
-
eventQueue.push("change",
|
|
7783
|
+
watcher.on("change", (path37) => {
|
|
7784
|
+
console.error(`[flywheel] RAW EVENT: change ${path37}`);
|
|
7785
|
+
if (shouldWatch(path37, vaultPath2)) {
|
|
7786
|
+
console.error(`[flywheel] ACCEPTED: change ${path37}`);
|
|
7787
|
+
eventQueue.push("change", path37);
|
|
7778
7788
|
} else {
|
|
7779
|
-
console.error(`[flywheel] FILTERED: change ${
|
|
7789
|
+
console.error(`[flywheel] FILTERED: change ${path37}`);
|
|
7780
7790
|
}
|
|
7781
7791
|
});
|
|
7782
|
-
watcher.on("unlink", (
|
|
7783
|
-
console.error(`[flywheel] RAW EVENT: unlink ${
|
|
7784
|
-
if (shouldWatch(
|
|
7785
|
-
console.error(`[flywheel] ACCEPTED: unlink ${
|
|
7786
|
-
eventQueue.push("unlink",
|
|
7792
|
+
watcher.on("unlink", (path37) => {
|
|
7793
|
+
console.error(`[flywheel] RAW EVENT: unlink ${path37}`);
|
|
7794
|
+
if (shouldWatch(path37, vaultPath2)) {
|
|
7795
|
+
console.error(`[flywheel] ACCEPTED: unlink ${path37}`);
|
|
7796
|
+
eventQueue.push("unlink", path37);
|
|
7787
7797
|
} else {
|
|
7788
|
-
console.error(`[flywheel] FILTERED: unlink ${
|
|
7798
|
+
console.error(`[flywheel] FILTERED: unlink ${path37}`);
|
|
7789
7799
|
}
|
|
7790
7800
|
});
|
|
7791
7801
|
watcher.on("ready", () => {
|
|
@@ -7817,8 +7827,8 @@ function createVaultWatcher(options) {
|
|
|
7817
7827
|
}
|
|
7818
7828
|
|
|
7819
7829
|
// src/core/read/watch/pipeline.ts
|
|
7820
|
-
import * as
|
|
7821
|
-
import * as
|
|
7830
|
+
import * as path15 from "node:path";
|
|
7831
|
+
import * as fs10 from "node:fs/promises";
|
|
7822
7832
|
import {
|
|
7823
7833
|
getAllEntitiesFromDb,
|
|
7824
7834
|
findEntityMatches,
|
|
@@ -8071,14 +8081,283 @@ init_cooccurrence();
|
|
|
8071
8081
|
init_wikilinks();
|
|
8072
8082
|
init_proactiveQueue();
|
|
8073
8083
|
init_retrievalCooccurrence();
|
|
8084
|
+
|
|
8085
|
+
// src/core/read/fts5.ts
|
|
8086
|
+
init_vault();
|
|
8087
|
+
init_serverLog();
|
|
8088
|
+
init_vault_scope();
|
|
8089
|
+
import * as fs8 from "fs";
|
|
8090
|
+
var EXCLUDED_DIRS3 = /* @__PURE__ */ new Set([
|
|
8091
|
+
".obsidian",
|
|
8092
|
+
".trash",
|
|
8093
|
+
".git",
|
|
8094
|
+
"node_modules",
|
|
8095
|
+
"templates",
|
|
8096
|
+
".claude",
|
|
8097
|
+
".flywheel"
|
|
8098
|
+
]);
|
|
8099
|
+
var MAX_INDEX_FILE_SIZE = 5 * 1024 * 1024;
|
|
8100
|
+
var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
8101
|
+
function splitFrontmatter(raw) {
|
|
8102
|
+
if (!raw.startsWith("---")) return { frontmatter: "", body: raw };
|
|
8103
|
+
const end = raw.indexOf("\n---", 3);
|
|
8104
|
+
if (end === -1) return { frontmatter: "", body: raw };
|
|
8105
|
+
const yaml = raw.substring(4, end);
|
|
8106
|
+
const values = yaml.split("\n").map((line) => line.replace(/^[\s-]*/, "").replace(/^[\w]+:\s*/, "")).filter((v) => v && !v.startsWith("[") && !v.startsWith("{")).join(" ");
|
|
8107
|
+
return { frontmatter: values, body: raw.substring(end + 4) };
|
|
8108
|
+
}
|
|
8109
|
+
var db2 = null;
|
|
8110
|
+
function getDb2() {
|
|
8111
|
+
return getActiveScopeOrNull()?.stateDb?.db ?? db2;
|
|
8112
|
+
}
|
|
8113
|
+
var state = {
|
|
8114
|
+
ready: false,
|
|
8115
|
+
building: false,
|
|
8116
|
+
lastBuilt: null,
|
|
8117
|
+
noteCount: 0,
|
|
8118
|
+
error: null
|
|
8119
|
+
};
|
|
8120
|
+
function setFTS5Database(database) {
|
|
8121
|
+
db2 = database;
|
|
8122
|
+
try {
|
|
8123
|
+
const row = db2.prepare(
|
|
8124
|
+
"SELECT value FROM fts_metadata WHERE key = ?"
|
|
8125
|
+
).get("last_built");
|
|
8126
|
+
if (row) {
|
|
8127
|
+
const lastBuilt = new Date(row.value);
|
|
8128
|
+
const countRow = db2.prepare("SELECT COUNT(*) as count FROM notes_fts").get();
|
|
8129
|
+
state = {
|
|
8130
|
+
ready: countRow.count > 0,
|
|
8131
|
+
building: false,
|
|
8132
|
+
lastBuilt,
|
|
8133
|
+
noteCount: countRow.count,
|
|
8134
|
+
error: null
|
|
8135
|
+
};
|
|
8136
|
+
}
|
|
8137
|
+
} catch {
|
|
8138
|
+
}
|
|
8139
|
+
}
|
|
8140
|
+
function shouldIndexFile2(filePath) {
|
|
8141
|
+
const parts = filePath.split("/");
|
|
8142
|
+
return !parts.some((part) => EXCLUDED_DIRS3.has(part));
|
|
8143
|
+
}
|
|
8144
|
+
async function buildFTS5Index(vaultPath2) {
|
|
8145
|
+
const db4 = getDb2();
|
|
8146
|
+
try {
|
|
8147
|
+
state.error = null;
|
|
8148
|
+
state.building = true;
|
|
8149
|
+
if (!db4) {
|
|
8150
|
+
throw new Error("FTS5 database not initialized. Call setFTS5Database() first.");
|
|
8151
|
+
}
|
|
8152
|
+
const files = await scanVault(vaultPath2);
|
|
8153
|
+
const indexableFiles = files.filter((f) => shouldIndexFile2(f.path));
|
|
8154
|
+
const rows = [];
|
|
8155
|
+
for (const file of indexableFiles) {
|
|
8156
|
+
try {
|
|
8157
|
+
const stats = fs8.statSync(file.absolutePath);
|
|
8158
|
+
if (stats.size > MAX_INDEX_FILE_SIZE) {
|
|
8159
|
+
continue;
|
|
8160
|
+
}
|
|
8161
|
+
const raw = fs8.readFileSync(file.absolutePath, "utf-8");
|
|
8162
|
+
const { frontmatter, body } = splitFrontmatter(raw);
|
|
8163
|
+
const title = file.path.replace(/\.md$/, "").split("/").pop() || file.path;
|
|
8164
|
+
rows.push([file.path, title, frontmatter, body]);
|
|
8165
|
+
} catch (err) {
|
|
8166
|
+
serverLog("fts5", `Skipping ${file.path}: ${err}`, "warn");
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
8169
|
+
const insert = db4.prepare(
|
|
8170
|
+
"INSERT INTO notes_fts (path, title, frontmatter, content) VALUES (?, ?, ?, ?)"
|
|
8171
|
+
);
|
|
8172
|
+
const now = /* @__PURE__ */ new Date();
|
|
8173
|
+
const swapAll = db4.transaction(() => {
|
|
8174
|
+
db4.exec("DELETE FROM notes_fts");
|
|
8175
|
+
for (const row of rows) {
|
|
8176
|
+
insert.run(...row);
|
|
8177
|
+
}
|
|
8178
|
+
db4.prepare(
|
|
8179
|
+
"INSERT OR REPLACE INTO fts_metadata (key, value) VALUES (?, ?)"
|
|
8180
|
+
).run("last_built", now.toISOString());
|
|
8181
|
+
});
|
|
8182
|
+
swapAll();
|
|
8183
|
+
const indexed = rows.length;
|
|
8184
|
+
state = {
|
|
8185
|
+
ready: true,
|
|
8186
|
+
building: false,
|
|
8187
|
+
lastBuilt: now,
|
|
8188
|
+
noteCount: indexed,
|
|
8189
|
+
error: null
|
|
8190
|
+
};
|
|
8191
|
+
serverLog("fts5", `Indexed ${indexed} notes`);
|
|
8192
|
+
return state;
|
|
8193
|
+
} catch (err) {
|
|
8194
|
+
state = {
|
|
8195
|
+
ready: false,
|
|
8196
|
+
building: false,
|
|
8197
|
+
lastBuilt: null,
|
|
8198
|
+
noteCount: 0,
|
|
8199
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8200
|
+
};
|
|
8201
|
+
throw err;
|
|
8202
|
+
}
|
|
8203
|
+
}
|
|
8204
|
+
function isIndexStale(_vaultPath) {
|
|
8205
|
+
const db4 = getDb2();
|
|
8206
|
+
if (!db4) {
|
|
8207
|
+
return true;
|
|
8208
|
+
}
|
|
8209
|
+
try {
|
|
8210
|
+
const row = db4.prepare(
|
|
8211
|
+
"SELECT value FROM fts_metadata WHERE key = ?"
|
|
8212
|
+
).get("last_built");
|
|
8213
|
+
if (!row) {
|
|
8214
|
+
return true;
|
|
8215
|
+
}
|
|
8216
|
+
const lastBuilt = new Date(row.value);
|
|
8217
|
+
const age = Date.now() - lastBuilt.getTime();
|
|
8218
|
+
return age > STALE_THRESHOLD_MS;
|
|
8219
|
+
} catch {
|
|
8220
|
+
return true;
|
|
8221
|
+
}
|
|
8222
|
+
}
|
|
8223
|
+
function updateFTS5Incremental(vaultPath2, changed, deleted) {
|
|
8224
|
+
const db4 = getDb2();
|
|
8225
|
+
if (!db4 || !state.ready) return { updated: 0, removed: 0 };
|
|
8226
|
+
const del = db4.prepare("DELETE FROM notes_fts WHERE path = ?");
|
|
8227
|
+
const ins = db4.prepare(
|
|
8228
|
+
"INSERT INTO notes_fts (path, title, frontmatter, content) VALUES (?, ?, ?, ?)"
|
|
8229
|
+
);
|
|
8230
|
+
let updated = 0;
|
|
8231
|
+
let removed = 0;
|
|
8232
|
+
const run = db4.transaction(() => {
|
|
8233
|
+
for (const p of deleted) {
|
|
8234
|
+
del.run(p);
|
|
8235
|
+
removed++;
|
|
8236
|
+
}
|
|
8237
|
+
for (const p of changed) {
|
|
8238
|
+
if (!shouldIndexFile2(p)) continue;
|
|
8239
|
+
const absPath = `${vaultPath2}/${p}`.replace(/\\/g, "/");
|
|
8240
|
+
try {
|
|
8241
|
+
const stats = fs8.statSync(absPath);
|
|
8242
|
+
if (stats.size > MAX_INDEX_FILE_SIZE) continue;
|
|
8243
|
+
const raw = fs8.readFileSync(absPath, "utf-8");
|
|
8244
|
+
const { frontmatter, body } = splitFrontmatter(raw);
|
|
8245
|
+
const title = p.replace(/\.md$/, "").split("/").pop() || p;
|
|
8246
|
+
del.run(p);
|
|
8247
|
+
ins.run(p, title, frontmatter, body);
|
|
8248
|
+
updated++;
|
|
8249
|
+
} catch {
|
|
8250
|
+
del.run(p);
|
|
8251
|
+
}
|
|
8252
|
+
}
|
|
8253
|
+
});
|
|
8254
|
+
run();
|
|
8255
|
+
if (updated > 0 || removed > 0) {
|
|
8256
|
+
state.noteCount = db4.prepare("SELECT COUNT(*) as count FROM notes_fts").get().count;
|
|
8257
|
+
}
|
|
8258
|
+
return { updated, removed };
|
|
8259
|
+
}
|
|
8260
|
+
function sanitizeFTS5Query(query) {
|
|
8261
|
+
if (!query?.trim()) return "";
|
|
8262
|
+
const phrases = [];
|
|
8263
|
+
const withoutPhrases = query.replace(/"([^"]+)"/g, (_, phrase) => {
|
|
8264
|
+
phrases.push(`"${phrase.replace(/"/g, '""')}"`);
|
|
8265
|
+
return "";
|
|
8266
|
+
});
|
|
8267
|
+
const cleaned = withoutPhrases.replace(/[(){}[\]^~:\-]/g, " ").replace(/\s+/g, " ").trim();
|
|
8268
|
+
const tokens = cleaned.split(" ").filter((t) => t && t !== "AND" && t !== "OR" && t !== "NOT");
|
|
8269
|
+
const parts = [...phrases];
|
|
8270
|
+
if (tokens.length === 1) {
|
|
8271
|
+
parts.push(tokens[0]);
|
|
8272
|
+
} else if (tokens.length > 1) {
|
|
8273
|
+
parts.push(tokens.join(" OR "));
|
|
8274
|
+
}
|
|
8275
|
+
return parts.join(" ") || "";
|
|
8276
|
+
}
|
|
8277
|
+
function searchFTS5(_vaultPath, query, limit = 10) {
|
|
8278
|
+
const db4 = getDb2();
|
|
8279
|
+
if (!db4) {
|
|
8280
|
+
throw new Error("FTS5 database not initialized. Call setFTS5Database() first.");
|
|
8281
|
+
}
|
|
8282
|
+
const sanitized = sanitizeFTS5Query(query);
|
|
8283
|
+
if (!sanitized) return [];
|
|
8284
|
+
try {
|
|
8285
|
+
const stmt = db4.prepare(`
|
|
8286
|
+
SELECT
|
|
8287
|
+
path,
|
|
8288
|
+
title,
|
|
8289
|
+
snippet(notes_fts, 3, '<mark>', '</mark>', '...', 64) as snippet
|
|
8290
|
+
FROM notes_fts
|
|
8291
|
+
WHERE notes_fts MATCH ?
|
|
8292
|
+
ORDER BY bm25(notes_fts, 0.0, 5.0, 10.0, 1.0)
|
|
8293
|
+
LIMIT ?
|
|
8294
|
+
`);
|
|
8295
|
+
const results = stmt.all(sanitized, limit);
|
|
8296
|
+
return results;
|
|
8297
|
+
} catch (err) {
|
|
8298
|
+
if (err instanceof Error && err.message.includes("fts5:")) {
|
|
8299
|
+
return [];
|
|
8300
|
+
}
|
|
8301
|
+
throw err;
|
|
8302
|
+
}
|
|
8303
|
+
}
|
|
8304
|
+
function getFTS5State() {
|
|
8305
|
+
if (state.building) return { ...state };
|
|
8306
|
+
const scopeDb = getDb2();
|
|
8307
|
+
if (scopeDb) {
|
|
8308
|
+
try {
|
|
8309
|
+
const row = scopeDb.prepare(
|
|
8310
|
+
"SELECT value FROM fts_metadata WHERE key = ?"
|
|
8311
|
+
).get("last_built");
|
|
8312
|
+
const countRow = scopeDb.prepare("SELECT COUNT(*) as count FROM notes_fts").get();
|
|
8313
|
+
return {
|
|
8314
|
+
ready: countRow.count > 0,
|
|
8315
|
+
building: false,
|
|
8316
|
+
lastBuilt: row ? new Date(row.value) : null,
|
|
8317
|
+
noteCount: countRow.count,
|
|
8318
|
+
error: null
|
|
8319
|
+
};
|
|
8320
|
+
} catch {
|
|
8321
|
+
}
|
|
8322
|
+
}
|
|
8323
|
+
return { ...state };
|
|
8324
|
+
}
|
|
8325
|
+
function getContentPreview(notePath, maxChars = 300) {
|
|
8326
|
+
const db4 = getDb2();
|
|
8327
|
+
if (!db4) return null;
|
|
8328
|
+
try {
|
|
8329
|
+
const row = db4.prepare(
|
|
8330
|
+
"SELECT substr(content, 1, ?) as preview FROM notes_fts WHERE path = ?"
|
|
8331
|
+
).get(maxChars + 50, notePath);
|
|
8332
|
+
if (!row?.preview) return null;
|
|
8333
|
+
const truncated = row.preview.length > maxChars ? row.preview.slice(0, maxChars).replace(/\s\S*$/, "") + "..." : row.preview;
|
|
8334
|
+
return truncated;
|
|
8335
|
+
} catch {
|
|
8336
|
+
return null;
|
|
8337
|
+
}
|
|
8338
|
+
}
|
|
8339
|
+
function countFTS5Mentions(term) {
|
|
8340
|
+
const db4 = getDb2();
|
|
8341
|
+
if (!db4) return 0;
|
|
8342
|
+
try {
|
|
8343
|
+
const result = db4.prepare(
|
|
8344
|
+
"SELECT COUNT(*) as cnt FROM notes_fts WHERE content MATCH ?"
|
|
8345
|
+
).get(`"${term}"`);
|
|
8346
|
+
return result?.cnt ?? 0;
|
|
8347
|
+
} catch {
|
|
8348
|
+
return 0;
|
|
8349
|
+
}
|
|
8350
|
+
}
|
|
8351
|
+
|
|
8352
|
+
// src/core/read/watch/pipeline.ts
|
|
8074
8353
|
init_embeddings();
|
|
8075
8354
|
|
|
8076
8355
|
// src/core/read/taskCache.ts
|
|
8077
|
-
import * as
|
|
8356
|
+
import * as path14 from "path";
|
|
8078
8357
|
|
|
8079
8358
|
// src/tools/read/tasks.ts
|
|
8080
|
-
import * as
|
|
8081
|
-
import * as
|
|
8359
|
+
import * as fs9 from "fs";
|
|
8360
|
+
import * as path13 from "path";
|
|
8082
8361
|
var TASK_REGEX = /^(\s*)- \[([ xX\-])\]\s+(.+)$/;
|
|
8083
8362
|
var TAG_REGEX2 = /#([a-zA-Z][a-zA-Z0-9_/-]*)/g;
|
|
8084
8363
|
var DATE_REGEX = /\b(\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4})\b/;
|
|
@@ -8104,7 +8383,7 @@ function extractDueDate(text) {
|
|
|
8104
8383
|
async function extractTasksFromNote(notePath, absolutePath) {
|
|
8105
8384
|
let content;
|
|
8106
8385
|
try {
|
|
8107
|
-
content = await
|
|
8386
|
+
content = await fs9.promises.readFile(absolutePath, "utf-8");
|
|
8108
8387
|
} catch {
|
|
8109
8388
|
return [];
|
|
8110
8389
|
}
|
|
@@ -8147,7 +8426,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
|
|
|
8147
8426
|
const allTasks = [];
|
|
8148
8427
|
for (const note of index.notes.values()) {
|
|
8149
8428
|
if (folder && !note.path.startsWith(folder)) continue;
|
|
8150
|
-
const absolutePath =
|
|
8429
|
+
const absolutePath = path13.join(vaultPath2, note.path);
|
|
8151
8430
|
const tasks = await extractTasksFromNote(note.path, absolutePath);
|
|
8152
8431
|
allTasks.push(...tasks);
|
|
8153
8432
|
}
|
|
@@ -8191,7 +8470,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
|
|
|
8191
8470
|
async function getTasksFromNote(index, notePath, vaultPath2, excludeTags = []) {
|
|
8192
8471
|
const note = index.notes.get(notePath);
|
|
8193
8472
|
if (!note) return null;
|
|
8194
|
-
const absolutePath =
|
|
8473
|
+
const absolutePath = path13.join(vaultPath2, notePath);
|
|
8195
8474
|
let tasks = await extractTasksFromNote(notePath, absolutePath);
|
|
8196
8475
|
if (excludeTags.length > 0) {
|
|
8197
8476
|
tasks = tasks.filter(
|
|
@@ -8213,17 +8492,17 @@ async function getTasksWithDueDates(index, vaultPath2, options = {}) {
|
|
|
8213
8492
|
// src/core/read/taskCache.ts
|
|
8214
8493
|
init_serverLog();
|
|
8215
8494
|
init_vault_scope();
|
|
8216
|
-
var
|
|
8217
|
-
function
|
|
8218
|
-
return getActiveScopeOrNull()?.stateDb?.db ??
|
|
8495
|
+
var db3 = null;
|
|
8496
|
+
function getDb3() {
|
|
8497
|
+
return getActiveScopeOrNull()?.stateDb?.db ?? db3;
|
|
8219
8498
|
}
|
|
8220
8499
|
var TASK_CACHE_STALE_MS = 30 * 60 * 1e3;
|
|
8221
8500
|
var cacheReady = false;
|
|
8222
8501
|
var rebuildInProgress = false;
|
|
8223
8502
|
function setTaskCacheDatabase(database) {
|
|
8224
|
-
|
|
8503
|
+
db3 = database;
|
|
8225
8504
|
try {
|
|
8226
|
-
const row =
|
|
8505
|
+
const row = db3.prepare(
|
|
8227
8506
|
"SELECT value FROM fts_metadata WHERE key = ?"
|
|
8228
8507
|
).get("task_cache_built");
|
|
8229
8508
|
if (row) {
|
|
@@ -8233,7 +8512,7 @@ function setTaskCacheDatabase(database) {
|
|
|
8233
8512
|
}
|
|
8234
8513
|
}
|
|
8235
8514
|
function isTaskCacheReady() {
|
|
8236
|
-
const scopeDb =
|
|
8515
|
+
const scopeDb = getDb3();
|
|
8237
8516
|
if (!scopeDb) return false;
|
|
8238
8517
|
try {
|
|
8239
8518
|
const row = scopeDb.prepare(
|
|
@@ -8248,7 +8527,7 @@ function isTaskCacheBuilding() {
|
|
|
8248
8527
|
return rebuildInProgress;
|
|
8249
8528
|
}
|
|
8250
8529
|
async function buildTaskCache(vaultPath2, index, excludeTags) {
|
|
8251
|
-
const db4 =
|
|
8530
|
+
const db4 = getDb3();
|
|
8252
8531
|
if (!db4) {
|
|
8253
8532
|
throw new Error("Task cache database not initialized. Call setTaskCacheDatabase() first.");
|
|
8254
8533
|
}
|
|
@@ -8262,7 +8541,7 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
|
|
|
8262
8541
|
}
|
|
8263
8542
|
const allRows = [];
|
|
8264
8543
|
for (const notePath of notePaths) {
|
|
8265
|
-
const absolutePath =
|
|
8544
|
+
const absolutePath = path14.join(vaultPath2, notePath);
|
|
8266
8545
|
const tasks = await extractTasksFromNote(notePath, absolutePath);
|
|
8267
8546
|
for (const task of tasks) {
|
|
8268
8547
|
if (excludeTags?.length && excludeTags.some((t) => task.tags.includes(t))) {
|
|
@@ -8302,10 +8581,10 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
|
|
|
8302
8581
|
}
|
|
8303
8582
|
}
|
|
8304
8583
|
async function updateTaskCacheForFile(vaultPath2, relativePath) {
|
|
8305
|
-
const db4 =
|
|
8584
|
+
const db4 = getDb3();
|
|
8306
8585
|
if (!db4) return;
|
|
8307
8586
|
db4.prepare("DELETE FROM tasks WHERE path = ?").run(relativePath);
|
|
8308
|
-
const absolutePath =
|
|
8587
|
+
const absolutePath = path14.join(vaultPath2, relativePath);
|
|
8309
8588
|
const tasks = await extractTasksFromNote(relativePath, absolutePath);
|
|
8310
8589
|
if (tasks.length > 0) {
|
|
8311
8590
|
const insertStmt = db4.prepare(`
|
|
@@ -8330,12 +8609,12 @@ async function updateTaskCacheForFile(vaultPath2, relativePath) {
|
|
|
8330
8609
|
}
|
|
8331
8610
|
}
|
|
8332
8611
|
function removeTaskCacheForFile(relativePath) {
|
|
8333
|
-
const db4 =
|
|
8612
|
+
const db4 = getDb3();
|
|
8334
8613
|
if (!db4) return;
|
|
8335
8614
|
db4.prepare("DELETE FROM tasks WHERE path = ?").run(relativePath);
|
|
8336
8615
|
}
|
|
8337
8616
|
function queryTasksFromCache(options) {
|
|
8338
|
-
const db4 =
|
|
8617
|
+
const db4 = getDb3();
|
|
8339
8618
|
if (!db4) {
|
|
8340
8619
|
throw new Error("Task cache database not initialized.");
|
|
8341
8620
|
}
|
|
@@ -8422,7 +8701,7 @@ function queryTasksFromCache(options) {
|
|
|
8422
8701
|
};
|
|
8423
8702
|
}
|
|
8424
8703
|
function isTaskCacheStale() {
|
|
8425
|
-
const db4 =
|
|
8704
|
+
const db4 = getDb3();
|
|
8426
8705
|
if (!db4) return true;
|
|
8427
8706
|
try {
|
|
8428
8707
|
const row = db4.prepare(
|
|
@@ -8478,6 +8757,7 @@ var PipelineRunner = class {
|
|
|
8478
8757
|
try {
|
|
8479
8758
|
await runStep("drain_proactive_queue", tracker, {}, () => this.drainQueue());
|
|
8480
8759
|
await this.indexRebuild();
|
|
8760
|
+
this.fts5Incremental();
|
|
8481
8761
|
this.noteMoves();
|
|
8482
8762
|
await this.entityScan();
|
|
8483
8763
|
await runStep("hub_scores", tracker, { entity_count: this.entitiesAfter.length }, () => this.hubScores());
|
|
@@ -8540,7 +8820,7 @@ var PipelineRunner = class {
|
|
|
8540
8820
|
} else {
|
|
8541
8821
|
const absoluteBatch = {
|
|
8542
8822
|
...p.batch,
|
|
8543
|
-
events: p.events.map((e) => ({ ...e, path:
|
|
8823
|
+
events: p.events.map((e) => ({ ...e, path: path15.join(p.vp, e.path) }))
|
|
8544
8824
|
};
|
|
8545
8825
|
const batchResult = await processBatch(vaultIndex2, p.vp, absoluteBatch);
|
|
8546
8826
|
serverLog("watcher", `Incremental: ${batchResult.successful}/${batchResult.total} files in ${batchResult.durationMs}ms`);
|
|
@@ -8549,6 +8829,26 @@ var PipelineRunner = class {
|
|
|
8549
8829
|
const idx = p.getVaultIndex();
|
|
8550
8830
|
tracker.end({ note_count: idx.notes.size, entity_count: idx.entities.size, tag_count: idx.tags.size });
|
|
8551
8831
|
}
|
|
8832
|
+
// ── Step 1.1: FTS5 incremental update ──────────────────────────────
|
|
8833
|
+
fts5Incremental() {
|
|
8834
|
+
const { p, tracker } = this;
|
|
8835
|
+
const changed = p.events.filter((e) => e.type === "upsert").map((e) => e.path);
|
|
8836
|
+
const deleted = [
|
|
8837
|
+
...p.events.filter((e) => e.type === "delete").map((e) => e.path),
|
|
8838
|
+
...p.renames.map((r) => r.oldPath)
|
|
8839
|
+
];
|
|
8840
|
+
if (changed.length === 0 && deleted.length === 0) {
|
|
8841
|
+
tracker.start("fts5_incremental", {});
|
|
8842
|
+
tracker.skip("fts5_incremental", "no changes");
|
|
8843
|
+
return;
|
|
8844
|
+
}
|
|
8845
|
+
tracker.start("fts5_incremental", { changed: changed.length, deleted: deleted.length });
|
|
8846
|
+
const result = updateFTS5Incremental(p.vp, changed, deleted);
|
|
8847
|
+
tracker.end(result);
|
|
8848
|
+
if (result.updated > 0 || result.removed > 0) {
|
|
8849
|
+
serverLog("watcher", `FTS5: ${result.updated} updated, ${result.removed} removed`);
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8552
8852
|
// ── Step 1.5: Note moves ──────────────────────────────────────────
|
|
8553
8853
|
noteMoves() {
|
|
8554
8854
|
const { p, tracker } = this;
|
|
@@ -8689,7 +8989,7 @@ var PipelineRunner = class {
|
|
|
8689
8989
|
removeEmbedding(event.path);
|
|
8690
8990
|
embRemoved++;
|
|
8691
8991
|
} else if (event.path.endsWith(".md")) {
|
|
8692
|
-
const absPath =
|
|
8992
|
+
const absPath = path15.join(p.vp, event.path);
|
|
8693
8993
|
await updateEmbedding(event.path, absPath);
|
|
8694
8994
|
embUpdated++;
|
|
8695
8995
|
}
|
|
@@ -8923,7 +9223,7 @@ var PipelineRunner = class {
|
|
|
8923
9223
|
for (const event of p.events) {
|
|
8924
9224
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
8925
9225
|
try {
|
|
8926
|
-
const content = await
|
|
9226
|
+
const content = await fs10.readFile(path15.join(p.vp, event.path), "utf-8");
|
|
8927
9227
|
const zones = getProtectedZones(content);
|
|
8928
9228
|
const linked = new Set(
|
|
8929
9229
|
(this.forwardLinkResults.find((r) => r.file === event.path)?.resolved ?? []).map((n) => n.toLowerCase())
|
|
@@ -8968,7 +9268,7 @@ var PipelineRunner = class {
|
|
|
8968
9268
|
for (const event of p.events) {
|
|
8969
9269
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
8970
9270
|
try {
|
|
8971
|
-
const content = await
|
|
9271
|
+
const content = await fs10.readFile(path15.join(p.vp, event.path), "utf-8");
|
|
8972
9272
|
const removed = processImplicitFeedback(p.sd, event.path, content);
|
|
8973
9273
|
for (const entity of removed) feedbackResults.push({ entity, file: event.path });
|
|
8974
9274
|
} catch {
|
|
@@ -9045,7 +9345,7 @@ var PipelineRunner = class {
|
|
|
9045
9345
|
for (const event of p.events) {
|
|
9046
9346
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
9047
9347
|
try {
|
|
9048
|
-
const content = await
|
|
9348
|
+
const content = await fs10.readFile(path15.join(p.vp, event.path), "utf-8");
|
|
9049
9349
|
const zones = getProtectedZones(content);
|
|
9050
9350
|
const linkedSet = new Set(
|
|
9051
9351
|
(this.forwardLinkResults.find((r) => r.file === event.path)?.resolved ?? []).concat(this.forwardLinkResults.find((r) => r.file === event.path)?.dead ?? []).map((n) => n.toLowerCase())
|
|
@@ -9081,7 +9381,7 @@ var PipelineRunner = class {
|
|
|
9081
9381
|
for (const event of p.events) {
|
|
9082
9382
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
9083
9383
|
try {
|
|
9084
|
-
const rawContent = await
|
|
9384
|
+
const rawContent = await fs10.readFile(path15.join(p.vp, event.path), "utf-8");
|
|
9085
9385
|
const content = rawContent.replace(/ → \[\[.*$/gm, "");
|
|
9086
9386
|
const result = await suggestRelatedLinks(content, {
|
|
9087
9387
|
maxSuggestions: 5,
|
|
@@ -9112,14 +9412,12 @@ var PipelineRunner = class {
|
|
|
9112
9412
|
if (!p.sd || p.flywheelConfig?.proactive_linking === false) {
|
|
9113
9413
|
return { skipped: true };
|
|
9114
9414
|
}
|
|
9115
|
-
const currentBatchPaths = new Set(p.events.map((e) => e.path));
|
|
9116
9415
|
const result = await drainProactiveQueue(
|
|
9117
9416
|
p.sd,
|
|
9118
9417
|
p.vp,
|
|
9119
|
-
currentBatchPaths,
|
|
9120
9418
|
{
|
|
9121
9419
|
minScore: p.flywheelConfig?.proactive_min_score ?? 20,
|
|
9122
|
-
maxPerFile: p.flywheelConfig?.proactive_max_per_file ??
|
|
9420
|
+
maxPerFile: p.flywheelConfig?.proactive_max_per_file ?? 5,
|
|
9123
9421
|
maxPerDay: p.flywheelConfig?.proactive_max_per_day ?? 10
|
|
9124
9422
|
},
|
|
9125
9423
|
applyProactiveSuggestions
|
|
@@ -9145,7 +9443,7 @@ var PipelineRunner = class {
|
|
|
9145
9443
|
tracker.start("proactive_enqueue", { files: this.suggestionResults.length });
|
|
9146
9444
|
try {
|
|
9147
9445
|
const minScore = p.flywheelConfig?.proactive_min_score ?? 20;
|
|
9148
|
-
const maxPerFile = p.flywheelConfig?.proactive_max_per_file ??
|
|
9446
|
+
const maxPerFile = p.flywheelConfig?.proactive_max_per_file ?? 5;
|
|
9149
9447
|
const entries = [];
|
|
9150
9448
|
for (const { file, top } of this.suggestionResults) {
|
|
9151
9449
|
const candidates = top.filter((s) => s.score >= minScore && s.confidence === "high").slice(0, maxPerFile);
|
|
@@ -9166,323 +9464,106 @@ var PipelineRunner = class {
|
|
|
9166
9464
|
// ── Step 13: Tag scan ─────────────────────────────────────────────
|
|
9167
9465
|
async tagScan() {
|
|
9168
9466
|
const { p } = this;
|
|
9169
|
-
const vaultIndex2 = p.getVaultIndex();
|
|
9170
|
-
const tagDiffs = [];
|
|
9171
|
-
if (p.sd) {
|
|
9172
|
-
const noteTagsForward = /* @__PURE__ */ new Map();
|
|
9173
|
-
for (const [tag, paths] of vaultIndex2.tags) {
|
|
9174
|
-
for (const notePath of paths) {
|
|
9175
|
-
if (!noteTagsForward.has(notePath)) noteTagsForward.set(notePath, /* @__PURE__ */ new Set());
|
|
9176
|
-
noteTagsForward.get(notePath).add(tag);
|
|
9177
|
-
}
|
|
9178
|
-
}
|
|
9179
|
-
for (const event of p.events) {
|
|
9180
|
-
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
9181
|
-
const currentSet = noteTagsForward.get(event.path) ?? /* @__PURE__ */ new Set();
|
|
9182
|
-
const previousSet = getStoredNoteTags(p.sd, event.path);
|
|
9183
|
-
if (previousSet.size === 0 && currentSet.size > 0) {
|
|
9184
|
-
updateStoredNoteTags(p.sd, event.path, currentSet);
|
|
9185
|
-
continue;
|
|
9186
|
-
}
|
|
9187
|
-
const added = [...currentSet].filter((t) => !previousSet.has(t));
|
|
9188
|
-
const removed = [...previousSet].filter((t) => !currentSet.has(t));
|
|
9189
|
-
if (added.length > 0 || removed.length > 0) {
|
|
9190
|
-
tagDiffs.push({ file: event.path, added, removed });
|
|
9191
|
-
}
|
|
9192
|
-
updateStoredNoteTags(p.sd, event.path, currentSet);
|
|
9193
|
-
}
|
|
9194
|
-
for (const event of p.events) {
|
|
9195
|
-
if (event.type === "delete") {
|
|
9196
|
-
const previousSet = getStoredNoteTags(p.sd, event.path);
|
|
9197
|
-
if (previousSet.size > 0) {
|
|
9198
|
-
tagDiffs.push({ file: event.path, added: [], removed: [...previousSet] });
|
|
9199
|
-
updateStoredNoteTags(p.sd, event.path, /* @__PURE__ */ new Set());
|
|
9200
|
-
}
|
|
9201
|
-
}
|
|
9202
|
-
}
|
|
9203
|
-
}
|
|
9204
|
-
const totalTagsAdded = tagDiffs.reduce((s, d) => s + d.added.length, 0);
|
|
9205
|
-
const totalTagsRemoved = tagDiffs.reduce((s, d) => s + d.removed.length, 0);
|
|
9206
|
-
if (tagDiffs.length > 0) {
|
|
9207
|
-
serverLog("watcher", `Tag scan: ${totalTagsAdded} added, ${totalTagsRemoved} removed across ${tagDiffs.length} files`);
|
|
9208
|
-
}
|
|
9209
|
-
return { total_added: totalTagsAdded, total_removed: totalTagsRemoved, tag_diffs: tagDiffs };
|
|
9210
|
-
}
|
|
9211
|
-
// ── Step 19: Retrieval co-occurrence ──────────────────────────────
|
|
9212
|
-
async retrievalCooccurrence() {
|
|
9213
|
-
const { p } = this;
|
|
9214
|
-
if (!p.sd) return { skipped: "no sd" };
|
|
9215
|
-
const inserted = mineRetrievalCooccurrence(p.sd);
|
|
9216
|
-
if (inserted > 0) {
|
|
9217
|
-
serverLog("watcher", `Retrieval co-occurrence: ${inserted} new pairs`);
|
|
9218
|
-
}
|
|
9219
|
-
return { pairs_inserted: inserted };
|
|
9220
|
-
}
|
|
9221
|
-
};
|
|
9222
|
-
|
|
9223
|
-
// src/core/read/logging.ts
|
|
9224
|
-
init_serverLog();
|
|
9225
|
-
import {
|
|
9226
|
-
createLoggerFromConfig,
|
|
9227
|
-
generateSessionId,
|
|
9228
|
-
setSessionId
|
|
9229
|
-
} from "@velvetmonkey/vault-core";
|
|
9230
|
-
var logger = null;
|
|
9231
|
-
async function initializeLogger(vaultPath2) {
|
|
9232
|
-
try {
|
|
9233
|
-
const sessionId = generateSessionId();
|
|
9234
|
-
setSessionId(sessionId);
|
|
9235
|
-
logger = await createLoggerFromConfig(vaultPath2, "flywheel");
|
|
9236
|
-
} catch (error) {
|
|
9237
|
-
serverLog("server", `Failed to initialize logger: ${error}`, "error");
|
|
9238
|
-
logger = null;
|
|
9239
|
-
}
|
|
9240
|
-
}
|
|
9241
|
-
function getLogger() {
|
|
9242
|
-
return logger;
|
|
9243
|
-
}
|
|
9244
|
-
|
|
9245
|
-
// src/index.ts
|
|
9246
|
-
init_wikilinks();
|
|
9247
|
-
|
|
9248
|
-
// src/core/write/logging.ts
|
|
9249
|
-
init_serverLog();
|
|
9250
|
-
import {
|
|
9251
|
-
createLoggerFromConfig as createLoggerFromConfig2,
|
|
9252
|
-
generateSessionId as generateSessionId2,
|
|
9253
|
-
setSessionId as setSessionId2
|
|
9254
|
-
} from "@velvetmonkey/vault-core";
|
|
9255
|
-
var logger2 = null;
|
|
9256
|
-
async function initializeLogger2(vaultPath2) {
|
|
9257
|
-
try {
|
|
9258
|
-
const sessionId = generateSessionId2();
|
|
9259
|
-
setSessionId2(sessionId);
|
|
9260
|
-
logger2 = await createLoggerFromConfig2(vaultPath2, "write");
|
|
9261
|
-
} catch (error) {
|
|
9262
|
-
serverLog("server", `Failed to initialize logger: ${error}`, "error");
|
|
9263
|
-
logger2 = null;
|
|
9264
|
-
}
|
|
9265
|
-
}
|
|
9266
|
-
async function flushLogs() {
|
|
9267
|
-
if (logger2) await logger2.flush();
|
|
9268
|
-
}
|
|
9269
|
-
|
|
9270
|
-
// src/core/read/fts5.ts
|
|
9271
|
-
init_vault();
|
|
9272
|
-
init_serverLog();
|
|
9273
|
-
init_vault_scope();
|
|
9274
|
-
import * as fs10 from "fs";
|
|
9275
|
-
var EXCLUDED_DIRS3 = /* @__PURE__ */ new Set([
|
|
9276
|
-
".obsidian",
|
|
9277
|
-
".trash",
|
|
9278
|
-
".git",
|
|
9279
|
-
"node_modules",
|
|
9280
|
-
"templates",
|
|
9281
|
-
".claude",
|
|
9282
|
-
".flywheel"
|
|
9283
|
-
]);
|
|
9284
|
-
var MAX_INDEX_FILE_SIZE = 5 * 1024 * 1024;
|
|
9285
|
-
var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
9286
|
-
function splitFrontmatter(raw) {
|
|
9287
|
-
if (!raw.startsWith("---")) return { frontmatter: "", body: raw };
|
|
9288
|
-
const end = raw.indexOf("\n---", 3);
|
|
9289
|
-
if (end === -1) return { frontmatter: "", body: raw };
|
|
9290
|
-
const yaml = raw.substring(4, end);
|
|
9291
|
-
const values = yaml.split("\n").map((line) => line.replace(/^[\s-]*/, "").replace(/^[\w]+:\s*/, "")).filter((v) => v && !v.startsWith("[") && !v.startsWith("{")).join(" ");
|
|
9292
|
-
return { frontmatter: values, body: raw.substring(end + 4) };
|
|
9293
|
-
}
|
|
9294
|
-
var db3 = null;
|
|
9295
|
-
function getDb3() {
|
|
9296
|
-
return getActiveScopeOrNull()?.stateDb?.db ?? db3;
|
|
9297
|
-
}
|
|
9298
|
-
var state = {
|
|
9299
|
-
ready: false,
|
|
9300
|
-
building: false,
|
|
9301
|
-
lastBuilt: null,
|
|
9302
|
-
noteCount: 0,
|
|
9303
|
-
error: null
|
|
9304
|
-
};
|
|
9305
|
-
function setFTS5Database(database) {
|
|
9306
|
-
db3 = database;
|
|
9307
|
-
try {
|
|
9308
|
-
const row = db3.prepare(
|
|
9309
|
-
"SELECT value FROM fts_metadata WHERE key = ?"
|
|
9310
|
-
).get("last_built");
|
|
9311
|
-
if (row) {
|
|
9312
|
-
const lastBuilt = new Date(row.value);
|
|
9313
|
-
const countRow = db3.prepare("SELECT COUNT(*) as count FROM notes_fts").get();
|
|
9314
|
-
state = {
|
|
9315
|
-
ready: countRow.count > 0,
|
|
9316
|
-
building: false,
|
|
9317
|
-
lastBuilt,
|
|
9318
|
-
noteCount: countRow.count,
|
|
9319
|
-
error: null
|
|
9320
|
-
};
|
|
9321
|
-
}
|
|
9322
|
-
} catch {
|
|
9323
|
-
}
|
|
9324
|
-
}
|
|
9325
|
-
function shouldIndexFile2(filePath) {
|
|
9326
|
-
const parts = filePath.split("/");
|
|
9327
|
-
return !parts.some((part) => EXCLUDED_DIRS3.has(part));
|
|
9328
|
-
}
|
|
9329
|
-
async function buildFTS5Index(vaultPath2) {
|
|
9330
|
-
const db4 = getDb3();
|
|
9331
|
-
try {
|
|
9332
|
-
state.error = null;
|
|
9333
|
-
state.building = true;
|
|
9334
|
-
if (!db4) {
|
|
9335
|
-
throw new Error("FTS5 database not initialized. Call setFTS5Database() first.");
|
|
9336
|
-
}
|
|
9337
|
-
const files = await scanVault(vaultPath2);
|
|
9338
|
-
const indexableFiles = files.filter((f) => shouldIndexFile2(f.path));
|
|
9339
|
-
const rows = [];
|
|
9340
|
-
for (const file of indexableFiles) {
|
|
9341
|
-
try {
|
|
9342
|
-
const stats = fs10.statSync(file.absolutePath);
|
|
9343
|
-
if (stats.size > MAX_INDEX_FILE_SIZE) {
|
|
9467
|
+
const vaultIndex2 = p.getVaultIndex();
|
|
9468
|
+
const tagDiffs = [];
|
|
9469
|
+
if (p.sd) {
|
|
9470
|
+
const noteTagsForward = /* @__PURE__ */ new Map();
|
|
9471
|
+
for (const [tag, paths] of vaultIndex2.tags) {
|
|
9472
|
+
for (const notePath of paths) {
|
|
9473
|
+
if (!noteTagsForward.has(notePath)) noteTagsForward.set(notePath, /* @__PURE__ */ new Set());
|
|
9474
|
+
noteTagsForward.get(notePath).add(tag);
|
|
9475
|
+
}
|
|
9476
|
+
}
|
|
9477
|
+
for (const event of p.events) {
|
|
9478
|
+
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
9479
|
+
const currentSet = noteTagsForward.get(event.path) ?? /* @__PURE__ */ new Set();
|
|
9480
|
+
const previousSet = getStoredNoteTags(p.sd, event.path);
|
|
9481
|
+
if (previousSet.size === 0 && currentSet.size > 0) {
|
|
9482
|
+
updateStoredNoteTags(p.sd, event.path, currentSet);
|
|
9344
9483
|
continue;
|
|
9345
9484
|
}
|
|
9346
|
-
const
|
|
9347
|
-
const
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9485
|
+
const added = [...currentSet].filter((t) => !previousSet.has(t));
|
|
9486
|
+
const removed = [...previousSet].filter((t) => !currentSet.has(t));
|
|
9487
|
+
if (added.length > 0 || removed.length > 0) {
|
|
9488
|
+
tagDiffs.push({ file: event.path, added, removed });
|
|
9489
|
+
}
|
|
9490
|
+
updateStoredNoteTags(p.sd, event.path, currentSet);
|
|
9352
9491
|
}
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
insert.run(...row);
|
|
9492
|
+
for (const event of p.events) {
|
|
9493
|
+
if (event.type === "delete") {
|
|
9494
|
+
const previousSet = getStoredNoteTags(p.sd, event.path);
|
|
9495
|
+
if (previousSet.size > 0) {
|
|
9496
|
+
tagDiffs.push({ file: event.path, added: [], removed: [...previousSet] });
|
|
9497
|
+
updateStoredNoteTags(p.sd, event.path, /* @__PURE__ */ new Set());
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9362
9500
|
}
|
|
9363
|
-
db4.prepare(
|
|
9364
|
-
"INSERT OR REPLACE INTO fts_metadata (key, value) VALUES (?, ?)"
|
|
9365
|
-
).run("last_built", now.toISOString());
|
|
9366
|
-
});
|
|
9367
|
-
swapAll();
|
|
9368
|
-
const indexed = rows.length;
|
|
9369
|
-
state = {
|
|
9370
|
-
ready: true,
|
|
9371
|
-
building: false,
|
|
9372
|
-
lastBuilt: now,
|
|
9373
|
-
noteCount: indexed,
|
|
9374
|
-
error: null
|
|
9375
|
-
};
|
|
9376
|
-
serverLog("fts5", `Indexed ${indexed} notes`);
|
|
9377
|
-
return state;
|
|
9378
|
-
} catch (err) {
|
|
9379
|
-
state = {
|
|
9380
|
-
ready: false,
|
|
9381
|
-
building: false,
|
|
9382
|
-
lastBuilt: null,
|
|
9383
|
-
noteCount: 0,
|
|
9384
|
-
error: err instanceof Error ? err.message : String(err)
|
|
9385
|
-
};
|
|
9386
|
-
throw err;
|
|
9387
|
-
}
|
|
9388
|
-
}
|
|
9389
|
-
function isIndexStale(_vaultPath) {
|
|
9390
|
-
const db4 = getDb3();
|
|
9391
|
-
if (!db4) {
|
|
9392
|
-
return true;
|
|
9393
|
-
}
|
|
9394
|
-
try {
|
|
9395
|
-
const row = db4.prepare(
|
|
9396
|
-
"SELECT value FROM fts_metadata WHERE key = ?"
|
|
9397
|
-
).get("last_built");
|
|
9398
|
-
if (!row) {
|
|
9399
|
-
return true;
|
|
9400
9501
|
}
|
|
9401
|
-
const
|
|
9402
|
-
const
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
return true;
|
|
9406
|
-
}
|
|
9407
|
-
}
|
|
9408
|
-
function sanitizeFTS5Query(query) {
|
|
9409
|
-
if (!query?.trim()) return "";
|
|
9410
|
-
return query.replace(/"/g, '""').replace(/[(){}[\]^~:\-]/g, " ").replace(/\s+/g, " ").trim();
|
|
9411
|
-
}
|
|
9412
|
-
function searchFTS5(_vaultPath, query, limit = 10) {
|
|
9413
|
-
const db4 = getDb3();
|
|
9414
|
-
if (!db4) {
|
|
9415
|
-
throw new Error("FTS5 database not initialized. Call setFTS5Database() first.");
|
|
9416
|
-
}
|
|
9417
|
-
const sanitized = sanitizeFTS5Query(query);
|
|
9418
|
-
if (!sanitized) return [];
|
|
9419
|
-
try {
|
|
9420
|
-
const stmt = db4.prepare(`
|
|
9421
|
-
SELECT
|
|
9422
|
-
path,
|
|
9423
|
-
title,
|
|
9424
|
-
snippet(notes_fts, 3, '<mark>', '</mark>', '...', 64) as snippet
|
|
9425
|
-
FROM notes_fts
|
|
9426
|
-
WHERE notes_fts MATCH ?
|
|
9427
|
-
ORDER BY bm25(notes_fts, 0.0, 5.0, 10.0, 1.0)
|
|
9428
|
-
LIMIT ?
|
|
9429
|
-
`);
|
|
9430
|
-
const results = stmt.all(sanitized, limit);
|
|
9431
|
-
return results;
|
|
9432
|
-
} catch (err) {
|
|
9433
|
-
if (err instanceof Error && err.message.includes("fts5:")) {
|
|
9434
|
-
return [];
|
|
9502
|
+
const totalTagsAdded = tagDiffs.reduce((s, d) => s + d.added.length, 0);
|
|
9503
|
+
const totalTagsRemoved = tagDiffs.reduce((s, d) => s + d.removed.length, 0);
|
|
9504
|
+
if (tagDiffs.length > 0) {
|
|
9505
|
+
serverLog("watcher", `Tag scan: ${totalTagsAdded} added, ${totalTagsRemoved} removed across ${tagDiffs.length} files`);
|
|
9435
9506
|
}
|
|
9436
|
-
|
|
9507
|
+
return { total_added: totalTagsAdded, total_removed: totalTagsRemoved, tag_diffs: tagDiffs };
|
|
9437
9508
|
}
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
"SELECT value FROM fts_metadata WHERE key = ?"
|
|
9446
|
-
).get("last_built");
|
|
9447
|
-
const countRow = scopeDb.prepare("SELECT COUNT(*) as count FROM notes_fts").get();
|
|
9448
|
-
return {
|
|
9449
|
-
ready: countRow.count > 0,
|
|
9450
|
-
building: false,
|
|
9451
|
-
lastBuilt: row ? new Date(row.value) : null,
|
|
9452
|
-
noteCount: countRow.count,
|
|
9453
|
-
error: null
|
|
9454
|
-
};
|
|
9455
|
-
} catch {
|
|
9509
|
+
// ── Step 19: Retrieval co-occurrence ──────────────────────────────
|
|
9510
|
+
async retrievalCooccurrence() {
|
|
9511
|
+
const { p } = this;
|
|
9512
|
+
if (!p.sd) return { skipped: "no sd" };
|
|
9513
|
+
const inserted = mineRetrievalCooccurrence(p.sd);
|
|
9514
|
+
if (inserted > 0) {
|
|
9515
|
+
serverLog("watcher", `Retrieval co-occurrence: ${inserted} new pairs`);
|
|
9456
9516
|
}
|
|
9517
|
+
return { pairs_inserted: inserted };
|
|
9457
9518
|
}
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9519
|
+
};
|
|
9520
|
+
|
|
9521
|
+
// src/core/read/logging.ts
|
|
9522
|
+
init_serverLog();
|
|
9523
|
+
import {
|
|
9524
|
+
createLoggerFromConfig,
|
|
9525
|
+
generateSessionId,
|
|
9526
|
+
setSessionId
|
|
9527
|
+
} from "@velvetmonkey/vault-core";
|
|
9528
|
+
var logger = null;
|
|
9529
|
+
async function initializeLogger(vaultPath2) {
|
|
9463
9530
|
try {
|
|
9464
|
-
const
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
} catch {
|
|
9471
|
-
return null;
|
|
9531
|
+
const sessionId = generateSessionId();
|
|
9532
|
+
setSessionId(sessionId);
|
|
9533
|
+
logger = await createLoggerFromConfig(vaultPath2, "flywheel");
|
|
9534
|
+
} catch (error) {
|
|
9535
|
+
serverLog("server", `Failed to initialize logger: ${error}`, "error");
|
|
9536
|
+
logger = null;
|
|
9472
9537
|
}
|
|
9473
9538
|
}
|
|
9474
|
-
function
|
|
9475
|
-
|
|
9476
|
-
|
|
9539
|
+
function getLogger() {
|
|
9540
|
+
return logger;
|
|
9541
|
+
}
|
|
9542
|
+
|
|
9543
|
+
// src/index.ts
|
|
9544
|
+
init_wikilinks();
|
|
9545
|
+
|
|
9546
|
+
// src/core/write/logging.ts
|
|
9547
|
+
init_serverLog();
|
|
9548
|
+
import {
|
|
9549
|
+
createLoggerFromConfig as createLoggerFromConfig2,
|
|
9550
|
+
generateSessionId as generateSessionId2,
|
|
9551
|
+
setSessionId as setSessionId2
|
|
9552
|
+
} from "@velvetmonkey/vault-core";
|
|
9553
|
+
var logger2 = null;
|
|
9554
|
+
async function initializeLogger2(vaultPath2) {
|
|
9477
9555
|
try {
|
|
9478
|
-
const
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9556
|
+
const sessionId = generateSessionId2();
|
|
9557
|
+
setSessionId2(sessionId);
|
|
9558
|
+
logger2 = await createLoggerFromConfig2(vaultPath2, "write");
|
|
9559
|
+
} catch (error) {
|
|
9560
|
+
serverLog("server", `Failed to initialize logger: ${error}`, "error");
|
|
9561
|
+
logger2 = null;
|
|
9484
9562
|
}
|
|
9485
9563
|
}
|
|
9564
|
+
async function flushLogs() {
|
|
9565
|
+
if (logger2) await logger2.flush();
|
|
9566
|
+
}
|
|
9486
9567
|
|
|
9487
9568
|
// src/index.ts
|
|
9488
9569
|
init_embeddings();
|
|
@@ -10183,8 +10264,8 @@ function getNoteAccessFrequency(stateDb2, daysBack = 30) {
|
|
|
10183
10264
|
}
|
|
10184
10265
|
}
|
|
10185
10266
|
}
|
|
10186
|
-
return Array.from(noteMap.entries()).map(([
|
|
10187
|
-
path:
|
|
10267
|
+
return Array.from(noteMap.entries()).map(([path37, stats]) => ({
|
|
10268
|
+
path: path37,
|
|
10188
10269
|
access_count: stats.access_count,
|
|
10189
10270
|
last_accessed: stats.last_accessed,
|
|
10190
10271
|
tools_used: Array.from(stats.tools)
|
|
@@ -10715,7 +10796,7 @@ var TOOL_CATEGORY = {
|
|
|
10715
10796
|
predict_stale_notes: "temporal",
|
|
10716
10797
|
track_concept_evolution: "temporal",
|
|
10717
10798
|
temporal_summary: "temporal",
|
|
10718
|
-
// diagnostics (
|
|
10799
|
+
// diagnostics (18 tools) -- vault health, stats, config, activity, merges, doctor, trust, benchmark, history
|
|
10719
10800
|
health_check: "diagnostics",
|
|
10720
10801
|
get_vault_stats: "diagnostics",
|
|
10721
10802
|
get_folder_structure: "diagnostics",
|
|
@@ -10856,9 +10937,9 @@ Use "note_intelligence" for per-note analysis (completeness, quality, suggestion
|
|
|
10856
10937
|
}
|
|
10857
10938
|
|
|
10858
10939
|
// src/tool-registry.ts
|
|
10859
|
-
import * as
|
|
10860
|
-
import { dirname as dirname5, join as
|
|
10861
|
-
import { statSync as
|
|
10940
|
+
import * as path35 from "path";
|
|
10941
|
+
import { dirname as dirname5, join as join19 } from "path";
|
|
10942
|
+
import { statSync as statSync6, readFileSync as readFileSync5 } from "fs";
|
|
10862
10943
|
import { fileURLToPath } from "url";
|
|
10863
10944
|
import { z as z38 } from "zod";
|
|
10864
10945
|
import { getSessionId } from "@velvetmonkey/vault-core";
|
|
@@ -10866,7 +10947,7 @@ init_vault_scope();
|
|
|
10866
10947
|
|
|
10867
10948
|
// src/tools/read/graph.ts
|
|
10868
10949
|
import * as fs11 from "fs";
|
|
10869
|
-
import * as
|
|
10950
|
+
import * as path16 from "path";
|
|
10870
10951
|
import { z } from "zod";
|
|
10871
10952
|
|
|
10872
10953
|
// src/core/read/constants.ts
|
|
@@ -11317,7 +11398,7 @@ function requireIndex() {
|
|
|
11317
11398
|
// src/tools/read/graph.ts
|
|
11318
11399
|
async function getContext(vaultPath2, sourcePath, line, contextLines = 1) {
|
|
11319
11400
|
try {
|
|
11320
|
-
const fullPath =
|
|
11401
|
+
const fullPath = path16.join(vaultPath2, sourcePath);
|
|
11321
11402
|
const content = await fs11.promises.readFile(fullPath, "utf-8");
|
|
11322
11403
|
const allLines = content.split("\n");
|
|
11323
11404
|
let fmLines = 0;
|
|
@@ -12108,14 +12189,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb3 = ()
|
|
|
12108
12189
|
};
|
|
12109
12190
|
function findSimilarEntity2(target, entities) {
|
|
12110
12191
|
const targetLower = target.toLowerCase();
|
|
12111
|
-
for (const [name,
|
|
12192
|
+
for (const [name, path37] of entities) {
|
|
12112
12193
|
if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
|
|
12113
|
-
return
|
|
12194
|
+
return path37;
|
|
12114
12195
|
}
|
|
12115
12196
|
}
|
|
12116
|
-
for (const [name,
|
|
12197
|
+
for (const [name, path37] of entities) {
|
|
12117
12198
|
if (name.includes(targetLower) || targetLower.includes(name)) {
|
|
12118
|
-
return
|
|
12199
|
+
return path37;
|
|
12119
12200
|
}
|
|
12120
12201
|
}
|
|
12121
12202
|
return void 0;
|
|
@@ -12953,8 +13034,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
12953
13034
|
daily_counts: z4.record(z4.number())
|
|
12954
13035
|
}).describe("Activity summary for the last 7 days")
|
|
12955
13036
|
};
|
|
12956
|
-
function isPeriodicNote3(
|
|
12957
|
-
const filename =
|
|
13037
|
+
function isPeriodicNote3(path37) {
|
|
13038
|
+
const filename = path37.split("/").pop() || "";
|
|
12958
13039
|
const nameWithoutExt = filename.replace(/\.md$/, "");
|
|
12959
13040
|
const patterns = [
|
|
12960
13041
|
/^\d{4}-\d{2}-\d{2}$/,
|
|
@@ -12969,7 +13050,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
12969
13050
|
// YYYY (yearly)
|
|
12970
13051
|
];
|
|
12971
13052
|
const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
|
|
12972
|
-
const folder =
|
|
13053
|
+
const folder = path37.split("/")[0]?.toLowerCase() || "";
|
|
12973
13054
|
return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
|
|
12974
13055
|
}
|
|
12975
13056
|
server2.registerTool(
|
|
@@ -13818,13 +13899,13 @@ function multiHopBackfill(primaryResults, index, stateDb2, config = {}) {
|
|
|
13818
13899
|
candidates.sort((a, b) => b.score - a.score);
|
|
13819
13900
|
return candidates.slice(0, cfg.maxBackfill).map((c) => c.result);
|
|
13820
13901
|
}
|
|
13821
|
-
function scoreCandidate(
|
|
13822
|
-
const note = index.notes.get(
|
|
13902
|
+
function scoreCandidate(path37, index, stateDb2) {
|
|
13903
|
+
const note = index.notes.get(path37);
|
|
13823
13904
|
const decay = recencyDecay(note?.modified);
|
|
13824
13905
|
let hubScore = 1;
|
|
13825
13906
|
if (stateDb2) {
|
|
13826
13907
|
try {
|
|
13827
|
-
const title = note?.title ??
|
|
13908
|
+
const title = note?.title ?? path37.replace(/\.md$/, "").split("/").pop() ?? "";
|
|
13828
13909
|
const entity = getEntityByName3(stateDb2, title);
|
|
13829
13910
|
if (entity) hubScore = entity.hubScore ?? 1;
|
|
13830
13911
|
} catch {
|
|
@@ -14352,7 +14433,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
|
14352
14433
|
|
|
14353
14434
|
// src/tools/read/system.ts
|
|
14354
14435
|
import * as fs14 from "fs";
|
|
14355
|
-
import * as
|
|
14436
|
+
import * as path17 from "path";
|
|
14356
14437
|
import { z as z6 } from "zod";
|
|
14357
14438
|
import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
|
|
14358
14439
|
|
|
@@ -14665,7 +14746,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
14665
14746
|
continue;
|
|
14666
14747
|
}
|
|
14667
14748
|
try {
|
|
14668
|
-
const fullPath =
|
|
14749
|
+
const fullPath = path17.join(vaultPath2, note.path);
|
|
14669
14750
|
const content = await fs14.promises.readFile(fullPath, "utf-8");
|
|
14670
14751
|
const lines = content.split("\n");
|
|
14671
14752
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -14926,7 +15007,7 @@ import { z as z7 } from "zod";
|
|
|
14926
15007
|
|
|
14927
15008
|
// src/tools/read/structure.ts
|
|
14928
15009
|
import * as fs15 from "fs";
|
|
14929
|
-
import * as
|
|
15010
|
+
import * as path18 from "path";
|
|
14930
15011
|
var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
|
|
14931
15012
|
function extractHeadings2(content) {
|
|
14932
15013
|
const lines = content.split("\n");
|
|
@@ -14980,7 +15061,7 @@ function buildSections(headings, totalLines) {
|
|
|
14980
15061
|
async function getNoteStructure(index, notePath, vaultPath2) {
|
|
14981
15062
|
const note = index.notes.get(notePath);
|
|
14982
15063
|
if (!note) return null;
|
|
14983
|
-
const absolutePath =
|
|
15064
|
+
const absolutePath = path18.join(vaultPath2, notePath);
|
|
14984
15065
|
let content;
|
|
14985
15066
|
try {
|
|
14986
15067
|
content = await fs15.promises.readFile(absolutePath, "utf-8");
|
|
@@ -15003,7 +15084,7 @@ async function getNoteStructure(index, notePath, vaultPath2) {
|
|
|
15003
15084
|
async function getSectionContent(index, notePath, headingText, vaultPath2, includeSubheadings = true) {
|
|
15004
15085
|
const note = index.notes.get(notePath);
|
|
15005
15086
|
if (!note) return null;
|
|
15006
|
-
const absolutePath =
|
|
15087
|
+
const absolutePath = path18.join(vaultPath2, notePath);
|
|
15007
15088
|
let content;
|
|
15008
15089
|
try {
|
|
15009
15090
|
content = await fs15.promises.readFile(absolutePath, "utf-8");
|
|
@@ -15045,7 +15126,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
|
|
|
15045
15126
|
const results = [];
|
|
15046
15127
|
for (const note of index.notes.values()) {
|
|
15047
15128
|
if (folder && !note.path.startsWith(folder)) continue;
|
|
15048
|
-
const absolutePath =
|
|
15129
|
+
const absolutePath = path18.join(vaultPath2, note.path);
|
|
15049
15130
|
let content;
|
|
15050
15131
|
try {
|
|
15051
15132
|
content = await fs15.promises.readFile(absolutePath, "utf-8");
|
|
@@ -15080,30 +15161,30 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
15080
15161
|
include_content: z7.boolean().default(true).describe("Include the text content under each top-level section. Set false to get structure only.")
|
|
15081
15162
|
}
|
|
15082
15163
|
},
|
|
15083
|
-
async ({ path:
|
|
15164
|
+
async ({ path: path37, include_content }) => {
|
|
15084
15165
|
const index = getIndex();
|
|
15085
15166
|
const vaultPath2 = getVaultPath();
|
|
15086
|
-
const result = await getNoteStructure(index,
|
|
15167
|
+
const result = await getNoteStructure(index, path37, vaultPath2);
|
|
15087
15168
|
if (!result) {
|
|
15088
15169
|
return {
|
|
15089
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
15170
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path37 }, null, 2) }]
|
|
15090
15171
|
};
|
|
15091
15172
|
}
|
|
15092
15173
|
if (include_content) {
|
|
15093
15174
|
for (const section of result.sections) {
|
|
15094
|
-
const sectionResult = await getSectionContent(index,
|
|
15175
|
+
const sectionResult = await getSectionContent(index, path37, section.heading.text, vaultPath2, true);
|
|
15095
15176
|
if (sectionResult) {
|
|
15096
15177
|
section.content = sectionResult.content;
|
|
15097
15178
|
}
|
|
15098
15179
|
}
|
|
15099
15180
|
}
|
|
15100
|
-
const note = index.notes.get(
|
|
15181
|
+
const note = index.notes.get(path37);
|
|
15101
15182
|
const enriched = { ...result };
|
|
15102
15183
|
if (note) {
|
|
15103
15184
|
enriched.frontmatter = note.frontmatter;
|
|
15104
15185
|
enriched.tags = note.tags;
|
|
15105
15186
|
enriched.aliases = note.aliases;
|
|
15106
|
-
const normalizedPath =
|
|
15187
|
+
const normalizedPath = path37.toLowerCase().replace(/\.md$/, "");
|
|
15107
15188
|
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
15108
15189
|
enriched.backlink_count = backlinks.length;
|
|
15109
15190
|
enriched.outlink_count = note.outlinks.length;
|
|
@@ -15136,15 +15217,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
15136
15217
|
include_subheadings: z7.boolean().default(true).describe("Include content under subheadings")
|
|
15137
15218
|
}
|
|
15138
15219
|
},
|
|
15139
|
-
async ({ path:
|
|
15220
|
+
async ({ path: path37, heading, include_subheadings }) => {
|
|
15140
15221
|
const index = getIndex();
|
|
15141
15222
|
const vaultPath2 = getVaultPath();
|
|
15142
|
-
const result = await getSectionContent(index,
|
|
15223
|
+
const result = await getSectionContent(index, path37, heading, vaultPath2, include_subheadings);
|
|
15143
15224
|
if (!result) {
|
|
15144
15225
|
return {
|
|
15145
15226
|
content: [{ type: "text", text: JSON.stringify({
|
|
15146
15227
|
error: "Section not found",
|
|
15147
|
-
path:
|
|
15228
|
+
path: path37,
|
|
15148
15229
|
heading
|
|
15149
15230
|
}, null, 2) }]
|
|
15150
15231
|
};
|
|
@@ -15198,16 +15279,16 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
15198
15279
|
offset: z7.coerce.number().default(0).describe("Number of results to skip (for pagination)")
|
|
15199
15280
|
}
|
|
15200
15281
|
},
|
|
15201
|
-
async ({ path:
|
|
15282
|
+
async ({ path: path37, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
|
|
15202
15283
|
const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
|
|
15203
15284
|
const index = getIndex();
|
|
15204
15285
|
const vaultPath2 = getVaultPath();
|
|
15205
15286
|
const config = getConfig2();
|
|
15206
|
-
if (
|
|
15207
|
-
const result2 = await getTasksFromNote(index,
|
|
15287
|
+
if (path37) {
|
|
15288
|
+
const result2 = await getTasksFromNote(index, path37, vaultPath2, config.exclude_task_tags || []);
|
|
15208
15289
|
if (!result2) {
|
|
15209
15290
|
return {
|
|
15210
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
15291
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path37 }, null, 2) }]
|
|
15211
15292
|
};
|
|
15212
15293
|
}
|
|
15213
15294
|
let filtered = result2;
|
|
@@ -15217,7 +15298,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
15217
15298
|
const paged2 = filtered.slice(offset, offset + limit);
|
|
15218
15299
|
return {
|
|
15219
15300
|
content: [{ type: "text", text: JSON.stringify({
|
|
15220
|
-
path:
|
|
15301
|
+
path: path37,
|
|
15221
15302
|
total_count: filtered.length,
|
|
15222
15303
|
returned_count: paged2.length,
|
|
15223
15304
|
open: result2.filter((t) => t.status === "open").length,
|
|
@@ -15373,7 +15454,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
15373
15454
|
// src/tools/read/migrations.ts
|
|
15374
15455
|
import { z as z8 } from "zod";
|
|
15375
15456
|
import * as fs16 from "fs/promises";
|
|
15376
|
-
import * as
|
|
15457
|
+
import * as path19 from "path";
|
|
15377
15458
|
import matter2 from "gray-matter";
|
|
15378
15459
|
function getNotesInFolder(index, folder) {
|
|
15379
15460
|
const notes = [];
|
|
@@ -15386,7 +15467,7 @@ function getNotesInFolder(index, folder) {
|
|
|
15386
15467
|
return notes;
|
|
15387
15468
|
}
|
|
15388
15469
|
async function readFileContent(notePath, vaultPath2) {
|
|
15389
|
-
const fullPath =
|
|
15470
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
15390
15471
|
try {
|
|
15391
15472
|
return await fs16.readFile(fullPath, "utf-8");
|
|
15392
15473
|
} catch {
|
|
@@ -15394,7 +15475,7 @@ async function readFileContent(notePath, vaultPath2) {
|
|
|
15394
15475
|
}
|
|
15395
15476
|
}
|
|
15396
15477
|
async function writeFileContent(notePath, vaultPath2, content) {
|
|
15397
|
-
const fullPath =
|
|
15478
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
15398
15479
|
try {
|
|
15399
15480
|
await fs16.writeFile(fullPath, content, "utf-8");
|
|
15400
15481
|
return true;
|
|
@@ -15575,7 +15656,7 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
|
|
|
15575
15656
|
|
|
15576
15657
|
// src/tools/read/graphAnalysis.ts
|
|
15577
15658
|
import fs17 from "node:fs";
|
|
15578
|
-
import
|
|
15659
|
+
import path20 from "node:path";
|
|
15579
15660
|
import { z as z9 } from "zod";
|
|
15580
15661
|
|
|
15581
15662
|
// src/tools/read/schema.ts
|
|
@@ -16101,7 +16182,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb3
|
|
|
16101
16182
|
const scored = allNotes.map((note) => {
|
|
16102
16183
|
let wordCount = 0;
|
|
16103
16184
|
try {
|
|
16104
|
-
const content = fs17.readFileSync(
|
|
16185
|
+
const content = fs17.readFileSync(path20.join(vaultPath2, note.path), "utf-8");
|
|
16105
16186
|
const body = content.replace(/^---[\s\S]*?---\n?/, "");
|
|
16106
16187
|
wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
|
|
16107
16188
|
} catch {
|
|
@@ -16732,12 +16813,12 @@ import { z as z12 } from "zod";
|
|
|
16732
16813
|
|
|
16733
16814
|
// src/tools/read/bidirectional.ts
|
|
16734
16815
|
import * as fs18 from "fs/promises";
|
|
16735
|
-
import * as
|
|
16816
|
+
import * as path21 from "path";
|
|
16736
16817
|
import matter3 from "gray-matter";
|
|
16737
16818
|
var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
|
|
16738
16819
|
var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
16739
16820
|
async function readFileContent2(notePath, vaultPath2) {
|
|
16740
|
-
const fullPath =
|
|
16821
|
+
const fullPath = path21.join(vaultPath2, notePath);
|
|
16741
16822
|
try {
|
|
16742
16823
|
return await fs18.readFile(fullPath, "utf-8");
|
|
16743
16824
|
} catch {
|
|
@@ -16916,10 +16997,10 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
|
|
|
16916
16997
|
|
|
16917
16998
|
// src/tools/read/computed.ts
|
|
16918
16999
|
import * as fs19 from "fs/promises";
|
|
16919
|
-
import * as
|
|
17000
|
+
import * as path22 from "path";
|
|
16920
17001
|
import matter4 from "gray-matter";
|
|
16921
17002
|
async function readFileContent3(notePath, vaultPath2) {
|
|
16922
|
-
const fullPath =
|
|
17003
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
16923
17004
|
try {
|
|
16924
17005
|
return await fs19.readFile(fullPath, "utf-8");
|
|
16925
17006
|
} catch {
|
|
@@ -16927,7 +17008,7 @@ async function readFileContent3(notePath, vaultPath2) {
|
|
|
16927
17008
|
}
|
|
16928
17009
|
}
|
|
16929
17010
|
async function getFileStats(notePath, vaultPath2) {
|
|
16930
|
-
const fullPath =
|
|
17011
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
16931
17012
|
try {
|
|
16932
17013
|
const stats = await fs19.stat(fullPath);
|
|
16933
17014
|
return {
|
|
@@ -17199,7 +17280,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
17199
17280
|
init_writer();
|
|
17200
17281
|
import { z as z13 } from "zod";
|
|
17201
17282
|
import fs23 from "fs/promises";
|
|
17202
|
-
import
|
|
17283
|
+
import path25 from "path";
|
|
17203
17284
|
|
|
17204
17285
|
// src/core/write/validator.ts
|
|
17205
17286
|
var TIMESTAMP_PATTERN = /^\*\*\d{2}:\d{2}\*\*/;
|
|
@@ -17418,7 +17499,7 @@ init_writer();
|
|
|
17418
17499
|
init_wikilinks();
|
|
17419
17500
|
init_wikilinkFeedback();
|
|
17420
17501
|
import fs22 from "fs/promises";
|
|
17421
|
-
import
|
|
17502
|
+
import path24 from "path";
|
|
17422
17503
|
function formatMcpResult(result) {
|
|
17423
17504
|
if (result.tokensEstimate === void 0 || result.tokensEstimate === 0) {
|
|
17424
17505
|
result.tokensEstimate = estimateTokens(result);
|
|
@@ -17466,7 +17547,7 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
|
|
|
17466
17547
|
}
|
|
17467
17548
|
async function getPolicyHint(vaultPath2) {
|
|
17468
17549
|
try {
|
|
17469
|
-
const policiesDir =
|
|
17550
|
+
const policiesDir = path24.join(vaultPath2, ".claude", "policies");
|
|
17470
17551
|
const files = await fs22.readdir(policiesDir);
|
|
17471
17552
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
17472
17553
|
if (yamlFiles.length > 0) {
|
|
@@ -17478,7 +17559,7 @@ async function getPolicyHint(vaultPath2) {
|
|
|
17478
17559
|
return "";
|
|
17479
17560
|
}
|
|
17480
17561
|
async function ensureFileExists(vaultPath2, notePath) {
|
|
17481
|
-
const fullPath =
|
|
17562
|
+
const fullPath = path24.join(vaultPath2, notePath);
|
|
17482
17563
|
try {
|
|
17483
17564
|
await fs22.access(fullPath);
|
|
17484
17565
|
return null;
|
|
@@ -17656,7 +17737,7 @@ async function executeCreateNote(options) {
|
|
|
17656
17737
|
if (!pathCheck.valid) {
|
|
17657
17738
|
return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
|
|
17658
17739
|
}
|
|
17659
|
-
const fullPath =
|
|
17740
|
+
const fullPath = path24.join(vaultPath2, notePath);
|
|
17660
17741
|
let fileExists = false;
|
|
17661
17742
|
try {
|
|
17662
17743
|
await fs22.access(fullPath);
|
|
@@ -17666,7 +17747,7 @@ async function executeCreateNote(options) {
|
|
|
17666
17747
|
if (fileExists && !overwrite) {
|
|
17667
17748
|
return { success: false, result: errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`), filesWritten: [] };
|
|
17668
17749
|
}
|
|
17669
|
-
await fs22.mkdir(
|
|
17750
|
+
await fs22.mkdir(path24.dirname(fullPath), { recursive: true });
|
|
17670
17751
|
const { maybeApplyWikilinks: maybeApplyWikilinks2 } = await Promise.resolve().then(() => (init_wikilinks(), wikilinks_exports));
|
|
17671
17752
|
const { content: processedContent } = maybeApplyWikilinks2(content, skipWikilinks ?? false, notePath);
|
|
17672
17753
|
let finalFrontmatter = frontmatter;
|
|
@@ -17700,7 +17781,7 @@ async function executeDeleteNote(options) {
|
|
|
17700
17781
|
if (!pathCheck.valid) {
|
|
17701
17782
|
return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
|
|
17702
17783
|
}
|
|
17703
|
-
const fullPath =
|
|
17784
|
+
const fullPath = path24.join(vaultPath2, notePath);
|
|
17704
17785
|
try {
|
|
17705
17786
|
await fs22.access(fullPath);
|
|
17706
17787
|
} catch {
|
|
@@ -17724,10 +17805,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
17724
17805
|
if (!validation.valid) {
|
|
17725
17806
|
throw new Error(`Path blocked: ${validation.reason}`);
|
|
17726
17807
|
}
|
|
17727
|
-
const fullPath =
|
|
17728
|
-
await fs23.mkdir(
|
|
17808
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
17809
|
+
await fs23.mkdir(path25.dirname(fullPath), { recursive: true });
|
|
17729
17810
|
const templates = config.templates || {};
|
|
17730
|
-
const filename =
|
|
17811
|
+
const filename = path25.basename(notePath, ".md").toLowerCase();
|
|
17731
17812
|
let templatePath;
|
|
17732
17813
|
const dailyPattern = /^\d{4}-\d{2}-\d{2}/;
|
|
17733
17814
|
const weeklyPattern = /^\d{4}-W\d{2}/;
|
|
@@ -17748,10 +17829,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
17748
17829
|
let templateContent;
|
|
17749
17830
|
if (templatePath) {
|
|
17750
17831
|
try {
|
|
17751
|
-
const absTemplatePath =
|
|
17832
|
+
const absTemplatePath = path25.join(vaultPath2, templatePath);
|
|
17752
17833
|
templateContent = await fs23.readFile(absTemplatePath, "utf-8");
|
|
17753
17834
|
} catch {
|
|
17754
|
-
const title =
|
|
17835
|
+
const title = path25.basename(notePath, ".md");
|
|
17755
17836
|
templateContent = `---
|
|
17756
17837
|
---
|
|
17757
17838
|
|
|
@@ -17760,7 +17841,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
17760
17841
|
templatePath = void 0;
|
|
17761
17842
|
}
|
|
17762
17843
|
} else {
|
|
17763
|
-
const title =
|
|
17844
|
+
const title = path25.basename(notePath, ".md");
|
|
17764
17845
|
templateContent = `---
|
|
17765
17846
|
---
|
|
17766
17847
|
|
|
@@ -17769,7 +17850,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
17769
17850
|
}
|
|
17770
17851
|
const now = /* @__PURE__ */ new Date();
|
|
17771
17852
|
const dateStr = now.toISOString().split("T")[0];
|
|
17772
|
-
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g,
|
|
17853
|
+
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path25.basename(notePath, ".md"));
|
|
17773
17854
|
const matter9 = (await import("gray-matter")).default;
|
|
17774
17855
|
const parsed = matter9(templateContent);
|
|
17775
17856
|
if (!parsed.data.date) {
|
|
@@ -17810,7 +17891,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
17810
17891
|
let noteCreated = false;
|
|
17811
17892
|
let templateUsed;
|
|
17812
17893
|
if (create_if_missing && !dry_run) {
|
|
17813
|
-
const fullPath =
|
|
17894
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
17814
17895
|
try {
|
|
17815
17896
|
await fs23.access(fullPath);
|
|
17816
17897
|
} catch {
|
|
@@ -17821,7 +17902,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
17821
17902
|
}
|
|
17822
17903
|
}
|
|
17823
17904
|
if (create_if_missing && dry_run) {
|
|
17824
|
-
const fullPath =
|
|
17905
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
17825
17906
|
try {
|
|
17826
17907
|
await fs23.access(fullPath);
|
|
17827
17908
|
} catch {
|
|
@@ -18309,7 +18390,7 @@ init_writer();
|
|
|
18309
18390
|
init_wikilinks();
|
|
18310
18391
|
import { z as z16 } from "zod";
|
|
18311
18392
|
import fs24 from "fs/promises";
|
|
18312
|
-
import
|
|
18393
|
+
import path26 from "path";
|
|
18313
18394
|
function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
18314
18395
|
server2.tool(
|
|
18315
18396
|
"vault_create_note",
|
|
@@ -18335,23 +18416,23 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
|
18335
18416
|
if (!validatePath(vaultPath2, notePath)) {
|
|
18336
18417
|
return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
|
|
18337
18418
|
}
|
|
18338
|
-
const fullPath =
|
|
18419
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
18339
18420
|
const existsCheck = await ensureFileExists(vaultPath2, notePath);
|
|
18340
18421
|
if (existsCheck === null && !overwrite) {
|
|
18341
18422
|
return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
|
|
18342
18423
|
}
|
|
18343
|
-
const dir =
|
|
18424
|
+
const dir = path26.dirname(fullPath);
|
|
18344
18425
|
await fs24.mkdir(dir, { recursive: true });
|
|
18345
18426
|
let effectiveContent = content;
|
|
18346
18427
|
let effectiveFrontmatter = frontmatter;
|
|
18347
18428
|
if (template) {
|
|
18348
|
-
const templatePath =
|
|
18429
|
+
const templatePath = path26.join(vaultPath2, template);
|
|
18349
18430
|
try {
|
|
18350
18431
|
const raw = await fs24.readFile(templatePath, "utf-8");
|
|
18351
18432
|
const matter9 = (await import("gray-matter")).default;
|
|
18352
18433
|
const parsed = matter9(raw);
|
|
18353
18434
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
18354
|
-
const title =
|
|
18435
|
+
const title = path26.basename(notePath, ".md");
|
|
18355
18436
|
let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
|
|
18356
18437
|
if (content) {
|
|
18357
18438
|
templateContent = templateContent.trimEnd() + "\n\n" + content;
|
|
@@ -18370,7 +18451,7 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
|
18370
18451
|
effectiveFrontmatter.created = now.toISOString();
|
|
18371
18452
|
}
|
|
18372
18453
|
const warnings = [];
|
|
18373
|
-
const noteName =
|
|
18454
|
+
const noteName = path26.basename(notePath, ".md");
|
|
18374
18455
|
const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
|
|
18375
18456
|
const preflight = await checkPreflightSimilarity(noteName);
|
|
18376
18457
|
if (preflight.existingEntity) {
|
|
@@ -18511,7 +18592,7 @@ ${sources}`;
|
|
|
18511
18592
|
}
|
|
18512
18593
|
return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
|
|
18513
18594
|
}
|
|
18514
|
-
const fullPath =
|
|
18595
|
+
const fullPath = path26.join(vaultPath2, notePath);
|
|
18515
18596
|
await fs24.unlink(fullPath);
|
|
18516
18597
|
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
|
|
18517
18598
|
const message = backlinkWarning ? `Deleted note: ${notePath}
|
|
@@ -18533,7 +18614,7 @@ init_git();
|
|
|
18533
18614
|
init_wikilinks();
|
|
18534
18615
|
import { z as z17 } from "zod";
|
|
18535
18616
|
import fs25 from "fs/promises";
|
|
18536
|
-
import
|
|
18617
|
+
import path27 from "path";
|
|
18537
18618
|
import matter6 from "gray-matter";
|
|
18538
18619
|
function escapeRegex(str) {
|
|
18539
18620
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -18552,7 +18633,7 @@ function extractWikilinks2(content) {
|
|
|
18552
18633
|
return wikilinks;
|
|
18553
18634
|
}
|
|
18554
18635
|
function getTitleFromPath(filePath) {
|
|
18555
|
-
return
|
|
18636
|
+
return path27.basename(filePath, ".md");
|
|
18556
18637
|
}
|
|
18557
18638
|
async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
18558
18639
|
const results = [];
|
|
@@ -18561,7 +18642,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
18561
18642
|
const files = [];
|
|
18562
18643
|
const entries = await fs25.readdir(dir, { withFileTypes: true });
|
|
18563
18644
|
for (const entry of entries) {
|
|
18564
|
-
const fullPath =
|
|
18645
|
+
const fullPath = path27.join(dir, entry.name);
|
|
18565
18646
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
18566
18647
|
files.push(...await scanDir(fullPath));
|
|
18567
18648
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -18572,7 +18653,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
18572
18653
|
}
|
|
18573
18654
|
const allFiles = await scanDir(vaultPath2);
|
|
18574
18655
|
for (const filePath of allFiles) {
|
|
18575
|
-
const relativePath =
|
|
18656
|
+
const relativePath = path27.relative(vaultPath2, filePath);
|
|
18576
18657
|
const content = await fs25.readFile(filePath, "utf-8");
|
|
18577
18658
|
const wikilinks = extractWikilinks2(content);
|
|
18578
18659
|
const matchingLinks = [];
|
|
@@ -18659,8 +18740,8 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
18659
18740
|
};
|
|
18660
18741
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
18661
18742
|
}
|
|
18662
|
-
const oldFullPath =
|
|
18663
|
-
const newFullPath =
|
|
18743
|
+
const oldFullPath = path27.join(vaultPath2, oldPath);
|
|
18744
|
+
const newFullPath = path27.join(vaultPath2, newPath);
|
|
18664
18745
|
try {
|
|
18665
18746
|
await fs25.access(oldFullPath);
|
|
18666
18747
|
} catch {
|
|
@@ -18741,7 +18822,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
18741
18822
|
};
|
|
18742
18823
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
18743
18824
|
}
|
|
18744
|
-
const destDir =
|
|
18825
|
+
const destDir = path27.dirname(newFullPath);
|
|
18745
18826
|
await fs25.mkdir(destDir, { recursive: true });
|
|
18746
18827
|
await fs25.rename(oldFullPath, newFullPath);
|
|
18747
18828
|
let gitCommit;
|
|
@@ -18817,10 +18898,10 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
18817
18898
|
if (sanitizedTitle !== newTitle) {
|
|
18818
18899
|
console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
|
|
18819
18900
|
}
|
|
18820
|
-
const fullPath =
|
|
18821
|
-
const dir =
|
|
18822
|
-
const newPath = dir === "." ? `${sanitizedTitle}.md` :
|
|
18823
|
-
const newFullPath =
|
|
18901
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
18902
|
+
const dir = path27.dirname(notePath);
|
|
18903
|
+
const newPath = dir === "." ? `${sanitizedTitle}.md` : path27.join(dir, `${sanitizedTitle}.md`);
|
|
18904
|
+
const newFullPath = path27.join(vaultPath2, newPath);
|
|
18824
18905
|
try {
|
|
18825
18906
|
await fs25.access(fullPath);
|
|
18826
18907
|
} catch {
|
|
@@ -19351,7 +19432,7 @@ init_schema();
|
|
|
19351
19432
|
// src/core/write/policy/parser.ts
|
|
19352
19433
|
init_schema();
|
|
19353
19434
|
import fs27 from "fs/promises";
|
|
19354
|
-
import
|
|
19435
|
+
import path28 from "path";
|
|
19355
19436
|
import matter7 from "gray-matter";
|
|
19356
19437
|
function parseYaml(content) {
|
|
19357
19438
|
const parsed = matter7(`---
|
|
@@ -19400,13 +19481,13 @@ async function loadPolicyFile(filePath) {
|
|
|
19400
19481
|
}
|
|
19401
19482
|
}
|
|
19402
19483
|
async function loadPolicy(vaultPath2, policyName) {
|
|
19403
|
-
const policiesDir =
|
|
19404
|
-
const policyPath =
|
|
19484
|
+
const policiesDir = path28.join(vaultPath2, ".claude", "policies");
|
|
19485
|
+
const policyPath = path28.join(policiesDir, `${policyName}.yaml`);
|
|
19405
19486
|
try {
|
|
19406
19487
|
await fs27.access(policyPath);
|
|
19407
19488
|
return loadPolicyFile(policyPath);
|
|
19408
19489
|
} catch {
|
|
19409
|
-
const ymlPath =
|
|
19490
|
+
const ymlPath = path28.join(policiesDir, `${policyName}.yml`);
|
|
19410
19491
|
try {
|
|
19411
19492
|
await fs27.access(ymlPath);
|
|
19412
19493
|
return loadPolicyFile(ymlPath);
|
|
@@ -19549,7 +19630,7 @@ init_writer();
|
|
|
19549
19630
|
init_git();
|
|
19550
19631
|
init_wikilinks();
|
|
19551
19632
|
import fs29 from "fs/promises";
|
|
19552
|
-
import
|
|
19633
|
+
import path30 from "path";
|
|
19553
19634
|
init_constants();
|
|
19554
19635
|
async function executeStep(step, vaultPath2, context, conditionResults, searchFn) {
|
|
19555
19636
|
const { execute, reason } = shouldStepExecute(step.when, conditionResults);
|
|
@@ -19757,7 +19838,7 @@ async function executeToggleTask(params, vaultPath2) {
|
|
|
19757
19838
|
const notePath = String(params.path || "");
|
|
19758
19839
|
const task = String(params.task || "");
|
|
19759
19840
|
const section = params.section ? String(params.section) : void 0;
|
|
19760
|
-
const fullPath =
|
|
19841
|
+
const fullPath = path30.join(vaultPath2, notePath);
|
|
19761
19842
|
try {
|
|
19762
19843
|
await fs29.access(fullPath);
|
|
19763
19844
|
} catch {
|
|
@@ -20040,7 +20121,7 @@ async function rollbackChanges(vaultPath2, originalContents, filesModified) {
|
|
|
20040
20121
|
const pathCheck = await validatePathSecure(vaultPath2, filePath);
|
|
20041
20122
|
if (!pathCheck.valid) continue;
|
|
20042
20123
|
const original = originalContents.get(filePath);
|
|
20043
|
-
const fullPath =
|
|
20124
|
+
const fullPath = path30.join(vaultPath2, filePath);
|
|
20044
20125
|
if (original === null) {
|
|
20045
20126
|
try {
|
|
20046
20127
|
await fs29.unlink(fullPath);
|
|
@@ -20095,9 +20176,9 @@ async function previewPolicy(policy, vaultPath2, variables) {
|
|
|
20095
20176
|
|
|
20096
20177
|
// src/core/write/policy/storage.ts
|
|
20097
20178
|
import fs30 from "fs/promises";
|
|
20098
|
-
import
|
|
20179
|
+
import path31 from "path";
|
|
20099
20180
|
function getPoliciesDir(vaultPath2) {
|
|
20100
|
-
return
|
|
20181
|
+
return path31.join(vaultPath2, ".claude", "policies");
|
|
20101
20182
|
}
|
|
20102
20183
|
async function ensurePoliciesDir(vaultPath2) {
|
|
20103
20184
|
const dir = getPoliciesDir(vaultPath2);
|
|
@@ -20112,7 +20193,7 @@ async function listPolicies(vaultPath2) {
|
|
|
20112
20193
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
|
20113
20194
|
continue;
|
|
20114
20195
|
}
|
|
20115
|
-
const filePath =
|
|
20196
|
+
const filePath = path31.join(dir, file);
|
|
20116
20197
|
const stat5 = await fs30.stat(filePath);
|
|
20117
20198
|
const content = await fs30.readFile(filePath, "utf-8");
|
|
20118
20199
|
const metadata = extractPolicyMetadata(content);
|
|
@@ -20137,7 +20218,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
|
|
|
20137
20218
|
const dir = getPoliciesDir(vaultPath2);
|
|
20138
20219
|
await ensurePoliciesDir(vaultPath2);
|
|
20139
20220
|
const filename = `${policyName}.yaml`;
|
|
20140
|
-
const filePath =
|
|
20221
|
+
const filePath = path31.join(dir, filename);
|
|
20141
20222
|
if (!overwrite) {
|
|
20142
20223
|
try {
|
|
20143
20224
|
await fs30.access(filePath);
|
|
@@ -20683,7 +20764,7 @@ import { z as z22 } from "zod";
|
|
|
20683
20764
|
|
|
20684
20765
|
// src/core/write/tagRename.ts
|
|
20685
20766
|
import * as fs31 from "fs/promises";
|
|
20686
|
-
import * as
|
|
20767
|
+
import * as path32 from "path";
|
|
20687
20768
|
import matter8 from "gray-matter";
|
|
20688
20769
|
import { getProtectedZones as getProtectedZones2 } from "@velvetmonkey/vault-core";
|
|
20689
20770
|
function getNotesInFolder3(index, folder) {
|
|
@@ -20789,7 +20870,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
|
|
|
20789
20870
|
const previews = [];
|
|
20790
20871
|
let totalChanges = 0;
|
|
20791
20872
|
for (const note of affectedNotes) {
|
|
20792
|
-
const fullPath =
|
|
20873
|
+
const fullPath = path32.join(vaultPath2, note.path);
|
|
20793
20874
|
let fileContent;
|
|
20794
20875
|
try {
|
|
20795
20876
|
fileContent = await fs31.readFile(fullPath, "utf-8");
|
|
@@ -22097,7 +22178,7 @@ init_wikilinks();
|
|
|
22097
22178
|
init_wikilinkFeedback();
|
|
22098
22179
|
import { z as z29 } from "zod";
|
|
22099
22180
|
import * as fs32 from "fs/promises";
|
|
22100
|
-
import * as
|
|
22181
|
+
import * as path33 from "path";
|
|
22101
22182
|
import { scanVaultEntities as scanVaultEntities3, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
|
|
22102
22183
|
init_embeddings();
|
|
22103
22184
|
function hasSkipWikilinks(content) {
|
|
@@ -22113,13 +22194,13 @@ async function collectMarkdownFiles(dirPath, basePath, excludeFolders) {
|
|
|
22113
22194
|
const entries = await fs32.readdir(dirPath, { withFileTypes: true });
|
|
22114
22195
|
for (const entry of entries) {
|
|
22115
22196
|
if (entry.name.startsWith(".")) continue;
|
|
22116
|
-
const fullPath =
|
|
22197
|
+
const fullPath = path33.join(dirPath, entry.name);
|
|
22117
22198
|
if (entry.isDirectory()) {
|
|
22118
22199
|
if (excludeFolders.some((f) => entry.name.toLowerCase() === f.toLowerCase())) continue;
|
|
22119
22200
|
const sub = await collectMarkdownFiles(fullPath, basePath, excludeFolders);
|
|
22120
22201
|
results.push(...sub);
|
|
22121
22202
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
22122
|
-
results.push(
|
|
22203
|
+
results.push(path33.relative(basePath, fullPath));
|
|
22123
22204
|
}
|
|
22124
22205
|
}
|
|
22125
22206
|
} catch {
|
|
@@ -22149,7 +22230,7 @@ var EXCLUDE_FOLDERS = [
|
|
|
22149
22230
|
];
|
|
22150
22231
|
function buildStatusReport(stateDb2, vaultPath2) {
|
|
22151
22232
|
const recommendations = [];
|
|
22152
|
-
const dbPath =
|
|
22233
|
+
const dbPath = path33.join(vaultPath2, ".flywheel", "state.db");
|
|
22153
22234
|
const statedbExists = stateDb2 !== null;
|
|
22154
22235
|
if (!statedbExists) {
|
|
22155
22236
|
recommendations.push("StateDb not initialized \u2014 server needs restart");
|
|
@@ -22275,7 +22356,7 @@ async function executeRun(stateDb2, vaultPath2) {
|
|
|
22275
22356
|
const allFiles = await collectMarkdownFiles(vaultPath2, vaultPath2, EXCLUDE_FOLDERS);
|
|
22276
22357
|
let eligible = 0;
|
|
22277
22358
|
for (const relativePath of allFiles) {
|
|
22278
|
-
const fullPath =
|
|
22359
|
+
const fullPath = path33.join(vaultPath2, relativePath);
|
|
22279
22360
|
let content;
|
|
22280
22361
|
try {
|
|
22281
22362
|
content = await fs32.readFile(fullPath, "utf-8");
|
|
@@ -22333,7 +22414,7 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
|
|
|
22333
22414
|
const eligible = [];
|
|
22334
22415
|
let notesSkipped = 0;
|
|
22335
22416
|
for (const relativePath of allFiles) {
|
|
22336
|
-
const fullPath =
|
|
22417
|
+
const fullPath = path33.join(vaultPath2, relativePath);
|
|
22337
22418
|
let content;
|
|
22338
22419
|
try {
|
|
22339
22420
|
content = await fs32.readFile(fullPath, "utf-8");
|
|
@@ -22363,7 +22444,7 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
|
|
|
22363
22444
|
match_count: result.linksAdded
|
|
22364
22445
|
});
|
|
22365
22446
|
if (!dryRun) {
|
|
22366
|
-
const fullPath =
|
|
22447
|
+
const fullPath = path33.join(vaultPath2, relativePath);
|
|
22367
22448
|
await fs32.writeFile(fullPath, result.content, "utf-8");
|
|
22368
22449
|
notesModified++;
|
|
22369
22450
|
if (stateDb2) {
|
|
@@ -22596,7 +22677,7 @@ import { z as z32 } from "zod";
|
|
|
22596
22677
|
// src/core/read/similarity.ts
|
|
22597
22678
|
init_embeddings();
|
|
22598
22679
|
import * as fs33 from "fs";
|
|
22599
|
-
import * as
|
|
22680
|
+
import * as path34 from "path";
|
|
22600
22681
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
22601
22682
|
"the",
|
|
22602
22683
|
"be",
|
|
@@ -22733,7 +22814,7 @@ function extractKeyTerms(content, maxTerms = 15) {
|
|
|
22733
22814
|
}
|
|
22734
22815
|
function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
|
|
22735
22816
|
const limit = options.limit ?? 10;
|
|
22736
|
-
const absPath =
|
|
22817
|
+
const absPath = path34.join(vaultPath2, sourcePath);
|
|
22737
22818
|
let content;
|
|
22738
22819
|
try {
|
|
22739
22820
|
content = fs33.readFileSync(absPath, "utf-8");
|
|
@@ -22875,7 +22956,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
|
22875
22956
|
diversity: z32.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
|
|
22876
22957
|
}
|
|
22877
22958
|
},
|
|
22878
|
-
async ({ path:
|
|
22959
|
+
async ({ path: path37, limit, diversity }) => {
|
|
22879
22960
|
const index = getIndex();
|
|
22880
22961
|
const vaultPath2 = getVaultPath();
|
|
22881
22962
|
const stateDb2 = getStateDb3();
|
|
@@ -22884,10 +22965,10 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
|
22884
22965
|
content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
|
|
22885
22966
|
};
|
|
22886
22967
|
}
|
|
22887
|
-
if (!index.notes.has(
|
|
22968
|
+
if (!index.notes.has(path37)) {
|
|
22888
22969
|
return {
|
|
22889
22970
|
content: [{ type: "text", text: JSON.stringify({
|
|
22890
|
-
error: `Note not found: ${
|
|
22971
|
+
error: `Note not found: ${path37}`,
|
|
22891
22972
|
hint: "Use the full relative path including .md extension"
|
|
22892
22973
|
}, null, 2) }]
|
|
22893
22974
|
};
|
|
@@ -22899,12 +22980,12 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb3) {
|
|
|
22899
22980
|
};
|
|
22900
22981
|
const useHybrid = hasEmbeddingsIndex();
|
|
22901
22982
|
const method = useHybrid ? "hybrid" : "bm25";
|
|
22902
|
-
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index,
|
|
22983
|
+
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path37, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path37, opts);
|
|
22903
22984
|
return {
|
|
22904
22985
|
content: [{
|
|
22905
22986
|
type: "text",
|
|
22906
22987
|
text: JSON.stringify({
|
|
22907
|
-
source:
|
|
22988
|
+
source: path37,
|
|
22908
22989
|
method,
|
|
22909
22990
|
count: results.length,
|
|
22910
22991
|
similar: results
|
|
@@ -24136,7 +24217,7 @@ function registerVaultResources(server2, getIndex) {
|
|
|
24136
24217
|
// src/tool-registry.ts
|
|
24137
24218
|
var __trFilename = fileURLToPath(import.meta.url);
|
|
24138
24219
|
var __trDirname = dirname5(__trFilename);
|
|
24139
|
-
var trPkg = JSON.parse(readFileSync5(
|
|
24220
|
+
var trPkg = JSON.parse(readFileSync5(join19(__trDirname, "../package.json"), "utf-8"));
|
|
24140
24221
|
function applyToolGating(targetServer, categories, getDb4, registry, getVaultPath, vaultCallbacks) {
|
|
24141
24222
|
let _registered = 0;
|
|
24142
24223
|
let _skipped = 0;
|
|
@@ -24201,7 +24282,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
24201
24282
|
let totalBytes = 0;
|
|
24202
24283
|
for (const p of notePaths) {
|
|
24203
24284
|
try {
|
|
24204
|
-
totalBytes +=
|
|
24285
|
+
totalBytes += statSync6(path35.join(vp, p)).size;
|
|
24205
24286
|
} catch {
|
|
24206
24287
|
}
|
|
24207
24288
|
}
|
|
@@ -24422,7 +24503,7 @@ function registerAllTools(targetServer, ctx) {
|
|
|
24422
24503
|
// src/index.ts
|
|
24423
24504
|
var __filename = fileURLToPath2(import.meta.url);
|
|
24424
24505
|
var __dirname = dirname7(__filename);
|
|
24425
|
-
var pkg = JSON.parse(readFileSync6(
|
|
24506
|
+
var pkg = JSON.parse(readFileSync6(join21(__dirname, "../package.json"), "utf-8"));
|
|
24426
24507
|
var vaultPath = process.env.PROJECT_PATH || process.env.VAULT_PATH || findVaultRoot();
|
|
24427
24508
|
var resolvedVaultPath;
|
|
24428
24509
|
try {
|
|
@@ -24774,7 +24855,7 @@ async function buildStartupCatchupBatch(vaultPath2, sinceMs) {
|
|
|
24774
24855
|
return;
|
|
24775
24856
|
}
|
|
24776
24857
|
for (const entry of entries) {
|
|
24777
|
-
const fullPath =
|
|
24858
|
+
const fullPath = path36.join(dir, entry.name);
|
|
24778
24859
|
if (entry.isDirectory()) {
|
|
24779
24860
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
24780
24861
|
await scanDir(fullPath);
|
|
@@ -24784,7 +24865,7 @@ async function buildStartupCatchupBatch(vaultPath2, sinceMs) {
|
|
|
24784
24865
|
if (stat5.mtimeMs > sinceMs) {
|
|
24785
24866
|
events.push({
|
|
24786
24867
|
type: "upsert",
|
|
24787
|
-
path:
|
|
24868
|
+
path: path36.relative(vaultPath2, fullPath),
|
|
24788
24869
|
originalEvents: []
|
|
24789
24870
|
});
|
|
24790
24871
|
}
|
|
@@ -24981,8 +25062,8 @@ async function runPostIndexWork(ctx) {
|
|
|
24981
25062
|
}
|
|
24982
25063
|
} catch {
|
|
24983
25064
|
try {
|
|
24984
|
-
const dir =
|
|
24985
|
-
const base =
|
|
25065
|
+
const dir = path36.dirname(rawPath);
|
|
25066
|
+
const base = path36.basename(rawPath);
|
|
24986
25067
|
const resolvedDir = realpathSync(dir).replace(/\\/g, "/");
|
|
24987
25068
|
for (const prefix of vaultPrefixes) {
|
|
24988
25069
|
if (resolvedDir.startsWith(prefix + "/") || resolvedDir === prefix) {
|
|
@@ -25014,7 +25095,7 @@ async function runPostIndexWork(ctx) {
|
|
|
25014
25095
|
continue;
|
|
25015
25096
|
}
|
|
25016
25097
|
try {
|
|
25017
|
-
const content = await fs34.readFile(
|
|
25098
|
+
const content = await fs34.readFile(path36.join(vp, event.path), "utf-8");
|
|
25018
25099
|
const hash = createHash3("sha256").update(content).digest("hex").slice(0, 16);
|
|
25019
25100
|
if (lastContentHashes.get(event.path) === hash) {
|
|
25020
25101
|
serverLog("watcher", `Hash unchanged, skipping: ${event.path}`);
|