@velvetmonkey/flywheel-memory 2.0.71 → 2.0.73

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.
Files changed (3) hide show
  1. package/README.md +363 -0
  2. package/dist/index.js +27 -6
  3. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,363 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/velvetmonkey/flywheel-memory/main/header.png" alt="Flywheel" width="256"/>
3
+ <h1>Flywheel</h1>
4
+ <p><strong>A knowledge graph engine that reads, writes, and learns.</strong><br/>Graph intelligence. Safe writes. A feedback loop that learns from every interaction.<br/>Zero cloud. Your Obsidian vault becomes a queryable second brain.</p>
5
+ </div>
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@velvetmonkey/flywheel-memory.svg)](https://www.npmjs.com/package/@velvetmonkey/flywheel-memory)
8
+ [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-blueviolet.svg)](https://modelcontextprotocol.io/)
9
+ [![CI](https://github.com/velvetmonkey/flywheel-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/velvetmonkey/flywheel-memory/actions/workflows/ci.yml)
10
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
11
+ [![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-blue.svg)](https://github.com/velvetmonkey/flywheel-memory)
12
+ [![Scale](https://img.shields.io/badge/scale-100k--line%20files%20%7C%202.5k%20entities-brightgreen.svg)](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TESTING.md#performance-benchmarks)
13
+ [![Tests](https://img.shields.io/badge/tests-2,456%20passed-brightgreen.svg)](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TESTING.md)
14
+
15
+ | | Without Flywheel | With Flywheel |
16
+ |---|---|---|
17
+ | "What's overdue?" | Read every file | Indexed query, <10ms |
18
+ | "What links here?" | grep every file | Pre-indexed backlink graph |
19
+ | "Add a meeting note" | Raw write, no linking | Write + auto-wikilink |
20
+ | "What should I link?" | Manual or grep | Smart scoring + semantic understanding |
21
+ | Token cost | 2,000-250,000 | 50-200 |
22
+
23
+ 51 tools across 17 categories. 6-line config. Zero cloud dependencies.
24
+
25
+ **Try in 60 seconds:**
26
+
27
+ ```bash
28
+ git clone https://github.com/velvetmonkey/flywheel-memory.git
29
+ cd flywheel-memory/demos/carter-strategy && claude
30
+ ```
31
+
32
+ Then ask: *"How much have I billed Acme Corp?"*
33
+
34
+ ---
35
+
36
+ ## See It Work
37
+
38
+ ![Flywheel demo](https://raw.githubusercontent.com/velvetmonkey/flywheel-memory/main/demos/flywheel-demo.gif)
39
+
40
+ ### Read: "How much have I billed Acme Corp?"
41
+
42
+ From the [carter-strategy](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/carter-strategy/) demo -- a solo consultant with 3 clients, 5 projects, and $27K in invoices.
43
+
44
+ ```
45
+ ❯ How much have I billed Acme Corp?
46
+
47
+ ● flywheel › get_backlinks
48
+ path: "clients/Acme Corp.md"
49
+ ← invoices/INV-2025-047.md
50
+ invoices/INV-2025-048.md
51
+ projects/Acme Data Migration.md
52
+ proposals/Acme Analytics Add-on.md
53
+
54
+ ● flywheel › get_note_metadata
55
+ path: "invoices/INV-2025-047.md"
56
+ ← amount: 15000, status: paid, period: November 2025
57
+
58
+ ● flywheel › get_note_metadata
59
+ path: "invoices/INV-2025-048.md"
60
+ ← amount: 12000, status: pending, period: December 2025
61
+
62
+ ┌─ RESULT ──────────────────────────────────────────────┐
63
+ │ Acme Corp Billing: $27,000 │
64
+ │ │
65
+ │ Paid: $15,000 — Acme Data Migration (Nov 2025) │
66
+ │ Pending: $12,000 — Acme Data Migration (Dec 2025) │
67
+ │ │
68
+ │ 4 tool calls · ~160 tokens · 0 files read │
69
+ └───────────────────────────────────────────────────────┘
70
+ ```
71
+
72
+ Claude didn't read any files. It navigated the graph: backlinks to find related notes, metadata to extract the numbers.
73
+
74
+ ```
75
+ Same 3 queries without Flywheel: 11,150 tokens (reading files repeatedly)
76
+ Same 3 queries with Flywheel: 300 tokens (querying the index)
77
+ 37x savings
78
+ ```
79
+
80
+ ### Write: Auto-wikilinks on every mutation
81
+
82
+ ```
83
+ ❯ Log that I finished the Acme strategy deck
84
+
85
+ ● flywheel › vault_add_to_section
86
+ path: "daily-notes/2026-01-04.md"
87
+ section: "Log"
88
+ content: "finished the [[Acme Corp]] strategy deck"
89
+ ↑ auto-linked because Acme Corp.md exists
90
+ ```
91
+
92
+ Try it yourself: `cd demos/carter-strategy && claude`
93
+
94
+ ---
95
+
96
+ ## What Makes Flywheel Different
97
+
98
+ ### 1. Hybrid Search
99
+
100
+ Search "authentication" -- exact matches. Search "login security" -- same notes, plus every note about auth that never uses the word.
101
+
102
+ Keyword search finds what you said. Semantic search finds what you meant. Flywheel runs both and fuses the results. Runs locally on a 23 MB model. Nothing leaves your machine.
103
+
104
+ ### 2. Every Suggestion Has a Receipt
105
+
106
+ Ask why Flywheel suggested `[[Marcus Johnson]]`:
107
+
108
+ ```
109
+ Entity Score Match Co-oc Type Context Recency Cross Hub Feedback Semantic Edge
110
+ ──────────────────────────────────────────────────────────────────────────────────────────────────────
111
+ Marcus Johnson 34 +10 +3 +5 +5 +5 +3 +1 +2 0 0
112
+ ```
113
+
114
+ 10 scoring dimensions, every number traceable to vault usage. Recency came from what you last wrote. Co-occurrence came from notes you've written before. Hub came from how many other notes link there. The score learns as you use it.
115
+
116
+ See [docs/ALGORITHM.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/ALGORITHM.md) for how scoring works.
117
+
118
+ ### 3. The Self-Improving Loop
119
+
120
+ **Every interaction is a graph-building operation — and a learning signal.**
121
+
122
+ When you write a note, entities are auto-linked — creating edges. When you keep a `[[link]]` through 10 edits, that edge gains weight. When two entities appear together in 20 notes, they build a co-occurrence bond (NPMI — a measure of how strongly two things associate beyond chance). When you read frequently, recent entities surface in suggestions. When you remove a bad link, the system learns what to stop suggesting (it tracks accept/reject ratios per entity and gradually suppresses low-quality matches).
123
+
124
+ This is the uncontested gap — no competitor has a feedback loop that learns from knowledge management actions.
125
+
126
+ We prove it: every auto-linked entity is correct (100% precision), and the system finds 72–82% of links it should (recall) — stable over 50 generations of noisy feedback. See [Graph Quality](#graph-quality) below.
127
+
128
+ Result: a queryable graph. "What's the shortest path between AlphaFold and my docking experiment?" Backlinks, forward links, hubs, orphans, shortest paths — every query leverages hundreds of accumulated connections. Denser graphs make every query more precise.
129
+
130
+ ### 4. Semantic Understanding
131
+
132
+ Content about "deployment automation" suggests `[[CI/CD]]` — no keyword match needed. Entity-level embeddings mean your knowledge graph understands meaning, not just words.
133
+
134
+ - **Semantic bridges**: Discovers high-value missing links between conceptually related but unlinked notes
135
+ - **Semantic clusters**: Groups notes by meaning instead of folder structure
136
+ - **Semantic wikilinks**: Suggestions based on what you *mean*, not just what you typed
137
+
138
+ Build once with `init_semantic`. Everything upgrades automatically. Configurable model via `EMBEDDING_MODEL` env var.
139
+
140
+ ### 5. Agentic Memory
141
+
142
+ The system remembers context across sessions. No more starting from scratch.
143
+
144
+ - **`brief`** assembles startup context: recent sessions, active entities, stored memories, corrections, vault pulse — token-budgeted
145
+ - **`recall`** retrieves across all knowledge channels: entities, notes, memories, and semantic search — ranked by the same scoring signals as the wikilink engine
146
+ - **`memory`** stores observations with confidence decay, TTL, and lifecycle management
147
+
148
+ Claude picks up where it left off.
149
+
150
+ ### How It Compares to Other Approaches
151
+
152
+ | | Pure Vector Search | Pure Keyword Search | Flywheel |
153
+ |---|---|---|---|
154
+ | "Why was this suggested?" | "Embeddings are close" | "Term frequency" | "10 + 3 + 5 + 5 + 3 + 1 = 34" |
155
+ | Semantic wikilinks | No | No | Yes (semantic) |
156
+ | Finds synonyms/concepts? | Yes | No | Yes (semantic search) |
157
+ | Exact phrase matching? | Weak | Yes | Yes |
158
+ | Same input → same output? | Not guaranteed | Always | Always |
159
+ | Runs offline? | Often not | Yes | Yes (local embeddings) |
160
+ | Learns from usage? | Retraining | No | Implicit feedback loop |
161
+ | Agent memory | No | No | Yes (brief + recall + memory) |
162
+
163
+ ---
164
+
165
+ ## The Flywheel Effect
166
+
167
+ The name is literal. A flywheel is hard to start but once spinning, each push adds to the momentum.
168
+
169
+ ### Day 1: Instant Value
170
+
171
+ You point Flywheel at your vault. It indexes every note, extracts entities, builds a backlink graph. First query returns in <10ms. First write auto-links three entities you would have missed. No training period. No configuration.
172
+
173
+ ### Week 1: Connections Appear
174
+
175
+ You have 30 disconnected notes. Auto-wikilinks create 47 connections on your first day of writing through Flywheel. You stop reading files and start querying a graph.
176
+
177
+ ### Month 1: Intelligence Emerges
178
+
179
+ Hub notes surface. "Sarah Mitchell" has 23 backlinks -- she's clearly important. When you write about a project, her name appears in suggestions because co-occurrence tracking knows she's relevant. You didn't configure this. The vault structure revealed it.
180
+
181
+ ### Month 3: The Graph Is Self-Sustaining
182
+
183
+ Every query leverages hundreds of accumulated connections. New content auto-links to the right places. You stop thinking about organization.
184
+
185
+ ### What This Looks Like
186
+
187
+ ```
188
+ Input: "Met with Sarah about the data migration"
189
+ Output: "Met with [[Sarah Mitchell]] about the [[Acme Data Migration]]"
190
+ ```
191
+
192
+ No manual linking. No broken references. Use compounds into structure, structure compounds into intelligence.
193
+
194
+ ---
195
+
196
+ ## Battle-Tested
197
+
198
+ **2,456 tests. 122 test files. 47,000+ lines of test code.**
199
+
200
+ ### Performance
201
+
202
+ | Operation | Threshold | Typical |
203
+ |---|---|---|
204
+ | 1k-line mutation | <100ms | ~15ms |
205
+ | 10k-line mutation | <500ms | -- |
206
+ | 100k-line mutation | <2s | -- |
207
+
208
+ - **100 parallel writes, zero corruption** -- concurrent mutations verified under stress
209
+ - **Property-based fuzzing** -- fast-check with 700+ randomized scenarios
210
+ - **SQL injection prevention** -- parameterized queries throughout
211
+ - **Path traversal blocking** -- all file paths validated against vault root
212
+ - **Deterministic output** -- every tool produces the same result given the same input
213
+
214
+ Every demo vault is a real test fixture. If it works in the README, it passes in CI.
215
+
216
+ ```bash
217
+ git clone https://github.com/velvetmonkey/flywheel-memory.git
218
+ cd flywheel-memory && npm install && npm test
219
+ ```
220
+
221
+ See [docs/PROVE-IT.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/PROVE-IT.md) and [docs/TESTING.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TESTING.md).
222
+
223
+ ### Graph Quality
224
+
225
+ The feedback loop claim isn't asserted — it's measured. We build a test vault with known-correct links, strip them out, and measure how well the engine rediscovers them. CI locks these baselines and fails if quality regresses.
226
+
227
+ | Mode | Precision | Recall | F1 |
228
+ |---|---|---|---|
229
+ | Conservative | 100% | 71.7% | 83.5% |
230
+ | Balanced | 100% | 80.0% | 88.9% |
231
+ | Aggressive | 100% | 81.7% | 89.9% |
232
+
233
+ **Precision** = "of the links suggested, how many were correct?" (100% = never suggests a wrong link). **Recall** = "of the links that should exist, how many were found?" **F1** = the balance of both — higher is better.
234
+
235
+ Measured against a 96-note/61-entity ground truth vault.
236
+
237
+ - **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
+ - **7 vault archetypes** — hub-and-spoke, hierarchical, dense-mesh, sparse-orphan, bridge-network, small-world, chaos
239
+ - **13 scoring layers** individually ablated, contribution measured
240
+ - **Regression gate** — CI fails if any mode's F1/precision/recall drops >5pp from baseline
241
+
242
+ 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).
243
+
244
+ ### Safe Writes
245
+
246
+ Every mutation is:
247
+
248
+ - **Git-committed** — one `vault_undo_last_mutation` away from reverting any change
249
+ - **Conflict-detected** — content hash check prevents clobbering concurrent edits (SHA-256)
250
+ - **Policy-governed** — configurable guardrails with warn/strict/off modes
251
+ - **Precise** — auto-wikilinks have 1.0 precision in production (never inserts a wrong link)
252
+
253
+ ---
254
+
255
+ ## How It Compares
256
+
257
+ | Feature | Flywheel Memory | Obsidian CLI (MCP) | Smart Connections | Khoj |
258
+ |---------|----------------|-------------------|-------------------|------|
259
+ | Backlink graph | Bidirectional | No | No | No |
260
+ | Hybrid search | Local (keyword + semantic) | No | Cloud only | Cloud |
261
+ | Auto-wikilinks | Yes (alias resolution) | No | No | No |
262
+ | Schema intelligence | 6 analysis modes | No | No | No |
263
+ | Entity extraction | Auto (18 categories) | No | No | No |
264
+ | Learns from usage | Feedback loop + suppression | No | No | No |
265
+ | Agent memory | brief + recall + memory | No | No | No |
266
+ | Safe writes | Git + conflict detection | No | N/A | N/A |
267
+ | Test coverage | 2,456 tests | Unknown | Unknown | Unknown |
268
+ | Tool count | 51 | ~10 | 0 (plugin) | ~5 |
269
+
270
+ ---
271
+
272
+ ## Try It
273
+
274
+ ### Step 1: Try a demo
275
+
276
+ ```bash
277
+ git clone https://github.com/velvetmonkey/flywheel-memory.git
278
+ cd flywheel-memory/demos/carter-strategy && claude
279
+ ```
280
+
281
+ | Demo | You are | Ask this |
282
+ |------|---------|----------|
283
+ | [carter-strategy](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/carter-strategy/) | Solo consultant | "How much have I billed Acme Corp?" |
284
+ | [artemis-rocket](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/artemis-rocket/) | Rocket engineer | "What's blocking propulsion?" |
285
+ | [startup-ops](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/startup-ops/) | SaaS co-founder | "What's our MRR?" |
286
+ | [nexus-lab](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/nexus-lab/) | PhD researcher | "How does AlphaFold connect to my experiment?" |
287
+ | [solo-operator](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/solo-operator/) | Content creator | "How's revenue this month?" |
288
+ | [support-desk](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/support-desk/) | Support agent | "What's Sarah Chen's situation?" |
289
+ | [zettelkasten](https://github.com/velvetmonkey/flywheel-memory/tree/main/demos/zettelkasten/) | Zettelkasten student | "How does spaced repetition connect to active recall?" |
290
+
291
+ ### Step 2: Your own vault
292
+
293
+ Add `.mcp.json` to your vault root:
294
+
295
+ ```json
296
+ {
297
+ "mcpServers": {
298
+ "flywheel": {
299
+ "command": "npx",
300
+ "args": ["-y", "@velvetmonkey/flywheel-memory"],
301
+ "env": {
302
+ "FLYWHEEL_PRESET": "minimal"
303
+ }
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ ```bash
310
+ cd /path/to/your/vault && claude
311
+ ```
312
+
313
+ Start with the `minimal` preset (11 tools). Add bundles as needed. See [docs/CONFIGURATION.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/CONFIGURATION.md) for all options.
314
+
315
+ > **Note:** Developed and tested with Claude Code. Other MCP clients may work but are untested.
316
+
317
+ ---
318
+
319
+ ## Tools Overview
320
+
321
+ | Preset | Tools | What you get |
322
+ |--------|-------|--------------|
323
+ | `full` (default) | 51 | Everything — graph, schema, tasks, policy, memory |
324
+ | `minimal` | 11 | Note-taking essentials — search, read, create, edit |
325
+ | `writer` | 14 | minimal + task management |
326
+ | `agent` | 14 | minimal + agent memory (brief, recall, memory) |
327
+ | `researcher` | 12 | Search + graph navigation — read-heavy exploration |
328
+
329
+ Composable bundles (add to presets or each other):
330
+
331
+ | Bundle | Tools | What it adds |
332
+ |--------|-------|--------------|
333
+ | `graph` | 7 | Backlinks, orphans, hubs, shortest paths |
334
+ | `analysis` | 9 | Schema intelligence, wikilink validation, content similarity |
335
+ | `tasks` | 3 | Task queries and mutations |
336
+ | `health` | 12 | Vault diagnostics, index management, growth, config, merges |
337
+ | `ops` | 2 | Git undo, policy automation |
338
+ | `note-ops` | 4 | Delete, move, rename notes, merge entities |
339
+
340
+ The fewer tools you load, the less context Claude needs to pick the right one. See [docs/TOOLS.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TOOLS.md) for the full reference.
341
+
342
+ ---
343
+
344
+ ## Documentation
345
+
346
+ | Doc | Why read this |
347
+ |---|---|
348
+ | [PROVE-IT.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/PROVE-IT.md) | See it working in 5 minutes |
349
+ | [TOOLS.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TOOLS.md) | All 51 tools documented |
350
+ | [ALGORITHM.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/ALGORITHM.md) | How the scoring works |
351
+ | [COOKBOOK.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/COOKBOOK.md) | Example prompts by use case |
352
+ | [SETUP.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/SETUP.md) | Full setup guide for your vault |
353
+ | [CONFIGURATION.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/CONFIGURATION.md) | Env vars, presets, custom tool sets |
354
+ | [ARCHITECTURE.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/ARCHITECTURE.md) | Index strategy, graph, auto-wikilinks |
355
+ | [TESTING.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TESTING.md) | Test methodology and benchmarks |
356
+ | [TROUBLESHOOTING.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/TROUBLESHOOTING.md) | Error recovery and diagnostics |
357
+ | [VISION.md](https://github.com/velvetmonkey/flywheel-memory/blob/main/docs/VISION.md) | Where this is going |
358
+
359
+ ---
360
+
361
+ ## License
362
+
363
+ Apache 2.0 — see [LICENSE](https://github.com/velvetmonkey/flywheel-memory/blob/main/LICENSE) for details.
package/dist/index.js CHANGED
@@ -1969,8 +1969,11 @@ async function buildEmbeddingsIndex(vaultPath2, onProgress) {
1969
1969
  const embedding = await embedText(content);
1970
1970
  const buf = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
1971
1971
  upsert.run(file.path, buf, hash, activeModelConfig.id, Date.now());
1972
- } catch {
1972
+ } catch (err) {
1973
1973
  progress.skipped++;
1974
+ if (progress.skipped <= 3) {
1975
+ console.error(`[Semantic] Failed to embed ${file.path}: ${err instanceof Error ? err.message : err}`);
1976
+ }
1974
1977
  }
1975
1978
  if (onProgress) onProgress(progress);
1976
1979
  }
@@ -8292,6 +8295,7 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
8292
8295
  },
8293
8296
  async ({ text, limit: requestedLimit, offset, detail }) => {
8294
8297
  const limit = Math.min(requestedLimit ?? 50, MAX_LIMIT);
8298
+ requireIndex();
8295
8299
  const index = getIndex();
8296
8300
  const allMatches = findEntityMatches(text, index.entities);
8297
8301
  const matches = allMatches.slice(offset, offset + limit);
@@ -8389,11 +8393,19 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
8389
8393
  });
8390
8394
  const ValidateLinksOutputSchema = {
8391
8395
  scope: z2.string().describe('What was validated (note path or "all")'),
8392
- total_links: z2.coerce.number().describe("Total number of links checked"),
8393
- valid_links: z2.coerce.number().describe("Number of valid links"),
8394
- broken_links: z2.coerce.number().describe("Total number of broken links"),
8396
+ total_links: z2.coerce.number().optional().describe("Total number of links checked"),
8397
+ valid_links: z2.coerce.number().optional().describe("Number of valid links"),
8398
+ broken_links: z2.coerce.number().optional().describe("Total number of broken links"),
8395
8399
  returned_count: z2.coerce.number().describe("Number of broken links returned (may be limited)"),
8396
- broken: z2.array(BrokenLinkSchema).describe("List of broken links")
8400
+ broken: z2.array(BrokenLinkSchema).optional().describe("List of broken links"),
8401
+ total_dead_targets: z2.coerce.number().optional().describe("Number of unique dead link targets (group_by_target mode)"),
8402
+ total_broken_links: z2.coerce.number().optional().describe("Total broken links across all targets (group_by_target mode)"),
8403
+ targets: z2.array(z2.object({
8404
+ target: z2.string(),
8405
+ mention_count: z2.coerce.number(),
8406
+ sources: z2.array(z2.string()),
8407
+ suggestion: z2.string().optional()
8408
+ })).optional().describe("Dead link targets grouped by frequency (group_by_target mode)")
8397
8409
  };
8398
8410
  function findSimilarEntity2(target, entities) {
8399
8411
  const targetLower = target.toLowerCase();
@@ -8425,6 +8437,7 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
8425
8437
  },
8426
8438
  async ({ path: notePath, typos_only, group_by_target, limit: requestedLimit, offset }) => {
8427
8439
  const limit = Math.min(requestedLimit ?? 50, MAX_LIMIT);
8440
+ requireIndex();
8428
8441
  const index = getIndex();
8429
8442
  const allBroken = [];
8430
8443
  let totalLinks = 0;
@@ -8530,6 +8543,7 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
8530
8543
  }
8531
8544
  },
8532
8545
  async ({ min_frequency, limit: requestedLimit }) => {
8546
+ requireIndex();
8533
8547
  const index = getIndex();
8534
8548
  const limit = Math.min(requestedLimit ?? 20, 100);
8535
8549
  const minFreq = min_frequency ?? 2;
@@ -8578,6 +8592,7 @@ function registerWikilinkTools(server2, getIndex, getVaultPath) {
8578
8592
  }
8579
8593
  },
8580
8594
  async ({ min_cooccurrence, limit: requestedLimit }) => {
8595
+ requireIndex();
8581
8596
  const index = getIndex();
8582
8597
  const coocIndex = getCooccurrenceIndex();
8583
8598
  const limit = Math.min(requestedLimit ?? 20, 100);
@@ -11770,6 +11785,10 @@ function isPeriodicNote(notePath) {
11770
11785
  const folder = notePath.split("/")[0]?.toLowerCase() || "";
11771
11786
  return patterns.some((p) => p.test(nameWithoutExt)) || periodicFolders.includes(folder);
11772
11787
  }
11788
+ function isTemplatePath(notePath) {
11789
+ const folder = notePath.split("/")[0]?.toLowerCase() || "";
11790
+ return folder === "templates" || folder === "template";
11791
+ }
11773
11792
  function getExcludedPaths(index, config) {
11774
11793
  const excluded = /* @__PURE__ */ new Set();
11775
11794
  const excludeTags = new Set((config.exclude_analysis_tags ?? []).map((t) => t.toLowerCase()));
@@ -11893,7 +11912,9 @@ function registerGraphAnalysisTools(server2, getIndex, getVaultPath, getStateDb,
11893
11912
  }, null, 2) }]
11894
11913
  };
11895
11914
  }
11896
- const result = getStaleNotes(index, days, min_backlinks).filter((n) => !excludedPaths.has(n.path)).slice(0, limit);
11915
+ const result = getStaleNotes(index, days, min_backlinks).filter(
11916
+ (n) => !excludedPaths.has(n.path) && !isPeriodicNote(n.path) && !isTemplatePath(n.path)
11917
+ ).slice(0, limit);
11897
11918
  return {
11898
11919
  content: [{ type: "text", text: JSON.stringify({
11899
11920
  analysis: "stale",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.0.71",
3
+ "version": "2.0.73",
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.71",
55
+ "@velvetmonkey/vault-core": "^2.0.73",
56
56
  "better-sqlite3": "^11.0.0",
57
57
  "chokidar": "^4.0.0",
58
58
  "gray-matter": "^4.0.3",