@velvetmonkey/flywheel-memory 2.0.22 → 2.0.24
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 +170 -15
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1839,6 +1839,7 @@ var MAX_FILE_SIZE2 = 5 * 1024 * 1024;
|
|
|
1839
1839
|
var db = null;
|
|
1840
1840
|
var pipeline = null;
|
|
1841
1841
|
var initPromise = null;
|
|
1842
|
+
var embeddingsBuilding = false;
|
|
1842
1843
|
var embeddingCache = /* @__PURE__ */ new Map();
|
|
1843
1844
|
var EMBEDDING_CACHE_MAX = 500;
|
|
1844
1845
|
var entityEmbeddingsMap = /* @__PURE__ */ new Map();
|
|
@@ -1894,6 +1895,7 @@ async function buildEmbeddingsIndex(vaultPath2, onProgress) {
|
|
|
1894
1895
|
if (!db) {
|
|
1895
1896
|
throw new Error("Embeddings database not initialized. Call setEmbeddingsDatabase() first.");
|
|
1896
1897
|
}
|
|
1898
|
+
embeddingsBuilding = true;
|
|
1897
1899
|
await initEmbeddings();
|
|
1898
1900
|
const files = await scanVault(vaultPath2);
|
|
1899
1901
|
const indexable = files.filter((f) => shouldIndexFile(f.path));
|
|
@@ -1937,6 +1939,7 @@ async function buildEmbeddingsIndex(vaultPath2, onProgress) {
|
|
|
1937
1939
|
deleteStmt.run(existingPath);
|
|
1938
1940
|
}
|
|
1939
1941
|
}
|
|
1942
|
+
embeddingsBuilding = false;
|
|
1940
1943
|
console.error(`[Semantic] Indexed ${progress.current - progress.skipped} notes, skipped ${progress.skipped}`);
|
|
1941
1944
|
return progress;
|
|
1942
1945
|
}
|
|
@@ -2034,6 +2037,12 @@ function reciprocalRankFusion(...lists) {
|
|
|
2034
2037
|
}
|
|
2035
2038
|
return scores;
|
|
2036
2039
|
}
|
|
2040
|
+
function isEmbeddingsBuilding() {
|
|
2041
|
+
return embeddingsBuilding;
|
|
2042
|
+
}
|
|
2043
|
+
function setEmbeddingsBuilding(value) {
|
|
2044
|
+
embeddingsBuilding = value;
|
|
2045
|
+
}
|
|
2037
2046
|
function hasEmbeddingsIndex() {
|
|
2038
2047
|
if (!db) return false;
|
|
2039
2048
|
try {
|
|
@@ -5010,6 +5019,15 @@ function getEntityIndexStats() {
|
|
|
5010
5019
|
organizations: entityIndex.organizations?.length ?? 0,
|
|
5011
5020
|
locations: entityIndex.locations?.length ?? 0,
|
|
5012
5021
|
concepts: entityIndex.concepts?.length ?? 0,
|
|
5022
|
+
animals: entityIndex.animals?.length ?? 0,
|
|
5023
|
+
media: entityIndex.media?.length ?? 0,
|
|
5024
|
+
events: entityIndex.events?.length ?? 0,
|
|
5025
|
+
documents: entityIndex.documents?.length ?? 0,
|
|
5026
|
+
vehicles: entityIndex.vehicles?.length ?? 0,
|
|
5027
|
+
health: entityIndex.health?.length ?? 0,
|
|
5028
|
+
finance: entityIndex.finance?.length ?? 0,
|
|
5029
|
+
food: entityIndex.food?.length ?? 0,
|
|
5030
|
+
hobbies: entityIndex.hobbies?.length ?? 0,
|
|
5013
5031
|
other: entityIndex.other.length
|
|
5014
5032
|
}
|
|
5015
5033
|
};
|
|
@@ -5146,14 +5164,32 @@ var DEFAULT_STRICTNESS = "conservative";
|
|
|
5146
5164
|
var TYPE_BOOST = {
|
|
5147
5165
|
people: 5,
|
|
5148
5166
|
// Names are high value for connections
|
|
5167
|
+
animals: 3,
|
|
5168
|
+
// Pets and animals are personal and specific
|
|
5149
5169
|
projects: 3,
|
|
5150
5170
|
// Projects provide context
|
|
5151
5171
|
organizations: 2,
|
|
5152
5172
|
// Companies/teams relevant
|
|
5173
|
+
events: 2,
|
|
5174
|
+
// Meetings, trips, milestones
|
|
5175
|
+
media: 2,
|
|
5176
|
+
// Movies, books, shows
|
|
5177
|
+
health: 2,
|
|
5178
|
+
// Medical, fitness — personal relevance
|
|
5179
|
+
vehicles: 2,
|
|
5180
|
+
// Cars, bikes — specific items
|
|
5153
5181
|
locations: 1,
|
|
5154
5182
|
// Geographic context
|
|
5155
5183
|
concepts: 1,
|
|
5156
5184
|
// Abstract concepts
|
|
5185
|
+
documents: 1,
|
|
5186
|
+
// Reports, guides
|
|
5187
|
+
food: 1,
|
|
5188
|
+
// Recipes, restaurants
|
|
5189
|
+
hobbies: 1,
|
|
5190
|
+
// Crafts, sports
|
|
5191
|
+
finance: 1,
|
|
5192
|
+
// Accounts, budgets
|
|
5157
5193
|
technologies: 0,
|
|
5158
5194
|
// Common, avoid over-suggesting
|
|
5159
5195
|
acronyms: 0,
|
|
@@ -5197,14 +5233,24 @@ var CONTEXT_BOOST = {
|
|
|
5197
5233
|
daily: {
|
|
5198
5234
|
people: 5,
|
|
5199
5235
|
// Daily notes often mention people
|
|
5200
|
-
|
|
5236
|
+
animals: 3,
|
|
5237
|
+
// Pets in daily life
|
|
5238
|
+
events: 3,
|
|
5239
|
+
// Daily events and meetings
|
|
5240
|
+
projects: 2,
|
|
5201
5241
|
// Work updates reference projects
|
|
5242
|
+
food: 2,
|
|
5243
|
+
// Meals and recipes in daily logs
|
|
5244
|
+
health: 2
|
|
5245
|
+
// Fitness and wellness tracking
|
|
5202
5246
|
},
|
|
5203
5247
|
project: {
|
|
5204
5248
|
projects: 5,
|
|
5205
5249
|
// Project docs reference other projects
|
|
5206
|
-
technologies: 2
|
|
5250
|
+
technologies: 2,
|
|
5207
5251
|
// Technical dependencies
|
|
5252
|
+
documents: 2
|
|
5253
|
+
// Reference documents
|
|
5208
5254
|
},
|
|
5209
5255
|
tech: {
|
|
5210
5256
|
technologies: 5,
|
|
@@ -5722,9 +5768,18 @@ var EXCLUDED_DIRS3 = /* @__PURE__ */ new Set([
|
|
|
5722
5768
|
]);
|
|
5723
5769
|
var MAX_INDEX_FILE_SIZE = 5 * 1024 * 1024;
|
|
5724
5770
|
var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
5771
|
+
function splitFrontmatter(raw) {
|
|
5772
|
+
if (!raw.startsWith("---")) return { frontmatter: "", body: raw };
|
|
5773
|
+
const end = raw.indexOf("\n---", 3);
|
|
5774
|
+
if (end === -1) return { frontmatter: "", body: raw };
|
|
5775
|
+
const yaml = raw.substring(4, end);
|
|
5776
|
+
const values = yaml.split("\n").map((line) => line.replace(/^[\s-]*/, "").replace(/^[\w]+:\s*/, "")).filter((v) => v && !v.startsWith("[") && !v.startsWith("{")).join(" ");
|
|
5777
|
+
return { frontmatter: values, body: raw.substring(end + 4) };
|
|
5778
|
+
}
|
|
5725
5779
|
var db2 = null;
|
|
5726
5780
|
var state = {
|
|
5727
5781
|
ready: false,
|
|
5782
|
+
building: false,
|
|
5728
5783
|
lastBuilt: null,
|
|
5729
5784
|
noteCount: 0,
|
|
5730
5785
|
error: null
|
|
@@ -5740,6 +5795,7 @@ function setFTS5Database(database) {
|
|
|
5740
5795
|
const countRow = db2.prepare("SELECT COUNT(*) as count FROM notes_fts").get();
|
|
5741
5796
|
state = {
|
|
5742
5797
|
ready: countRow.count > 0,
|
|
5798
|
+
building: false,
|
|
5743
5799
|
lastBuilt,
|
|
5744
5800
|
noteCount: countRow.count,
|
|
5745
5801
|
error: null
|
|
@@ -5755,6 +5811,7 @@ function shouldIndexFile2(filePath) {
|
|
|
5755
5811
|
async function buildFTS5Index(vaultPath2) {
|
|
5756
5812
|
try {
|
|
5757
5813
|
state.error = null;
|
|
5814
|
+
state.building = true;
|
|
5758
5815
|
if (!db2) {
|
|
5759
5816
|
throw new Error("FTS5 database not initialized. Call setFTS5Database() first.");
|
|
5760
5817
|
}
|
|
@@ -5762,7 +5819,7 @@ async function buildFTS5Index(vaultPath2) {
|
|
|
5762
5819
|
const files = await scanVault(vaultPath2);
|
|
5763
5820
|
const indexableFiles = files.filter((f) => shouldIndexFile2(f.path));
|
|
5764
5821
|
const insert = db2.prepare(
|
|
5765
|
-
"INSERT INTO notes_fts (path, title, content) VALUES (?, ?, ?)"
|
|
5822
|
+
"INSERT INTO notes_fts (path, title, frontmatter, content) VALUES (?, ?, ?, ?)"
|
|
5766
5823
|
);
|
|
5767
5824
|
const insertMany = db2.transaction((filesToIndex) => {
|
|
5768
5825
|
let indexed2 = 0;
|
|
@@ -5772,9 +5829,10 @@ async function buildFTS5Index(vaultPath2) {
|
|
|
5772
5829
|
if (stats.size > MAX_INDEX_FILE_SIZE) {
|
|
5773
5830
|
continue;
|
|
5774
5831
|
}
|
|
5775
|
-
const
|
|
5832
|
+
const raw = fs7.readFileSync(file.absolutePath, "utf-8");
|
|
5833
|
+
const { frontmatter, body } = splitFrontmatter(raw);
|
|
5776
5834
|
const title = file.path.replace(/\.md$/, "").split("/").pop() || file.path;
|
|
5777
|
-
insert.run(file.path, title,
|
|
5835
|
+
insert.run(file.path, title, frontmatter, body);
|
|
5778
5836
|
indexed2++;
|
|
5779
5837
|
} catch (err) {
|
|
5780
5838
|
console.error(`[FTS5] Skipping ${file.path}:`, err);
|
|
@@ -5789,6 +5847,7 @@ async function buildFTS5Index(vaultPath2) {
|
|
|
5789
5847
|
).run("last_built", now.toISOString());
|
|
5790
5848
|
state = {
|
|
5791
5849
|
ready: true,
|
|
5850
|
+
building: false,
|
|
5792
5851
|
lastBuilt: now,
|
|
5793
5852
|
noteCount: indexed,
|
|
5794
5853
|
error: null
|
|
@@ -5798,6 +5857,7 @@ async function buildFTS5Index(vaultPath2) {
|
|
|
5798
5857
|
} catch (err) {
|
|
5799
5858
|
state = {
|
|
5800
5859
|
ready: false,
|
|
5860
|
+
building: false,
|
|
5801
5861
|
lastBuilt: null,
|
|
5802
5862
|
noteCount: 0,
|
|
5803
5863
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -5832,10 +5892,10 @@ function searchFTS5(_vaultPath, query, limit = 10) {
|
|
|
5832
5892
|
SELECT
|
|
5833
5893
|
path,
|
|
5834
5894
|
title,
|
|
5835
|
-
snippet(notes_fts,
|
|
5895
|
+
snippet(notes_fts, 3, '<mark>', '</mark>', '...', 20) as snippet
|
|
5836
5896
|
FROM notes_fts
|
|
5837
5897
|
WHERE notes_fts MATCH ?
|
|
5838
|
-
ORDER BY
|
|
5898
|
+
ORDER BY bm25(notes_fts, 0.0, 5.0, 10.0, 1.0)
|
|
5839
5899
|
LIMIT ?
|
|
5840
5900
|
`);
|
|
5841
5901
|
const results = stmt.all(query, limit);
|
|
@@ -6766,6 +6826,9 @@ function getActivitySummary(index, days) {
|
|
|
6766
6826
|
};
|
|
6767
6827
|
}
|
|
6768
6828
|
|
|
6829
|
+
// src/tools/read/health.ts
|
|
6830
|
+
import { SCHEMA_VERSION } from "@velvetmonkey/vault-core";
|
|
6831
|
+
|
|
6769
6832
|
// src/core/shared/indexActivity.ts
|
|
6770
6833
|
function recordIndexEvent(stateDb2, event) {
|
|
6771
6834
|
stateDb2.db.prepare(
|
|
@@ -6858,6 +6921,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
6858
6921
|
});
|
|
6859
6922
|
const HealthCheckOutputSchema = {
|
|
6860
6923
|
status: z3.enum(["healthy", "degraded", "unhealthy"]).describe("Overall health status"),
|
|
6924
|
+
schema_version: z3.coerce.number().describe("StateDb schema version"),
|
|
6861
6925
|
vault_accessible: z3.boolean().describe("Whether the vault path is accessible"),
|
|
6862
6926
|
vault_path: z3.string().describe("The vault path being used"),
|
|
6863
6927
|
index_state: z3.enum(["building", "ready", "error"]).describe("Current state of the vault index"),
|
|
@@ -6877,6 +6941,11 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
6877
6941
|
duration_ms: z3.number(),
|
|
6878
6942
|
ago_seconds: z3.number()
|
|
6879
6943
|
}).optional().describe("Most recent index rebuild event"),
|
|
6944
|
+
fts5_ready: z3.boolean().describe("Whether the FTS5 keyword search index is ready"),
|
|
6945
|
+
fts5_building: z3.boolean().describe("Whether the FTS5 keyword search index is currently building"),
|
|
6946
|
+
embeddings_building: z3.boolean().describe("Whether semantic embeddings are currently building"),
|
|
6947
|
+
embeddings_ready: z3.boolean().describe("Whether semantic embeddings have been built (enables hybrid keyword+semantic search)"),
|
|
6948
|
+
embeddings_count: z3.coerce.number().describe("Number of notes with semantic embeddings"),
|
|
6880
6949
|
recommendations: z3.array(z3.string()).describe("Suggested actions if any issues detected")
|
|
6881
6950
|
};
|
|
6882
6951
|
server2.registerTool(
|
|
@@ -6962,8 +7031,10 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
6962
7031
|
} catch {
|
|
6963
7032
|
}
|
|
6964
7033
|
}
|
|
7034
|
+
const ftsState = getFTS5State();
|
|
6965
7035
|
const output = {
|
|
6966
7036
|
status,
|
|
7037
|
+
schema_version: SCHEMA_VERSION,
|
|
6967
7038
|
vault_accessible: vaultAccessible,
|
|
6968
7039
|
vault_path: vaultPath2,
|
|
6969
7040
|
index_state: indexState2,
|
|
@@ -6978,6 +7049,11 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
6978
7049
|
periodic_notes: periodicNotes && periodicNotes.length > 0 ? periodicNotes : void 0,
|
|
6979
7050
|
config: configInfo,
|
|
6980
7051
|
last_rebuild: lastRebuild,
|
|
7052
|
+
fts5_ready: ftsState.ready,
|
|
7053
|
+
fts5_building: ftsState.building,
|
|
7054
|
+
embeddings_building: isEmbeddingsBuilding(),
|
|
7055
|
+
embeddings_ready: hasEmbeddingsIndex(),
|
|
7056
|
+
embeddings_count: getEmbeddingsCount(),
|
|
6981
7057
|
recommendations
|
|
6982
7058
|
};
|
|
6983
7059
|
return {
|
|
@@ -7320,27 +7396,55 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
7320
7396
|
return { content: [{ type: "text", text: JSON.stringify({ error: "query is required for content search" }, null, 2) }] };
|
|
7321
7397
|
}
|
|
7322
7398
|
const ftsState = getFTS5State();
|
|
7399
|
+
if (ftsState.building) {
|
|
7400
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
7401
|
+
scope,
|
|
7402
|
+
method: "fts5",
|
|
7403
|
+
query,
|
|
7404
|
+
building: true,
|
|
7405
|
+
total_results: 0,
|
|
7406
|
+
results: [],
|
|
7407
|
+
message: "Search index is building, try again shortly"
|
|
7408
|
+
}, null, 2) }] };
|
|
7409
|
+
}
|
|
7323
7410
|
if (!ftsState.ready || isIndexStale(vaultPath2)) {
|
|
7324
7411
|
console.error("[FTS5] Index stale or missing, rebuilding...");
|
|
7325
7412
|
await buildFTS5Index(vaultPath2);
|
|
7326
7413
|
}
|
|
7327
7414
|
const fts5Results = searchFTS5(vaultPath2, query, limit);
|
|
7415
|
+
let entityResults = [];
|
|
7416
|
+
if (scope === "all") {
|
|
7417
|
+
const stateDb2 = getStateDb();
|
|
7418
|
+
if (stateDb2) {
|
|
7419
|
+
try {
|
|
7420
|
+
entityResults = searchEntities(stateDb2, query, limit);
|
|
7421
|
+
} catch {
|
|
7422
|
+
}
|
|
7423
|
+
}
|
|
7424
|
+
}
|
|
7328
7425
|
if (hasEmbeddingsIndex()) {
|
|
7329
7426
|
try {
|
|
7330
7427
|
const semanticResults = await semanticSearch(query, limit);
|
|
7331
7428
|
const fts5Ranked = fts5Results.map((r) => ({ path: r.path, title: r.title, snippet: r.snippet }));
|
|
7332
7429
|
const semanticRanked = semanticResults.map((r) => ({ path: r.path, title: r.title }));
|
|
7333
|
-
const
|
|
7334
|
-
const
|
|
7430
|
+
const entityRanked = entityResults.map((r) => ({ path: r.path, title: r.name }));
|
|
7431
|
+
const rrfScores = reciprocalRankFusion(fts5Ranked, semanticRanked, entityRanked);
|
|
7432
|
+
const allPaths = /* @__PURE__ */ new Set([
|
|
7433
|
+
...fts5Results.map((r) => r.path),
|
|
7434
|
+
...semanticResults.map((r) => r.path),
|
|
7435
|
+
...entityResults.map((r) => r.path)
|
|
7436
|
+
]);
|
|
7335
7437
|
const fts5Map = new Map(fts5Results.map((r) => [r.path, r]));
|
|
7336
7438
|
const semanticMap = new Map(semanticResults.map((r) => [r.path, r]));
|
|
7439
|
+
const entityMap = new Map(entityResults.map((r) => [r.path, r]));
|
|
7337
7440
|
const merged = Array.from(allPaths).map((p) => ({
|
|
7338
7441
|
path: p,
|
|
7339
|
-
title: fts5Map.get(p)?.title || semanticMap.get(p)?.title || p.replace(/\.md$/, "").split("/").pop() || p,
|
|
7442
|
+
title: fts5Map.get(p)?.title || semanticMap.get(p)?.title || entityMap.get(p)?.name || p.replace(/\.md$/, "").split("/").pop() || p,
|
|
7340
7443
|
snippet: fts5Map.get(p)?.snippet,
|
|
7341
7444
|
rrf_score: Math.round((rrfScores.get(p) || 0) * 1e4) / 1e4,
|
|
7342
7445
|
in_fts5: fts5Map.has(p),
|
|
7343
|
-
in_semantic: semanticMap.has(p)
|
|
7446
|
+
in_semantic: semanticMap.has(p),
|
|
7447
|
+
in_entity: entityMap.has(p)
|
|
7344
7448
|
}));
|
|
7345
7449
|
merged.sort((a, b) => b.rrf_score - a.rrf_score);
|
|
7346
7450
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
@@ -7354,6 +7458,21 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
7354
7458
|
console.error("[Semantic] Hybrid search failed, falling back to FTS5:", err instanceof Error ? err.message : err);
|
|
7355
7459
|
}
|
|
7356
7460
|
}
|
|
7461
|
+
if (entityResults.length > 0) {
|
|
7462
|
+
const fts5Map = new Map(fts5Results.map((r) => [r.path, r]));
|
|
7463
|
+
const entityRanked = entityResults.filter((r) => !fts5Map.has(r.path));
|
|
7464
|
+
const merged = [
|
|
7465
|
+
...fts5Results.map((r) => ({ path: r.path, title: r.title, snippet: r.snippet, in_entity: fts5Map.has(r.path) && entityResults.some((e) => e.path === r.path) })),
|
|
7466
|
+
...entityRanked.map((r) => ({ path: r.path, title: r.name, snippet: void 0, in_entity: true }))
|
|
7467
|
+
];
|
|
7468
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
7469
|
+
scope: "content",
|
|
7470
|
+
method: "fts5",
|
|
7471
|
+
query,
|
|
7472
|
+
total_results: merged.length,
|
|
7473
|
+
results: merged.slice(0, limit)
|
|
7474
|
+
}, null, 2) }] };
|
|
7475
|
+
}
|
|
7357
7476
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
7358
7477
|
scope: "content",
|
|
7359
7478
|
method: "fts5",
|
|
@@ -14147,11 +14266,11 @@ function findSimilarNotes(db3, vaultPath2, index, sourcePath, options = {}) {
|
|
|
14147
14266
|
SELECT
|
|
14148
14267
|
path,
|
|
14149
14268
|
title,
|
|
14150
|
-
bm25(notes_fts) as score,
|
|
14151
|
-
snippet(notes_fts,
|
|
14269
|
+
bm25(notes_fts, 0.0, 5.0, 10.0, 1.0) as score,
|
|
14270
|
+
snippet(notes_fts, 3, '[', ']', '...', 15) as snippet
|
|
14152
14271
|
FROM notes_fts
|
|
14153
14272
|
WHERE notes_fts MATCH ?
|
|
14154
|
-
ORDER BY
|
|
14273
|
+
ORDER BY bm25(notes_fts, 0.0, 5.0, 10.0, 1.0)
|
|
14155
14274
|
LIMIT ?
|
|
14156
14275
|
`).all(query, limit + 20);
|
|
14157
14276
|
let filtered = results.filter((r) => r.path !== sourcePath);
|
|
@@ -14763,7 +14882,6 @@ async function main() {
|
|
|
14763
14882
|
setEmbeddingsDatabase(stateDb.db);
|
|
14764
14883
|
loadEntityEmbeddingsToMemory();
|
|
14765
14884
|
setWriteStateDb(stateDb);
|
|
14766
|
-
await initializeEntityIndex(vaultPath);
|
|
14767
14885
|
} catch (err) {
|
|
14768
14886
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14769
14887
|
console.error(`[Memory] StateDb initialization failed: ${msg}`);
|
|
@@ -14782,6 +14900,15 @@ async function main() {
|
|
|
14782
14900
|
initializeLogger2(vaultPath).catch((err) => {
|
|
14783
14901
|
console.error(`[Memory] Write logger initialization failed: ${err}`);
|
|
14784
14902
|
});
|
|
14903
|
+
if (isIndexStale(vaultPath)) {
|
|
14904
|
+
buildFTS5Index(vaultPath).then(() => {
|
|
14905
|
+
console.error("[Memory] FTS5 search index ready");
|
|
14906
|
+
}).catch((err) => {
|
|
14907
|
+
console.error("[Memory] FTS5 build failed:", err);
|
|
14908
|
+
});
|
|
14909
|
+
} else {
|
|
14910
|
+
console.error("[Memory] FTS5 search index already fresh, skipping rebuild");
|
|
14911
|
+
}
|
|
14785
14912
|
let cachedIndex = null;
|
|
14786
14913
|
if (stateDb) {
|
|
14787
14914
|
try {
|
|
@@ -14879,6 +15006,7 @@ async function updateEntitiesInStateDb() {
|
|
|
14879
15006
|
}
|
|
14880
15007
|
async function runPostIndexWork(index) {
|
|
14881
15008
|
await updateEntitiesInStateDb();
|
|
15009
|
+
await initializeEntityIndex(vaultPath);
|
|
14882
15010
|
await exportHubScores(index, stateDb);
|
|
14883
15011
|
if (stateDb) {
|
|
14884
15012
|
try {
|
|
@@ -14917,6 +15045,33 @@ async function runPostIndexWork(index) {
|
|
|
14917
15045
|
if (flywheelConfig.vault_name) {
|
|
14918
15046
|
console.error(`[Memory] Vault: ${flywheelConfig.vault_name}`);
|
|
14919
15047
|
}
|
|
15048
|
+
if (hasEmbeddingsIndex()) {
|
|
15049
|
+
console.error("[Memory] Embeddings already built, skipping full scan");
|
|
15050
|
+
} else {
|
|
15051
|
+
setEmbeddingsBuilding(true);
|
|
15052
|
+
buildEmbeddingsIndex(vaultPath, (p) => {
|
|
15053
|
+
if (p.current % 100 === 0 || p.current === p.total) {
|
|
15054
|
+
console.error(`[Semantic] ${p.current}/${p.total}`);
|
|
15055
|
+
}
|
|
15056
|
+
}).then(async () => {
|
|
15057
|
+
if (stateDb) {
|
|
15058
|
+
const entities = getAllEntitiesFromDb2(stateDb);
|
|
15059
|
+
if (entities.length > 0) {
|
|
15060
|
+
const entityMap = new Map(entities.map((e) => [e.name, {
|
|
15061
|
+
name: e.name,
|
|
15062
|
+
path: e.path,
|
|
15063
|
+
category: e.category,
|
|
15064
|
+
aliases: e.aliases
|
|
15065
|
+
}]));
|
|
15066
|
+
await buildEntityEmbeddingsIndex(vaultPath, entityMap);
|
|
15067
|
+
}
|
|
15068
|
+
}
|
|
15069
|
+
loadEntityEmbeddingsToMemory();
|
|
15070
|
+
console.error("[Memory] Embeddings refreshed");
|
|
15071
|
+
}).catch((err) => {
|
|
15072
|
+
console.error("[Memory] Embeddings refresh failed:", err);
|
|
15073
|
+
});
|
|
15074
|
+
}
|
|
14920
15075
|
if (process.env.FLYWHEEL_WATCH !== "false") {
|
|
14921
15076
|
const config = parseWatcherConfig();
|
|
14922
15077
|
console.error(`[Memory] File watcher enabled (debounce: ${config.debounceMs}ms)`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.24",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. 42 tools for search, backlinks, graph queries, mutations, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
53
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
53
|
+
"@velvetmonkey/vault-core": "^2.0.24",
|
|
54
54
|
"better-sqlite3": "^11.0.0",
|
|
55
55
|
"chokidar": "^4.0.0",
|
|
56
56
|
"gray-matter": "^4.0.3",
|