@velvetmonkey/flywheel-memory 2.3.1 → 2.3.3

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 +103 -37
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -11847,10 +11847,11 @@ function resolveToolConfig(envValue) {
11847
11847
  };
11848
11848
  }
11849
11849
  var TOOL_CATEGORY = {
11850
- // search (3 tools)
11850
+ // search
11851
11851
  search: "search",
11852
11852
  init_semantic: "search",
11853
11853
  find_similar: "search",
11854
+ discover_tools: "search",
11854
11855
  // read (3 tools) -- note reading
11855
11856
  get_note_structure: "read",
11856
11857
  get_section_content: "read",
@@ -11938,10 +11939,11 @@ var TOOL_CATEGORY = {
11938
11939
  tool_selection_feedback: "diagnostics"
11939
11940
  };
11940
11941
  var TOOL_TIER = {
11941
- // Tier 1 — always visible (= agent preset, 18 tools)
11942
+ // Tier 1 — always visible (= agent preset, see TIER_1_TOOL_COUNT)
11942
11943
  search: 1,
11943
11944
  init_semantic: 1,
11944
11945
  find_similar: 1,
11946
+ discover_tools: 1,
11945
11947
  get_note_structure: 1,
11946
11948
  get_section_content: 1,
11947
11949
  find_sections: 1,
@@ -11957,7 +11959,7 @@ var TOOL_TIER = {
11957
11959
  vault_add_task: 1,
11958
11960
  memory: 1,
11959
11961
  brief: 1,
11960
- // Tier 2 — context-triggered categories + core diagnostics (33 tools)
11962
+ // Tier 2 — context-triggered categories + core diagnostics (see TIER_2_TOOL_COUNT)
11961
11963
  graph_analysis: 2,
11962
11964
  semantic_analysis: 2,
11963
11965
  get_backlinks: 2,
@@ -11991,7 +11993,7 @@ var TOOL_TIER = {
11991
11993
  flywheel_config: 2,
11992
11994
  server_log: 2,
11993
11995
  flywheel_doctor: 2,
11994
- // Tier 3 — explicit or advanced operations (26 tools)
11996
+ // Tier 3 — explicit or advanced operations (see TIER_3_TOOL_COUNT)
11995
11997
  vault_schema: 3,
11996
11998
  schema_conventions: 3,
11997
11999
  schema_validate: 3,
@@ -12031,6 +12033,10 @@ function assertToolTierCoverage() {
12031
12033
  }
12032
12034
  }
12033
12035
  assertToolTierCoverage();
12036
+ var TOTAL_TOOL_COUNT = Object.keys(TOOL_CATEGORY).length;
12037
+ var TIER_1_TOOL_COUNT = Object.values(TOOL_TIER).filter((t) => t === 1).length;
12038
+ var TIER_2_TOOL_COUNT = Object.values(TOOL_TIER).filter((t) => t === 2).length;
12039
+ var TIER_3_TOOL_COUNT = Object.values(TOOL_TIER).filter((t) => t === 3).length;
12034
12040
  function generateInstructions(categories, registry, activeTierCategories) {
12035
12041
  const parts = [];
12036
12042
  const tieringActive = activeTierCategories !== void 0;
@@ -12140,8 +12146,6 @@ Use "get_connection_strength" to measure link strength between two entities.
12140
12146
  Use "get_link_path" to trace the shortest path between any two entities or notes.
12141
12147
  Use "get_strong_connections" to find the strongest or most-connected relationships for an entity.`);
12142
12148
  } else if (tieringActive && categories.has("graph")) {
12143
- parts.push(`
12144
- **More tools available:** Ask about graph connections, backlinks, hubs, clusters, or paths to unlock graph analysis tools.`);
12145
12149
  }
12146
12150
  if (isCategoryVisible("note-ops")) {
12147
12151
  parts.push(`
@@ -12167,8 +12171,6 @@ Use "schema_conventions" to infer frontmatter conventions from folder usage patt
12167
12171
  Use "schema_validate" to validate frontmatter against explicit rules or find notes missing expected fields by folder.
12168
12172
  Use "note_intelligence" for per-note analysis (completeness, quality, suggestions).`);
12169
12173
  } else if (tieringActive && categories.has("schema")) {
12170
- parts.push(`
12171
- **Advanced tools:** Ask to unlock schema tools for conventions, validation, migrations, and bulk metadata analysis.`);
12172
12174
  }
12173
12175
  if (isCategoryVisible("wikilinks")) {
12174
12176
  parts.push(`
@@ -12184,8 +12186,6 @@ Link quality and discovery \u2014 not for finding content (use search for that).
12184
12186
  - "Was that link correct?" \u2192 wikilink_feedback (accept/reject, improves future suggestions)
12185
12187
  - "What aliases am I missing?" \u2192 suggest_entity_aliases (acronyms, short forms, alternate names)`);
12186
12188
  } else if (tieringActive && categories.has("wikilinks")) {
12187
- parts.push(`
12188
- **More tools available:** Ask about wikilinks, suggestions, stubs, or unlinked mentions to unlock wikilink tools.`);
12189
12189
  }
12190
12190
  if (isCategoryVisible("corrections")) {
12191
12191
  parts.push(`
@@ -12198,8 +12198,6 @@ When the user says something is wrong \u2014 a bad link, wrong entity, wrong cat
12198
12198
  "vault_resolve_correction" marks a correction as applied or dismissed.
12199
12199
  Use "absorb_as_alias" when two names should resolve to the same entity without merging note bodies.`);
12200
12200
  } else if (tieringActive && categories.has("corrections")) {
12201
- parts.push(`
12202
- **More tools available:** Ask about errors, wrong links, or fixes to unlock correction tools.`);
12203
12201
  }
12204
12202
  if (isCategoryVisible("temporal")) {
12205
12203
  parts.push(`
@@ -12215,8 +12213,6 @@ Temporal tools analyze *patterns and changes* over time \u2014 use them for "wha
12215
12213
 
12216
12214
  temporal_summary composes the other three \u2014 use it for weekly/monthly reviews.`);
12217
12215
  } else if (tieringActive && categories.has("temporal")) {
12218
- parts.push(`
12219
- **More tools available:** Ask about time, history, evolution, or stale notes to unlock temporal tools.`);
12220
12216
  }
12221
12217
  if (isCategoryVisible("diagnostics")) {
12222
12218
  parts.push(`
@@ -12231,9 +12227,10 @@ temporal_summary composes the other three \u2014 use it for weekly/monthly revie
12231
12227
 
12232
12228
  Use "flywheel_config" to inspect runtime configuration and set "tool_tier_override" to "auto", "full", or "minimal" for this vault.`);
12233
12229
  } else if (tieringActive && categories.has("diagnostics")) {
12230
+ }
12231
+ if (tieringActive) {
12234
12232
  parts.push(`
12235
- **More tools available:** Ask about vault health, indexing, status, or configuration to unlock diagnostic tools.
12236
- **Advanced tools:** Ask to unlock note operations or deep diagnostics for note mutations, benchmarks, history, graph exports, and learning reports.`);
12233
+ **More tools available:** Call \`discover_tools({ query: "your need" })\` to find and activate specialized tools for graph analysis, wikilinks, diagnostics, schema, temporal analysis, note operations, and more. Returns tool names, descriptions, and input schemas.`);
12237
12234
  }
12238
12235
  return parts.join("\n");
12239
12236
  }
@@ -12243,7 +12240,7 @@ import * as path38 from "path";
12243
12240
  import { dirname as dirname5, join as join19 } from "path";
12244
12241
  import { statSync as statSync6, readFileSync as readFileSync5 } from "fs";
12245
12242
  import { fileURLToPath } from "url";
12246
- import { z as z40 } from "zod";
12243
+ import { z as z41 } from "zod";
12247
12244
  import { CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
12248
12245
  import { getSessionId } from "@velvetmonkey/vault-core";
12249
12246
 
@@ -26550,6 +26547,61 @@ function registerCalibrationExportTools(server2, getIndex, getStateDb4, getConfi
26550
26547
  );
26551
26548
  }
26552
26549
 
26550
+ // src/tools/read/discovery.ts
26551
+ import { z as z40 } from "zod";
26552
+ import { normalizeObjectSchema } from "@modelcontextprotocol/sdk/server/zod-compat.js";
26553
+ import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
26554
+ function toJsonSchema(inputSchema) {
26555
+ const obj = normalizeObjectSchema(inputSchema);
26556
+ if (!obj) return { type: "object" };
26557
+ return toJsonSchemaCompat(obj, { strictUnions: true, pipeStrategy: "input" });
26558
+ }
26559
+ function registerDiscoveryTools(server2, controller) {
26560
+ server2.tool(
26561
+ "discover_tools",
26562
+ 'Find and activate specialized tools. Call with what you need \u2014 e.g. "vault health", "graph connections", "schema migration". Returns matching tool names, descriptions, and input schemas. Does not execute discovered tools \u2014 call them separately after discovery.',
26563
+ {
26564
+ query: z40.string().describe(
26565
+ 'Natural language description of what you need \u2014 e.g. "vault health", "backlinks and graph", "schema migration"'
26566
+ )
26567
+ },
26568
+ async ({ query }) => {
26569
+ const signals = unionSignalsByCategory(getPatternSignals(query));
26570
+ const matchedCategories = [];
26571
+ const newlyActivatedCategories = [];
26572
+ for (const { category, tier } of signals) {
26573
+ const wasActive = controller.activeCategories.has(category);
26574
+ matchedCategories.push(category);
26575
+ controller.activateCategory(category, tier);
26576
+ if (!wasActive && controller.activeCategories.has(category)) {
26577
+ newlyActivatedCategories.push(category);
26578
+ }
26579
+ }
26580
+ const tools = [];
26581
+ for (const [name, handle] of controller.getRegisteredTools()) {
26582
+ const cat = TOOL_CATEGORY[name];
26583
+ if (handle.enabled && cat && matchedCategories.includes(cat)) {
26584
+ tools.push({
26585
+ name,
26586
+ description: handle.description ?? "",
26587
+ category: cat,
26588
+ inputSchema: toJsonSchema(handle.inputSchema)
26589
+ });
26590
+ }
26591
+ }
26592
+ const result = {
26593
+ matched_categories: matchedCategories,
26594
+ newly_activated_categories: newlyActivatedCategories,
26595
+ tools,
26596
+ hint: tools.length === 0 ? `No tools matched "${query}". Available categories: ${ALL_CATEGORIES.join(", ")}` : `${tools.length} tools available across ${matchedCategories.join(", ")}`
26597
+ };
26598
+ return {
26599
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
26600
+ };
26601
+ }
26602
+ );
26603
+ }
26604
+
26553
26605
  // src/resources/vault.ts
26554
26606
  function registerVaultResources(server2, getIndex) {
26555
26607
  server2.registerResource(
@@ -26698,6 +26750,14 @@ function getPatternSignals(raw) {
26698
26750
  if (!raw) return [];
26699
26751
  return ACTIVATION_PATTERNS.filter(({ patterns }) => patterns.some((pattern) => pattern.test(raw))).map(({ category, tier }) => ({ category, tier }));
26700
26752
  }
26753
+ function unionSignalsByCategory(signals) {
26754
+ const best = /* @__PURE__ */ new Map();
26755
+ for (const { category, tier } of signals) {
26756
+ const existing = best.get(category);
26757
+ if (!existing || tier > existing) best.set(category, tier);
26758
+ }
26759
+ return Array.from(best.entries()).map(([category, tier]) => ({ category, tier }));
26760
+ }
26701
26761
  async function getActivationSignals(toolName, params, searchMethod, toolTierMode2 = "off") {
26702
26762
  if (toolName !== "search" && toolName !== "brief") return [];
26703
26763
  if (!params || typeof params !== "object") return [];
@@ -26715,14 +26775,7 @@ async function getActivationSignals(toolName, params, searchMethod, toolTierMode
26715
26775
  if (routingMode === "semantic" && searchMethod !== "hybrid") {
26716
26776
  return getPatternSignals(raw);
26717
26777
  }
26718
- const categoryBest = /* @__PURE__ */ new Map();
26719
- for (const { category, tier } of [...patternSignals, ...semanticSignals]) {
26720
- const existing = categoryBest.get(category);
26721
- if (!existing || tier > existing) {
26722
- categoryBest.set(category, tier);
26723
- }
26724
- }
26725
- return Array.from(categoryBest.entries()).map(([category, tier]) => ({ category, tier }));
26778
+ return unionSignalsByCategory([...patternSignals, ...semanticSignals]);
26726
26779
  }
26727
26780
  function extractSearchMethod(result) {
26728
26781
  if (!result || typeof result !== "object") return void 0;
@@ -26759,12 +26812,12 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
26759
26812
  return true;
26760
26813
  }
26761
26814
  function enableCategory(category, tier) {
26762
- if (!categories.has(category)) return;
26815
+ if (!categories.has(category)) return [];
26763
26816
  const previousTier = activatedCategoryTiers.get(category) ?? 0;
26764
26817
  if (tier > previousTier) {
26765
26818
  activatedCategoryTiers.set(category, tier);
26766
26819
  }
26767
- refreshToolVisibility();
26820
+ return refreshToolVisibility();
26768
26821
  }
26769
26822
  function shouldEnableTool(toolName) {
26770
26823
  const tier = TOOL_TIER[toolName];
@@ -26779,26 +26832,29 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
26779
26832
  return activatedTier >= tier;
26780
26833
  }
26781
26834
  function refreshToolVisibility() {
26782
- let changed = false;
26835
+ const newlyEnabled = [];
26783
26836
  for (const [name, handle] of toolHandles) {
26784
26837
  const enabled = shouldEnableTool(name);
26785
26838
  if (enabled !== handle.enabled) {
26786
26839
  handle.enabled = enabled;
26787
- changed = true;
26840
+ if (enabled) newlyEnabled.push(name);
26788
26841
  }
26789
26842
  }
26790
- if (changed) {
26843
+ if (newlyEnabled.length > 0) {
26791
26844
  targetServer.sendToolListChanged();
26792
26845
  }
26793
26846
  if (controllerRef) {
26794
26847
  onTierStateChange?.(controllerRef);
26795
26848
  }
26849
+ return newlyEnabled;
26796
26850
  }
26797
26851
  async function maybeActivateFromContext(toolName, params, searchMethod) {
26798
- if (tierMode !== "tiered" || tierOverride === "full") return;
26852
+ if (tierMode !== "tiered" || tierOverride === "full") return [];
26853
+ const newlyEnabled = [];
26799
26854
  for (const { category, tier } of await getActivationSignals(toolName, params, searchMethod, tierMode)) {
26800
- enableCategory(category, tier);
26855
+ newlyEnabled.push(...enableCategory(category, tier));
26801
26856
  }
26857
+ return newlyEnabled;
26802
26858
  }
26803
26859
  function ensureToolEnabledForDirectCall(toolName) {
26804
26860
  if (tierMode !== "tiered") return;
@@ -26844,7 +26900,14 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
26844
26900
  try {
26845
26901
  result = await handler(...args);
26846
26902
  const searchMethod = extractSearchMethod(result);
26847
- await maybeActivateFromContext(toolName, params, searchMethod);
26903
+ const newlyActivated = await maybeActivateFromContext(toolName, params, searchMethod);
26904
+ if (newlyActivated.length > 0 && result?.content && Array.isArray(result.content)) {
26905
+ result.content.push({
26906
+ type: "text",
26907
+ text: `
26908
+ [Progressive disclosure: ${newlyActivated.length} new tools activated: ${newlyActivated.join(", ")}. Call tools/list to refresh.]`
26909
+ });
26910
+ }
26848
26911
  return result;
26849
26912
  } catch (err) {
26850
26913
  success = false;
@@ -27010,7 +27073,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
27010
27073
  const schemaIdx = handlerIdx - 1;
27011
27074
  const schema = args[schemaIdx];
27012
27075
  if (schema && typeof schema === "object" && !Array.isArray(schema)) {
27013
- schema.vault = z40.string().optional().describe(
27076
+ schema.vault = z41.string().optional().describe(
27014
27077
  `Vault name for multi-vault mode. Available: ${registry.getVaultNames().join(", ")}. Default: ${registry.primaryName}`
27015
27078
  );
27016
27079
  }
@@ -27127,7 +27190,7 @@ function applyToolGating(targetServer, categories, getDb4, registry, getVaultPat
27127
27190
  controllerRef = controller;
27128
27191
  return controller;
27129
27192
  }
27130
- function registerAllTools(targetServer, ctx) {
27193
+ function registerAllTools(targetServer, ctx, controller) {
27131
27194
  const { getVaultPath: gvp, getVaultIndex: gvi, getStateDb: gsd, getFlywheelConfig: gcf } = ctx;
27132
27195
  registerHealthTools(targetServer, gvi, gvp, gcf, gsd, ctx.getWatcherStatus, () => trPkg.version, ctx.getPipelineActivity);
27133
27196
  registerSystemTools(
@@ -27220,6 +27283,9 @@ function registerAllTools(targetServer, ctx) {
27220
27283
  registerCalibrationExportTools(targetServer, gvi, gsd, gcf);
27221
27284
  registerMemoryTools(targetServer, gsd);
27222
27285
  registerBriefTools(targetServer, gsd);
27286
+ if (controller) {
27287
+ registerDiscoveryTools(targetServer, controller);
27288
+ }
27223
27289
  registerVaultResources(targetServer, () => gvi() ?? null);
27224
27290
  }
27225
27291
 
@@ -27305,7 +27371,7 @@ function createConfiguredServer() {
27305
27371
  toolTierMode,
27306
27372
  handleTierStateChange
27307
27373
  );
27308
- registerAllTools(s, ctx);
27374
+ registerAllTools(s, ctx, toolTierController);
27309
27375
  toolTierController.setOverride(runtimeToolTierOverride);
27310
27376
  for (const [category, tier] of runtimeActiveCategoryTiers) {
27311
27377
  toolTierController.activateCategory(category, tier);
@@ -27358,7 +27424,7 @@ var _gatingResult = applyToolGating(
27358
27424
  toolTierMode,
27359
27425
  handleTierStateChange
27360
27426
  );
27361
- registerAllTools(server, _registryCtx);
27427
+ registerAllTools(server, _registryCtx, _gatingResult);
27362
27428
  _gatingResult.setOverride(runtimeToolTierOverride);
27363
27429
  _gatingResult.finalizeRegistration();
27364
27430
  primaryToolTierController = _gatingResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "MCP tools that search, write, and auto-link your Obsidian vault — and learn from your edits.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@huggingface/transformers": "^3.8.1",
57
57
  "@modelcontextprotocol/sdk": "^1.25.1",
58
- "@velvetmonkey/vault-core": "^2.3.1",
58
+ "@velvetmonkey/vault-core": "^2.3.3",
59
59
  "better-sqlite3": "^12.0.0",
60
60
  "chokidar": "^4.0.0",
61
61
  "gray-matter": "^4.0.3",