@velvetmonkey/flywheel-memory 2.0.124 → 2.0.126
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 +433 -329
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -895,17 +895,17 @@ function updateSuppressionList(stateDb2, now) {
|
|
|
895
895
|
`DELETE FROM wikilink_suppressions
|
|
896
896
|
WHERE datetime(updated_at, '+' || ? || ' days') <= datetime('now')`
|
|
897
897
|
).run(SUPPRESSION_TTL_DAYS);
|
|
898
|
-
for (const
|
|
899
|
-
const posteriorMean = computePosteriorMean(
|
|
900
|
-
const totalObs = PRIOR_ALPHA +
|
|
898
|
+
for (const stat5 of weightedStats) {
|
|
899
|
+
const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp);
|
|
900
|
+
const totalObs = PRIOR_ALPHA + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
|
|
901
901
|
if (totalObs < SUPPRESSION_MIN_OBSERVATIONS) {
|
|
902
902
|
continue;
|
|
903
903
|
}
|
|
904
904
|
if (posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD) {
|
|
905
|
-
upsert.run(
|
|
905
|
+
upsert.run(stat5.entity, 1 - posteriorMean);
|
|
906
906
|
updated++;
|
|
907
907
|
} else {
|
|
908
|
-
remove.run(
|
|
908
|
+
remove.run(stat5.entity);
|
|
909
909
|
}
|
|
910
910
|
}
|
|
911
911
|
});
|
|
@@ -974,31 +974,31 @@ function getAllFeedbackBoosts(stateDb2, folder, now) {
|
|
|
974
974
|
if (folder !== void 0) {
|
|
975
975
|
folderStatsMap = /* @__PURE__ */ new Map();
|
|
976
976
|
for (const gs of globalStats) {
|
|
977
|
-
const
|
|
978
|
-
if (
|
|
977
|
+
const fs34 = getWeightedFolderStats(stateDb2, gs.entity, folder, now);
|
|
978
|
+
if (fs34.rawTotal >= FEEDBACK_BOOST_MIN_SAMPLES) {
|
|
979
979
|
folderStatsMap.set(gs.entity, {
|
|
980
|
-
weightedAccuracy:
|
|
981
|
-
rawCount:
|
|
980
|
+
weightedAccuracy: fs34.weightedAccuracy,
|
|
981
|
+
rawCount: fs34.rawTotal
|
|
982
982
|
});
|
|
983
983
|
}
|
|
984
984
|
}
|
|
985
985
|
}
|
|
986
986
|
const boosts = /* @__PURE__ */ new Map();
|
|
987
|
-
for (const
|
|
988
|
-
if (
|
|
987
|
+
for (const stat5 of globalStats) {
|
|
988
|
+
if (stat5.rawTotal < FEEDBACK_BOOST_MIN_SAMPLES) continue;
|
|
989
989
|
let accuracy;
|
|
990
990
|
let sampleCount;
|
|
991
|
-
const
|
|
992
|
-
if (
|
|
993
|
-
accuracy =
|
|
994
|
-
sampleCount =
|
|
991
|
+
const fs34 = folderStatsMap?.get(stat5.entity);
|
|
992
|
+
if (fs34 && fs34.rawCount >= FEEDBACK_BOOST_MIN_SAMPLES) {
|
|
993
|
+
accuracy = fs34.weightedAccuracy;
|
|
994
|
+
sampleCount = fs34.rawCount;
|
|
995
995
|
} else {
|
|
996
|
-
accuracy =
|
|
997
|
-
sampleCount =
|
|
996
|
+
accuracy = stat5.weightedAccuracy;
|
|
997
|
+
sampleCount = stat5.rawTotal;
|
|
998
998
|
}
|
|
999
999
|
const boost = computeBoostFromAccuracy(accuracy, sampleCount);
|
|
1000
1000
|
if (boost !== 0) {
|
|
1001
|
-
boosts.set(
|
|
1001
|
+
boosts.set(stat5.entity, boost);
|
|
1002
1002
|
}
|
|
1003
1003
|
}
|
|
1004
1004
|
return boosts;
|
|
@@ -1006,13 +1006,13 @@ function getAllFeedbackBoosts(stateDb2, folder, now) {
|
|
|
1006
1006
|
function getAllSuppressionPenalties(stateDb2, now) {
|
|
1007
1007
|
const penalties = /* @__PURE__ */ new Map();
|
|
1008
1008
|
const weightedStats = getWeightedEntityStats(stateDb2, now);
|
|
1009
|
-
for (const
|
|
1010
|
-
const posteriorMean = computePosteriorMean(
|
|
1011
|
-
const totalObs = PRIOR_ALPHA +
|
|
1009
|
+
for (const stat5 of weightedStats) {
|
|
1010
|
+
const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp);
|
|
1011
|
+
const totalObs = PRIOR_ALPHA + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
|
|
1012
1012
|
if (totalObs >= SUPPRESSION_MIN_OBSERVATIONS && posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD) {
|
|
1013
1013
|
const penalty = Math.round(MAX_SUPPRESSION_PENALTY * (1 - posteriorMean / SUPPRESSION_POSTERIOR_THRESHOLD));
|
|
1014
1014
|
if (penalty < 0) {
|
|
1015
|
-
penalties.set(
|
|
1015
|
+
penalties.set(stat5.entity, penalty);
|
|
1016
1016
|
}
|
|
1017
1017
|
}
|
|
1018
1018
|
}
|
|
@@ -1554,8 +1554,8 @@ function clearLastMutationCommit() {
|
|
|
1554
1554
|
async function checkGitLock(vaultPath2) {
|
|
1555
1555
|
const lockPath = path8.join(vaultPath2, ".git/index.lock");
|
|
1556
1556
|
try {
|
|
1557
|
-
const
|
|
1558
|
-
const ageMs = Date.now() -
|
|
1557
|
+
const stat5 = await fs6.stat(lockPath);
|
|
1558
|
+
const ageMs = Date.now() - stat5.mtimeMs;
|
|
1559
1559
|
return {
|
|
1560
1560
|
locked: true,
|
|
1561
1561
|
stale: ageMs > STALE_LOCK_THRESHOLD_MS,
|
|
@@ -1577,8 +1577,8 @@ async function isGitRepo(vaultPath2) {
|
|
|
1577
1577
|
async function checkLockFile(vaultPath2) {
|
|
1578
1578
|
const lockPath = path8.join(vaultPath2, ".git/index.lock");
|
|
1579
1579
|
try {
|
|
1580
|
-
const
|
|
1581
|
-
const ageMs = Date.now() -
|
|
1580
|
+
const stat5 = await fs6.stat(lockPath);
|
|
1581
|
+
const ageMs = Date.now() - stat5.mtimeMs;
|
|
1582
1582
|
return { stale: ageMs > STALE_LOCK_THRESHOLD_MS, ageMs };
|
|
1583
1583
|
} catch {
|
|
1584
1584
|
return null;
|
|
@@ -3282,6 +3282,7 @@ var init_edgeWeights = __esm({
|
|
|
3282
3282
|
// src/core/write/wikilinks.ts
|
|
3283
3283
|
var wikilinks_exports = {};
|
|
3284
3284
|
__export(wikilinks_exports, {
|
|
3285
|
+
applyProactiveSuggestions: () => applyProactiveSuggestions,
|
|
3285
3286
|
checkAndRefreshIfStale: () => checkAndRefreshIfStale,
|
|
3286
3287
|
checkPreflightSimilarity: () => checkPreflightSimilarity,
|
|
3287
3288
|
detectAliasCollisions: () => detectAliasCollisions,
|
|
@@ -3289,6 +3290,7 @@ __export(wikilinks_exports, {
|
|
|
3289
3290
|
getCooccurrenceIndex: () => getCooccurrenceIndex,
|
|
3290
3291
|
getEntityIndex: () => getEntityIndex,
|
|
3291
3292
|
getEntityIndexStats: () => getEntityIndexStats,
|
|
3293
|
+
getNoteContext: () => getNoteContext,
|
|
3292
3294
|
getWikilinkStrictness: () => getWikilinkStrictness,
|
|
3293
3295
|
getWriteStateDb: () => getWriteStateDb,
|
|
3294
3296
|
initializeEntityIndex: () => initializeEntityIndex,
|
|
@@ -3317,6 +3319,8 @@ import {
|
|
|
3317
3319
|
getEntitiesByAlias,
|
|
3318
3320
|
searchEntities as searchEntitiesDb
|
|
3319
3321
|
} from "@velvetmonkey/vault-core";
|
|
3322
|
+
import path11 from "path";
|
|
3323
|
+
import * as fs7 from "fs/promises";
|
|
3320
3324
|
function setWriteStateDb(stateDb2) {
|
|
3321
3325
|
moduleStateDb5 = stateDb2;
|
|
3322
3326
|
setGitStateDb(stateDb2);
|
|
@@ -4233,6 +4237,76 @@ async function checkPreflightSimilarity(noteName) {
|
|
|
4233
4237
|
}
|
|
4234
4238
|
return result;
|
|
4235
4239
|
}
|
|
4240
|
+
async function applyProactiveSuggestions(filePath, vaultPath2, suggestions, config) {
|
|
4241
|
+
const stateDb2 = getWriteStateDb();
|
|
4242
|
+
const candidates = suggestions.filter((s) => s.score >= config.minScore && s.confidence === "high").slice(0, config.maxPerFile);
|
|
4243
|
+
if (candidates.length === 0) {
|
|
4244
|
+
return { applied: [], skipped: [] };
|
|
4245
|
+
}
|
|
4246
|
+
const fullPath = path11.join(vaultPath2, filePath);
|
|
4247
|
+
try {
|
|
4248
|
+
const stat5 = await fs7.stat(fullPath);
|
|
4249
|
+
if (Date.now() - stat5.mtimeMs < 3e4) {
|
|
4250
|
+
return { applied: [], skipped: candidates.map((c) => c.entity) };
|
|
4251
|
+
}
|
|
4252
|
+
} catch {
|
|
4253
|
+
return { applied: [], skipped: candidates.map((c) => c.entity) };
|
|
4254
|
+
}
|
|
4255
|
+
let content;
|
|
4256
|
+
try {
|
|
4257
|
+
content = await fs7.readFile(fullPath, "utf-8");
|
|
4258
|
+
} catch {
|
|
4259
|
+
return { applied: [], skipped: candidates.map((c) => c.entity) };
|
|
4260
|
+
}
|
|
4261
|
+
const entityObjects = [];
|
|
4262
|
+
for (const candidate of candidates) {
|
|
4263
|
+
if (stateDb2 && isSuppressed(stateDb2, candidate.entity)) continue;
|
|
4264
|
+
if (stateDb2) {
|
|
4265
|
+
const entityObj = getEntityByName(stateDb2, candidate.entity);
|
|
4266
|
+
if (entityObj) {
|
|
4267
|
+
entityObjects.push({
|
|
4268
|
+
name: entityObj.name,
|
|
4269
|
+
path: entityObj.path,
|
|
4270
|
+
aliases: entityObj.aliases ?? []
|
|
4271
|
+
});
|
|
4272
|
+
continue;
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
entityObjects.push(candidate.entity);
|
|
4276
|
+
}
|
|
4277
|
+
if (entityObjects.length === 0) {
|
|
4278
|
+
return { applied: [], skipped: candidates.map((c) => c.entity) };
|
|
4279
|
+
}
|
|
4280
|
+
const result = applyWikilinks(content, entityObjects, {
|
|
4281
|
+
firstOccurrenceOnly: true,
|
|
4282
|
+
caseInsensitive: true
|
|
4283
|
+
});
|
|
4284
|
+
if (result.linksAdded === 0) {
|
|
4285
|
+
return { applied: [], skipped: candidates.map((c) => c.entity) };
|
|
4286
|
+
}
|
|
4287
|
+
try {
|
|
4288
|
+
await fs7.writeFile(fullPath, result.content, "utf-8");
|
|
4289
|
+
} catch {
|
|
4290
|
+
return { applied: [], skipped: candidates.map((c) => c.entity) };
|
|
4291
|
+
}
|
|
4292
|
+
if (stateDb2) {
|
|
4293
|
+
trackWikilinkApplications(stateDb2, filePath, result.linkedEntities);
|
|
4294
|
+
try {
|
|
4295
|
+
const markApplied = stateDb2.db.prepare(
|
|
4296
|
+
`UPDATE suggestion_events SET applied = 1
|
|
4297
|
+
WHERE note_path = ? AND entity = ? AND applied = 0`
|
|
4298
|
+
);
|
|
4299
|
+
for (const entity of result.linkedEntities) {
|
|
4300
|
+
markApplied.run(filePath, entity);
|
|
4301
|
+
}
|
|
4302
|
+
} catch {
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
return {
|
|
4306
|
+
applied: result.linkedEntities,
|
|
4307
|
+
skipped: candidates.map((c) => c.entity).filter((e) => !result.linkedEntities.includes(e))
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4236
4310
|
var moduleStateDb5, moduleConfig, ALL_IMPLICIT_PATTERNS, entityIndex, indexReady, indexError2, lastLoadedAt, cooccurrenceIndex, recencyIndex, DEFAULT_EXCLUDE_FOLDERS, SUGGESTION_PATTERN, GENERIC_WORDS, MAX_ENTITY_LENGTH, MAX_ENTITY_WORDS, ARTICLE_PATTERNS, STRICTNESS_CONFIGS, TYPE_BOOST, CROSS_FOLDER_BOOST, SEMANTIC_MIN_SIMILARITY, SEMANTIC_MAX_BOOST, CONTEXT_BOOST, MIN_SUGGESTION_SCORE, MIN_MATCH_RATIO, FULL_ALIAS_MATCH_BONUS;
|
|
4237
4311
|
var init_wikilinks = __esm({
|
|
4238
4312
|
"src/core/write/wikilinks.ts"() {
|
|
@@ -4478,8 +4552,8 @@ var init_constants = __esm({
|
|
|
4478
4552
|
});
|
|
4479
4553
|
|
|
4480
4554
|
// src/core/write/writer.ts
|
|
4481
|
-
import
|
|
4482
|
-
import
|
|
4555
|
+
import fs19 from "fs/promises";
|
|
4556
|
+
import path21 from "path";
|
|
4483
4557
|
import matter5 from "gray-matter";
|
|
4484
4558
|
import { createHash as createHash2 } from "node:crypto";
|
|
4485
4559
|
function isSensitivePath(filePath) {
|
|
@@ -4807,13 +4881,13 @@ function validatePath(vaultPath2, notePath) {
|
|
|
4807
4881
|
if (notePath.startsWith("\\")) {
|
|
4808
4882
|
return false;
|
|
4809
4883
|
}
|
|
4810
|
-
const resolvedVault =
|
|
4811
|
-
const resolvedNote =
|
|
4884
|
+
const resolvedVault = path21.resolve(vaultPath2);
|
|
4885
|
+
const resolvedNote = path21.resolve(vaultPath2, notePath);
|
|
4812
4886
|
return resolvedNote.startsWith(resolvedVault);
|
|
4813
4887
|
}
|
|
4814
4888
|
function sanitizeNotePath(notePath) {
|
|
4815
|
-
const dir =
|
|
4816
|
-
let filename =
|
|
4889
|
+
const dir = path21.dirname(notePath);
|
|
4890
|
+
let filename = path21.basename(notePath);
|
|
4817
4891
|
const ext = filename.endsWith(".md") ? ".md" : "";
|
|
4818
4892
|
let stem2 = ext ? filename.slice(0, -ext.length) : filename;
|
|
4819
4893
|
stem2 = stem2.replace(/\s+/g, "-");
|
|
@@ -4822,7 +4896,7 @@ function sanitizeNotePath(notePath) {
|
|
|
4822
4896
|
stem2 = stem2.replace(/-{2,}/g, "-");
|
|
4823
4897
|
stem2 = stem2.replace(/^-+|-+$/g, "");
|
|
4824
4898
|
filename = stem2 + (ext || ".md");
|
|
4825
|
-
return dir === "." ? filename :
|
|
4899
|
+
return dir === "." ? filename : path21.join(dir, filename).replace(/\\/g, "/");
|
|
4826
4900
|
}
|
|
4827
4901
|
async function validatePathSecure(vaultPath2, notePath) {
|
|
4828
4902
|
if (notePath.startsWith("/")) {
|
|
@@ -4849,8 +4923,8 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
4849
4923
|
reason: "Path traversal not allowed"
|
|
4850
4924
|
};
|
|
4851
4925
|
}
|
|
4852
|
-
const resolvedVault =
|
|
4853
|
-
const resolvedNote =
|
|
4926
|
+
const resolvedVault = path21.resolve(vaultPath2);
|
|
4927
|
+
const resolvedNote = path21.resolve(vaultPath2, notePath);
|
|
4854
4928
|
if (!resolvedNote.startsWith(resolvedVault)) {
|
|
4855
4929
|
return {
|
|
4856
4930
|
valid: false,
|
|
@@ -4864,18 +4938,18 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
4864
4938
|
};
|
|
4865
4939
|
}
|
|
4866
4940
|
try {
|
|
4867
|
-
const fullPath =
|
|
4941
|
+
const fullPath = path21.join(vaultPath2, notePath);
|
|
4868
4942
|
try {
|
|
4869
|
-
await
|
|
4870
|
-
const realPath = await
|
|
4871
|
-
const realVaultPath = await
|
|
4943
|
+
await fs19.access(fullPath);
|
|
4944
|
+
const realPath = await fs19.realpath(fullPath);
|
|
4945
|
+
const realVaultPath = await fs19.realpath(vaultPath2);
|
|
4872
4946
|
if (!realPath.startsWith(realVaultPath)) {
|
|
4873
4947
|
return {
|
|
4874
4948
|
valid: false,
|
|
4875
4949
|
reason: "Symlink target is outside vault"
|
|
4876
4950
|
};
|
|
4877
4951
|
}
|
|
4878
|
-
const relativePath =
|
|
4952
|
+
const relativePath = path21.relative(realVaultPath, realPath);
|
|
4879
4953
|
if (isSensitivePath(relativePath)) {
|
|
4880
4954
|
return {
|
|
4881
4955
|
valid: false,
|
|
@@ -4883,11 +4957,11 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
4883
4957
|
};
|
|
4884
4958
|
}
|
|
4885
4959
|
} catch {
|
|
4886
|
-
const parentDir =
|
|
4960
|
+
const parentDir = path21.dirname(fullPath);
|
|
4887
4961
|
try {
|
|
4888
|
-
await
|
|
4889
|
-
const realParentPath = await
|
|
4890
|
-
const realVaultPath = await
|
|
4962
|
+
await fs19.access(parentDir);
|
|
4963
|
+
const realParentPath = await fs19.realpath(parentDir);
|
|
4964
|
+
const realVaultPath = await fs19.realpath(vaultPath2);
|
|
4891
4965
|
if (!realParentPath.startsWith(realVaultPath)) {
|
|
4892
4966
|
return {
|
|
4893
4967
|
valid: false,
|
|
@@ -4912,10 +4986,10 @@ async function readVaultFile(vaultPath2, notePath) {
|
|
|
4912
4986
|
if (!validatePath(vaultPath2, notePath)) {
|
|
4913
4987
|
throw new Error("Invalid path: path traversal not allowed");
|
|
4914
4988
|
}
|
|
4915
|
-
const fullPath =
|
|
4916
|
-
const [rawContent,
|
|
4917
|
-
|
|
4918
|
-
|
|
4989
|
+
const fullPath = path21.join(vaultPath2, notePath);
|
|
4990
|
+
const [rawContent, stat5] = await Promise.all([
|
|
4991
|
+
fs19.readFile(fullPath, "utf-8"),
|
|
4992
|
+
fs19.stat(fullPath)
|
|
4919
4993
|
]);
|
|
4920
4994
|
const contentHash2 = computeContentHash(rawContent);
|
|
4921
4995
|
const lineEnding = detectLineEnding(rawContent);
|
|
@@ -4927,7 +5001,7 @@ async function readVaultFile(vaultPath2, notePath) {
|
|
|
4927
5001
|
frontmatter,
|
|
4928
5002
|
rawContent,
|
|
4929
5003
|
lineEnding,
|
|
4930
|
-
mtimeMs:
|
|
5004
|
+
mtimeMs: stat5.mtimeMs,
|
|
4931
5005
|
contentHash: contentHash2
|
|
4932
5006
|
};
|
|
4933
5007
|
}
|
|
@@ -4967,9 +5041,9 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
|
|
|
4967
5041
|
if (!validation.valid) {
|
|
4968
5042
|
throw new Error(`Invalid path: ${validation.reason}`);
|
|
4969
5043
|
}
|
|
4970
|
-
const fullPath =
|
|
5044
|
+
const fullPath = path21.join(vaultPath2, notePath);
|
|
4971
5045
|
if (expectedHash) {
|
|
4972
|
-
const currentRaw = await
|
|
5046
|
+
const currentRaw = await fs19.readFile(fullPath, "utf-8");
|
|
4973
5047
|
const currentHash = computeContentHash(currentRaw);
|
|
4974
5048
|
if (currentHash !== expectedHash) {
|
|
4975
5049
|
throw new WriteConflictError(notePath);
|
|
@@ -4978,7 +5052,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
|
|
|
4978
5052
|
let output = matter5.stringify(content, frontmatter);
|
|
4979
5053
|
output = normalizeTrailingNewline(output);
|
|
4980
5054
|
output = convertLineEndings(output, lineEnding);
|
|
4981
|
-
await
|
|
5055
|
+
await fs19.writeFile(fullPath, output, "utf-8");
|
|
4982
5056
|
}
|
|
4983
5057
|
function removeFromSection(content, section, pattern, mode = "first", useRegex = false) {
|
|
4984
5058
|
const lines = content.split("\n");
|
|
@@ -5367,8 +5441,8 @@ function createContext(variables = {}) {
|
|
|
5367
5441
|
steps: {}
|
|
5368
5442
|
};
|
|
5369
5443
|
}
|
|
5370
|
-
function resolvePath(obj,
|
|
5371
|
-
const parts =
|
|
5444
|
+
function resolvePath(obj, path34) {
|
|
5445
|
+
const parts = path34.split(".");
|
|
5372
5446
|
let current = obj;
|
|
5373
5447
|
for (const part of parts) {
|
|
5374
5448
|
if (current === void 0 || current === null) {
|
|
@@ -5825,8 +5899,8 @@ __export(conditions_exports, {
|
|
|
5825
5899
|
evaluateCondition: () => evaluateCondition,
|
|
5826
5900
|
shouldStepExecute: () => shouldStepExecute
|
|
5827
5901
|
});
|
|
5828
|
-
import
|
|
5829
|
-
import
|
|
5902
|
+
import fs26 from "fs/promises";
|
|
5903
|
+
import path27 from "path";
|
|
5830
5904
|
async function evaluateCondition(condition, vaultPath2, context) {
|
|
5831
5905
|
const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
|
|
5832
5906
|
const interpolatedSection = condition.section ? interpolate(condition.section, context) : void 0;
|
|
@@ -5879,9 +5953,9 @@ async function evaluateCondition(condition, vaultPath2, context) {
|
|
|
5879
5953
|
}
|
|
5880
5954
|
}
|
|
5881
5955
|
async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
5882
|
-
const fullPath =
|
|
5956
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
5883
5957
|
try {
|
|
5884
|
-
await
|
|
5958
|
+
await fs26.access(fullPath);
|
|
5885
5959
|
return {
|
|
5886
5960
|
met: expectExists,
|
|
5887
5961
|
reason: expectExists ? `File exists: ${notePath}` : `File exists (expected not to): ${notePath}`
|
|
@@ -5894,9 +5968,9 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
|
5894
5968
|
}
|
|
5895
5969
|
}
|
|
5896
5970
|
async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
|
|
5897
|
-
const fullPath =
|
|
5971
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
5898
5972
|
try {
|
|
5899
|
-
await
|
|
5973
|
+
await fs26.access(fullPath);
|
|
5900
5974
|
} catch {
|
|
5901
5975
|
return {
|
|
5902
5976
|
met: !expectExists,
|
|
@@ -5925,9 +5999,9 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
|
|
|
5925
5999
|
}
|
|
5926
6000
|
}
|
|
5927
6001
|
async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
|
|
5928
|
-
const fullPath =
|
|
6002
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
5929
6003
|
try {
|
|
5930
|
-
await
|
|
6004
|
+
await fs26.access(fullPath);
|
|
5931
6005
|
} catch {
|
|
5932
6006
|
return {
|
|
5933
6007
|
met: !expectExists,
|
|
@@ -5956,9 +6030,9 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
|
|
|
5956
6030
|
}
|
|
5957
6031
|
}
|
|
5958
6032
|
async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
|
|
5959
|
-
const fullPath =
|
|
6033
|
+
const fullPath = path27.join(vaultPath2, notePath);
|
|
5960
6034
|
try {
|
|
5961
|
-
await
|
|
6035
|
+
await fs26.access(fullPath);
|
|
5962
6036
|
} catch {
|
|
5963
6037
|
return {
|
|
5964
6038
|
met: false,
|
|
@@ -6100,7 +6174,7 @@ var init_taskHelpers = __esm({
|
|
|
6100
6174
|
});
|
|
6101
6175
|
|
|
6102
6176
|
// src/index.ts
|
|
6103
|
-
import * as
|
|
6177
|
+
import * as path33 from "path";
|
|
6104
6178
|
import { readFileSync as readFileSync5, realpathSync, existsSync as existsSync3, statSync as statSync5 } from "fs";
|
|
6105
6179
|
import { fileURLToPath } from "url";
|
|
6106
6180
|
import { dirname as dirname6, join as join17 } from "path";
|
|
@@ -6339,8 +6413,8 @@ function updateIndexProgress(parsed, total) {
|
|
|
6339
6413
|
function normalizeTarget(target) {
|
|
6340
6414
|
return target.toLowerCase().replace(/\.md$/, "");
|
|
6341
6415
|
}
|
|
6342
|
-
function normalizeNotePath(
|
|
6343
|
-
return
|
|
6416
|
+
function normalizeNotePath(path34) {
|
|
6417
|
+
return path34.toLowerCase().replace(/\.md$/, "");
|
|
6344
6418
|
}
|
|
6345
6419
|
async function buildVaultIndex(vaultPath2, options = {}) {
|
|
6346
6420
|
const { timeoutMs = DEFAULT_TIMEOUT_MS, onProgress } = options;
|
|
@@ -6509,7 +6583,7 @@ function findSimilarEntity(index, target) {
|
|
|
6509
6583
|
}
|
|
6510
6584
|
const maxDist = normalizedLen <= 10 ? 1 : 2;
|
|
6511
6585
|
let bestMatch;
|
|
6512
|
-
for (const [entity,
|
|
6586
|
+
for (const [entity, path34] of index.entities) {
|
|
6513
6587
|
const lenDiff = Math.abs(entity.length - normalizedLen);
|
|
6514
6588
|
if (lenDiff > maxDist) {
|
|
6515
6589
|
continue;
|
|
@@ -6517,7 +6591,7 @@ function findSimilarEntity(index, target) {
|
|
|
6517
6591
|
const dist = levenshteinDistance(normalized, entity);
|
|
6518
6592
|
if (dist > 0 && dist <= maxDist) {
|
|
6519
6593
|
if (!bestMatch || dist < bestMatch.distance) {
|
|
6520
|
-
bestMatch = { path:
|
|
6594
|
+
bestMatch = { path: path34, entity, distance: dist };
|
|
6521
6595
|
if (dist === 1) {
|
|
6522
6596
|
return bestMatch;
|
|
6523
6597
|
}
|
|
@@ -7048,30 +7122,30 @@ var EventQueue = class {
|
|
|
7048
7122
|
* Add a new event to the queue
|
|
7049
7123
|
*/
|
|
7050
7124
|
push(type, rawPath) {
|
|
7051
|
-
const
|
|
7125
|
+
const path34 = normalizePath(rawPath);
|
|
7052
7126
|
const now = Date.now();
|
|
7053
7127
|
const event = {
|
|
7054
7128
|
type,
|
|
7055
|
-
path:
|
|
7129
|
+
path: path34,
|
|
7056
7130
|
timestamp: now
|
|
7057
7131
|
};
|
|
7058
|
-
let pending = this.pending.get(
|
|
7132
|
+
let pending = this.pending.get(path34);
|
|
7059
7133
|
if (!pending) {
|
|
7060
7134
|
pending = {
|
|
7061
7135
|
events: [],
|
|
7062
7136
|
timer: null,
|
|
7063
7137
|
lastEvent: now
|
|
7064
7138
|
};
|
|
7065
|
-
this.pending.set(
|
|
7139
|
+
this.pending.set(path34, pending);
|
|
7066
7140
|
}
|
|
7067
7141
|
pending.events.push(event);
|
|
7068
7142
|
pending.lastEvent = now;
|
|
7069
|
-
console.error(`[flywheel] QUEUE: pushed ${type} for ${
|
|
7143
|
+
console.error(`[flywheel] QUEUE: pushed ${type} for ${path34}, pending=${this.pending.size}`);
|
|
7070
7144
|
if (pending.timer) {
|
|
7071
7145
|
clearTimeout(pending.timer);
|
|
7072
7146
|
}
|
|
7073
7147
|
pending.timer = setTimeout(() => {
|
|
7074
|
-
this.flushPath(
|
|
7148
|
+
this.flushPath(path34);
|
|
7075
7149
|
}, this.config.debounceMs);
|
|
7076
7150
|
if (this.pending.size >= this.config.batchSize) {
|
|
7077
7151
|
this.flush();
|
|
@@ -7092,10 +7166,10 @@ var EventQueue = class {
|
|
|
7092
7166
|
/**
|
|
7093
7167
|
* Flush a single path's events
|
|
7094
7168
|
*/
|
|
7095
|
-
flushPath(
|
|
7096
|
-
const pending = this.pending.get(
|
|
7169
|
+
flushPath(path34) {
|
|
7170
|
+
const pending = this.pending.get(path34);
|
|
7097
7171
|
if (!pending || pending.events.length === 0) return;
|
|
7098
|
-
console.error(`[flywheel] QUEUE: flushing ${
|
|
7172
|
+
console.error(`[flywheel] QUEUE: flushing ${path34}, events=${pending.events.length}`);
|
|
7099
7173
|
if (pending.timer) {
|
|
7100
7174
|
clearTimeout(pending.timer);
|
|
7101
7175
|
pending.timer = null;
|
|
@@ -7104,7 +7178,7 @@ var EventQueue = class {
|
|
|
7104
7178
|
if (coalescedType) {
|
|
7105
7179
|
const coalesced = {
|
|
7106
7180
|
type: coalescedType,
|
|
7107
|
-
path:
|
|
7181
|
+
path: path34,
|
|
7108
7182
|
originalEvents: [...pending.events]
|
|
7109
7183
|
};
|
|
7110
7184
|
this.onBatch({
|
|
@@ -7113,7 +7187,7 @@ var EventQueue = class {
|
|
|
7113
7187
|
timestamp: Date.now()
|
|
7114
7188
|
});
|
|
7115
7189
|
}
|
|
7116
|
-
this.pending.delete(
|
|
7190
|
+
this.pending.delete(path34);
|
|
7117
7191
|
}
|
|
7118
7192
|
/**
|
|
7119
7193
|
* Flush all pending events
|
|
@@ -7125,7 +7199,7 @@ var EventQueue = class {
|
|
|
7125
7199
|
}
|
|
7126
7200
|
if (this.pending.size === 0) return;
|
|
7127
7201
|
const events = [];
|
|
7128
|
-
for (const [
|
|
7202
|
+
for (const [path34, pending] of this.pending) {
|
|
7129
7203
|
if (pending.timer) {
|
|
7130
7204
|
clearTimeout(pending.timer);
|
|
7131
7205
|
}
|
|
@@ -7133,7 +7207,7 @@ var EventQueue = class {
|
|
|
7133
7207
|
if (coalescedType) {
|
|
7134
7208
|
events.push({
|
|
7135
7209
|
type: coalescedType,
|
|
7136
|
-
path:
|
|
7210
|
+
path: path34,
|
|
7137
7211
|
originalEvents: [...pending.events]
|
|
7138
7212
|
});
|
|
7139
7213
|
}
|
|
@@ -7326,8 +7400,8 @@ async function upsertNote(index, vaultPath2, notePath) {
|
|
|
7326
7400
|
releasedKeys = removeNoteFromIndex(index, notePath);
|
|
7327
7401
|
}
|
|
7328
7402
|
const fullPath = path7.join(vaultPath2, notePath);
|
|
7329
|
-
const
|
|
7330
|
-
const stats = await
|
|
7403
|
+
const fs34 = await import("fs/promises");
|
|
7404
|
+
const stats = await fs34.stat(fullPath);
|
|
7331
7405
|
const vaultFile = {
|
|
7332
7406
|
path: notePath,
|
|
7333
7407
|
absolutePath: fullPath,
|
|
@@ -7519,31 +7593,31 @@ function createVaultWatcher(options) {
|
|
|
7519
7593
|
usePolling: config.usePolling,
|
|
7520
7594
|
interval: config.usePolling ? config.pollInterval : void 0
|
|
7521
7595
|
});
|
|
7522
|
-
watcher.on("add", (
|
|
7523
|
-
console.error(`[flywheel] RAW EVENT: add ${
|
|
7524
|
-
if (shouldWatch(
|
|
7525
|
-
console.error(`[flywheel] ACCEPTED: add ${
|
|
7526
|
-
eventQueue.push("add",
|
|
7596
|
+
watcher.on("add", (path34) => {
|
|
7597
|
+
console.error(`[flywheel] RAW EVENT: add ${path34}`);
|
|
7598
|
+
if (shouldWatch(path34, vaultPath2)) {
|
|
7599
|
+
console.error(`[flywheel] ACCEPTED: add ${path34}`);
|
|
7600
|
+
eventQueue.push("add", path34);
|
|
7527
7601
|
} else {
|
|
7528
|
-
console.error(`[flywheel] FILTERED: add ${
|
|
7602
|
+
console.error(`[flywheel] FILTERED: add ${path34}`);
|
|
7529
7603
|
}
|
|
7530
7604
|
});
|
|
7531
|
-
watcher.on("change", (
|
|
7532
|
-
console.error(`[flywheel] RAW EVENT: change ${
|
|
7533
|
-
if (shouldWatch(
|
|
7534
|
-
console.error(`[flywheel] ACCEPTED: change ${
|
|
7535
|
-
eventQueue.push("change",
|
|
7605
|
+
watcher.on("change", (path34) => {
|
|
7606
|
+
console.error(`[flywheel] RAW EVENT: change ${path34}`);
|
|
7607
|
+
if (shouldWatch(path34, vaultPath2)) {
|
|
7608
|
+
console.error(`[flywheel] ACCEPTED: change ${path34}`);
|
|
7609
|
+
eventQueue.push("change", path34);
|
|
7536
7610
|
} else {
|
|
7537
|
-
console.error(`[flywheel] FILTERED: change ${
|
|
7611
|
+
console.error(`[flywheel] FILTERED: change ${path34}`);
|
|
7538
7612
|
}
|
|
7539
7613
|
});
|
|
7540
|
-
watcher.on("unlink", (
|
|
7541
|
-
console.error(`[flywheel] RAW EVENT: unlink ${
|
|
7542
|
-
if (shouldWatch(
|
|
7543
|
-
console.error(`[flywheel] ACCEPTED: unlink ${
|
|
7544
|
-
eventQueue.push("unlink",
|
|
7614
|
+
watcher.on("unlink", (path34) => {
|
|
7615
|
+
console.error(`[flywheel] RAW EVENT: unlink ${path34}`);
|
|
7616
|
+
if (shouldWatch(path34, vaultPath2)) {
|
|
7617
|
+
console.error(`[flywheel] ACCEPTED: unlink ${path34}`);
|
|
7618
|
+
eventQueue.push("unlink", path34);
|
|
7545
7619
|
} else {
|
|
7546
|
-
console.error(`[flywheel] FILTERED: unlink ${
|
|
7620
|
+
console.error(`[flywheel] FILTERED: unlink ${path34}`);
|
|
7547
7621
|
}
|
|
7548
7622
|
});
|
|
7549
7623
|
watcher.on("ready", () => {
|
|
@@ -7722,7 +7796,7 @@ async function flushLogs() {
|
|
|
7722
7796
|
init_vault();
|
|
7723
7797
|
init_serverLog();
|
|
7724
7798
|
init_vault_scope();
|
|
7725
|
-
import * as
|
|
7799
|
+
import * as fs8 from "fs";
|
|
7726
7800
|
var EXCLUDED_DIRS3 = /* @__PURE__ */ new Set([
|
|
7727
7801
|
".obsidian",
|
|
7728
7802
|
".trash",
|
|
@@ -7790,11 +7864,11 @@ async function buildFTS5Index(vaultPath2) {
|
|
|
7790
7864
|
const rows = [];
|
|
7791
7865
|
for (const file of indexableFiles) {
|
|
7792
7866
|
try {
|
|
7793
|
-
const stats =
|
|
7867
|
+
const stats = fs8.statSync(file.absolutePath);
|
|
7794
7868
|
if (stats.size > MAX_INDEX_FILE_SIZE) {
|
|
7795
7869
|
continue;
|
|
7796
7870
|
}
|
|
7797
|
-
const raw =
|
|
7871
|
+
const raw = fs8.readFileSync(file.absolutePath, "utf-8");
|
|
7798
7872
|
const { frontmatter, body } = splitFrontmatter(raw);
|
|
7799
7873
|
const title = file.path.replace(/\.md$/, "").split("/").pop() || file.path;
|
|
7800
7874
|
rows.push([file.path, title, frontmatter, body]);
|
|
@@ -7921,11 +7995,11 @@ function countFTS5Mentions(term) {
|
|
|
7921
7995
|
init_embeddings();
|
|
7922
7996
|
|
|
7923
7997
|
// src/core/read/taskCache.ts
|
|
7924
|
-
import * as
|
|
7998
|
+
import * as path13 from "path";
|
|
7925
7999
|
|
|
7926
8000
|
// src/tools/read/tasks.ts
|
|
7927
|
-
import * as
|
|
7928
|
-
import * as
|
|
8001
|
+
import * as fs9 from "fs";
|
|
8002
|
+
import * as path12 from "path";
|
|
7929
8003
|
var TASK_REGEX = /^(\s*)- \[([ xX\-])\]\s+(.+)$/;
|
|
7930
8004
|
var TAG_REGEX2 = /#([a-zA-Z][a-zA-Z0-9_/-]*)/g;
|
|
7931
8005
|
var DATE_REGEX = /\b(\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4})\b/;
|
|
@@ -7951,7 +8025,7 @@ function extractDueDate(text) {
|
|
|
7951
8025
|
async function extractTasksFromNote(notePath, absolutePath) {
|
|
7952
8026
|
let content;
|
|
7953
8027
|
try {
|
|
7954
|
-
content = await
|
|
8028
|
+
content = await fs9.promises.readFile(absolutePath, "utf-8");
|
|
7955
8029
|
} catch {
|
|
7956
8030
|
return [];
|
|
7957
8031
|
}
|
|
@@ -7994,7 +8068,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
|
|
|
7994
8068
|
const allTasks = [];
|
|
7995
8069
|
for (const note of index.notes.values()) {
|
|
7996
8070
|
if (folder && !note.path.startsWith(folder)) continue;
|
|
7997
|
-
const absolutePath =
|
|
8071
|
+
const absolutePath = path12.join(vaultPath2, note.path);
|
|
7998
8072
|
const tasks = await extractTasksFromNote(note.path, absolutePath);
|
|
7999
8073
|
allTasks.push(...tasks);
|
|
8000
8074
|
}
|
|
@@ -8038,7 +8112,7 @@ async function getAllTasks(index, vaultPath2, options = {}) {
|
|
|
8038
8112
|
async function getTasksFromNote(index, notePath, vaultPath2, excludeTags = []) {
|
|
8039
8113
|
const note = index.notes.get(notePath);
|
|
8040
8114
|
if (!note) return null;
|
|
8041
|
-
const absolutePath =
|
|
8115
|
+
const absolutePath = path12.join(vaultPath2, notePath);
|
|
8042
8116
|
let tasks = await extractTasksFromNote(notePath, absolutePath);
|
|
8043
8117
|
if (excludeTags.length > 0) {
|
|
8044
8118
|
tasks = tasks.filter(
|
|
@@ -8100,7 +8174,7 @@ async function buildTaskCache(vaultPath2, index, excludeTags) {
|
|
|
8100
8174
|
}
|
|
8101
8175
|
const allRows = [];
|
|
8102
8176
|
for (const notePath of notePaths) {
|
|
8103
|
-
const absolutePath =
|
|
8177
|
+
const absolutePath = path13.join(vaultPath2, notePath);
|
|
8104
8178
|
const tasks = await extractTasksFromNote(notePath, absolutePath);
|
|
8105
8179
|
for (const task of tasks) {
|
|
8106
8180
|
if (excludeTags?.length && excludeTags.some((t) => task.tags.includes(t))) {
|
|
@@ -8143,7 +8217,7 @@ async function updateTaskCacheForFile(vaultPath2, relativePath) {
|
|
|
8143
8217
|
const db4 = getDb3();
|
|
8144
8218
|
if (!db4) return;
|
|
8145
8219
|
db4.prepare("DELETE FROM tasks WHERE path = ?").run(relativePath);
|
|
8146
|
-
const absolutePath =
|
|
8220
|
+
const absolutePath = path13.join(vaultPath2, relativePath);
|
|
8147
8221
|
const tasks = await extractTasksFromNote(relativePath, absolutePath);
|
|
8148
8222
|
if (tasks.length > 0) {
|
|
8149
8223
|
const insertStmt = db4.prepare(`
|
|
@@ -8285,8 +8359,8 @@ function refreshIfStale(vaultPath2, index, excludeTags) {
|
|
|
8285
8359
|
import { openStateDb, scanVaultEntities as scanVaultEntities4, getSessionId, getAllEntitiesFromDb as getAllEntitiesFromDb3, findEntityMatches as findEntityMatches2, getProtectedZones as getProtectedZones2, rangeOverlapsProtectedZone, detectImplicitEntities as detectImplicitEntities3, loadContentHashes, saveContentHashBatch, renameContentHash } from "@velvetmonkey/vault-core";
|
|
8286
8360
|
|
|
8287
8361
|
// src/tools/read/graph.ts
|
|
8288
|
-
import * as
|
|
8289
|
-
import * as
|
|
8362
|
+
import * as fs10 from "fs";
|
|
8363
|
+
import * as path14 from "path";
|
|
8290
8364
|
import { z } from "zod";
|
|
8291
8365
|
|
|
8292
8366
|
// src/core/read/constants.ts
|
|
@@ -8737,8 +8811,8 @@ function requireIndex() {
|
|
|
8737
8811
|
// src/tools/read/graph.ts
|
|
8738
8812
|
async function getContext(vaultPath2, sourcePath, line, contextLines = 1) {
|
|
8739
8813
|
try {
|
|
8740
|
-
const fullPath =
|
|
8741
|
-
const content = await
|
|
8814
|
+
const fullPath = path14.join(vaultPath2, sourcePath);
|
|
8815
|
+
const content = await fs10.promises.readFile(fullPath, "utf-8");
|
|
8742
8816
|
const allLines = content.split("\n");
|
|
8743
8817
|
let fmLines = 0;
|
|
8744
8818
|
if (allLines[0]?.trimEnd() === "---") {
|
|
@@ -9211,15 +9285,15 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb2 = ()
|
|
|
9211
9285
|
const weightedStats = getWeightedEntityStats(stateDb2);
|
|
9212
9286
|
const statsMap = new Map(weightedStats.map((s) => [s.entity.toLowerCase(), s]));
|
|
9213
9287
|
for (const suggestion of scored.detailed) {
|
|
9214
|
-
const
|
|
9215
|
-
if (
|
|
9216
|
-
const posteriorMean = computePosteriorMean(
|
|
9217
|
-
const totalObs = PRIOR_ALPHA +
|
|
9288
|
+
const stat5 = statsMap.get(suggestion.entity.toLowerCase());
|
|
9289
|
+
if (stat5) {
|
|
9290
|
+
const posteriorMean = computePosteriorMean(stat5.weightedCorrect, stat5.weightedFp);
|
|
9291
|
+
const totalObs = PRIOR_ALPHA + stat5.weightedCorrect + PRIOR_BETA + stat5.weightedFp;
|
|
9218
9292
|
suggestion.suppressionContext = {
|
|
9219
9293
|
posteriorMean: Math.round(posteriorMean * 1e3) / 1e3,
|
|
9220
9294
|
totalObservations: Math.round(totalObs * 10) / 10,
|
|
9221
9295
|
isSuppressed: totalObs >= SUPPRESSION_MIN_OBSERVATIONS && posteriorMean < SUPPRESSION_POSTERIOR_THRESHOLD,
|
|
9222
|
-
falsePositiveRate: Math.round(
|
|
9296
|
+
falsePositiveRate: Math.round(stat5.weightedFpRate * 1e3) / 1e3
|
|
9223
9297
|
};
|
|
9224
9298
|
}
|
|
9225
9299
|
}
|
|
@@ -9264,14 +9338,14 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb2 = ()
|
|
|
9264
9338
|
};
|
|
9265
9339
|
function findSimilarEntity2(target, entities) {
|
|
9266
9340
|
const targetLower = target.toLowerCase();
|
|
9267
|
-
for (const [name,
|
|
9341
|
+
for (const [name, path34] of entities) {
|
|
9268
9342
|
if (name.startsWith(targetLower) || targetLower.startsWith(name)) {
|
|
9269
|
-
return
|
|
9343
|
+
return path34;
|
|
9270
9344
|
}
|
|
9271
9345
|
}
|
|
9272
|
-
for (const [name,
|
|
9346
|
+
for (const [name, path34] of entities) {
|
|
9273
9347
|
if (name.includes(targetLower) || targetLower.includes(name)) {
|
|
9274
|
-
return
|
|
9348
|
+
return path34;
|
|
9275
9349
|
}
|
|
9276
9350
|
}
|
|
9277
9351
|
return void 0;
|
|
@@ -9493,7 +9567,7 @@ function registerWikilinkTools(server2, getIndex, getVaultPath, getStateDb2 = ()
|
|
|
9493
9567
|
}
|
|
9494
9568
|
|
|
9495
9569
|
// src/tools/read/health.ts
|
|
9496
|
-
import * as
|
|
9570
|
+
import * as fs11 from "fs";
|
|
9497
9571
|
import { z as z3 } from "zod";
|
|
9498
9572
|
|
|
9499
9573
|
// src/tools/read/periodic.ts
|
|
@@ -10112,7 +10186,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
10112
10186
|
const indexErrorObj = getIndexError();
|
|
10113
10187
|
let vaultAccessible = false;
|
|
10114
10188
|
try {
|
|
10115
|
-
|
|
10189
|
+
fs11.accessSync(vaultPath2, fs11.constants.R_OK);
|
|
10116
10190
|
vaultAccessible = true;
|
|
10117
10191
|
} catch {
|
|
10118
10192
|
vaultAccessible = false;
|
|
@@ -10354,8 +10428,8 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
10354
10428
|
daily_counts: z3.record(z3.number())
|
|
10355
10429
|
}).describe("Activity summary for the last 7 days")
|
|
10356
10430
|
};
|
|
10357
|
-
function isPeriodicNote3(
|
|
10358
|
-
const filename =
|
|
10431
|
+
function isPeriodicNote3(path34) {
|
|
10432
|
+
const filename = path34.split("/").pop() || "";
|
|
10359
10433
|
const nameWithoutExt = filename.replace(/\.md$/, "");
|
|
10360
10434
|
const patterns = [
|
|
10361
10435
|
/^\d{4}-\d{2}-\d{2}$/,
|
|
@@ -10370,7 +10444,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
10370
10444
|
// YYYY (yearly)
|
|
10371
10445
|
];
|
|
10372
10446
|
const periodicFolders = ["daily", "weekly", "monthly", "quarterly", "yearly", "journal", "journals"];
|
|
10373
|
-
const folder =
|
|
10447
|
+
const folder = path34.split("/")[0]?.toLowerCase() || "";
|
|
10374
10448
|
return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
|
|
10375
10449
|
}
|
|
10376
10450
|
server2.registerTool(
|
|
@@ -10519,7 +10593,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
10519
10593
|
detail: `Schema version ${SCHEMA_VERSION}`
|
|
10520
10594
|
});
|
|
10521
10595
|
try {
|
|
10522
|
-
|
|
10596
|
+
fs11.accessSync(vaultPath2, fs11.constants.R_OK | fs11.constants.W_OK);
|
|
10523
10597
|
checks.push({ name: "vault_access", status: "ok", detail: `Vault readable and writable at ${vaultPath2}` });
|
|
10524
10598
|
} catch {
|
|
10525
10599
|
checks.push({ name: "vault_access", status: "error", detail: `Vault not accessible at ${vaultPath2}`, fix: "Check PROJECT_PATH environment variable and directory permissions" });
|
|
@@ -10605,11 +10679,11 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
|
|
|
10605
10679
|
try {
|
|
10606
10680
|
const dbPath = stateDb2.db.name;
|
|
10607
10681
|
if (dbPath && dbPath !== ":memory:") {
|
|
10608
|
-
const dbStat =
|
|
10682
|
+
const dbStat = fs11.statSync(dbPath);
|
|
10609
10683
|
const dbSizeMb = Math.round(dbStat.size / 1024 / 1024 * 10) / 10;
|
|
10610
10684
|
let walSizeMb = 0;
|
|
10611
10685
|
try {
|
|
10612
|
-
const walStat =
|
|
10686
|
+
const walStat = fs11.statSync(dbPath + "-wal");
|
|
10613
10687
|
walSizeMb = Math.round(walStat.size / 1024 / 1024 * 10) / 10;
|
|
10614
10688
|
} catch {
|
|
10615
10689
|
}
|
|
@@ -10838,6 +10912,7 @@ function enrichResultCompact(result, index, stateDb2, opts) {
|
|
|
10838
10912
|
if (preview) enriched.snippet = preview;
|
|
10839
10913
|
}
|
|
10840
10914
|
if (note) {
|
|
10915
|
+
if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
|
|
10841
10916
|
enriched.backlink_count = backlinks.length;
|
|
10842
10917
|
enriched.modified = note.modified.toISOString();
|
|
10843
10918
|
if (note.tags.length > 0) enriched.tags = note.tags;
|
|
@@ -10968,6 +11043,7 @@ function enrichEntityCompact(entityName, stateDb2, index) {
|
|
|
10968
11043
|
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
10969
11044
|
enriched.backlink_count = backlinks.length;
|
|
10970
11045
|
if (note) {
|
|
11046
|
+
if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
|
|
10971
11047
|
if (note.tags.length > 0) enriched.tags = note.tags;
|
|
10972
11048
|
if (note.outlinks.length > 0) {
|
|
10973
11049
|
enriched.outlink_names = getOutlinkNames(note.outlinks, entityPath, index, stateDb2, COMPACT_OUTLINK_NAMES);
|
|
@@ -10984,6 +11060,7 @@ function enrichNoteCompact(notePath, stateDb2, index) {
|
|
|
10984
11060
|
if (!note) return enriched;
|
|
10985
11061
|
const normalizedPath = notePath.toLowerCase().replace(/\.md$/, "");
|
|
10986
11062
|
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
11063
|
+
if (Object.keys(note.frontmatter).length > 0) enriched.frontmatter = note.frontmatter;
|
|
10987
11064
|
if (note.tags.length > 0) enriched.tags = note.tags;
|
|
10988
11065
|
enriched.backlink_count = backlinks.length;
|
|
10989
11066
|
enriched.modified = note.modified.toISOString();
|
|
@@ -11064,13 +11141,13 @@ function multiHopBackfill(primaryResults, index, stateDb2, config = {}) {
|
|
|
11064
11141
|
candidates.sort((a, b) => b.score - a.score);
|
|
11065
11142
|
return candidates.slice(0, cfg.maxBackfill).map((c) => c.result);
|
|
11066
11143
|
}
|
|
11067
|
-
function scoreCandidate(
|
|
11068
|
-
const note = index.notes.get(
|
|
11144
|
+
function scoreCandidate(path34, index, stateDb2) {
|
|
11145
|
+
const note = index.notes.get(path34);
|
|
11069
11146
|
const decay = recencyDecay(note?.modified);
|
|
11070
11147
|
let hubScore = 1;
|
|
11071
11148
|
if (stateDb2) {
|
|
11072
11149
|
try {
|
|
11073
|
-
const title = note?.title ??
|
|
11150
|
+
const title = note?.title ?? path34.replace(/\.md$/, "").split("/").pop() ?? "";
|
|
11074
11151
|
const entity = getEntityByName3(stateDb2, title);
|
|
11075
11152
|
if (entity) hubScore = entity.hubScore ?? 1;
|
|
11076
11153
|
} catch {
|
|
@@ -11448,8 +11525,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
11448
11525
|
}
|
|
11449
11526
|
|
|
11450
11527
|
// src/tools/read/system.ts
|
|
11451
|
-
import * as
|
|
11452
|
-
import * as
|
|
11528
|
+
import * as fs12 from "fs";
|
|
11529
|
+
import * as path15 from "path";
|
|
11453
11530
|
import { z as z5 } from "zod";
|
|
11454
11531
|
import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
|
|
11455
11532
|
|
|
@@ -11762,8 +11839,8 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
11762
11839
|
continue;
|
|
11763
11840
|
}
|
|
11764
11841
|
try {
|
|
11765
|
-
const fullPath =
|
|
11766
|
-
const content = await
|
|
11842
|
+
const fullPath = path15.join(vaultPath2, note.path);
|
|
11843
|
+
const content = await fs12.promises.readFile(fullPath, "utf-8");
|
|
11767
11844
|
const lines = content.split("\n");
|
|
11768
11845
|
for (let i = 0; i < lines.length; i++) {
|
|
11769
11846
|
const line = lines[i];
|
|
@@ -12022,8 +12099,8 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
12022
12099
|
import { z as z6 } from "zod";
|
|
12023
12100
|
|
|
12024
12101
|
// src/tools/read/structure.ts
|
|
12025
|
-
import * as
|
|
12026
|
-
import * as
|
|
12102
|
+
import * as fs13 from "fs";
|
|
12103
|
+
import * as path16 from "path";
|
|
12027
12104
|
var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
|
|
12028
12105
|
function extractHeadings2(content) {
|
|
12029
12106
|
const lines = content.split("\n");
|
|
@@ -12077,10 +12154,10 @@ function buildSections(headings, totalLines) {
|
|
|
12077
12154
|
async function getNoteStructure(index, notePath, vaultPath2) {
|
|
12078
12155
|
const note = index.notes.get(notePath);
|
|
12079
12156
|
if (!note) return null;
|
|
12080
|
-
const absolutePath =
|
|
12157
|
+
const absolutePath = path16.join(vaultPath2, notePath);
|
|
12081
12158
|
let content;
|
|
12082
12159
|
try {
|
|
12083
|
-
content = await
|
|
12160
|
+
content = await fs13.promises.readFile(absolutePath, "utf-8");
|
|
12084
12161
|
} catch {
|
|
12085
12162
|
return null;
|
|
12086
12163
|
}
|
|
@@ -12100,10 +12177,10 @@ async function getNoteStructure(index, notePath, vaultPath2) {
|
|
|
12100
12177
|
async function getSectionContent(index, notePath, headingText, vaultPath2, includeSubheadings = true) {
|
|
12101
12178
|
const note = index.notes.get(notePath);
|
|
12102
12179
|
if (!note) return null;
|
|
12103
|
-
const absolutePath =
|
|
12180
|
+
const absolutePath = path16.join(vaultPath2, notePath);
|
|
12104
12181
|
let content;
|
|
12105
12182
|
try {
|
|
12106
|
-
content = await
|
|
12183
|
+
content = await fs13.promises.readFile(absolutePath, "utf-8");
|
|
12107
12184
|
} catch {
|
|
12108
12185
|
return null;
|
|
12109
12186
|
}
|
|
@@ -12142,10 +12219,10 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
|
|
|
12142
12219
|
const results = [];
|
|
12143
12220
|
for (const note of index.notes.values()) {
|
|
12144
12221
|
if (folder && !note.path.startsWith(folder)) continue;
|
|
12145
|
-
const absolutePath =
|
|
12222
|
+
const absolutePath = path16.join(vaultPath2, note.path);
|
|
12146
12223
|
let content;
|
|
12147
12224
|
try {
|
|
12148
|
-
content = await
|
|
12225
|
+
content = await fs13.promises.readFile(absolutePath, "utf-8");
|
|
12149
12226
|
} catch {
|
|
12150
12227
|
continue;
|
|
12151
12228
|
}
|
|
@@ -12177,30 +12254,30 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
12177
12254
|
include_content: z6.boolean().default(true).describe("Include the text content under each top-level section. Set false to get structure only.")
|
|
12178
12255
|
}
|
|
12179
12256
|
},
|
|
12180
|
-
async ({ path:
|
|
12257
|
+
async ({ path: path34, include_content }) => {
|
|
12181
12258
|
const index = getIndex();
|
|
12182
12259
|
const vaultPath2 = getVaultPath();
|
|
12183
|
-
const result = await getNoteStructure(index,
|
|
12260
|
+
const result = await getNoteStructure(index, path34, vaultPath2);
|
|
12184
12261
|
if (!result) {
|
|
12185
12262
|
return {
|
|
12186
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
12263
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path34 }, null, 2) }]
|
|
12187
12264
|
};
|
|
12188
12265
|
}
|
|
12189
12266
|
if (include_content) {
|
|
12190
12267
|
for (const section of result.sections) {
|
|
12191
|
-
const sectionResult = await getSectionContent(index,
|
|
12268
|
+
const sectionResult = await getSectionContent(index, path34, section.heading.text, vaultPath2, true);
|
|
12192
12269
|
if (sectionResult) {
|
|
12193
12270
|
section.content = sectionResult.content;
|
|
12194
12271
|
}
|
|
12195
12272
|
}
|
|
12196
12273
|
}
|
|
12197
|
-
const note = index.notes.get(
|
|
12274
|
+
const note = index.notes.get(path34);
|
|
12198
12275
|
const enriched = { ...result };
|
|
12199
12276
|
if (note) {
|
|
12200
12277
|
enriched.frontmatter = note.frontmatter;
|
|
12201
12278
|
enriched.tags = note.tags;
|
|
12202
12279
|
enriched.aliases = note.aliases;
|
|
12203
|
-
const normalizedPath =
|
|
12280
|
+
const normalizedPath = path34.toLowerCase().replace(/\.md$/, "");
|
|
12204
12281
|
const backlinks = index.backlinks.get(normalizedPath) || [];
|
|
12205
12282
|
enriched.backlink_count = backlinks.length;
|
|
12206
12283
|
enriched.outlink_count = note.outlinks.length;
|
|
@@ -12233,15 +12310,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
12233
12310
|
include_subheadings: z6.boolean().default(true).describe("Include content under subheadings")
|
|
12234
12311
|
}
|
|
12235
12312
|
},
|
|
12236
|
-
async ({ path:
|
|
12313
|
+
async ({ path: path34, heading, include_subheadings }) => {
|
|
12237
12314
|
const index = getIndex();
|
|
12238
12315
|
const vaultPath2 = getVaultPath();
|
|
12239
|
-
const result = await getSectionContent(index,
|
|
12316
|
+
const result = await getSectionContent(index, path34, heading, vaultPath2, include_subheadings);
|
|
12240
12317
|
if (!result) {
|
|
12241
12318
|
return {
|
|
12242
12319
|
content: [{ type: "text", text: JSON.stringify({
|
|
12243
12320
|
error: "Section not found",
|
|
12244
|
-
path:
|
|
12321
|
+
path: path34,
|
|
12245
12322
|
heading
|
|
12246
12323
|
}, null, 2) }]
|
|
12247
12324
|
};
|
|
@@ -12295,16 +12372,16 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
12295
12372
|
offset: z6.coerce.number().default(0).describe("Number of results to skip (for pagination)")
|
|
12296
12373
|
}
|
|
12297
12374
|
},
|
|
12298
|
-
async ({ path:
|
|
12375
|
+
async ({ path: path34, status, has_due_date, folder, tag, limit: requestedLimit, offset }) => {
|
|
12299
12376
|
const limit = Math.min(requestedLimit ?? 25, MAX_LIMIT);
|
|
12300
12377
|
const index = getIndex();
|
|
12301
12378
|
const vaultPath2 = getVaultPath();
|
|
12302
12379
|
const config = getConfig2();
|
|
12303
|
-
if (
|
|
12304
|
-
const result2 = await getTasksFromNote(index,
|
|
12380
|
+
if (path34) {
|
|
12381
|
+
const result2 = await getTasksFromNote(index, path34, vaultPath2, config.exclude_task_tags || []);
|
|
12305
12382
|
if (!result2) {
|
|
12306
12383
|
return {
|
|
12307
|
-
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path:
|
|
12384
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path34 }, null, 2) }]
|
|
12308
12385
|
};
|
|
12309
12386
|
}
|
|
12310
12387
|
let filtered = result2;
|
|
@@ -12314,7 +12391,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
12314
12391
|
const paged2 = filtered.slice(offset, offset + limit);
|
|
12315
12392
|
return {
|
|
12316
12393
|
content: [{ type: "text", text: JSON.stringify({
|
|
12317
|
-
path:
|
|
12394
|
+
path: path34,
|
|
12318
12395
|
total_count: filtered.length,
|
|
12319
12396
|
returned_count: paged2.length,
|
|
12320
12397
|
open: result2.filter((t) => t.status === "open").length,
|
|
@@ -12469,8 +12546,8 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
12469
12546
|
|
|
12470
12547
|
// src/tools/read/migrations.ts
|
|
12471
12548
|
import { z as z7 } from "zod";
|
|
12472
|
-
import * as
|
|
12473
|
-
import * as
|
|
12549
|
+
import * as fs14 from "fs/promises";
|
|
12550
|
+
import * as path17 from "path";
|
|
12474
12551
|
import matter2 from "gray-matter";
|
|
12475
12552
|
function getNotesInFolder(index, folder) {
|
|
12476
12553
|
const notes = [];
|
|
@@ -12483,17 +12560,17 @@ function getNotesInFolder(index, folder) {
|
|
|
12483
12560
|
return notes;
|
|
12484
12561
|
}
|
|
12485
12562
|
async function readFileContent(notePath, vaultPath2) {
|
|
12486
|
-
const fullPath =
|
|
12563
|
+
const fullPath = path17.join(vaultPath2, notePath);
|
|
12487
12564
|
try {
|
|
12488
|
-
return await
|
|
12565
|
+
return await fs14.readFile(fullPath, "utf-8");
|
|
12489
12566
|
} catch {
|
|
12490
12567
|
return null;
|
|
12491
12568
|
}
|
|
12492
12569
|
}
|
|
12493
12570
|
async function writeFileContent(notePath, vaultPath2, content) {
|
|
12494
|
-
const fullPath =
|
|
12571
|
+
const fullPath = path17.join(vaultPath2, notePath);
|
|
12495
12572
|
try {
|
|
12496
|
-
await
|
|
12573
|
+
await fs14.writeFile(fullPath, content, "utf-8");
|
|
12497
12574
|
return true;
|
|
12498
12575
|
} catch {
|
|
12499
12576
|
return false;
|
|
@@ -12671,8 +12748,8 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
|
|
|
12671
12748
|
}
|
|
12672
12749
|
|
|
12673
12750
|
// src/tools/read/graphAnalysis.ts
|
|
12674
|
-
import
|
|
12675
|
-
import
|
|
12751
|
+
import fs15 from "node:fs";
|
|
12752
|
+
import path18 from "node:path";
|
|
12676
12753
|
import { z as z8 } from "zod";
|
|
12677
12754
|
|
|
12678
12755
|
// src/tools/read/schema.ts
|
|
@@ -13385,7 +13462,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb2
|
|
|
13385
13462
|
const scored = allNotes.map((note) => {
|
|
13386
13463
|
let wordCount = 0;
|
|
13387
13464
|
try {
|
|
13388
|
-
const content =
|
|
13465
|
+
const content = fs15.readFileSync(path18.join(vaultPath2, note.path), "utf-8");
|
|
13389
13466
|
const body = content.replace(/^---[\s\S]*?---\n?/, "");
|
|
13390
13467
|
wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
|
|
13391
13468
|
} catch {
|
|
@@ -14015,15 +14092,15 @@ function registerSemanticAnalysisTools(server2, getIndex, getVaultPath) {
|
|
|
14015
14092
|
import { z as z11 } from "zod";
|
|
14016
14093
|
|
|
14017
14094
|
// src/tools/read/bidirectional.ts
|
|
14018
|
-
import * as
|
|
14019
|
-
import * as
|
|
14095
|
+
import * as fs16 from "fs/promises";
|
|
14096
|
+
import * as path19 from "path";
|
|
14020
14097
|
import matter3 from "gray-matter";
|
|
14021
14098
|
var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
|
|
14022
14099
|
var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
14023
14100
|
async function readFileContent2(notePath, vaultPath2) {
|
|
14024
|
-
const fullPath =
|
|
14101
|
+
const fullPath = path19.join(vaultPath2, notePath);
|
|
14025
14102
|
try {
|
|
14026
|
-
return await
|
|
14103
|
+
return await fs16.readFile(fullPath, "utf-8");
|
|
14027
14104
|
} catch {
|
|
14028
14105
|
return null;
|
|
14029
14106
|
}
|
|
@@ -14199,21 +14276,21 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
|
|
|
14199
14276
|
}
|
|
14200
14277
|
|
|
14201
14278
|
// src/tools/read/computed.ts
|
|
14202
|
-
import * as
|
|
14203
|
-
import * as
|
|
14279
|
+
import * as fs17 from "fs/promises";
|
|
14280
|
+
import * as path20 from "path";
|
|
14204
14281
|
import matter4 from "gray-matter";
|
|
14205
14282
|
async function readFileContent3(notePath, vaultPath2) {
|
|
14206
|
-
const fullPath =
|
|
14283
|
+
const fullPath = path20.join(vaultPath2, notePath);
|
|
14207
14284
|
try {
|
|
14208
|
-
return await
|
|
14285
|
+
return await fs17.readFile(fullPath, "utf-8");
|
|
14209
14286
|
} catch {
|
|
14210
14287
|
return null;
|
|
14211
14288
|
}
|
|
14212
14289
|
}
|
|
14213
14290
|
async function getFileStats(notePath, vaultPath2) {
|
|
14214
|
-
const fullPath =
|
|
14291
|
+
const fullPath = path20.join(vaultPath2, notePath);
|
|
14215
14292
|
try {
|
|
14216
|
-
const stats = await
|
|
14293
|
+
const stats = await fs17.stat(fullPath);
|
|
14217
14294
|
return {
|
|
14218
14295
|
modified: stats.mtime,
|
|
14219
14296
|
created: stats.birthtime
|
|
@@ -14343,7 +14420,7 @@ async function computeFrontmatter(index, notePath, vaultPath2, fields) {
|
|
|
14343
14420
|
|
|
14344
14421
|
// src/tools/read/noteIntelligence.ts
|
|
14345
14422
|
init_embeddings();
|
|
14346
|
-
import
|
|
14423
|
+
import fs18 from "node:fs";
|
|
14347
14424
|
import nodePath from "node:path";
|
|
14348
14425
|
function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfig2) {
|
|
14349
14426
|
server2.registerTool(
|
|
@@ -14403,7 +14480,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
14403
14480
|
}
|
|
14404
14481
|
let noteContent;
|
|
14405
14482
|
try {
|
|
14406
|
-
noteContent =
|
|
14483
|
+
noteContent = fs18.readFileSync(nodePath.join(vaultPath2, notePath), "utf-8");
|
|
14407
14484
|
} catch {
|
|
14408
14485
|
return {
|
|
14409
14486
|
content: [{ type: "text", text: JSON.stringify({
|
|
@@ -14482,8 +14559,8 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
14482
14559
|
// src/tools/write/mutations.ts
|
|
14483
14560
|
init_writer();
|
|
14484
14561
|
import { z as z12 } from "zod";
|
|
14485
|
-
import
|
|
14486
|
-
import
|
|
14562
|
+
import fs21 from "fs/promises";
|
|
14563
|
+
import path23 from "path";
|
|
14487
14564
|
|
|
14488
14565
|
// src/core/write/validator.ts
|
|
14489
14566
|
var TIMESTAMP_PATTERN = /^\*\*\d{2}:\d{2}\*\*/;
|
|
@@ -14701,8 +14778,8 @@ init_constants();
|
|
|
14701
14778
|
init_writer();
|
|
14702
14779
|
init_wikilinks();
|
|
14703
14780
|
init_wikilinkFeedback();
|
|
14704
|
-
import
|
|
14705
|
-
import
|
|
14781
|
+
import fs20 from "fs/promises";
|
|
14782
|
+
import path22 from "path";
|
|
14706
14783
|
function formatMcpResult(result) {
|
|
14707
14784
|
if (result.tokensEstimate === void 0 || result.tokensEstimate === 0) {
|
|
14708
14785
|
result.tokensEstimate = estimateTokens(result);
|
|
@@ -14750,8 +14827,8 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
|
|
|
14750
14827
|
}
|
|
14751
14828
|
async function getPolicyHint(vaultPath2) {
|
|
14752
14829
|
try {
|
|
14753
|
-
const policiesDir =
|
|
14754
|
-
const files = await
|
|
14830
|
+
const policiesDir = path22.join(vaultPath2, ".claude", "policies");
|
|
14831
|
+
const files = await fs20.readdir(policiesDir);
|
|
14755
14832
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
14756
14833
|
if (yamlFiles.length > 0) {
|
|
14757
14834
|
const names = yamlFiles.map((f) => f.replace(/\.ya?ml$/, "")).join(", ");
|
|
@@ -14762,9 +14839,9 @@ async function getPolicyHint(vaultPath2) {
|
|
|
14762
14839
|
return "";
|
|
14763
14840
|
}
|
|
14764
14841
|
async function ensureFileExists(vaultPath2, notePath) {
|
|
14765
|
-
const fullPath =
|
|
14842
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
14766
14843
|
try {
|
|
14767
|
-
await
|
|
14844
|
+
await fs20.access(fullPath);
|
|
14768
14845
|
return null;
|
|
14769
14846
|
} catch {
|
|
14770
14847
|
const hint = await getPolicyHint(vaultPath2);
|
|
@@ -14940,17 +15017,17 @@ async function executeCreateNote(options) {
|
|
|
14940
15017
|
if (!pathCheck.valid) {
|
|
14941
15018
|
return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
|
|
14942
15019
|
}
|
|
14943
|
-
const fullPath =
|
|
15020
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
14944
15021
|
let fileExists = false;
|
|
14945
15022
|
try {
|
|
14946
|
-
await
|
|
15023
|
+
await fs20.access(fullPath);
|
|
14947
15024
|
fileExists = true;
|
|
14948
15025
|
} catch {
|
|
14949
15026
|
}
|
|
14950
15027
|
if (fileExists && !overwrite) {
|
|
14951
15028
|
return { success: false, result: errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`), filesWritten: [] };
|
|
14952
15029
|
}
|
|
14953
|
-
await
|
|
15030
|
+
await fs20.mkdir(path22.dirname(fullPath), { recursive: true });
|
|
14954
15031
|
const { maybeApplyWikilinks: maybeApplyWikilinks2 } = await Promise.resolve().then(() => (init_wikilinks(), wikilinks_exports));
|
|
14955
15032
|
const { content: processedContent } = maybeApplyWikilinks2(content, skipWikilinks ?? false, notePath);
|
|
14956
15033
|
let finalFrontmatter = frontmatter;
|
|
@@ -14984,13 +15061,13 @@ async function executeDeleteNote(options) {
|
|
|
14984
15061
|
if (!pathCheck.valid) {
|
|
14985
15062
|
return { success: false, result: errorResult(notePath, `Path blocked: ${pathCheck.reason}`), filesWritten: [] };
|
|
14986
15063
|
}
|
|
14987
|
-
const fullPath =
|
|
15064
|
+
const fullPath = path22.join(vaultPath2, notePath);
|
|
14988
15065
|
try {
|
|
14989
|
-
await
|
|
15066
|
+
await fs20.access(fullPath);
|
|
14990
15067
|
} catch {
|
|
14991
15068
|
return { success: false, result: errorResult(notePath, `File not found: ${notePath}`), filesWritten: [] };
|
|
14992
15069
|
}
|
|
14993
|
-
await
|
|
15070
|
+
await fs20.unlink(fullPath);
|
|
14994
15071
|
const result = successResult(notePath, `Deleted note: ${notePath}`, {});
|
|
14995
15072
|
return { success: true, result, filesWritten: [notePath] };
|
|
14996
15073
|
} catch (error) {
|
|
@@ -15008,10 +15085,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
15008
15085
|
if (!validation.valid) {
|
|
15009
15086
|
throw new Error(`Path blocked: ${validation.reason}`);
|
|
15010
15087
|
}
|
|
15011
|
-
const fullPath =
|
|
15012
|
-
await
|
|
15088
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
15089
|
+
await fs21.mkdir(path23.dirname(fullPath), { recursive: true });
|
|
15013
15090
|
const templates = config.templates || {};
|
|
15014
|
-
const filename =
|
|
15091
|
+
const filename = path23.basename(notePath, ".md").toLowerCase();
|
|
15015
15092
|
let templatePath;
|
|
15016
15093
|
const dailyPattern = /^\d{4}-\d{2}-\d{2}/;
|
|
15017
15094
|
const weeklyPattern = /^\d{4}-W\d{2}/;
|
|
@@ -15032,10 +15109,10 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
15032
15109
|
let templateContent;
|
|
15033
15110
|
if (templatePath) {
|
|
15034
15111
|
try {
|
|
15035
|
-
const absTemplatePath =
|
|
15036
|
-
templateContent = await
|
|
15112
|
+
const absTemplatePath = path23.join(vaultPath2, templatePath);
|
|
15113
|
+
templateContent = await fs21.readFile(absTemplatePath, "utf-8");
|
|
15037
15114
|
} catch {
|
|
15038
|
-
const title =
|
|
15115
|
+
const title = path23.basename(notePath, ".md");
|
|
15039
15116
|
templateContent = `---
|
|
15040
15117
|
---
|
|
15041
15118
|
|
|
@@ -15044,7 +15121,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
15044
15121
|
templatePath = void 0;
|
|
15045
15122
|
}
|
|
15046
15123
|
} else {
|
|
15047
|
-
const title =
|
|
15124
|
+
const title = path23.basename(notePath, ".md");
|
|
15048
15125
|
templateContent = `---
|
|
15049
15126
|
---
|
|
15050
15127
|
|
|
@@ -15053,7 +15130,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
15053
15130
|
}
|
|
15054
15131
|
const now = /* @__PURE__ */ new Date();
|
|
15055
15132
|
const dateStr = now.toISOString().split("T")[0];
|
|
15056
|
-
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g,
|
|
15133
|
+
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path23.basename(notePath, ".md"));
|
|
15057
15134
|
const matter9 = (await import("gray-matter")).default;
|
|
15058
15135
|
const parsed = matter9(templateContent);
|
|
15059
15136
|
if (!parsed.data.date) {
|
|
@@ -15094,9 +15171,9 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
15094
15171
|
let noteCreated = false;
|
|
15095
15172
|
let templateUsed;
|
|
15096
15173
|
if (create_if_missing && !dry_run) {
|
|
15097
|
-
const fullPath =
|
|
15174
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
15098
15175
|
try {
|
|
15099
|
-
await
|
|
15176
|
+
await fs21.access(fullPath);
|
|
15100
15177
|
} catch {
|
|
15101
15178
|
const config = getConfig2();
|
|
15102
15179
|
const result = await createNoteFromTemplate(vaultPath2, notePath, config);
|
|
@@ -15105,9 +15182,9 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
15105
15182
|
}
|
|
15106
15183
|
}
|
|
15107
15184
|
if (create_if_missing && dry_run) {
|
|
15108
|
-
const fullPath =
|
|
15185
|
+
const fullPath = path23.join(vaultPath2, notePath);
|
|
15109
15186
|
try {
|
|
15110
|
-
await
|
|
15187
|
+
await fs21.access(fullPath);
|
|
15111
15188
|
} catch {
|
|
15112
15189
|
return {
|
|
15113
15190
|
content: [{
|
|
@@ -15592,8 +15669,8 @@ Example: vault_update_frontmatter({ path: "projects/alpha.md", frontmatter: { st
|
|
|
15592
15669
|
init_writer();
|
|
15593
15670
|
init_wikilinks();
|
|
15594
15671
|
import { z as z15 } from "zod";
|
|
15595
|
-
import
|
|
15596
|
-
import
|
|
15672
|
+
import fs22 from "fs/promises";
|
|
15673
|
+
import path24 from "path";
|
|
15597
15674
|
function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
15598
15675
|
server2.tool(
|
|
15599
15676
|
"vault_create_note",
|
|
@@ -15619,23 +15696,23 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
|
15619
15696
|
if (!validatePath(vaultPath2, notePath)) {
|
|
15620
15697
|
return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
|
|
15621
15698
|
}
|
|
15622
|
-
const fullPath =
|
|
15699
|
+
const fullPath = path24.join(vaultPath2, notePath);
|
|
15623
15700
|
const existsCheck = await ensureFileExists(vaultPath2, notePath);
|
|
15624
15701
|
if (existsCheck === null && !overwrite) {
|
|
15625
15702
|
return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
|
|
15626
15703
|
}
|
|
15627
|
-
const dir =
|
|
15628
|
-
await
|
|
15704
|
+
const dir = path24.dirname(fullPath);
|
|
15705
|
+
await fs22.mkdir(dir, { recursive: true });
|
|
15629
15706
|
let effectiveContent = content;
|
|
15630
15707
|
let effectiveFrontmatter = frontmatter;
|
|
15631
15708
|
if (template) {
|
|
15632
|
-
const templatePath =
|
|
15709
|
+
const templatePath = path24.join(vaultPath2, template);
|
|
15633
15710
|
try {
|
|
15634
|
-
const raw = await
|
|
15711
|
+
const raw = await fs22.readFile(templatePath, "utf-8");
|
|
15635
15712
|
const matter9 = (await import("gray-matter")).default;
|
|
15636
15713
|
const parsed = matter9(raw);
|
|
15637
15714
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
15638
|
-
const title =
|
|
15715
|
+
const title = path24.basename(notePath, ".md");
|
|
15639
15716
|
let templateContent = parsed.content.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, title);
|
|
15640
15717
|
if (content) {
|
|
15641
15718
|
templateContent = templateContent.trimEnd() + "\n\n" + content;
|
|
@@ -15654,7 +15731,7 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
|
15654
15731
|
effectiveFrontmatter.created = now.toISOString();
|
|
15655
15732
|
}
|
|
15656
15733
|
const warnings = [];
|
|
15657
|
-
const noteName =
|
|
15734
|
+
const noteName = path24.basename(notePath, ".md");
|
|
15658
15735
|
const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
|
|
15659
15736
|
const preflight = await checkPreflightSimilarity(noteName);
|
|
15660
15737
|
if (preflight.existingEntity) {
|
|
@@ -15795,8 +15872,8 @@ ${sources}`;
|
|
|
15795
15872
|
}
|
|
15796
15873
|
return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
|
|
15797
15874
|
}
|
|
15798
|
-
const fullPath =
|
|
15799
|
-
await
|
|
15875
|
+
const fullPath = path24.join(vaultPath2, notePath);
|
|
15876
|
+
await fs22.unlink(fullPath);
|
|
15800
15877
|
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
|
|
15801
15878
|
const message = backlinkWarning ? `Deleted note: ${notePath}
|
|
15802
15879
|
|
|
@@ -15816,8 +15893,8 @@ init_writer();
|
|
|
15816
15893
|
init_git();
|
|
15817
15894
|
init_wikilinks();
|
|
15818
15895
|
import { z as z16 } from "zod";
|
|
15819
|
-
import
|
|
15820
|
-
import
|
|
15896
|
+
import fs23 from "fs/promises";
|
|
15897
|
+
import path25 from "path";
|
|
15821
15898
|
import matter6 from "gray-matter";
|
|
15822
15899
|
function escapeRegex(str) {
|
|
15823
15900
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -15836,16 +15913,16 @@ function extractWikilinks2(content) {
|
|
|
15836
15913
|
return wikilinks;
|
|
15837
15914
|
}
|
|
15838
15915
|
function getTitleFromPath(filePath) {
|
|
15839
|
-
return
|
|
15916
|
+
return path25.basename(filePath, ".md");
|
|
15840
15917
|
}
|
|
15841
15918
|
async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
15842
15919
|
const results = [];
|
|
15843
15920
|
const allTargets = [targetTitle, ...targetAliases].map((t) => t.toLowerCase());
|
|
15844
15921
|
async function scanDir(dir) {
|
|
15845
15922
|
const files = [];
|
|
15846
|
-
const entries = await
|
|
15923
|
+
const entries = await fs23.readdir(dir, { withFileTypes: true });
|
|
15847
15924
|
for (const entry of entries) {
|
|
15848
|
-
const fullPath =
|
|
15925
|
+
const fullPath = path25.join(dir, entry.name);
|
|
15849
15926
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
15850
15927
|
files.push(...await scanDir(fullPath));
|
|
15851
15928
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -15856,8 +15933,8 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
15856
15933
|
}
|
|
15857
15934
|
const allFiles = await scanDir(vaultPath2);
|
|
15858
15935
|
for (const filePath of allFiles) {
|
|
15859
|
-
const relativePath =
|
|
15860
|
-
const content = await
|
|
15936
|
+
const relativePath = path25.relative(vaultPath2, filePath);
|
|
15937
|
+
const content = await fs23.readFile(filePath, "utf-8");
|
|
15861
15938
|
const wikilinks = extractWikilinks2(content);
|
|
15862
15939
|
const matchingLinks = [];
|
|
15863
15940
|
for (const link of wikilinks) {
|
|
@@ -15943,10 +16020,10 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
15943
16020
|
};
|
|
15944
16021
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
15945
16022
|
}
|
|
15946
|
-
const oldFullPath =
|
|
15947
|
-
const newFullPath =
|
|
16023
|
+
const oldFullPath = path25.join(vaultPath2, oldPath);
|
|
16024
|
+
const newFullPath = path25.join(vaultPath2, newPath);
|
|
15948
16025
|
try {
|
|
15949
|
-
await
|
|
16026
|
+
await fs23.access(oldFullPath);
|
|
15950
16027
|
} catch {
|
|
15951
16028
|
const result2 = {
|
|
15952
16029
|
success: false,
|
|
@@ -15956,7 +16033,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
15956
16033
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
15957
16034
|
}
|
|
15958
16035
|
try {
|
|
15959
|
-
await
|
|
16036
|
+
await fs23.access(newFullPath);
|
|
15960
16037
|
const result2 = {
|
|
15961
16038
|
success: false,
|
|
15962
16039
|
message: `Destination already exists: ${newPath}`,
|
|
@@ -15965,7 +16042,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
15965
16042
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
15966
16043
|
} catch {
|
|
15967
16044
|
}
|
|
15968
|
-
const sourceContent = await
|
|
16045
|
+
const sourceContent = await fs23.readFile(oldFullPath, "utf-8");
|
|
15969
16046
|
const parsed = matter6(sourceContent);
|
|
15970
16047
|
const aliases = extractAliases2(parsed.data);
|
|
15971
16048
|
const oldTitle = getTitleFromPath(oldPath);
|
|
@@ -16025,9 +16102,9 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16025
16102
|
};
|
|
16026
16103
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
16027
16104
|
}
|
|
16028
|
-
const destDir =
|
|
16029
|
-
await
|
|
16030
|
-
await
|
|
16105
|
+
const destDir = path25.dirname(newFullPath);
|
|
16106
|
+
await fs23.mkdir(destDir, { recursive: true });
|
|
16107
|
+
await fs23.rename(oldFullPath, newFullPath);
|
|
16031
16108
|
let gitCommit;
|
|
16032
16109
|
let undoAvailable;
|
|
16033
16110
|
let staleLockDetected;
|
|
@@ -16101,12 +16178,12 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16101
16178
|
if (sanitizedTitle !== newTitle) {
|
|
16102
16179
|
console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
|
|
16103
16180
|
}
|
|
16104
|
-
const fullPath =
|
|
16105
|
-
const dir =
|
|
16106
|
-
const newPath = dir === "." ? `${sanitizedTitle}.md` :
|
|
16107
|
-
const newFullPath =
|
|
16181
|
+
const fullPath = path25.join(vaultPath2, notePath);
|
|
16182
|
+
const dir = path25.dirname(notePath);
|
|
16183
|
+
const newPath = dir === "." ? `${sanitizedTitle}.md` : path25.join(dir, `${sanitizedTitle}.md`);
|
|
16184
|
+
const newFullPath = path25.join(vaultPath2, newPath);
|
|
16108
16185
|
try {
|
|
16109
|
-
await
|
|
16186
|
+
await fs23.access(fullPath);
|
|
16110
16187
|
} catch {
|
|
16111
16188
|
const result2 = {
|
|
16112
16189
|
success: false,
|
|
@@ -16117,7 +16194,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16117
16194
|
}
|
|
16118
16195
|
if (fullPath !== newFullPath) {
|
|
16119
16196
|
try {
|
|
16120
|
-
await
|
|
16197
|
+
await fs23.access(newFullPath);
|
|
16121
16198
|
const result2 = {
|
|
16122
16199
|
success: false,
|
|
16123
16200
|
message: `A note with this title already exists: ${newPath}`,
|
|
@@ -16127,7 +16204,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16127
16204
|
} catch {
|
|
16128
16205
|
}
|
|
16129
16206
|
}
|
|
16130
|
-
const sourceContent = await
|
|
16207
|
+
const sourceContent = await fs23.readFile(fullPath, "utf-8");
|
|
16131
16208
|
const parsed = matter6(sourceContent);
|
|
16132
16209
|
const aliases = extractAliases2(parsed.data);
|
|
16133
16210
|
const oldTitle = getTitleFromPath(notePath);
|
|
@@ -16187,7 +16264,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16187
16264
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
16188
16265
|
}
|
|
16189
16266
|
if (fullPath !== newFullPath) {
|
|
16190
|
-
await
|
|
16267
|
+
await fs23.rename(fullPath, newFullPath);
|
|
16191
16268
|
}
|
|
16192
16269
|
let gitCommit;
|
|
16193
16270
|
let undoAvailable;
|
|
@@ -16235,7 +16312,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16235
16312
|
init_writer();
|
|
16236
16313
|
init_wikilinks();
|
|
16237
16314
|
import { z as z17 } from "zod";
|
|
16238
|
-
import
|
|
16315
|
+
import fs24 from "fs/promises";
|
|
16239
16316
|
function registerMergeTools(server2, getVaultPath) {
|
|
16240
16317
|
server2.tool(
|
|
16241
16318
|
"merge_entities",
|
|
@@ -16362,7 +16439,7 @@ ${trimmedSource}`;
|
|
|
16362
16439
|
}
|
|
16363
16440
|
await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter, "LF", targetContentHash);
|
|
16364
16441
|
const fullSourcePath = `${vaultPath2}/${source_path}`;
|
|
16365
|
-
await
|
|
16442
|
+
await fs24.unlink(fullSourcePath);
|
|
16366
16443
|
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
16367
16444
|
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
16368
16445
|
});
|
|
@@ -16634,8 +16711,8 @@ init_schema();
|
|
|
16634
16711
|
|
|
16635
16712
|
// src/core/write/policy/parser.ts
|
|
16636
16713
|
init_schema();
|
|
16637
|
-
import
|
|
16638
|
-
import
|
|
16714
|
+
import fs25 from "fs/promises";
|
|
16715
|
+
import path26 from "path";
|
|
16639
16716
|
import matter7 from "gray-matter";
|
|
16640
16717
|
function parseYaml(content) {
|
|
16641
16718
|
const parsed = matter7(`---
|
|
@@ -16660,7 +16737,7 @@ function parsePolicyString(yamlContent) {
|
|
|
16660
16737
|
}
|
|
16661
16738
|
async function loadPolicyFile(filePath) {
|
|
16662
16739
|
try {
|
|
16663
|
-
const content = await
|
|
16740
|
+
const content = await fs25.readFile(filePath, "utf-8");
|
|
16664
16741
|
return parsePolicyString(content);
|
|
16665
16742
|
} catch (error) {
|
|
16666
16743
|
if (error.code === "ENOENT") {
|
|
@@ -16684,15 +16761,15 @@ async function loadPolicyFile(filePath) {
|
|
|
16684
16761
|
}
|
|
16685
16762
|
}
|
|
16686
16763
|
async function loadPolicy(vaultPath2, policyName) {
|
|
16687
|
-
const policiesDir =
|
|
16688
|
-
const policyPath =
|
|
16764
|
+
const policiesDir = path26.join(vaultPath2, ".claude", "policies");
|
|
16765
|
+
const policyPath = path26.join(policiesDir, `${policyName}.yaml`);
|
|
16689
16766
|
try {
|
|
16690
|
-
await
|
|
16767
|
+
await fs25.access(policyPath);
|
|
16691
16768
|
return loadPolicyFile(policyPath);
|
|
16692
16769
|
} catch {
|
|
16693
|
-
const ymlPath =
|
|
16770
|
+
const ymlPath = path26.join(policiesDir, `${policyName}.yml`);
|
|
16694
16771
|
try {
|
|
16695
|
-
await
|
|
16772
|
+
await fs25.access(ymlPath);
|
|
16696
16773
|
return loadPolicyFile(ymlPath);
|
|
16697
16774
|
} catch {
|
|
16698
16775
|
return {
|
|
@@ -16832,8 +16909,8 @@ init_schema();
|
|
|
16832
16909
|
init_writer();
|
|
16833
16910
|
init_git();
|
|
16834
16911
|
init_wikilinks();
|
|
16835
|
-
import
|
|
16836
|
-
import
|
|
16912
|
+
import fs27 from "fs/promises";
|
|
16913
|
+
import path28 from "path";
|
|
16837
16914
|
init_constants();
|
|
16838
16915
|
async function executeStep(step, vaultPath2, context, conditionResults, searchFn) {
|
|
16839
16916
|
const { execute, reason } = shouldStepExecute(step.when, conditionResults);
|
|
@@ -17041,9 +17118,9 @@ async function executeToggleTask(params, vaultPath2) {
|
|
|
17041
17118
|
const notePath = String(params.path || "");
|
|
17042
17119
|
const task = String(params.task || "");
|
|
17043
17120
|
const section = params.section ? String(params.section) : void 0;
|
|
17044
|
-
const fullPath =
|
|
17121
|
+
const fullPath = path28.join(vaultPath2, notePath);
|
|
17045
17122
|
try {
|
|
17046
|
-
await
|
|
17123
|
+
await fs27.access(fullPath);
|
|
17047
17124
|
} catch {
|
|
17048
17125
|
return { success: false, message: `File not found: ${notePath}`, path: notePath };
|
|
17049
17126
|
}
|
|
@@ -17324,15 +17401,15 @@ async function rollbackChanges(vaultPath2, originalContents, filesModified) {
|
|
|
17324
17401
|
const pathCheck = await validatePathSecure(vaultPath2, filePath);
|
|
17325
17402
|
if (!pathCheck.valid) continue;
|
|
17326
17403
|
const original = originalContents.get(filePath);
|
|
17327
|
-
const fullPath =
|
|
17404
|
+
const fullPath = path28.join(vaultPath2, filePath);
|
|
17328
17405
|
if (original === null) {
|
|
17329
17406
|
try {
|
|
17330
|
-
await
|
|
17407
|
+
await fs27.unlink(fullPath);
|
|
17331
17408
|
} catch {
|
|
17332
17409
|
}
|
|
17333
17410
|
} else if (original !== void 0) {
|
|
17334
17411
|
try {
|
|
17335
|
-
await
|
|
17412
|
+
await fs27.writeFile(fullPath, original);
|
|
17336
17413
|
} catch {
|
|
17337
17414
|
}
|
|
17338
17415
|
}
|
|
@@ -17378,33 +17455,33 @@ async function previewPolicy(policy, vaultPath2, variables) {
|
|
|
17378
17455
|
}
|
|
17379
17456
|
|
|
17380
17457
|
// src/core/write/policy/storage.ts
|
|
17381
|
-
import
|
|
17382
|
-
import
|
|
17458
|
+
import fs28 from "fs/promises";
|
|
17459
|
+
import path29 from "path";
|
|
17383
17460
|
function getPoliciesDir(vaultPath2) {
|
|
17384
|
-
return
|
|
17461
|
+
return path29.join(vaultPath2, ".claude", "policies");
|
|
17385
17462
|
}
|
|
17386
17463
|
async function ensurePoliciesDir(vaultPath2) {
|
|
17387
17464
|
const dir = getPoliciesDir(vaultPath2);
|
|
17388
|
-
await
|
|
17465
|
+
await fs28.mkdir(dir, { recursive: true });
|
|
17389
17466
|
}
|
|
17390
17467
|
async function listPolicies(vaultPath2) {
|
|
17391
17468
|
const dir = getPoliciesDir(vaultPath2);
|
|
17392
17469
|
const policies = [];
|
|
17393
17470
|
try {
|
|
17394
|
-
const files = await
|
|
17471
|
+
const files = await fs28.readdir(dir);
|
|
17395
17472
|
for (const file of files) {
|
|
17396
17473
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
|
17397
17474
|
continue;
|
|
17398
17475
|
}
|
|
17399
|
-
const filePath =
|
|
17400
|
-
const
|
|
17401
|
-
const content = await
|
|
17476
|
+
const filePath = path29.join(dir, file);
|
|
17477
|
+
const stat5 = await fs28.stat(filePath);
|
|
17478
|
+
const content = await fs28.readFile(filePath, "utf-8");
|
|
17402
17479
|
const metadata = extractPolicyMetadata(content);
|
|
17403
17480
|
policies.push({
|
|
17404
17481
|
name: metadata.name || file.replace(/\.ya?ml$/, ""),
|
|
17405
17482
|
description: metadata.description || "No description",
|
|
17406
17483
|
path: file,
|
|
17407
|
-
lastModified:
|
|
17484
|
+
lastModified: stat5.mtime,
|
|
17408
17485
|
version: metadata.version || "1.0",
|
|
17409
17486
|
requiredVariables: metadata.variables || []
|
|
17410
17487
|
});
|
|
@@ -17421,10 +17498,10 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
|
|
|
17421
17498
|
const dir = getPoliciesDir(vaultPath2);
|
|
17422
17499
|
await ensurePoliciesDir(vaultPath2);
|
|
17423
17500
|
const filename = `${policyName}.yaml`;
|
|
17424
|
-
const filePath =
|
|
17501
|
+
const filePath = path29.join(dir, filename);
|
|
17425
17502
|
if (!overwrite) {
|
|
17426
17503
|
try {
|
|
17427
|
-
await
|
|
17504
|
+
await fs28.access(filePath);
|
|
17428
17505
|
return {
|
|
17429
17506
|
success: false,
|
|
17430
17507
|
path: filename,
|
|
@@ -17441,7 +17518,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
|
|
|
17441
17518
|
message: `Invalid policy: ${validation.errors.map((e) => e.message).join("; ")}`
|
|
17442
17519
|
};
|
|
17443
17520
|
}
|
|
17444
|
-
await
|
|
17521
|
+
await fs28.writeFile(filePath, content, "utf-8");
|
|
17445
17522
|
return {
|
|
17446
17523
|
success: true,
|
|
17447
17524
|
path: filename,
|
|
@@ -17966,8 +18043,8 @@ function registerPolicyTools(server2, getVaultPath, getSearchFn) {
|
|
|
17966
18043
|
import { z as z21 } from "zod";
|
|
17967
18044
|
|
|
17968
18045
|
// src/core/write/tagRename.ts
|
|
17969
|
-
import * as
|
|
17970
|
-
import * as
|
|
18046
|
+
import * as fs29 from "fs/promises";
|
|
18047
|
+
import * as path30 from "path";
|
|
17971
18048
|
import matter8 from "gray-matter";
|
|
17972
18049
|
import { getProtectedZones } from "@velvetmonkey/vault-core";
|
|
17973
18050
|
function getNotesInFolder3(index, folder) {
|
|
@@ -18073,10 +18150,10 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
|
|
|
18073
18150
|
const previews = [];
|
|
18074
18151
|
let totalChanges = 0;
|
|
18075
18152
|
for (const note of affectedNotes) {
|
|
18076
|
-
const fullPath =
|
|
18153
|
+
const fullPath = path30.join(vaultPath2, note.path);
|
|
18077
18154
|
let fileContent;
|
|
18078
18155
|
try {
|
|
18079
|
-
fileContent = await
|
|
18156
|
+
fileContent = await fs29.readFile(fullPath, "utf-8");
|
|
18080
18157
|
} catch {
|
|
18081
18158
|
continue;
|
|
18082
18159
|
}
|
|
@@ -18149,7 +18226,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
|
|
|
18149
18226
|
previews.push(preview);
|
|
18150
18227
|
if (!dryRun) {
|
|
18151
18228
|
const newContent = matter8.stringify(updatedContent, fm);
|
|
18152
|
-
await
|
|
18229
|
+
await fs29.writeFile(fullPath, newContent, "utf-8");
|
|
18153
18230
|
}
|
|
18154
18231
|
}
|
|
18155
18232
|
}
|
|
@@ -19084,7 +19161,7 @@ function selectByMmr(candidates, limit, lambda = 0.7) {
|
|
|
19084
19161
|
// src/core/read/snippets.ts
|
|
19085
19162
|
init_embeddings();
|
|
19086
19163
|
init_stemmer();
|
|
19087
|
-
import * as
|
|
19164
|
+
import * as fs30 from "fs";
|
|
19088
19165
|
function stripFrontmatter(content) {
|
|
19089
19166
|
const match = content.match(/^---[\s\S]*?---\n([\s\S]*)$/);
|
|
19090
19167
|
return match ? match[1] : content;
|
|
@@ -19130,7 +19207,7 @@ async function extractBestSnippets(filePath, queryEmbedding, queryTokens, option
|
|
|
19130
19207
|
const maxChunkChars = options?.maxChunkChars ?? 500;
|
|
19131
19208
|
let content;
|
|
19132
19209
|
try {
|
|
19133
|
-
content =
|
|
19210
|
+
content = fs30.readFileSync(filePath, "utf-8");
|
|
19134
19211
|
} catch {
|
|
19135
19212
|
return [];
|
|
19136
19213
|
}
|
|
@@ -19449,6 +19526,14 @@ function registerRecallTools(server2, getStateDb2, getVaultPath, getIndex) {
|
|
|
19449
19526
|
const notes = results.filter((r) => r.type === "note");
|
|
19450
19527
|
const memories = results.filter((r) => r.type === "memory");
|
|
19451
19528
|
const index = getIndex?.() ?? null;
|
|
19529
|
+
const enrichedNotes = notes.map((n) => ({
|
|
19530
|
+
path: n.id,
|
|
19531
|
+
snippet: n.content,
|
|
19532
|
+
...enrichNoteCompact(n.id, stateDb2, index)
|
|
19533
|
+
}));
|
|
19534
|
+
const hopResults = index ? multiHopBackfill(enrichedNotes, index, stateDb2, {
|
|
19535
|
+
maxBackfill: Math.max(5, (args.max_results ?? 10) - notes.length)
|
|
19536
|
+
}) : [];
|
|
19452
19537
|
return {
|
|
19453
19538
|
content: [{
|
|
19454
19539
|
type: "text",
|
|
@@ -19458,20 +19543,12 @@ function registerRecallTools(server2, getStateDb2, getVaultPath, getIndex) {
|
|
|
19458
19543
|
entities: entities.map((e) => ({
|
|
19459
19544
|
name: e.id,
|
|
19460
19545
|
description: e.content,
|
|
19461
|
-
score: Math.round(e.score * 10) / 10,
|
|
19462
|
-
breakdown: e.breakdown,
|
|
19463
19546
|
...enrichEntityCompact(e.id, stateDb2, index)
|
|
19464
19547
|
})),
|
|
19465
|
-
notes:
|
|
19466
|
-
path: n.id,
|
|
19467
|
-
snippet: n.content,
|
|
19468
|
-
score: Math.round(n.score * 10) / 10,
|
|
19469
|
-
...enrichNoteCompact(n.id, stateDb2, index)
|
|
19470
|
-
})),
|
|
19548
|
+
notes: [...enrichedNotes, ...hopResults],
|
|
19471
19549
|
memories: memories.map((m) => ({
|
|
19472
19550
|
key: m.id,
|
|
19473
|
-
value: m.content
|
|
19474
|
-
score: Math.round(m.score * 10) / 10
|
|
19551
|
+
value: m.content
|
|
19475
19552
|
}))
|
|
19476
19553
|
}, null, 2)
|
|
19477
19554
|
}]
|
|
@@ -19565,8 +19642,8 @@ function getNoteAccessFrequency(stateDb2, daysBack = 30) {
|
|
|
19565
19642
|
}
|
|
19566
19643
|
}
|
|
19567
19644
|
}
|
|
19568
|
-
return Array.from(noteMap.entries()).map(([
|
|
19569
|
-
path:
|
|
19645
|
+
return Array.from(noteMap.entries()).map(([path34, stats]) => ({
|
|
19646
|
+
path: path34,
|
|
19570
19647
|
access_count: stats.access_count,
|
|
19571
19648
|
last_accessed: stats.last_accessed,
|
|
19572
19649
|
tools_used: Array.from(stats.tools)
|
|
@@ -19905,8 +19982,8 @@ function registerConfigTools(server2, getConfig2, setConfig, getStateDb2) {
|
|
|
19905
19982
|
init_wikilinks();
|
|
19906
19983
|
init_wikilinkFeedback();
|
|
19907
19984
|
import { z as z28 } from "zod";
|
|
19908
|
-
import * as
|
|
19909
|
-
import * as
|
|
19985
|
+
import * as fs31 from "fs/promises";
|
|
19986
|
+
import * as path31 from "path";
|
|
19910
19987
|
import { scanVaultEntities as scanVaultEntities3, SCHEMA_VERSION as SCHEMA_VERSION2 } from "@velvetmonkey/vault-core";
|
|
19911
19988
|
init_embeddings();
|
|
19912
19989
|
function hasSkipWikilinks(content) {
|
|
@@ -19919,16 +19996,16 @@ function hasSkipWikilinks(content) {
|
|
|
19919
19996
|
async function collectMarkdownFiles(dirPath, basePath, excludeFolders) {
|
|
19920
19997
|
const results = [];
|
|
19921
19998
|
try {
|
|
19922
|
-
const entries = await
|
|
19999
|
+
const entries = await fs31.readdir(dirPath, { withFileTypes: true });
|
|
19923
20000
|
for (const entry of entries) {
|
|
19924
20001
|
if (entry.name.startsWith(".")) continue;
|
|
19925
|
-
const fullPath =
|
|
20002
|
+
const fullPath = path31.join(dirPath, entry.name);
|
|
19926
20003
|
if (entry.isDirectory()) {
|
|
19927
20004
|
if (excludeFolders.some((f) => entry.name.toLowerCase() === f.toLowerCase())) continue;
|
|
19928
20005
|
const sub = await collectMarkdownFiles(fullPath, basePath, excludeFolders);
|
|
19929
20006
|
results.push(...sub);
|
|
19930
20007
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
19931
|
-
results.push(
|
|
20008
|
+
results.push(path31.relative(basePath, fullPath));
|
|
19932
20009
|
}
|
|
19933
20010
|
}
|
|
19934
20011
|
} catch {
|
|
@@ -19958,7 +20035,7 @@ var EXCLUDE_FOLDERS = [
|
|
|
19958
20035
|
];
|
|
19959
20036
|
function buildStatusReport(stateDb2, vaultPath2) {
|
|
19960
20037
|
const recommendations = [];
|
|
19961
|
-
const dbPath =
|
|
20038
|
+
const dbPath = path31.join(vaultPath2, ".flywheel", "state.db");
|
|
19962
20039
|
const statedbExists = stateDb2 !== null;
|
|
19963
20040
|
if (!statedbExists) {
|
|
19964
20041
|
recommendations.push("StateDb not initialized \u2014 server needs restart");
|
|
@@ -20084,10 +20161,10 @@ async function executeRun(stateDb2, vaultPath2) {
|
|
|
20084
20161
|
const allFiles = await collectMarkdownFiles(vaultPath2, vaultPath2, EXCLUDE_FOLDERS);
|
|
20085
20162
|
let eligible = 0;
|
|
20086
20163
|
for (const relativePath of allFiles) {
|
|
20087
|
-
const fullPath =
|
|
20164
|
+
const fullPath = path31.join(vaultPath2, relativePath);
|
|
20088
20165
|
let content;
|
|
20089
20166
|
try {
|
|
20090
|
-
content = await
|
|
20167
|
+
content = await fs31.readFile(fullPath, "utf-8");
|
|
20091
20168
|
} catch {
|
|
20092
20169
|
continue;
|
|
20093
20170
|
}
|
|
@@ -20142,10 +20219,10 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
|
|
|
20142
20219
|
const eligible = [];
|
|
20143
20220
|
let notesSkipped = 0;
|
|
20144
20221
|
for (const relativePath of allFiles) {
|
|
20145
|
-
const fullPath =
|
|
20222
|
+
const fullPath = path31.join(vaultPath2, relativePath);
|
|
20146
20223
|
let content;
|
|
20147
20224
|
try {
|
|
20148
|
-
content = await
|
|
20225
|
+
content = await fs31.readFile(fullPath, "utf-8");
|
|
20149
20226
|
} catch {
|
|
20150
20227
|
continue;
|
|
20151
20228
|
}
|
|
@@ -20172,8 +20249,8 @@ async function executeEnrich(stateDb2, vaultPath2, dryRun, batchSize, offset) {
|
|
|
20172
20249
|
match_count: result.linksAdded
|
|
20173
20250
|
});
|
|
20174
20251
|
if (!dryRun) {
|
|
20175
|
-
const fullPath =
|
|
20176
|
-
await
|
|
20252
|
+
const fullPath = path31.join(vaultPath2, relativePath);
|
|
20253
|
+
await fs31.writeFile(fullPath, result.content, "utf-8");
|
|
20177
20254
|
notesModified++;
|
|
20178
20255
|
if (stateDb2) {
|
|
20179
20256
|
trackWikilinkApplications(stateDb2, relativePath, entities);
|
|
@@ -20563,8 +20640,8 @@ import { z as z31 } from "zod";
|
|
|
20563
20640
|
|
|
20564
20641
|
// src/core/read/similarity.ts
|
|
20565
20642
|
init_embeddings();
|
|
20566
|
-
import * as
|
|
20567
|
-
import * as
|
|
20643
|
+
import * as fs32 from "fs";
|
|
20644
|
+
import * as path32 from "path";
|
|
20568
20645
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
20569
20646
|
"the",
|
|
20570
20647
|
"be",
|
|
@@ -20701,10 +20778,10 @@ function extractKeyTerms(content, maxTerms = 15) {
|
|
|
20701
20778
|
}
|
|
20702
20779
|
function findSimilarNotes(db4, vaultPath2, index, sourcePath, options = {}) {
|
|
20703
20780
|
const limit = options.limit ?? 10;
|
|
20704
|
-
const absPath =
|
|
20781
|
+
const absPath = path32.join(vaultPath2, sourcePath);
|
|
20705
20782
|
let content;
|
|
20706
20783
|
try {
|
|
20707
|
-
content =
|
|
20784
|
+
content = fs32.readFileSync(absPath, "utf-8");
|
|
20708
20785
|
} catch {
|
|
20709
20786
|
return [];
|
|
20710
20787
|
}
|
|
@@ -20843,7 +20920,7 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
20843
20920
|
diversity: z31.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
|
|
20844
20921
|
}
|
|
20845
20922
|
},
|
|
20846
|
-
async ({ path:
|
|
20923
|
+
async ({ path: path34, limit, diversity }) => {
|
|
20847
20924
|
const index = getIndex();
|
|
20848
20925
|
const vaultPath2 = getVaultPath();
|
|
20849
20926
|
const stateDb2 = getStateDb2();
|
|
@@ -20852,10 +20929,10 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
20852
20929
|
content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
|
|
20853
20930
|
};
|
|
20854
20931
|
}
|
|
20855
|
-
if (!index.notes.has(
|
|
20932
|
+
if (!index.notes.has(path34)) {
|
|
20856
20933
|
return {
|
|
20857
20934
|
content: [{ type: "text", text: JSON.stringify({
|
|
20858
|
-
error: `Note not found: ${
|
|
20935
|
+
error: `Note not found: ${path34}`,
|
|
20859
20936
|
hint: "Use the full relative path including .md extension"
|
|
20860
20937
|
}, null, 2) }]
|
|
20861
20938
|
};
|
|
@@ -20867,12 +20944,12 @@ function registerSimilarityTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
20867
20944
|
};
|
|
20868
20945
|
const useHybrid = hasEmbeddingsIndex();
|
|
20869
20946
|
const method = useHybrid ? "hybrid" : "bm25";
|
|
20870
|
-
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index,
|
|
20947
|
+
const results = useHybrid ? await findHybridSimilarNotes(stateDb2.db, vaultPath2, index, path34, opts) : findSimilarNotes(stateDb2.db, vaultPath2, index, path34, opts);
|
|
20871
20948
|
return {
|
|
20872
20949
|
content: [{
|
|
20873
20950
|
type: "text",
|
|
20874
20951
|
text: JSON.stringify({
|
|
20875
|
-
source:
|
|
20952
|
+
source: path34,
|
|
20876
20953
|
method,
|
|
20877
20954
|
count: results.length,
|
|
20878
20955
|
similar: results
|
|
@@ -21747,7 +21824,7 @@ init_cooccurrence();
|
|
|
21747
21824
|
init_retrievalCooccurrence();
|
|
21748
21825
|
init_corrections();
|
|
21749
21826
|
init_edgeWeights();
|
|
21750
|
-
import * as
|
|
21827
|
+
import * as fs33 from "node:fs/promises";
|
|
21751
21828
|
import { createHash as createHash3 } from "node:crypto";
|
|
21752
21829
|
|
|
21753
21830
|
// src/resources/vault.ts
|
|
@@ -22293,7 +22370,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
|
|
|
22293
22370
|
let totalBytes = 0;
|
|
22294
22371
|
for (const p of notePaths) {
|
|
22295
22372
|
try {
|
|
22296
|
-
totalBytes += statSync5(
|
|
22373
|
+
totalBytes += statSync5(path33.join(vp, p)).size;
|
|
22297
22374
|
} catch {
|
|
22298
22375
|
}
|
|
22299
22376
|
}
|
|
@@ -22792,22 +22869,22 @@ async function buildStartupCatchupBatch(vaultPath2, sinceMs) {
|
|
|
22792
22869
|
async function scanDir(dir) {
|
|
22793
22870
|
let entries;
|
|
22794
22871
|
try {
|
|
22795
|
-
entries = await
|
|
22872
|
+
entries = await fs33.readdir(dir, { withFileTypes: true });
|
|
22796
22873
|
} catch {
|
|
22797
22874
|
return;
|
|
22798
22875
|
}
|
|
22799
22876
|
for (const entry of entries) {
|
|
22800
|
-
const fullPath =
|
|
22877
|
+
const fullPath = path33.join(dir, entry.name);
|
|
22801
22878
|
if (entry.isDirectory()) {
|
|
22802
22879
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
22803
22880
|
await scanDir(fullPath);
|
|
22804
22881
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
22805
22882
|
try {
|
|
22806
|
-
const
|
|
22807
|
-
if (
|
|
22883
|
+
const stat5 = await fs33.stat(fullPath);
|
|
22884
|
+
if (stat5.mtimeMs > sinceMs) {
|
|
22808
22885
|
events.push({
|
|
22809
22886
|
type: "upsert",
|
|
22810
|
-
path:
|
|
22887
|
+
path: path33.relative(vaultPath2, fullPath),
|
|
22811
22888
|
originalEvents: []
|
|
22812
22889
|
});
|
|
22813
22890
|
}
|
|
@@ -22997,8 +23074,8 @@ async function runPostIndexWork(index) {
|
|
|
22997
23074
|
}
|
|
22998
23075
|
} catch {
|
|
22999
23076
|
try {
|
|
23000
|
-
const dir =
|
|
23001
|
-
const base =
|
|
23077
|
+
const dir = path33.dirname(rawPath);
|
|
23078
|
+
const base = path33.basename(rawPath);
|
|
23002
23079
|
const resolvedDir = realpathSync(dir).replace(/\\/g, "/");
|
|
23003
23080
|
for (const prefix of vaultPrefixes) {
|
|
23004
23081
|
if (resolvedDir.startsWith(prefix + "/") || resolvedDir === prefix) {
|
|
@@ -23030,7 +23107,7 @@ async function runPostIndexWork(index) {
|
|
|
23030
23107
|
continue;
|
|
23031
23108
|
}
|
|
23032
23109
|
try {
|
|
23033
|
-
const content = await
|
|
23110
|
+
const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
|
|
23034
23111
|
const hash = createHash3("sha256").update(content).digest("hex").slice(0, 16);
|
|
23035
23112
|
if (lastContentHashes.get(event.path) === hash) {
|
|
23036
23113
|
serverLog("watcher", `Hash unchanged, skipping: ${event.path}`);
|
|
@@ -23111,7 +23188,7 @@ async function runPostIndexWork(index) {
|
|
|
23111
23188
|
...batch,
|
|
23112
23189
|
events: filteredEvents.map((e) => ({
|
|
23113
23190
|
...e,
|
|
23114
|
-
path:
|
|
23191
|
+
path: path33.join(vaultPath, e.path)
|
|
23115
23192
|
}))
|
|
23116
23193
|
};
|
|
23117
23194
|
const batchResult = await processBatch(vaultIndex, vaultPath, absoluteBatch);
|
|
@@ -23262,7 +23339,7 @@ async function runPostIndexWork(index) {
|
|
|
23262
23339
|
removeEmbedding(event.path);
|
|
23263
23340
|
embRemoved++;
|
|
23264
23341
|
} else if (event.path.endsWith(".md")) {
|
|
23265
|
-
const absPath =
|
|
23342
|
+
const absPath = path33.join(vaultPath, event.path);
|
|
23266
23343
|
await updateEmbedding(event.path, absPath);
|
|
23267
23344
|
embUpdated++;
|
|
23268
23345
|
}
|
|
@@ -23482,7 +23559,7 @@ async function runPostIndexWork(index) {
|
|
|
23482
23559
|
for (const event of filteredEvents) {
|
|
23483
23560
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
23484
23561
|
try {
|
|
23485
|
-
const content = await
|
|
23562
|
+
const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
|
|
23486
23563
|
const zones = getProtectedZones2(content);
|
|
23487
23564
|
const linked = new Set(
|
|
23488
23565
|
(forwardLinkResults.find((r) => r.file === event.path)?.resolved ?? []).map((n) => n.toLowerCase())
|
|
@@ -23523,7 +23600,7 @@ async function runPostIndexWork(index) {
|
|
|
23523
23600
|
for (const event of filteredEvents) {
|
|
23524
23601
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
23525
23602
|
try {
|
|
23526
|
-
const content = await
|
|
23603
|
+
const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
|
|
23527
23604
|
const removed = processImplicitFeedback(stateDb, event.path, content);
|
|
23528
23605
|
for (const entity of removed) feedbackResults.push({ entity, file: event.path });
|
|
23529
23606
|
} catch {
|
|
@@ -23603,7 +23680,7 @@ async function runPostIndexWork(index) {
|
|
|
23603
23680
|
for (const event of filteredEvents) {
|
|
23604
23681
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
23605
23682
|
try {
|
|
23606
|
-
const content = await
|
|
23683
|
+
const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
|
|
23607
23684
|
const zones = getProtectedZones2(content);
|
|
23608
23685
|
const linkedSet = new Set(
|
|
23609
23686
|
(forwardLinkResults.find((r) => r.file === event.path)?.resolved ?? []).concat(forwardLinkResults.find((r) => r.file === event.path)?.dead ?? []).map((n) => n.toLowerCase())
|
|
@@ -23636,7 +23713,7 @@ async function runPostIndexWork(index) {
|
|
|
23636
23713
|
for (const event of filteredEvents) {
|
|
23637
23714
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
23638
23715
|
try {
|
|
23639
|
-
const content = await
|
|
23716
|
+
const content = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
|
|
23640
23717
|
const result = await suggestRelatedLinks(content, {
|
|
23641
23718
|
maxSuggestions: 5,
|
|
23642
23719
|
strictness: "balanced",
|
|
@@ -23660,6 +23737,33 @@ async function runPostIndexWork(index) {
|
|
|
23660
23737
|
if (suggestionResults.length > 0) {
|
|
23661
23738
|
serverLog("watcher", `Suggestion scoring: ${suggestionResults.length} files scored`);
|
|
23662
23739
|
}
|
|
23740
|
+
if (flywheelConfig?.proactive_linking !== false && suggestionResults.length > 0) {
|
|
23741
|
+
tracker.start("proactive_linking", { files: suggestionResults.length });
|
|
23742
|
+
try {
|
|
23743
|
+
const proactiveResults = [];
|
|
23744
|
+
for (const { file, top } of suggestionResults) {
|
|
23745
|
+
if (getNoteContext(file) === "daily") continue;
|
|
23746
|
+
try {
|
|
23747
|
+
const result = await applyProactiveSuggestions(file, vaultPath, top, {
|
|
23748
|
+
minScore: flywheelConfig?.proactive_min_score ?? 20,
|
|
23749
|
+
maxPerFile: flywheelConfig?.proactive_max_per_file ?? 3
|
|
23750
|
+
});
|
|
23751
|
+
if (result.applied.length > 0) {
|
|
23752
|
+
proactiveResults.push({ file, applied: result.applied });
|
|
23753
|
+
}
|
|
23754
|
+
} catch {
|
|
23755
|
+
}
|
|
23756
|
+
}
|
|
23757
|
+
const totalApplied = proactiveResults.reduce((s, r) => s + r.applied.length, 0);
|
|
23758
|
+
tracker.end({ files_modified: proactiveResults.length, total_applied: totalApplied, results: proactiveResults });
|
|
23759
|
+
if (totalApplied > 0) {
|
|
23760
|
+
serverLog("watcher", `Proactive linking: ${totalApplied} links in ${proactiveResults.length} files`);
|
|
23761
|
+
}
|
|
23762
|
+
} catch (e) {
|
|
23763
|
+
tracker.end({ error: String(e) });
|
|
23764
|
+
serverLog("watcher", `Proactive linking failed: ${e}`, "error");
|
|
23765
|
+
}
|
|
23766
|
+
}
|
|
23663
23767
|
tracker.start("tag_scan", { files: filteredEvents.length });
|
|
23664
23768
|
try {
|
|
23665
23769
|
const tagDiffs = [];
|