@velvetmonkey/flywheel-memory 2.0.126 → 2.0.128
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 +242 -174
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4065,8 +4065,8 @@ async function suggestRelatedLinks(content, options = {}) {
|
|
|
4065
4065
|
return emptyResult;
|
|
4066
4066
|
}
|
|
4067
4067
|
const MAX_SUFFIX_ENTRIES = 3;
|
|
4068
|
-
const MIN_SUFFIX_SCORE = 12;
|
|
4069
|
-
const MIN_SUFFIX_CONTENT = 3;
|
|
4068
|
+
const MIN_SUFFIX_SCORE = noteContext === "daily" ? 8 : 12;
|
|
4069
|
+
const MIN_SUFFIX_CONTENT = noteContext === "daily" ? 2 : 3;
|
|
4070
4070
|
const suffixEntries = topEntries.filter(
|
|
4071
4071
|
(e) => e.score >= MIN_SUFFIX_SCORE && (e.breakdown.contentMatch >= MIN_SUFFIX_CONTENT || e.breakdown.cooccurrenceBoost >= MIN_SUFFIX_CONTENT || (e.breakdown.semanticBoost ?? 0) >= MIN_SUFFIX_CONTENT)
|
|
4072
4072
|
).slice(0, MAX_SUFFIX_ENTRIES);
|
|
@@ -4552,7 +4552,7 @@ var init_constants = __esm({
|
|
|
4552
4552
|
});
|
|
4553
4553
|
|
|
4554
4554
|
// src/core/write/writer.ts
|
|
4555
|
-
import
|
|
4555
|
+
import fs20 from "fs/promises";
|
|
4556
4556
|
import path21 from "path";
|
|
4557
4557
|
import matter5 from "gray-matter";
|
|
4558
4558
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -4940,9 +4940,9 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
4940
4940
|
try {
|
|
4941
4941
|
const fullPath = path21.join(vaultPath2, notePath);
|
|
4942
4942
|
try {
|
|
4943
|
-
await
|
|
4944
|
-
const realPath = await
|
|
4945
|
-
const realVaultPath = await
|
|
4943
|
+
await fs20.access(fullPath);
|
|
4944
|
+
const realPath = await fs20.realpath(fullPath);
|
|
4945
|
+
const realVaultPath = await fs20.realpath(vaultPath2);
|
|
4946
4946
|
if (!realPath.startsWith(realVaultPath)) {
|
|
4947
4947
|
return {
|
|
4948
4948
|
valid: false,
|
|
@@ -4959,9 +4959,9 @@ async function validatePathSecure(vaultPath2, notePath) {
|
|
|
4959
4959
|
} catch {
|
|
4960
4960
|
const parentDir = path21.dirname(fullPath);
|
|
4961
4961
|
try {
|
|
4962
|
-
await
|
|
4963
|
-
const realParentPath = await
|
|
4964
|
-
const realVaultPath = await
|
|
4962
|
+
await fs20.access(parentDir);
|
|
4963
|
+
const realParentPath = await fs20.realpath(parentDir);
|
|
4964
|
+
const realVaultPath = await fs20.realpath(vaultPath2);
|
|
4965
4965
|
if (!realParentPath.startsWith(realVaultPath)) {
|
|
4966
4966
|
return {
|
|
4967
4967
|
valid: false,
|
|
@@ -4988,8 +4988,8 @@ async function readVaultFile(vaultPath2, notePath) {
|
|
|
4988
4988
|
}
|
|
4989
4989
|
const fullPath = path21.join(vaultPath2, notePath);
|
|
4990
4990
|
const [rawContent, stat5] = await Promise.all([
|
|
4991
|
-
|
|
4992
|
-
|
|
4991
|
+
fs20.readFile(fullPath, "utf-8"),
|
|
4992
|
+
fs20.stat(fullPath)
|
|
4993
4993
|
]);
|
|
4994
4994
|
const contentHash2 = computeContentHash(rawContent);
|
|
4995
4995
|
const lineEnding = detectLineEnding(rawContent);
|
|
@@ -5043,7 +5043,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
|
|
|
5043
5043
|
}
|
|
5044
5044
|
const fullPath = path21.join(vaultPath2, notePath);
|
|
5045
5045
|
if (expectedHash) {
|
|
5046
|
-
const currentRaw = await
|
|
5046
|
+
const currentRaw = await fs20.readFile(fullPath, "utf-8");
|
|
5047
5047
|
const currentHash = computeContentHash(currentRaw);
|
|
5048
5048
|
if (currentHash !== expectedHash) {
|
|
5049
5049
|
throw new WriteConflictError(notePath);
|
|
@@ -5052,7 +5052,7 @@ async function writeVaultFile(vaultPath2, notePath, content, frontmatter, lineEn
|
|
|
5052
5052
|
let output = matter5.stringify(content, frontmatter);
|
|
5053
5053
|
output = normalizeTrailingNewline(output);
|
|
5054
5054
|
output = convertLineEndings(output, lineEnding);
|
|
5055
|
-
await
|
|
5055
|
+
await fs20.writeFile(fullPath, output, "utf-8");
|
|
5056
5056
|
}
|
|
5057
5057
|
function removeFromSection(content, section, pattern, mode = "first", useRegex = false) {
|
|
5058
5058
|
const lines = content.split("\n");
|
|
@@ -5899,7 +5899,7 @@ __export(conditions_exports, {
|
|
|
5899
5899
|
evaluateCondition: () => evaluateCondition,
|
|
5900
5900
|
shouldStepExecute: () => shouldStepExecute
|
|
5901
5901
|
});
|
|
5902
|
-
import
|
|
5902
|
+
import fs27 from "fs/promises";
|
|
5903
5903
|
import path27 from "path";
|
|
5904
5904
|
async function evaluateCondition(condition, vaultPath2, context) {
|
|
5905
5905
|
const interpolatedPath = condition.path ? interpolate(condition.path, context) : void 0;
|
|
@@ -5955,7 +5955,7 @@ async function evaluateCondition(condition, vaultPath2, context) {
|
|
|
5955
5955
|
async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
5956
5956
|
const fullPath = path27.join(vaultPath2, notePath);
|
|
5957
5957
|
try {
|
|
5958
|
-
await
|
|
5958
|
+
await fs27.access(fullPath);
|
|
5959
5959
|
return {
|
|
5960
5960
|
met: expectExists,
|
|
5961
5961
|
reason: expectExists ? `File exists: ${notePath}` : `File exists (expected not to): ${notePath}`
|
|
@@ -5970,7 +5970,7 @@ async function evaluateFileExists(vaultPath2, notePath, expectExists) {
|
|
|
5970
5970
|
async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectExists) {
|
|
5971
5971
|
const fullPath = path27.join(vaultPath2, notePath);
|
|
5972
5972
|
try {
|
|
5973
|
-
await
|
|
5973
|
+
await fs27.access(fullPath);
|
|
5974
5974
|
} catch {
|
|
5975
5975
|
return {
|
|
5976
5976
|
met: !expectExists,
|
|
@@ -6001,7 +6001,7 @@ async function evaluateSectionExists(vaultPath2, notePath, sectionName, expectEx
|
|
|
6001
6001
|
async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expectExists) {
|
|
6002
6002
|
const fullPath = path27.join(vaultPath2, notePath);
|
|
6003
6003
|
try {
|
|
6004
|
-
await
|
|
6004
|
+
await fs27.access(fullPath);
|
|
6005
6005
|
} catch {
|
|
6006
6006
|
return {
|
|
6007
6007
|
met: !expectExists,
|
|
@@ -6032,7 +6032,7 @@ async function evaluateFrontmatterExists(vaultPath2, notePath, fieldName, expect
|
|
|
6032
6032
|
async function evaluateFrontmatterEquals(vaultPath2, notePath, fieldName, expectedValue) {
|
|
6033
6033
|
const fullPath = path27.join(vaultPath2, notePath);
|
|
6034
6034
|
try {
|
|
6035
|
-
await
|
|
6035
|
+
await fs27.access(fullPath);
|
|
6036
6036
|
} catch {
|
|
6037
6037
|
return {
|
|
6038
6038
|
met: false,
|
|
@@ -11206,6 +11206,149 @@ function expandQuery(expansionTerms, primaryResults, index, stateDb2) {
|
|
|
11206
11206
|
}
|
|
11207
11207
|
|
|
11208
11208
|
// src/tools/read/query.ts
|
|
11209
|
+
init_wikilinkFeedback();
|
|
11210
|
+
init_cooccurrence();
|
|
11211
|
+
init_wikilinks();
|
|
11212
|
+
init_recency();
|
|
11213
|
+
init_edgeWeights();
|
|
11214
|
+
|
|
11215
|
+
// src/core/read/snippets.ts
|
|
11216
|
+
init_embeddings();
|
|
11217
|
+
init_stemmer();
|
|
11218
|
+
import * as fs12 from "fs";
|
|
11219
|
+
function stripFrontmatter(content) {
|
|
11220
|
+
const match = content.match(/^---[\s\S]*?---\n([\s\S]*)$/);
|
|
11221
|
+
return match ? match[1] : content;
|
|
11222
|
+
}
|
|
11223
|
+
function splitIntoParagraphs(content, maxChunkChars) {
|
|
11224
|
+
const MIN_PARAGRAPH_CHARS = 50;
|
|
11225
|
+
const raw = content.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
11226
|
+
const merged = [];
|
|
11227
|
+
let buffer2 = "";
|
|
11228
|
+
for (const paragraph of raw) {
|
|
11229
|
+
if (buffer2) {
|
|
11230
|
+
buffer2 += "\n\n" + paragraph;
|
|
11231
|
+
if (buffer2.length >= MIN_PARAGRAPH_CHARS) {
|
|
11232
|
+
merged.push(buffer2.slice(0, maxChunkChars));
|
|
11233
|
+
buffer2 = "";
|
|
11234
|
+
}
|
|
11235
|
+
} else if (paragraph.length < MIN_PARAGRAPH_CHARS) {
|
|
11236
|
+
buffer2 = paragraph;
|
|
11237
|
+
} else {
|
|
11238
|
+
merged.push(paragraph.slice(0, maxChunkChars));
|
|
11239
|
+
}
|
|
11240
|
+
}
|
|
11241
|
+
if (buffer2) {
|
|
11242
|
+
merged.push(buffer2.slice(0, maxChunkChars));
|
|
11243
|
+
}
|
|
11244
|
+
return merged;
|
|
11245
|
+
}
|
|
11246
|
+
function scoreByKeywords(chunk, queryTokens, queryStems) {
|
|
11247
|
+
const chunkTokens = new Set(tokenize(chunk.toLowerCase()));
|
|
11248
|
+
const chunkStems = new Set([...chunkTokens].map((t) => stem(t)));
|
|
11249
|
+
let score = 0;
|
|
11250
|
+
for (let i = 0; i < queryTokens.length; i++) {
|
|
11251
|
+
if (chunkTokens.has(queryTokens[i])) {
|
|
11252
|
+
score += 10;
|
|
11253
|
+
} else if (chunkStems.has(queryStems[i])) {
|
|
11254
|
+
score += 5;
|
|
11255
|
+
}
|
|
11256
|
+
}
|
|
11257
|
+
return score;
|
|
11258
|
+
}
|
|
11259
|
+
async function extractBestSnippets(filePath, queryEmbedding, queryTokens, options) {
|
|
11260
|
+
const maxSnippets = options?.maxSnippets ?? 1;
|
|
11261
|
+
const maxChunkChars = options?.maxChunkChars ?? 500;
|
|
11262
|
+
let content;
|
|
11263
|
+
try {
|
|
11264
|
+
content = fs12.readFileSync(filePath, "utf-8");
|
|
11265
|
+
} catch {
|
|
11266
|
+
return [];
|
|
11267
|
+
}
|
|
11268
|
+
const body = stripFrontmatter(content);
|
|
11269
|
+
if (body.length < 50) {
|
|
11270
|
+
return body.length > 0 ? [{ text: body, score: 1 }] : [];
|
|
11271
|
+
}
|
|
11272
|
+
const paragraphs = splitIntoParagraphs(body, maxChunkChars);
|
|
11273
|
+
if (paragraphs.length === 0) return [];
|
|
11274
|
+
const queryStems = queryTokens.map((t) => stem(t));
|
|
11275
|
+
const scored = paragraphs.map((text, idx) => ({
|
|
11276
|
+
text,
|
|
11277
|
+
idx,
|
|
11278
|
+
keywordScore: scoreByKeywords(text, queryTokens, queryStems)
|
|
11279
|
+
}));
|
|
11280
|
+
scored.sort((a, b) => b.keywordScore - a.keywordScore);
|
|
11281
|
+
const topKeyword = scored.slice(0, 5);
|
|
11282
|
+
if (queryEmbedding && hasEmbeddingsIndex()) {
|
|
11283
|
+
try {
|
|
11284
|
+
const reranked = [];
|
|
11285
|
+
for (const chunk of topKeyword) {
|
|
11286
|
+
const chunkEmbedding = await embedTextCached(chunk.text);
|
|
11287
|
+
const sim = cosineSimilarity(queryEmbedding, chunkEmbedding);
|
|
11288
|
+
reranked.push({ text: chunk.text, score: sim });
|
|
11289
|
+
}
|
|
11290
|
+
reranked.sort((a, b) => b.score - a.score);
|
|
11291
|
+
return reranked.slice(0, maxSnippets);
|
|
11292
|
+
} catch {
|
|
11293
|
+
}
|
|
11294
|
+
}
|
|
11295
|
+
return topKeyword.slice(0, maxSnippets).map((c) => ({
|
|
11296
|
+
text: c.text,
|
|
11297
|
+
score: c.keywordScore
|
|
11298
|
+
}));
|
|
11299
|
+
}
|
|
11300
|
+
|
|
11301
|
+
// src/tools/read/query.ts
|
|
11302
|
+
init_stemmer();
|
|
11303
|
+
function applyGraphReranking(results, stateDb2) {
|
|
11304
|
+
if (!stateDb2) return;
|
|
11305
|
+
const cooccurrenceIndex2 = getCooccurrenceIndex();
|
|
11306
|
+
const recencyIndex2 = loadRecencyFromStateDb();
|
|
11307
|
+
const feedbackBoosts = getAllFeedbackBoosts(stateDb2);
|
|
11308
|
+
const edgeWeightMap = getEntityEdgeWeightMap(stateDb2);
|
|
11309
|
+
if (!cooccurrenceIndex2 && !recencyIndex2) return;
|
|
11310
|
+
const seedEntities = /* @__PURE__ */ new Set();
|
|
11311
|
+
for (const r of results) {
|
|
11312
|
+
const name = r.title || r.path?.replace(/\.md$/, "").split("/").pop() || "";
|
|
11313
|
+
if (name) seedEntities.add(name);
|
|
11314
|
+
}
|
|
11315
|
+
for (const r of results) {
|
|
11316
|
+
const name = r.title || r.path?.replace(/\.md$/, "").split("/").pop() || "";
|
|
11317
|
+
let graphBoost = 0;
|
|
11318
|
+
if (cooccurrenceIndex2) graphBoost += getCooccurrenceBoost(name, seedEntities, cooccurrenceIndex2, recencyIndex2);
|
|
11319
|
+
if (recencyIndex2) graphBoost += getRecencyBoost(name, recencyIndex2);
|
|
11320
|
+
graphBoost += feedbackBoosts.get(name) ?? 0;
|
|
11321
|
+
const avgWeight = edgeWeightMap.get(name.toLowerCase());
|
|
11322
|
+
if (avgWeight && avgWeight > 1) graphBoost += Math.min((avgWeight - 1) * 3, 6);
|
|
11323
|
+
if (graphBoost > 0) {
|
|
11324
|
+
r.graph_boost = graphBoost;
|
|
11325
|
+
const baseScore = r.rrf_score ?? 0;
|
|
11326
|
+
r._combined_score = baseScore + graphBoost / 50;
|
|
11327
|
+
}
|
|
11328
|
+
}
|
|
11329
|
+
results.sort(
|
|
11330
|
+
(a, b) => (b._combined_score ?? b.rrf_score ?? 0) - (a._combined_score ?? a.rrf_score ?? 0)
|
|
11331
|
+
);
|
|
11332
|
+
}
|
|
11333
|
+
async function enhanceSnippets(results, query, vaultPath2) {
|
|
11334
|
+
if (!hasEmbeddingsIndex()) return;
|
|
11335
|
+
const queryTokens = tokenize(query).map((t) => t.toLowerCase());
|
|
11336
|
+
let queryEmb = null;
|
|
11337
|
+
try {
|
|
11338
|
+
queryEmb = await embedTextCached(query);
|
|
11339
|
+
} catch {
|
|
11340
|
+
}
|
|
11341
|
+
for (const r of results) {
|
|
11342
|
+
if (!r.path) continue;
|
|
11343
|
+
try {
|
|
11344
|
+
const snippets = await extractBestSnippets(`${vaultPath2}/${r.path}`, queryEmb, queryTokens);
|
|
11345
|
+
if (snippets.length > 0 && snippets[0].text.length > 0) {
|
|
11346
|
+
r.snippet = snippets[0].text;
|
|
11347
|
+
}
|
|
11348
|
+
} catch {
|
|
11349
|
+
}
|
|
11350
|
+
}
|
|
11351
|
+
}
|
|
11209
11352
|
function matchesFrontmatter(note, where) {
|
|
11210
11353
|
for (const [key, value] of Object.entries(where)) {
|
|
11211
11354
|
const noteValue = note.frontmatter[key];
|
|
@@ -11469,6 +11612,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
11469
11612
|
const expansionTerms2 = extractExpansionTerms(results2, query, index);
|
|
11470
11613
|
const expansionResults2 = expandQuery(expansionTerms2, [...results2, ...hopResults2], index, stateDb2);
|
|
11471
11614
|
results2.push(...hopResults2, ...expansionResults2);
|
|
11615
|
+
applyGraphReranking(results2, stateDb2);
|
|
11616
|
+
await enhanceSnippets(results2, query, vaultPath2);
|
|
11472
11617
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
11473
11618
|
method: "hybrid",
|
|
11474
11619
|
query,
|
|
@@ -11498,6 +11643,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
11498
11643
|
const expansionTerms2 = extractExpansionTerms(results2, query, index);
|
|
11499
11644
|
const expansionResults2 = expandQuery(expansionTerms2, [...results2, ...hopResults2], index, stateDb2);
|
|
11500
11645
|
results2.push(...hopResults2, ...expansionResults2);
|
|
11646
|
+
applyGraphReranking(results2, stateDb2);
|
|
11647
|
+
await enhanceSnippets(results2, query, vaultPath2);
|
|
11501
11648
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
11502
11649
|
method: "fts5",
|
|
11503
11650
|
query,
|
|
@@ -11512,6 +11659,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
11512
11659
|
const expansionTerms = extractExpansionTerms(results, query, index);
|
|
11513
11660
|
const expansionResults = expandQuery(expansionTerms, [...results, ...hopResults], index, stateDbFts);
|
|
11514
11661
|
results.push(...hopResults, ...expansionResults);
|
|
11662
|
+
applyGraphReranking(results, stateDbFts);
|
|
11663
|
+
await enhanceSnippets(results, query, vaultPath2);
|
|
11515
11664
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
11516
11665
|
method: "fts5",
|
|
11517
11666
|
query,
|
|
@@ -11525,7 +11674,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb2) {
|
|
|
11525
11674
|
}
|
|
11526
11675
|
|
|
11527
11676
|
// src/tools/read/system.ts
|
|
11528
|
-
import * as
|
|
11677
|
+
import * as fs13 from "fs";
|
|
11529
11678
|
import * as path15 from "path";
|
|
11530
11679
|
import { z as z5 } from "zod";
|
|
11531
11680
|
import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
|
|
@@ -11840,7 +11989,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
11840
11989
|
}
|
|
11841
11990
|
try {
|
|
11842
11991
|
const fullPath = path15.join(vaultPath2, note.path);
|
|
11843
|
-
const content = await
|
|
11992
|
+
const content = await fs13.promises.readFile(fullPath, "utf-8");
|
|
11844
11993
|
const lines = content.split("\n");
|
|
11845
11994
|
for (let i = 0; i < lines.length; i++) {
|
|
11846
11995
|
const line = lines[i];
|
|
@@ -12099,7 +12248,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
12099
12248
|
import { z as z6 } from "zod";
|
|
12100
12249
|
|
|
12101
12250
|
// src/tools/read/structure.ts
|
|
12102
|
-
import * as
|
|
12251
|
+
import * as fs14 from "fs";
|
|
12103
12252
|
import * as path16 from "path";
|
|
12104
12253
|
var HEADING_REGEX2 = /^(#{1,6})\s+(.+)$/;
|
|
12105
12254
|
function extractHeadings2(content) {
|
|
@@ -12157,7 +12306,7 @@ async function getNoteStructure(index, notePath, vaultPath2) {
|
|
|
12157
12306
|
const absolutePath = path16.join(vaultPath2, notePath);
|
|
12158
12307
|
let content;
|
|
12159
12308
|
try {
|
|
12160
|
-
content = await
|
|
12309
|
+
content = await fs14.promises.readFile(absolutePath, "utf-8");
|
|
12161
12310
|
} catch {
|
|
12162
12311
|
return null;
|
|
12163
12312
|
}
|
|
@@ -12180,7 +12329,7 @@ async function getSectionContent(index, notePath, headingText, vaultPath2, inclu
|
|
|
12180
12329
|
const absolutePath = path16.join(vaultPath2, notePath);
|
|
12181
12330
|
let content;
|
|
12182
12331
|
try {
|
|
12183
|
-
content = await
|
|
12332
|
+
content = await fs14.promises.readFile(absolutePath, "utf-8");
|
|
12184
12333
|
} catch {
|
|
12185
12334
|
return null;
|
|
12186
12335
|
}
|
|
@@ -12222,7 +12371,7 @@ async function findSections(index, headingPattern, vaultPath2, folder) {
|
|
|
12222
12371
|
const absolutePath = path16.join(vaultPath2, note.path);
|
|
12223
12372
|
let content;
|
|
12224
12373
|
try {
|
|
12225
|
-
content = await
|
|
12374
|
+
content = await fs14.promises.readFile(absolutePath, "utf-8");
|
|
12226
12375
|
} catch {
|
|
12227
12376
|
continue;
|
|
12228
12377
|
}
|
|
@@ -12546,7 +12695,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
|
|
|
12546
12695
|
|
|
12547
12696
|
// src/tools/read/migrations.ts
|
|
12548
12697
|
import { z as z7 } from "zod";
|
|
12549
|
-
import * as
|
|
12698
|
+
import * as fs15 from "fs/promises";
|
|
12550
12699
|
import * as path17 from "path";
|
|
12551
12700
|
import matter2 from "gray-matter";
|
|
12552
12701
|
function getNotesInFolder(index, folder) {
|
|
@@ -12562,7 +12711,7 @@ function getNotesInFolder(index, folder) {
|
|
|
12562
12711
|
async function readFileContent(notePath, vaultPath2) {
|
|
12563
12712
|
const fullPath = path17.join(vaultPath2, notePath);
|
|
12564
12713
|
try {
|
|
12565
|
-
return await
|
|
12714
|
+
return await fs15.readFile(fullPath, "utf-8");
|
|
12566
12715
|
} catch {
|
|
12567
12716
|
return null;
|
|
12568
12717
|
}
|
|
@@ -12570,7 +12719,7 @@ async function readFileContent(notePath, vaultPath2) {
|
|
|
12570
12719
|
async function writeFileContent(notePath, vaultPath2, content) {
|
|
12571
12720
|
const fullPath = path17.join(vaultPath2, notePath);
|
|
12572
12721
|
try {
|
|
12573
|
-
await
|
|
12722
|
+
await fs15.writeFile(fullPath, content, "utf-8");
|
|
12574
12723
|
return true;
|
|
12575
12724
|
} catch {
|
|
12576
12725
|
return false;
|
|
@@ -12748,7 +12897,7 @@ function registerMigrationTools(server2, getIndex, getVaultPath) {
|
|
|
12748
12897
|
}
|
|
12749
12898
|
|
|
12750
12899
|
// src/tools/read/graphAnalysis.ts
|
|
12751
|
-
import
|
|
12900
|
+
import fs16 from "node:fs";
|
|
12752
12901
|
import path18 from "node:path";
|
|
12753
12902
|
import { z as z8 } from "zod";
|
|
12754
12903
|
|
|
@@ -13462,7 +13611,7 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb2
|
|
|
13462
13611
|
const scored = allNotes.map((note) => {
|
|
13463
13612
|
let wordCount = 0;
|
|
13464
13613
|
try {
|
|
13465
|
-
const content =
|
|
13614
|
+
const content = fs16.readFileSync(path18.join(vaultPath2, note.path), "utf-8");
|
|
13466
13615
|
const body = content.replace(/^---[\s\S]*?---\n?/, "");
|
|
13467
13616
|
wordCount = body.split(/\s+/).filter((w) => w.length > 0).length;
|
|
13468
13617
|
} catch {
|
|
@@ -14092,7 +14241,7 @@ function registerSemanticAnalysisTools(server2, getIndex, getVaultPath) {
|
|
|
14092
14241
|
import { z as z11 } from "zod";
|
|
14093
14242
|
|
|
14094
14243
|
// src/tools/read/bidirectional.ts
|
|
14095
|
-
import * as
|
|
14244
|
+
import * as fs17 from "fs/promises";
|
|
14096
14245
|
import * as path19 from "path";
|
|
14097
14246
|
import matter3 from "gray-matter";
|
|
14098
14247
|
var PROSE_PATTERN_REGEX = /^([A-Za-z][A-Za-z0-9 _-]*):\s*(?:\[\[([^\]]+)\]\]|"([^"]+)"|([^\n]+?))\s*$/gm;
|
|
@@ -14100,7 +14249,7 @@ var CODE_BLOCK_REGEX2 = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
|
14100
14249
|
async function readFileContent2(notePath, vaultPath2) {
|
|
14101
14250
|
const fullPath = path19.join(vaultPath2, notePath);
|
|
14102
14251
|
try {
|
|
14103
|
-
return await
|
|
14252
|
+
return await fs17.readFile(fullPath, "utf-8");
|
|
14104
14253
|
} catch {
|
|
14105
14254
|
return null;
|
|
14106
14255
|
}
|
|
@@ -14276,13 +14425,13 @@ async function suggestWikilinksInFrontmatter(index, notePath, vaultPath2) {
|
|
|
14276
14425
|
}
|
|
14277
14426
|
|
|
14278
14427
|
// src/tools/read/computed.ts
|
|
14279
|
-
import * as
|
|
14428
|
+
import * as fs18 from "fs/promises";
|
|
14280
14429
|
import * as path20 from "path";
|
|
14281
14430
|
import matter4 from "gray-matter";
|
|
14282
14431
|
async function readFileContent3(notePath, vaultPath2) {
|
|
14283
14432
|
const fullPath = path20.join(vaultPath2, notePath);
|
|
14284
14433
|
try {
|
|
14285
|
-
return await
|
|
14434
|
+
return await fs18.readFile(fullPath, "utf-8");
|
|
14286
14435
|
} catch {
|
|
14287
14436
|
return null;
|
|
14288
14437
|
}
|
|
@@ -14290,7 +14439,7 @@ async function readFileContent3(notePath, vaultPath2) {
|
|
|
14290
14439
|
async function getFileStats(notePath, vaultPath2) {
|
|
14291
14440
|
const fullPath = path20.join(vaultPath2, notePath);
|
|
14292
14441
|
try {
|
|
14293
|
-
const stats = await
|
|
14442
|
+
const stats = await fs18.stat(fullPath);
|
|
14294
14443
|
return {
|
|
14295
14444
|
modified: stats.mtime,
|
|
14296
14445
|
created: stats.birthtime
|
|
@@ -14420,7 +14569,7 @@ async function computeFrontmatter(index, notePath, vaultPath2, fields) {
|
|
|
14420
14569
|
|
|
14421
14570
|
// src/tools/read/noteIntelligence.ts
|
|
14422
14571
|
init_embeddings();
|
|
14423
|
-
import
|
|
14572
|
+
import fs19 from "node:fs";
|
|
14424
14573
|
import nodePath from "node:path";
|
|
14425
14574
|
function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfig2) {
|
|
14426
14575
|
server2.registerTool(
|
|
@@ -14480,7 +14629,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
14480
14629
|
}
|
|
14481
14630
|
let noteContent;
|
|
14482
14631
|
try {
|
|
14483
|
-
noteContent =
|
|
14632
|
+
noteContent = fs19.readFileSync(nodePath.join(vaultPath2, notePath), "utf-8");
|
|
14484
14633
|
} catch {
|
|
14485
14634
|
return {
|
|
14486
14635
|
content: [{ type: "text", text: JSON.stringify({
|
|
@@ -14559,7 +14708,7 @@ function registerNoteIntelligenceTools(server2, getIndex, getVaultPath, getConfi
|
|
|
14559
14708
|
// src/tools/write/mutations.ts
|
|
14560
14709
|
init_writer();
|
|
14561
14710
|
import { z as z12 } from "zod";
|
|
14562
|
-
import
|
|
14711
|
+
import fs22 from "fs/promises";
|
|
14563
14712
|
import path23 from "path";
|
|
14564
14713
|
|
|
14565
14714
|
// src/core/write/validator.ts
|
|
@@ -14778,7 +14927,7 @@ init_constants();
|
|
|
14778
14927
|
init_writer();
|
|
14779
14928
|
init_wikilinks();
|
|
14780
14929
|
init_wikilinkFeedback();
|
|
14781
|
-
import
|
|
14930
|
+
import fs21 from "fs/promises";
|
|
14782
14931
|
import path22 from "path";
|
|
14783
14932
|
function formatMcpResult(result) {
|
|
14784
14933
|
if (result.tokensEstimate === void 0 || result.tokensEstimate === 0) {
|
|
@@ -14828,7 +14977,7 @@ async function handleGitCommit(vaultPath2, notePath, commit, prefix) {
|
|
|
14828
14977
|
async function getPolicyHint(vaultPath2) {
|
|
14829
14978
|
try {
|
|
14830
14979
|
const policiesDir = path22.join(vaultPath2, ".claude", "policies");
|
|
14831
|
-
const files = await
|
|
14980
|
+
const files = await fs21.readdir(policiesDir);
|
|
14832
14981
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
14833
14982
|
if (yamlFiles.length > 0) {
|
|
14834
14983
|
const names = yamlFiles.map((f) => f.replace(/\.ya?ml$/, "")).join(", ");
|
|
@@ -14841,7 +14990,7 @@ async function getPolicyHint(vaultPath2) {
|
|
|
14841
14990
|
async function ensureFileExists(vaultPath2, notePath) {
|
|
14842
14991
|
const fullPath = path22.join(vaultPath2, notePath);
|
|
14843
14992
|
try {
|
|
14844
|
-
await
|
|
14993
|
+
await fs21.access(fullPath);
|
|
14845
14994
|
return null;
|
|
14846
14995
|
} catch {
|
|
14847
14996
|
const hint = await getPolicyHint(vaultPath2);
|
|
@@ -15020,14 +15169,14 @@ async function executeCreateNote(options) {
|
|
|
15020
15169
|
const fullPath = path22.join(vaultPath2, notePath);
|
|
15021
15170
|
let fileExists = false;
|
|
15022
15171
|
try {
|
|
15023
|
-
await
|
|
15172
|
+
await fs21.access(fullPath);
|
|
15024
15173
|
fileExists = true;
|
|
15025
15174
|
} catch {
|
|
15026
15175
|
}
|
|
15027
15176
|
if (fileExists && !overwrite) {
|
|
15028
15177
|
return { success: false, result: errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`), filesWritten: [] };
|
|
15029
15178
|
}
|
|
15030
|
-
await
|
|
15179
|
+
await fs21.mkdir(path22.dirname(fullPath), { recursive: true });
|
|
15031
15180
|
const { maybeApplyWikilinks: maybeApplyWikilinks2 } = await Promise.resolve().then(() => (init_wikilinks(), wikilinks_exports));
|
|
15032
15181
|
const { content: processedContent } = maybeApplyWikilinks2(content, skipWikilinks ?? false, notePath);
|
|
15033
15182
|
let finalFrontmatter = frontmatter;
|
|
@@ -15063,11 +15212,11 @@ async function executeDeleteNote(options) {
|
|
|
15063
15212
|
}
|
|
15064
15213
|
const fullPath = path22.join(vaultPath2, notePath);
|
|
15065
15214
|
try {
|
|
15066
|
-
await
|
|
15215
|
+
await fs21.access(fullPath);
|
|
15067
15216
|
} catch {
|
|
15068
15217
|
return { success: false, result: errorResult(notePath, `File not found: ${notePath}`), filesWritten: [] };
|
|
15069
15218
|
}
|
|
15070
|
-
await
|
|
15219
|
+
await fs21.unlink(fullPath);
|
|
15071
15220
|
const result = successResult(notePath, `Deleted note: ${notePath}`, {});
|
|
15072
15221
|
return { success: true, result, filesWritten: [notePath] };
|
|
15073
15222
|
} catch (error) {
|
|
@@ -15086,7 +15235,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
15086
15235
|
throw new Error(`Path blocked: ${validation.reason}`);
|
|
15087
15236
|
}
|
|
15088
15237
|
const fullPath = path23.join(vaultPath2, notePath);
|
|
15089
|
-
await
|
|
15238
|
+
await fs22.mkdir(path23.dirname(fullPath), { recursive: true });
|
|
15090
15239
|
const templates = config.templates || {};
|
|
15091
15240
|
const filename = path23.basename(notePath, ".md").toLowerCase();
|
|
15092
15241
|
let templatePath;
|
|
@@ -15110,7 +15259,7 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
15110
15259
|
if (templatePath) {
|
|
15111
15260
|
try {
|
|
15112
15261
|
const absTemplatePath = path23.join(vaultPath2, templatePath);
|
|
15113
|
-
templateContent = await
|
|
15262
|
+
templateContent = await fs22.readFile(absTemplatePath, "utf-8");
|
|
15114
15263
|
} catch {
|
|
15115
15264
|
const title = path23.basename(notePath, ".md");
|
|
15116
15265
|
templateContent = `---
|
|
@@ -15173,7 +15322,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
15173
15322
|
if (create_if_missing && !dry_run) {
|
|
15174
15323
|
const fullPath = path23.join(vaultPath2, notePath);
|
|
15175
15324
|
try {
|
|
15176
|
-
await
|
|
15325
|
+
await fs22.access(fullPath);
|
|
15177
15326
|
} catch {
|
|
15178
15327
|
const config = getConfig2();
|
|
15179
15328
|
const result = await createNoteFromTemplate(vaultPath2, notePath, config);
|
|
@@ -15184,7 +15333,7 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
15184
15333
|
if (create_if_missing && dry_run) {
|
|
15185
15334
|
const fullPath = path23.join(vaultPath2, notePath);
|
|
15186
15335
|
try {
|
|
15187
|
-
await
|
|
15336
|
+
await fs22.access(fullPath);
|
|
15188
15337
|
} catch {
|
|
15189
15338
|
return {
|
|
15190
15339
|
content: [{
|
|
@@ -15669,7 +15818,7 @@ Example: vault_update_frontmatter({ path: "projects/alpha.md", frontmatter: { st
|
|
|
15669
15818
|
init_writer();
|
|
15670
15819
|
init_wikilinks();
|
|
15671
15820
|
import { z as z15 } from "zod";
|
|
15672
|
-
import
|
|
15821
|
+
import fs23 from "fs/promises";
|
|
15673
15822
|
import path24 from "path";
|
|
15674
15823
|
function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
15675
15824
|
server2.tool(
|
|
@@ -15702,13 +15851,13 @@ function registerNoteTools(server2, getVaultPath, getIndex) {
|
|
|
15702
15851
|
return formatMcpResult(errorResult(notePath, `File already exists: ${notePath}. Use overwrite=true to replace.`));
|
|
15703
15852
|
}
|
|
15704
15853
|
const dir = path24.dirname(fullPath);
|
|
15705
|
-
await
|
|
15854
|
+
await fs23.mkdir(dir, { recursive: true });
|
|
15706
15855
|
let effectiveContent = content;
|
|
15707
15856
|
let effectiveFrontmatter = frontmatter;
|
|
15708
15857
|
if (template) {
|
|
15709
15858
|
const templatePath = path24.join(vaultPath2, template);
|
|
15710
15859
|
try {
|
|
15711
|
-
const raw = await
|
|
15860
|
+
const raw = await fs23.readFile(templatePath, "utf-8");
|
|
15712
15861
|
const matter9 = (await import("gray-matter")).default;
|
|
15713
15862
|
const parsed = matter9(raw);
|
|
15714
15863
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -15873,7 +16022,7 @@ ${sources}`;
|
|
|
15873
16022
|
return formatMcpResult(errorResult(notePath, previewLines.join("\n")));
|
|
15874
16023
|
}
|
|
15875
16024
|
const fullPath = path24.join(vaultPath2, notePath);
|
|
15876
|
-
await
|
|
16025
|
+
await fs23.unlink(fullPath);
|
|
15877
16026
|
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
|
|
15878
16027
|
const message = backlinkWarning ? `Deleted note: ${notePath}
|
|
15879
16028
|
|
|
@@ -15893,7 +16042,7 @@ init_writer();
|
|
|
15893
16042
|
init_git();
|
|
15894
16043
|
init_wikilinks();
|
|
15895
16044
|
import { z as z16 } from "zod";
|
|
15896
|
-
import
|
|
16045
|
+
import fs24 from "fs/promises";
|
|
15897
16046
|
import path25 from "path";
|
|
15898
16047
|
import matter6 from "gray-matter";
|
|
15899
16048
|
function escapeRegex(str) {
|
|
@@ -15920,7 +16069,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
15920
16069
|
const allTargets = [targetTitle, ...targetAliases].map((t) => t.toLowerCase());
|
|
15921
16070
|
async function scanDir(dir) {
|
|
15922
16071
|
const files = [];
|
|
15923
|
-
const entries = await
|
|
16072
|
+
const entries = await fs24.readdir(dir, { withFileTypes: true });
|
|
15924
16073
|
for (const entry of entries) {
|
|
15925
16074
|
const fullPath = path25.join(dir, entry.name);
|
|
15926
16075
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
@@ -15934,7 +16083,7 @@ async function findBacklinks(vaultPath2, targetTitle, targetAliases) {
|
|
|
15934
16083
|
const allFiles = await scanDir(vaultPath2);
|
|
15935
16084
|
for (const filePath of allFiles) {
|
|
15936
16085
|
const relativePath = path25.relative(vaultPath2, filePath);
|
|
15937
|
-
const content = await
|
|
16086
|
+
const content = await fs24.readFile(filePath, "utf-8");
|
|
15938
16087
|
const wikilinks = extractWikilinks2(content);
|
|
15939
16088
|
const matchingLinks = [];
|
|
15940
16089
|
for (const link of wikilinks) {
|
|
@@ -16023,7 +16172,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16023
16172
|
const oldFullPath = path25.join(vaultPath2, oldPath);
|
|
16024
16173
|
const newFullPath = path25.join(vaultPath2, newPath);
|
|
16025
16174
|
try {
|
|
16026
|
-
await
|
|
16175
|
+
await fs24.access(oldFullPath);
|
|
16027
16176
|
} catch {
|
|
16028
16177
|
const result2 = {
|
|
16029
16178
|
success: false,
|
|
@@ -16033,7 +16182,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16033
16182
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
16034
16183
|
}
|
|
16035
16184
|
try {
|
|
16036
|
-
await
|
|
16185
|
+
await fs24.access(newFullPath);
|
|
16037
16186
|
const result2 = {
|
|
16038
16187
|
success: false,
|
|
16039
16188
|
message: `Destination already exists: ${newPath}`,
|
|
@@ -16042,7 +16191,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16042
16191
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
16043
16192
|
} catch {
|
|
16044
16193
|
}
|
|
16045
|
-
const sourceContent = await
|
|
16194
|
+
const sourceContent = await fs24.readFile(oldFullPath, "utf-8");
|
|
16046
16195
|
const parsed = matter6(sourceContent);
|
|
16047
16196
|
const aliases = extractAliases2(parsed.data);
|
|
16048
16197
|
const oldTitle = getTitleFromPath(oldPath);
|
|
@@ -16103,8 +16252,8 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16103
16252
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
16104
16253
|
}
|
|
16105
16254
|
const destDir = path25.dirname(newFullPath);
|
|
16106
|
-
await
|
|
16107
|
-
await
|
|
16255
|
+
await fs24.mkdir(destDir, { recursive: true });
|
|
16256
|
+
await fs24.rename(oldFullPath, newFullPath);
|
|
16108
16257
|
let gitCommit;
|
|
16109
16258
|
let undoAvailable;
|
|
16110
16259
|
let staleLockDetected;
|
|
@@ -16183,7 +16332,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16183
16332
|
const newPath = dir === "." ? `${sanitizedTitle}.md` : path25.join(dir, `${sanitizedTitle}.md`);
|
|
16184
16333
|
const newFullPath = path25.join(vaultPath2, newPath);
|
|
16185
16334
|
try {
|
|
16186
|
-
await
|
|
16335
|
+
await fs24.access(fullPath);
|
|
16187
16336
|
} catch {
|
|
16188
16337
|
const result2 = {
|
|
16189
16338
|
success: false,
|
|
@@ -16194,7 +16343,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16194
16343
|
}
|
|
16195
16344
|
if (fullPath !== newFullPath) {
|
|
16196
16345
|
try {
|
|
16197
|
-
await
|
|
16346
|
+
await fs24.access(newFullPath);
|
|
16198
16347
|
const result2 = {
|
|
16199
16348
|
success: false,
|
|
16200
16349
|
message: `A note with this title already exists: ${newPath}`,
|
|
@@ -16204,7 +16353,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16204
16353
|
} catch {
|
|
16205
16354
|
}
|
|
16206
16355
|
}
|
|
16207
|
-
const sourceContent = await
|
|
16356
|
+
const sourceContent = await fs24.readFile(fullPath, "utf-8");
|
|
16208
16357
|
const parsed = matter6(sourceContent);
|
|
16209
16358
|
const aliases = extractAliases2(parsed.data);
|
|
16210
16359
|
const oldTitle = getTitleFromPath(notePath);
|
|
@@ -16264,7 +16413,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16264
16413
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
16265
16414
|
}
|
|
16266
16415
|
if (fullPath !== newFullPath) {
|
|
16267
|
-
await
|
|
16416
|
+
await fs24.rename(fullPath, newFullPath);
|
|
16268
16417
|
}
|
|
16269
16418
|
let gitCommit;
|
|
16270
16419
|
let undoAvailable;
|
|
@@ -16312,7 +16461,7 @@ function registerMoveNoteTools(server2, getVaultPath) {
|
|
|
16312
16461
|
init_writer();
|
|
16313
16462
|
init_wikilinks();
|
|
16314
16463
|
import { z as z17 } from "zod";
|
|
16315
|
-
import
|
|
16464
|
+
import fs25 from "fs/promises";
|
|
16316
16465
|
function registerMergeTools(server2, getVaultPath) {
|
|
16317
16466
|
server2.tool(
|
|
16318
16467
|
"merge_entities",
|
|
@@ -16439,7 +16588,7 @@ ${trimmedSource}`;
|
|
|
16439
16588
|
}
|
|
16440
16589
|
await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter, "LF", targetContentHash);
|
|
16441
16590
|
const fullSourcePath = `${vaultPath2}/${source_path}`;
|
|
16442
|
-
await
|
|
16591
|
+
await fs25.unlink(fullSourcePath);
|
|
16443
16592
|
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
16444
16593
|
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
16445
16594
|
});
|
|
@@ -16711,7 +16860,7 @@ init_schema();
|
|
|
16711
16860
|
|
|
16712
16861
|
// src/core/write/policy/parser.ts
|
|
16713
16862
|
init_schema();
|
|
16714
|
-
import
|
|
16863
|
+
import fs26 from "fs/promises";
|
|
16715
16864
|
import path26 from "path";
|
|
16716
16865
|
import matter7 from "gray-matter";
|
|
16717
16866
|
function parseYaml(content) {
|
|
@@ -16737,7 +16886,7 @@ function parsePolicyString(yamlContent) {
|
|
|
16737
16886
|
}
|
|
16738
16887
|
async function loadPolicyFile(filePath) {
|
|
16739
16888
|
try {
|
|
16740
|
-
const content = await
|
|
16889
|
+
const content = await fs26.readFile(filePath, "utf-8");
|
|
16741
16890
|
return parsePolicyString(content);
|
|
16742
16891
|
} catch (error) {
|
|
16743
16892
|
if (error.code === "ENOENT") {
|
|
@@ -16764,12 +16913,12 @@ async function loadPolicy(vaultPath2, policyName) {
|
|
|
16764
16913
|
const policiesDir = path26.join(vaultPath2, ".claude", "policies");
|
|
16765
16914
|
const policyPath = path26.join(policiesDir, `${policyName}.yaml`);
|
|
16766
16915
|
try {
|
|
16767
|
-
await
|
|
16916
|
+
await fs26.access(policyPath);
|
|
16768
16917
|
return loadPolicyFile(policyPath);
|
|
16769
16918
|
} catch {
|
|
16770
16919
|
const ymlPath = path26.join(policiesDir, `${policyName}.yml`);
|
|
16771
16920
|
try {
|
|
16772
|
-
await
|
|
16921
|
+
await fs26.access(ymlPath);
|
|
16773
16922
|
return loadPolicyFile(ymlPath);
|
|
16774
16923
|
} catch {
|
|
16775
16924
|
return {
|
|
@@ -16909,7 +17058,7 @@ init_schema();
|
|
|
16909
17058
|
init_writer();
|
|
16910
17059
|
init_git();
|
|
16911
17060
|
init_wikilinks();
|
|
16912
|
-
import
|
|
17061
|
+
import fs28 from "fs/promises";
|
|
16913
17062
|
import path28 from "path";
|
|
16914
17063
|
init_constants();
|
|
16915
17064
|
async function executeStep(step, vaultPath2, context, conditionResults, searchFn) {
|
|
@@ -17120,7 +17269,7 @@ async function executeToggleTask(params, vaultPath2) {
|
|
|
17120
17269
|
const section = params.section ? String(params.section) : void 0;
|
|
17121
17270
|
const fullPath = path28.join(vaultPath2, notePath);
|
|
17122
17271
|
try {
|
|
17123
|
-
await
|
|
17272
|
+
await fs28.access(fullPath);
|
|
17124
17273
|
} catch {
|
|
17125
17274
|
return { success: false, message: `File not found: ${notePath}`, path: notePath };
|
|
17126
17275
|
}
|
|
@@ -17404,12 +17553,12 @@ async function rollbackChanges(vaultPath2, originalContents, filesModified) {
|
|
|
17404
17553
|
const fullPath = path28.join(vaultPath2, filePath);
|
|
17405
17554
|
if (original === null) {
|
|
17406
17555
|
try {
|
|
17407
|
-
await
|
|
17556
|
+
await fs28.unlink(fullPath);
|
|
17408
17557
|
} catch {
|
|
17409
17558
|
}
|
|
17410
17559
|
} else if (original !== void 0) {
|
|
17411
17560
|
try {
|
|
17412
|
-
await
|
|
17561
|
+
await fs28.writeFile(fullPath, original);
|
|
17413
17562
|
} catch {
|
|
17414
17563
|
}
|
|
17415
17564
|
}
|
|
@@ -17455,27 +17604,27 @@ async function previewPolicy(policy, vaultPath2, variables) {
|
|
|
17455
17604
|
}
|
|
17456
17605
|
|
|
17457
17606
|
// src/core/write/policy/storage.ts
|
|
17458
|
-
import
|
|
17607
|
+
import fs29 from "fs/promises";
|
|
17459
17608
|
import path29 from "path";
|
|
17460
17609
|
function getPoliciesDir(vaultPath2) {
|
|
17461
17610
|
return path29.join(vaultPath2, ".claude", "policies");
|
|
17462
17611
|
}
|
|
17463
17612
|
async function ensurePoliciesDir(vaultPath2) {
|
|
17464
17613
|
const dir = getPoliciesDir(vaultPath2);
|
|
17465
|
-
await
|
|
17614
|
+
await fs29.mkdir(dir, { recursive: true });
|
|
17466
17615
|
}
|
|
17467
17616
|
async function listPolicies(vaultPath2) {
|
|
17468
17617
|
const dir = getPoliciesDir(vaultPath2);
|
|
17469
17618
|
const policies = [];
|
|
17470
17619
|
try {
|
|
17471
|
-
const files = await
|
|
17620
|
+
const files = await fs29.readdir(dir);
|
|
17472
17621
|
for (const file of files) {
|
|
17473
17622
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
|
17474
17623
|
continue;
|
|
17475
17624
|
}
|
|
17476
17625
|
const filePath = path29.join(dir, file);
|
|
17477
|
-
const stat5 = await
|
|
17478
|
-
const content = await
|
|
17626
|
+
const stat5 = await fs29.stat(filePath);
|
|
17627
|
+
const content = await fs29.readFile(filePath, "utf-8");
|
|
17479
17628
|
const metadata = extractPolicyMetadata(content);
|
|
17480
17629
|
policies.push({
|
|
17481
17630
|
name: metadata.name || file.replace(/\.ya?ml$/, ""),
|
|
@@ -17501,7 +17650,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
|
|
|
17501
17650
|
const filePath = path29.join(dir, filename);
|
|
17502
17651
|
if (!overwrite) {
|
|
17503
17652
|
try {
|
|
17504
|
-
await
|
|
17653
|
+
await fs29.access(filePath);
|
|
17505
17654
|
return {
|
|
17506
17655
|
success: false,
|
|
17507
17656
|
path: filename,
|
|
@@ -17518,7 +17667,7 @@ async function writePolicyRaw(vaultPath2, policyName, content, overwrite = false
|
|
|
17518
17667
|
message: `Invalid policy: ${validation.errors.map((e) => e.message).join("; ")}`
|
|
17519
17668
|
};
|
|
17520
17669
|
}
|
|
17521
|
-
await
|
|
17670
|
+
await fs29.writeFile(filePath, content, "utf-8");
|
|
17522
17671
|
return {
|
|
17523
17672
|
success: true,
|
|
17524
17673
|
path: filename,
|
|
@@ -18043,7 +18192,7 @@ function registerPolicyTools(server2, getVaultPath, getSearchFn) {
|
|
|
18043
18192
|
import { z as z21 } from "zod";
|
|
18044
18193
|
|
|
18045
18194
|
// src/core/write/tagRename.ts
|
|
18046
|
-
import * as
|
|
18195
|
+
import * as fs30 from "fs/promises";
|
|
18047
18196
|
import * as path30 from "path";
|
|
18048
18197
|
import matter8 from "gray-matter";
|
|
18049
18198
|
import { getProtectedZones } from "@velvetmonkey/vault-core";
|
|
@@ -18153,7 +18302,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
|
|
|
18153
18302
|
const fullPath = path30.join(vaultPath2, note.path);
|
|
18154
18303
|
let fileContent;
|
|
18155
18304
|
try {
|
|
18156
|
-
fileContent = await
|
|
18305
|
+
fileContent = await fs30.readFile(fullPath, "utf-8");
|
|
18157
18306
|
} catch {
|
|
18158
18307
|
continue;
|
|
18159
18308
|
}
|
|
@@ -18226,7 +18375,7 @@ async function renameTag(index, vaultPath2, oldTag, newTag, options) {
|
|
|
18226
18375
|
previews.push(preview);
|
|
18227
18376
|
if (!dryRun) {
|
|
18228
18377
|
const newContent = matter8.stringify(updatedContent, fm);
|
|
18229
|
-
await
|
|
18378
|
+
await fs30.writeFile(fullPath, newContent, "utf-8");
|
|
18230
18379
|
}
|
|
18231
18380
|
}
|
|
18232
18381
|
}
|
|
@@ -19158,92 +19307,6 @@ function selectByMmr(candidates, limit, lambda = 0.7) {
|
|
|
19158
19307
|
return selected;
|
|
19159
19308
|
}
|
|
19160
19309
|
|
|
19161
|
-
// src/core/read/snippets.ts
|
|
19162
|
-
init_embeddings();
|
|
19163
|
-
init_stemmer();
|
|
19164
|
-
import * as fs30 from "fs";
|
|
19165
|
-
function stripFrontmatter(content) {
|
|
19166
|
-
const match = content.match(/^---[\s\S]*?---\n([\s\S]*)$/);
|
|
19167
|
-
return match ? match[1] : content;
|
|
19168
|
-
}
|
|
19169
|
-
function splitIntoParagraphs(content, maxChunkChars) {
|
|
19170
|
-
const MIN_PARAGRAPH_CHARS = 50;
|
|
19171
|
-
const raw = content.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
19172
|
-
const merged = [];
|
|
19173
|
-
let buffer2 = "";
|
|
19174
|
-
for (const paragraph of raw) {
|
|
19175
|
-
if (buffer2) {
|
|
19176
|
-
buffer2 += "\n\n" + paragraph;
|
|
19177
|
-
if (buffer2.length >= MIN_PARAGRAPH_CHARS) {
|
|
19178
|
-
merged.push(buffer2.slice(0, maxChunkChars));
|
|
19179
|
-
buffer2 = "";
|
|
19180
|
-
}
|
|
19181
|
-
} else if (paragraph.length < MIN_PARAGRAPH_CHARS) {
|
|
19182
|
-
buffer2 = paragraph;
|
|
19183
|
-
} else {
|
|
19184
|
-
merged.push(paragraph.slice(0, maxChunkChars));
|
|
19185
|
-
}
|
|
19186
|
-
}
|
|
19187
|
-
if (buffer2) {
|
|
19188
|
-
merged.push(buffer2.slice(0, maxChunkChars));
|
|
19189
|
-
}
|
|
19190
|
-
return merged;
|
|
19191
|
-
}
|
|
19192
|
-
function scoreByKeywords(chunk, queryTokens, queryStems) {
|
|
19193
|
-
const chunkTokens = new Set(tokenize(chunk.toLowerCase()));
|
|
19194
|
-
const chunkStems = new Set([...chunkTokens].map((t) => stem(t)));
|
|
19195
|
-
let score = 0;
|
|
19196
|
-
for (let i = 0; i < queryTokens.length; i++) {
|
|
19197
|
-
if (chunkTokens.has(queryTokens[i])) {
|
|
19198
|
-
score += 10;
|
|
19199
|
-
} else if (chunkStems.has(queryStems[i])) {
|
|
19200
|
-
score += 5;
|
|
19201
|
-
}
|
|
19202
|
-
}
|
|
19203
|
-
return score;
|
|
19204
|
-
}
|
|
19205
|
-
async function extractBestSnippets(filePath, queryEmbedding, queryTokens, options) {
|
|
19206
|
-
const maxSnippets = options?.maxSnippets ?? 1;
|
|
19207
|
-
const maxChunkChars = options?.maxChunkChars ?? 500;
|
|
19208
|
-
let content;
|
|
19209
|
-
try {
|
|
19210
|
-
content = fs30.readFileSync(filePath, "utf-8");
|
|
19211
|
-
} catch {
|
|
19212
|
-
return [];
|
|
19213
|
-
}
|
|
19214
|
-
const body = stripFrontmatter(content);
|
|
19215
|
-
if (body.length < 50) {
|
|
19216
|
-
return body.length > 0 ? [{ text: body, score: 1 }] : [];
|
|
19217
|
-
}
|
|
19218
|
-
const paragraphs = splitIntoParagraphs(body, maxChunkChars);
|
|
19219
|
-
if (paragraphs.length === 0) return [];
|
|
19220
|
-
const queryStems = queryTokens.map((t) => stem(t));
|
|
19221
|
-
const scored = paragraphs.map((text, idx) => ({
|
|
19222
|
-
text,
|
|
19223
|
-
idx,
|
|
19224
|
-
keywordScore: scoreByKeywords(text, queryTokens, queryStems)
|
|
19225
|
-
}));
|
|
19226
|
-
scored.sort((a, b) => b.keywordScore - a.keywordScore);
|
|
19227
|
-
const topKeyword = scored.slice(0, 5);
|
|
19228
|
-
if (queryEmbedding && hasEmbeddingsIndex()) {
|
|
19229
|
-
try {
|
|
19230
|
-
const reranked = [];
|
|
19231
|
-
for (const chunk of topKeyword) {
|
|
19232
|
-
const chunkEmbedding = await embedTextCached(chunk.text);
|
|
19233
|
-
const sim = cosineSimilarity(queryEmbedding, chunkEmbedding);
|
|
19234
|
-
reranked.push({ text: chunk.text, score: sim });
|
|
19235
|
-
}
|
|
19236
|
-
reranked.sort((a, b) => b.score - a.score);
|
|
19237
|
-
return reranked.slice(0, maxSnippets);
|
|
19238
|
-
} catch {
|
|
19239
|
-
}
|
|
19240
|
-
}
|
|
19241
|
-
return topKeyword.slice(0, maxSnippets).map((c) => ({
|
|
19242
|
-
text: c.text,
|
|
19243
|
-
score: c.keywordScore
|
|
19244
|
-
}));
|
|
19245
|
-
}
|
|
19246
|
-
|
|
19247
19310
|
// src/tools/read/recall.ts
|
|
19248
19311
|
function scoreTextRelevance(query, content) {
|
|
19249
19312
|
const queryTokens = tokenize(query).map((t) => t.toLowerCase());
|
|
@@ -19277,7 +19340,7 @@ async function performRecall(stateDb2, query, options = {}) {
|
|
|
19277
19340
|
focus,
|
|
19278
19341
|
entity,
|
|
19279
19342
|
max_tokens,
|
|
19280
|
-
diversity =
|
|
19343
|
+
diversity = 1,
|
|
19281
19344
|
vaultPath: vaultPath2
|
|
19282
19345
|
} = options;
|
|
19283
19346
|
const results = [];
|
|
@@ -19534,6 +19597,12 @@ function registerRecallTools(server2, getStateDb2, getVaultPath, getIndex) {
|
|
|
19534
19597
|
const hopResults = index ? multiHopBackfill(enrichedNotes, index, stateDb2, {
|
|
19535
19598
|
maxBackfill: Math.max(5, (args.max_results ?? 10) - notes.length)
|
|
19536
19599
|
}) : [];
|
|
19600
|
+
if (index) {
|
|
19601
|
+
const allNoteResults = [...enrichedNotes, ...hopResults];
|
|
19602
|
+
const expansionTerms = extractExpansionTerms(allNoteResults, args.query, index);
|
|
19603
|
+
const expansionResults = expandQuery(expansionTerms, allNoteResults, index, stateDb2);
|
|
19604
|
+
hopResults.push(...expansionResults);
|
|
19605
|
+
}
|
|
19537
19606
|
return {
|
|
19538
19607
|
content: [{
|
|
19539
19608
|
type: "text",
|
|
@@ -23713,10 +23782,10 @@ async function runPostIndexWork(index) {
|
|
|
23713
23782
|
for (const event of filteredEvents) {
|
|
23714
23783
|
if (event.type === "delete" || !event.path.endsWith(".md")) continue;
|
|
23715
23784
|
try {
|
|
23716
|
-
const
|
|
23785
|
+
const rawContent = await fs33.readFile(path33.join(vaultPath, event.path), "utf-8");
|
|
23786
|
+
const content = rawContent.replace(/ → \[\[.*$/gm, "");
|
|
23717
23787
|
const result = await suggestRelatedLinks(content, {
|
|
23718
23788
|
maxSuggestions: 5,
|
|
23719
|
-
strictness: "balanced",
|
|
23720
23789
|
notePath: event.path,
|
|
23721
23790
|
detail: true
|
|
23722
23791
|
});
|
|
@@ -23742,7 +23811,6 @@ async function runPostIndexWork(index) {
|
|
|
23742
23811
|
try {
|
|
23743
23812
|
const proactiveResults = [];
|
|
23744
23813
|
for (const { file, top } of suggestionResults) {
|
|
23745
|
-
if (getNoteContext(file) === "daily") continue;
|
|
23746
23814
|
try {
|
|
23747
23815
|
const result = await applyProactiveSuggestions(file, vaultPath, top, {
|
|
23748
23816
|
minScore: flywheelConfig?.proactive_min_score ?? 20,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.128",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 69 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
56
|
-
"@velvetmonkey/vault-core": "
|
|
56
|
+
"@velvetmonkey/vault-core": "2.0.128",
|
|
57
57
|
"better-sqlite3": "^11.0.0",
|
|
58
58
|
"chokidar": "^4.0.0",
|
|
59
59
|
"gray-matter": "^4.0.3",
|