@velvetmonkey/flywheel-memory 2.0.84 → 2.0.86

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 (2) hide show
  1. package/dist/index.js +77 -33
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
4
10
  var __esm = (fn, res) => function __init() {
5
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
12
  };
@@ -1575,7 +1581,7 @@ var init_taskHelpers = __esm({
1575
1581
  import * as path32 from "path";
1576
1582
  import { readFileSync as readFileSync5, realpathSync } from "fs";
1577
1583
  import { fileURLToPath } from "url";
1578
- import { dirname as dirname4, join as join17 } from "path";
1584
+ import { dirname as dirname5, join as join17 } from "path";
1579
1585
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1580
1586
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1581
1587
 
@@ -1891,12 +1897,41 @@ function setEmbeddingsBuildState(state2) {
1891
1897
  function setEmbeddingsDatabase(database) {
1892
1898
  db = database;
1893
1899
  }
1900
+ function clearModelCache(modelId) {
1901
+ try {
1902
+ const candidates = [];
1903
+ try {
1904
+ const transformersDir = path2.dirname(__require.resolve("@huggingface/transformers/package.json"));
1905
+ candidates.push(path2.join(transformersDir, ".cache", ...modelId.split("/")));
1906
+ } catch {
1907
+ }
1908
+ const home = process.env.HOME || process.env.USERPROFILE || "";
1909
+ if (home) {
1910
+ const npxDir = path2.join(home, ".npm", "_npx");
1911
+ if (fs3.existsSync(npxDir)) {
1912
+ for (const hash of fs3.readdirSync(npxDir)) {
1913
+ const candidate = path2.join(npxDir, hash, "node_modules", "@huggingface", "transformers", ".cache", ...modelId.split("/"));
1914
+ if (fs3.existsSync(candidate)) candidates.push(candidate);
1915
+ }
1916
+ }
1917
+ }
1918
+ for (const cacheDir of candidates) {
1919
+ if (fs3.existsSync(cacheDir)) {
1920
+ fs3.rmSync(cacheDir, { recursive: true, force: true });
1921
+ console.error(`[Semantic] Deleted corrupted model cache: ${cacheDir}`);
1922
+ }
1923
+ }
1924
+ } catch (e) {
1925
+ console.error(`[Semantic] Could not clear model cache: ${e instanceof Error ? e.message : e}`);
1926
+ }
1927
+ }
1894
1928
  async function initEmbeddings() {
1895
1929
  if (pipeline) return;
1896
1930
  if (initPromise) return initPromise;
1897
1931
  initPromise = (async () => {
1898
1932
  const MAX_RETRIES = 3;
1899
1933
  const RETRY_DELAYS = [2e3, 5e3, 1e4];
1934
+ let cacheCleared = false;
1900
1935
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
1901
1936
  try {
1902
1937
  const transformers = await Function("specifier", "return import(specifier)")("@huggingface/transformers");
@@ -1918,9 +1953,17 @@ async function initEmbeddings() {
1918
1953
  "Semantic search requires @huggingface/transformers. Install it with: npm install @huggingface/transformers"
1919
1954
  );
1920
1955
  }
1956
+ const errMsg = err instanceof Error ? err.message : String(err);
1957
+ if (!cacheCleared && (errMsg.includes("Protobuf parsing failed") || errMsg.includes("onnx"))) {
1958
+ console.error(`[Semantic] Corrupted model cache detected: ${errMsg}`);
1959
+ clearModelCache(activeModelConfig.id);
1960
+ cacheCleared = true;
1961
+ pipeline = null;
1962
+ continue;
1963
+ }
1921
1964
  if (attempt < MAX_RETRIES) {
1922
1965
  const delay = RETRY_DELAYS[attempt - 1];
1923
- console.error(`[Semantic] Model load failed (attempt ${attempt}/${MAX_RETRIES}): ${err instanceof Error ? err.message : err}`);
1966
+ console.error(`[Semantic] Model load failed (attempt ${attempt}/${MAX_RETRIES}): ${errMsg}`);
1924
1967
  console.error(`[Semantic] Retrying in ${delay / 1e3}s...`);
1925
1968
  await new Promise((resolve2) => setTimeout(resolve2, delay));
1926
1969
  pipeline = null;
@@ -7551,10 +7594,10 @@ function queryTasksFromCache(options) {
7551
7594
  if (!db3) {
7552
7595
  throw new Error("Task cache database not initialized.");
7553
7596
  }
7554
- const { status = "all", folder, tag, excludeTags = [], has_due_date, limit, offset = 0 } = options;
7597
+ const { status, folder, tag, excludeTags = [], has_due_date, limit, offset = 0 } = options;
7555
7598
  const conditions = [];
7556
7599
  const params = [];
7557
- if (status !== "all") {
7600
+ if (status) {
7558
7601
  conditions.push("status = ?");
7559
7602
  params.push(status);
7560
7603
  }
@@ -9731,11 +9774,11 @@ function sortNotes(notes, sortBy, order) {
9731
9774
  function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
9732
9775
  server2.tool(
9733
9776
  "search",
9734
- 'Search the vault \u2014 always try this before reading files. Returns frontmatter, backlinks (with lines), outlinks (with lines + exists), headings, content snippet or preview, entity metadata, and timestamps for every hit.\n\nSearch the vault across metadata, content, and entities. Scope controls what to search: "metadata" for frontmatter/tags/folders, "content" for full-text search (FTS5), "entities" for people/projects/technologies, "all" (default) tries metadata then falls back to content search. When embeddings have been built (via init_semantic), content and all scopes automatically include embedding-based results via hybrid ranking.\n\nExample: search({ query: "quarterly review", scope: "content", limit: 5 })\nExample: search({ where: { type: "project", status: "active" }, scope: "metadata" })',
9777
+ 'Search the vault \u2014 always try this before reading files. Returns frontmatter, backlinks (with lines), outlinks (with lines + exists), headings, content snippet or preview, entity metadata, and timestamps for every hit.\n\nSearches across metadata (frontmatter/tags/folders), content (FTS5 full-text + hybrid semantic), and entities (people/projects/technologies). Uses filters to narrow by frontmatter fields, tags, folders, or dates. Hybrid semantic results are automatically included when embeddings have been built (via init_semantic).\n\nExample: search({ query: "quarterly review", limit: 5 })\nExample: search({ where: { type: "project", status: "active" } })',
9735
9778
  {
9736
- query: z4.string().optional().describe('Search query text. Required for scope "content", "entities", "all". For "metadata" scope, use filters instead.'),
9737
- scope: z4.enum(["metadata", "content", "entities", "all"]).default("all").describe("What to search: metadata (frontmatter/tags/folders), content (FTS5 full-text), entities (people/projects), all (metadata then content). Semantic results are automatically included when embeddings have been built (via init_semantic)."),
9738
- // Metadata filters (used with scope "metadata" or "all")
9779
+ query: z4.string().optional().describe("Search query text. Required unless using metadata filters (where, has_tag, folder, etc.)"),
9780
+ scope: z4.enum(["metadata", "content", "entities", "all"]).optional().describe('Narrow to a specific search type. Omit for best results (searches everything). Use "metadata" for frontmatter-only queries, "entities" for entity lookup.'),
9781
+ // Metadata filters
9739
9782
  where: z4.record(z4.unknown()).optional().describe('Frontmatter filters as key-value pairs. Example: { "type": "project", "status": "active" }'),
9740
9783
  has_tag: z4.string().optional().describe("Filter to notes with this tag"),
9741
9784
  has_any_tag: z4.array(z4.string()).optional().describe("Filter to notes with any of these tags"),
@@ -9756,7 +9799,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
9756
9799
  // Context boost (edge weights)
9757
9800
  context_note: z4.string().optional().describe("Path of the note providing context. When set, results connected to this note via weighted edges get an RRF boost.")
9758
9801
  },
9759
- async ({ query, scope, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit, context_note }) => {
9802
+ async ({ query, scope: rawScope, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit, context_note }) => {
9803
+ const scope = rawScope || "all";
9760
9804
  const limit = Math.min(requestedLimit ?? 20, MAX_LIMIT);
9761
9805
  const index = getIndex();
9762
9806
  const vaultPath2 = getVaultPath();
@@ -10678,7 +10722,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
10678
10722
  description: "Read the structure of a specific note. Use after search identifies a note you need more detail on. Returns headings, frontmatter, tags, word count. Set include_content: true to get the full markdown.",
10679
10723
  inputSchema: {
10680
10724
  path: z6.string().describe("Path to the note"),
10681
- include_content: z6.boolean().default(false).describe("Include the text content under each top-level section")
10725
+ include_content: z6.boolean().default(true).describe("Include the text content under each top-level section. Set false to get structure only.")
10682
10726
  }
10683
10727
  },
10684
10728
  async ({ path: path33, include_content }) => {
@@ -10791,7 +10835,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
10791
10835
  description: 'Query tasks from the vault. Use path to scope to a single note. Use status to filter (default: "open"). Use has_due_date to find tasks with due dates.',
10792
10836
  inputSchema: {
10793
10837
  path: z6.string().optional().describe("Scope to tasks from this specific note path"),
10794
- status: z6.enum(["open", "completed", "cancelled", "all"]).default("open").describe("Filter by task status"),
10838
+ status: z6.enum(["open", "completed", "cancelled"]).default("open").describe("Filter by task status"),
10795
10839
  has_due_date: z6.boolean().optional().describe("If true, only return tasks with due dates (sorted by date)"),
10796
10840
  folder: z6.string().optional().describe("Limit to tasks in notes within this folder"),
10797
10841
  tag: z6.string().optional().describe("Filter to tasks with this tag"),
@@ -10812,7 +10856,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
10812
10856
  };
10813
10857
  }
10814
10858
  let filtered = result2;
10815
- if (status !== "all") {
10859
+ if (status) {
10816
10860
  filtered = result2.filter((t) => t.status === status);
10817
10861
  }
10818
10862
  const paged2 = filtered.slice(offset, offset + limit);
@@ -13465,19 +13509,19 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
13465
13509
  format: z11.enum(["plain", "bullet", "task", "numbered", "timestamp-bullet"]).default("plain").describe("How to format the content"),
13466
13510
  commit: z11.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
13467
13511
  skipWikilinks: z11.boolean().default(false).describe("If true, skip auto-wikilink application (wikilinks are applied by default)"),
13468
- preserveListNesting: z11.boolean().default(true).describe("Detect and preserve the indentation level of surrounding list items. Set false to disable."),
13469
- bumpHeadings: z11.boolean().default(true).describe("Auto-bump heading levels in inserted content so they nest under the target section (e.g., ## in a ## section becomes ###). Set false to disable."),
13470
- suggestOutgoingLinks: z11.boolean().default(true).describe('Append suggested outgoing wikilinks based on content (e.g., "\u2192 [[AI]], [[Philosophy]]"). Set false to disable.'),
13471
- maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks to append (1-10, default: 5)"),
13472
- validate: z11.boolean().default(true).describe("Check input for common issues (double timestamps, non-markdown bullets, etc.)"),
13473
- normalize: z11.boolean().default(true).describe("Auto-fix common issues before formatting (replace \u2022 with -, trim excessive whitespace, etc.)"),
13474
- guardrails: z11.enum(["warn", "strict", "off"]).default("warn").describe('Output validation mode: "warn" returns issues but proceeds, "strict" blocks on errors, "off" disables'),
13512
+ suggestOutgoingLinks: z11.boolean().default(true).describe("Suggest related outgoing wikilinks based on content. Set false to disable."),
13513
+ maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks (1-10, default: 5)"),
13475
13514
  linkedEntities: z11.array(z11.string()).optional().describe("Entity names already linked in the content. When skipWikilinks=true, these are tracked for feedback without re-processing the content."),
13476
13515
  dry_run: z11.boolean().optional().default(false).describe("Preview changes without writing to disk"),
13477
13516
  agent_id: z11.string().optional().describe('Agent identifier for multi-agent scoping (e.g., "claude-opus", "planning-agent")'),
13478
13517
  session_id: z11.string().optional().describe('Session identifier for conversation scoping (e.g., "sess-abc123")')
13479
13518
  },
13480
- async ({ path: notePath, section, content, create_if_missing, position, format, commit, skipWikilinks, preserveListNesting, bumpHeadings, suggestOutgoingLinks, maxSuggestions, validate, normalize, guardrails, linkedEntities, dry_run, agent_id, session_id }) => {
13519
+ async ({ path: notePath, section, content, create_if_missing, position, format, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, linkedEntities, dry_run, agent_id, session_id }) => {
13520
+ const preserveListNesting = true;
13521
+ const bumpHeadings = true;
13522
+ const validate = true;
13523
+ const normalize = true;
13524
+ const guardrails = "warn";
13481
13525
  let noteCreated = false;
13482
13526
  let templateUsed;
13483
13527
  if (create_if_missing) {
@@ -13609,16 +13653,16 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
13609
13653
  useRegex: z11.boolean().default(false).describe("Treat search as regex"),
13610
13654
  commit: z11.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
13611
13655
  skipWikilinks: z11.boolean().default(false).describe("If true, skip auto-wikilink application on replacement text"),
13612
- suggestOutgoingLinks: z11.boolean().default(true).describe('Append suggested outgoing wikilinks based on content (e.g., "\u2192 [[AI]], [[Philosophy]]"). Set false to disable.'),
13613
- maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks to append (1-10, default: 5)"),
13614
- validate: z11.boolean().default(true).describe("Check input for common issues (double timestamps, non-markdown bullets, etc.)"),
13615
- normalize: z11.boolean().default(true).describe("Auto-fix common issues before formatting (replace \u2022 with -, trim excessive whitespace, etc.)"),
13616
- guardrails: z11.enum(["warn", "strict", "off"]).default("warn").describe('Output validation mode: "warn" returns issues but proceeds, "strict" blocks on errors, "off" disables'),
13656
+ suggestOutgoingLinks: z11.boolean().default(true).describe("Suggest related outgoing wikilinks based on content. Set false to disable."),
13657
+ maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks (1-10, default: 5)"),
13617
13658
  dry_run: z11.boolean().optional().default(false).describe("Preview changes without writing to disk"),
13618
13659
  agent_id: z11.string().optional().describe("Agent identifier for multi-agent scoping"),
13619
13660
  session_id: z11.string().optional().describe("Session identifier for conversation scoping")
13620
13661
  },
13621
- async ({ path: notePath, section, search, replacement, mode, useRegex, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, validate, normalize, guardrails, dry_run, agent_id, session_id }) => {
13662
+ async ({ path: notePath, section, search, replacement, mode, useRegex, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, dry_run, agent_id, session_id }) => {
13663
+ const validate = true;
13664
+ const normalize = true;
13665
+ const guardrails = "warn";
13622
13666
  return withVaultFile(
13623
13667
  {
13624
13668
  vaultPath: vaultPath2,
@@ -17527,7 +17571,7 @@ function getEdgeWeightBoost(entityName, edgeWeightMap) {
17527
17571
  async function performRecall(stateDb2, query, options = {}) {
17528
17572
  const {
17529
17573
  max_results = 20,
17530
- focus = "all",
17574
+ focus,
17531
17575
  entity,
17532
17576
  max_tokens,
17533
17577
  diversity = 0.7,
@@ -17537,7 +17581,7 @@ async function performRecall(stateDb2, query, options = {}) {
17537
17581
  const recencyIndex2 = loadRecencyFromStateDb();
17538
17582
  const edgeWeightMap = getEntityEdgeWeightMap(stateDb2);
17539
17583
  const feedbackBoosts = getAllFeedbackBoosts(stateDb2);
17540
- if (focus === "all" || focus === "entities") {
17584
+ if (!focus || focus === "entities") {
17541
17585
  try {
17542
17586
  const entityResults = searchEntitiesDb2(stateDb2, query, max_results);
17543
17587
  for (const e of entityResults) {
@@ -17566,7 +17610,7 @@ async function performRecall(stateDb2, query, options = {}) {
17566
17610
  } catch {
17567
17611
  }
17568
17612
  }
17569
- if (focus === "all" || focus === "notes") {
17613
+ if (!focus || focus === "notes") {
17570
17614
  try {
17571
17615
  const noteResults = searchFTS5("", query, max_results);
17572
17616
  for (const n of noteResults) {
@@ -17589,7 +17633,7 @@ async function performRecall(stateDb2, query, options = {}) {
17589
17633
  } catch {
17590
17634
  }
17591
17635
  }
17592
- if (focus === "all" || focus === "memories") {
17636
+ if (!focus || focus === "memories") {
17593
17637
  try {
17594
17638
  const memResults = searchMemories(stateDb2, {
17595
17639
  query,
@@ -17617,7 +17661,7 @@ async function performRecall(stateDb2, query, options = {}) {
17617
17661
  } catch {
17618
17662
  }
17619
17663
  }
17620
- if ((focus === "all" || focus === "entities") && query.length >= 20 && hasEntityEmbeddingsIndex()) {
17664
+ if ((!focus || focus === "entities") && query.length >= 20 && hasEntityEmbeddingsIndex()) {
17621
17665
  try {
17622
17666
  const embedding = await embedTextCached(query);
17623
17667
  const semanticMatches = findSemanticallySimilarEntities(embedding, max_results);
@@ -17726,7 +17770,7 @@ function registerRecallTools(server2, getStateDb, getVaultPath) {
17726
17770
  {
17727
17771
  query: z24.string().describe('What to recall (e.g., "Project X", "meetings about auth")'),
17728
17772
  max_results: z24.number().min(1).max(100).optional().describe("Max results (default: 20)"),
17729
- focus: z24.enum(["entities", "notes", "memories", "all"]).optional().describe("Limit search to specific type (default: all)"),
17773
+ focus: z24.enum(["entities", "notes", "memories"]).optional().describe("Limit to a specific result type. Omit for best results (searches everything)."),
17730
17774
  entity: z24.string().optional().describe("Filter memories by entity association"),
17731
17775
  max_tokens: z24.number().optional().describe("Token budget for response (truncates lower-ranked results)"),
17732
17776
  diversity: z24.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
@@ -19309,7 +19353,7 @@ function registerVaultResources(server2, getIndex) {
19309
19353
 
19310
19354
  // src/index.ts
19311
19355
  var __filename = fileURLToPath(import.meta.url);
19312
- var __dirname = dirname4(__filename);
19356
+ var __dirname = dirname5(__filename);
19313
19357
  var pkg = JSON.parse(readFileSync5(join17(__dirname, "../package.json"), "utf-8"));
19314
19358
  var vaultPath = process.env.PROJECT_PATH || process.env.VAULT_PATH || findVaultRoot();
19315
19359
  var resolvedVaultPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.0.84",
3
+ "version": "2.0.86",
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",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@modelcontextprotocol/sdk": "^1.25.1",
56
- "@velvetmonkey/vault-core": "^2.0.84",
56
+ "@velvetmonkey/vault-core": "^2.0.86",
57
57
  "better-sqlite3": "^11.0.0",
58
58
  "chokidar": "^4.0.0",
59
59
  "gray-matter": "^4.0.3",