@velvetmonkey/flywheel-memory 2.0.74 → 2.0.76
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/README.md +11 -2
- package/dist/index.js +18 -57
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|---|---|---|
|
|
17
17
|
| "What's overdue?" | Read every file | Indexed query, <10ms |
|
|
18
18
|
| "What links here?" | grep every file | Pre-indexed backlink graph |
|
|
19
|
-
| "Add a meeting note" | Raw write, no linking |
|
|
19
|
+
| "Add a meeting note" | Raw write, no linking | Structured write + auto-wikilink |
|
|
20
20
|
| "What should I link?" | Manual or grep | Smart scoring + semantic understanding |
|
|
21
21
|
| Token cost | 2,000-250,000 | 50-200 |
|
|
22
22
|
|
|
@@ -184,6 +184,15 @@ Every query leverages hundreds of accumulated connections. New content auto-link
|
|
|
184
184
|
|
|
185
185
|
### What This Looks Like
|
|
186
186
|
|
|
187
|
+
```mermaid
|
|
188
|
+
graph LR
|
|
189
|
+
W[Write] --> A[Auto-link]
|
|
190
|
+
A --> D[Denser Graph]
|
|
191
|
+
D --> B[Better Queries]
|
|
192
|
+
B --> M[More Use]
|
|
193
|
+
M --> W
|
|
194
|
+
```
|
|
195
|
+
|
|
187
196
|
```
|
|
188
197
|
Input: "Met with Sarah about the data migration"
|
|
189
198
|
Output: "Met with [[Sarah Mitchell]] about the [[Acme Data Migration]]"
|
|
@@ -236,7 +245,7 @@ Measured against a 96-note/61-entity ground truth vault.
|
|
|
236
245
|
|
|
237
246
|
- **50-generation stress test** — suggest → accept/reject (85% correct, 15% noise) → mutate vault → rebuild index → repeat. F1 holds steady — the feedback loop doesn't degrade under realistic noise.
|
|
238
247
|
- **7 vault archetypes** — hub-and-spoke, hierarchical, dense-mesh, sparse-orphan, bridge-network, small-world, chaos
|
|
239
|
-
- **13
|
|
248
|
+
- **13 pipeline stages** (10 scoring dimensions + filters + suppression) individually ablated, contribution measured
|
|
240
249
|
- **Regression gate** — CI fails if any mode's F1/precision/recall drops >5pp from baseline
|
|
241
250
|
|
|
242
251
|
See [docs/TESTING.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TESTING.md) for full methodology. Auto-generated report: [docs/QUALITY_REPORT.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/QUALITY_REPORT.md).
|
package/dist/index.js
CHANGED
|
@@ -6126,7 +6126,6 @@ function sortEntitiesByPriority(entities, notePath) {
|
|
|
6126
6126
|
}
|
|
6127
6127
|
function processWikilinks(content, notePath, existingContent) {
|
|
6128
6128
|
if (!isEntityIndexReady() || !entityIndex) {
|
|
6129
|
-
console.error("[Flywheel:DEBUG] Entity index not ready, entities:", entityIndex?._metadata?.total_entities ?? 0);
|
|
6130
6129
|
return {
|
|
6131
6130
|
content,
|
|
6132
6131
|
linksAdded: 0,
|
|
@@ -6134,7 +6133,6 @@ function processWikilinks(content, notePath, existingContent) {
|
|
|
6134
6133
|
};
|
|
6135
6134
|
}
|
|
6136
6135
|
let entities = getAllEntities(entityIndex);
|
|
6137
|
-
console.error(`[Flywheel:DEBUG] Processing wikilinks with ${entities.length} entities`);
|
|
6138
6136
|
if (moduleStateDb5) {
|
|
6139
6137
|
const folder = notePath ? notePath.split("/")[0] : void 0;
|
|
6140
6138
|
entities = entities.filter((e) => {
|
|
@@ -6239,39 +6237,6 @@ function maybeApplyWikilinks(content, skipWikilinks, notePath, existingContent)
|
|
|
6239
6237
|
}
|
|
6240
6238
|
return { content: result.content };
|
|
6241
6239
|
}
|
|
6242
|
-
function getEntityIndexStats() {
|
|
6243
|
-
if (!indexReady || !entityIndex) {
|
|
6244
|
-
return {
|
|
6245
|
-
ready: false,
|
|
6246
|
-
totalEntities: 0,
|
|
6247
|
-
categories: {},
|
|
6248
|
-
error: indexError2?.message
|
|
6249
|
-
};
|
|
6250
|
-
}
|
|
6251
|
-
return {
|
|
6252
|
-
ready: true,
|
|
6253
|
-
totalEntities: entityIndex._metadata.total_entities,
|
|
6254
|
-
categories: {
|
|
6255
|
-
technologies: entityIndex.technologies.length,
|
|
6256
|
-
acronyms: entityIndex.acronyms.length,
|
|
6257
|
-
people: entityIndex.people.length,
|
|
6258
|
-
projects: entityIndex.projects.length,
|
|
6259
|
-
organizations: entityIndex.organizations?.length ?? 0,
|
|
6260
|
-
locations: entityIndex.locations?.length ?? 0,
|
|
6261
|
-
concepts: entityIndex.concepts?.length ?? 0,
|
|
6262
|
-
animals: entityIndex.animals?.length ?? 0,
|
|
6263
|
-
media: entityIndex.media?.length ?? 0,
|
|
6264
|
-
events: entityIndex.events?.length ?? 0,
|
|
6265
|
-
documents: entityIndex.documents?.length ?? 0,
|
|
6266
|
-
vehicles: entityIndex.vehicles?.length ?? 0,
|
|
6267
|
-
health: entityIndex.health?.length ?? 0,
|
|
6268
|
-
finance: entityIndex.finance?.length ?? 0,
|
|
6269
|
-
food: entityIndex.food?.length ?? 0,
|
|
6270
|
-
hobbies: entityIndex.hobbies?.length ?? 0,
|
|
6271
|
-
other: entityIndex.other.length
|
|
6272
|
-
}
|
|
6273
|
-
};
|
|
6274
|
-
}
|
|
6275
6240
|
var SUGGESTION_PATTERN = /→\s*\[\[.+$/;
|
|
6276
6241
|
var GENERIC_WORDS = /* @__PURE__ */ new Set([
|
|
6277
6242
|
// Common nouns that appear everywhere and rarely mean anything specific
|
|
@@ -9843,21 +9808,23 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
9843
9808
|
if (hasEmbeddingsIndex()) {
|
|
9844
9809
|
try {
|
|
9845
9810
|
const semanticResults = await semanticSearch(query, limit);
|
|
9846
|
-
const
|
|
9847
|
-
const
|
|
9848
|
-
const
|
|
9811
|
+
const normalizePath2 = (p) => p.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
9812
|
+
const fts5Ranked = fts5Results.map((r) => ({ path: normalizePath2(r.path), title: r.title, snippet: r.snippet }));
|
|
9813
|
+
const semanticRanked = semanticResults.map((r) => ({ path: normalizePath2(r.path), title: r.title }));
|
|
9814
|
+
const entityRankedList = entityResults.map((r) => ({ path: normalizePath2(r.path), title: r.name }));
|
|
9815
|
+
const edgeRankedNorm = edgeRanked.map((r) => ({ path: normalizePath2(r.path), title: r.title }));
|
|
9849
9816
|
const rrfLists = [fts5Ranked, semanticRanked, entityRankedList];
|
|
9850
|
-
if (
|
|
9817
|
+
if (edgeRankedNorm.length > 0) rrfLists.push(edgeRankedNorm);
|
|
9851
9818
|
const rrfScores = reciprocalRankFusion(...rrfLists);
|
|
9852
9819
|
const allPaths = /* @__PURE__ */ new Set([
|
|
9853
|
-
...fts5Results.map((r) => r.path),
|
|
9854
|
-
...semanticResults.map((r) => r.path),
|
|
9855
|
-
...entityResults.map((r) => r.path),
|
|
9856
|
-
...edgeRanked.map((r) => r.path)
|
|
9820
|
+
...fts5Results.map((r) => normalizePath2(r.path)),
|
|
9821
|
+
...semanticResults.map((r) => normalizePath2(r.path)),
|
|
9822
|
+
...entityResults.map((r) => normalizePath2(r.path)),
|
|
9823
|
+
...edgeRanked.map((r) => normalizePath2(r.path))
|
|
9857
9824
|
]);
|
|
9858
|
-
const fts5Map = new Map(fts5Results.map((r) => [r.path, r]));
|
|
9859
|
-
const semanticMap = new Map(semanticResults.map((r) => [r.path, r]));
|
|
9860
|
-
const entityMap = new Map(entityResults.map((r) => [r.path, r]));
|
|
9825
|
+
const fts5Map = new Map(fts5Results.map((r) => [normalizePath2(r.path), r]));
|
|
9826
|
+
const semanticMap = new Map(semanticResults.map((r) => [normalizePath2(r.path), r]));
|
|
9827
|
+
const entityMap = new Map(entityResults.map((r) => [normalizePath2(r.path), r]));
|
|
9861
9828
|
const merged = Array.from(allPaths).map((p) => ({
|
|
9862
9829
|
path: p,
|
|
9863
9830
|
title: fts5Map.get(p)?.title || semanticMap.get(p)?.title || entityMap.get(p)?.name || p.replace(/\.md$/, "").split("/").pop() || p,
|
|
@@ -9880,10 +9847,12 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
9880
9847
|
}
|
|
9881
9848
|
}
|
|
9882
9849
|
if (entityResults.length > 0) {
|
|
9883
|
-
const
|
|
9884
|
-
const
|
|
9850
|
+
const normalizePath2 = (p) => p.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
9851
|
+
const fts5Map = new Map(fts5Results.map((r) => [normalizePath2(r.path), r]));
|
|
9852
|
+
const entityNormMap = new Map(entityResults.map((r) => [normalizePath2(r.path), r]));
|
|
9853
|
+
const entityRanked = entityResults.filter((r) => !fts5Map.has(normalizePath2(r.path)));
|
|
9885
9854
|
const merged = [
|
|
9886
|
-
...fts5Results.map((r) => ({ path: r.path, title: r.title, snippet: r.snippet, in_entity:
|
|
9855
|
+
...fts5Results.map((r) => ({ path: r.path, title: r.title, snippet: r.snippet, in_entity: entityNormMap.has(normalizePath2(r.path)) })),
|
|
9887
9856
|
...entityRanked.map((r) => ({ path: r.path, title: r.name, snippet: void 0, in_entity: true }))
|
|
9888
9857
|
];
|
|
9889
9858
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
@@ -13536,12 +13505,6 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13536
13505
|
trackWikilinkApplications(stateDb2, notePath, linkedEntities);
|
|
13537
13506
|
}
|
|
13538
13507
|
}
|
|
13539
|
-
const _debug = {
|
|
13540
|
-
entityCount: getEntityIndexStats().totalEntities,
|
|
13541
|
-
indexReady: getEntityIndexStats().ready,
|
|
13542
|
-
skipWikilinks,
|
|
13543
|
-
wikilinkInfo: wikilinkInfo || "none"
|
|
13544
|
-
};
|
|
13545
13508
|
let suggestInfo;
|
|
13546
13509
|
if (suggestOutgoingLinks && !skipWikilinks && processedContent.length >= 100) {
|
|
13547
13510
|
const result = await suggestRelatedLinks(processedContent, { maxSuggestions, notePath });
|
|
@@ -13566,8 +13529,6 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13566
13529
|
updatedContent,
|
|
13567
13530
|
message: `Added content to section "${ctx.sectionBoundary.name}" in ${notePath}${createdInfo}`,
|
|
13568
13531
|
preview,
|
|
13569
|
-
_debug,
|
|
13570
|
-
// Temporary debug field for production troubleshooting
|
|
13571
13532
|
warnings: validationResult.inputWarnings.length > 0 ? validationResult.inputWarnings : void 0,
|
|
13572
13533
|
outputIssues: validationResult.outputIssues.length > 0 ? validationResult.outputIssues : void 0,
|
|
13573
13534
|
normalizationChanges: validationResult.normalizationChanges.length > 0 ? validationResult.normalizationChanges : void 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.76",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 51 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
55
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
55
|
+
"@velvetmonkey/vault-core": "^2.0.75",
|
|
56
56
|
"better-sqlite3": "^11.0.0",
|
|
57
57
|
"chokidar": "^4.0.0",
|
|
58
58
|
"gray-matter": "^4.0.3",
|