neuro-vault-mcp 8.2.0 → 9.0.0

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/cli.d.ts CHANGED
@@ -88,7 +88,6 @@ interface BlockSearchResult {
88
88
  interface SearchResult {
89
89
  path: string;
90
90
  similarity: number;
91
- via_expansion?: true;
92
91
  }
93
92
  interface DuplicatePair {
94
93
  note_a: string;
package/dist/cli.js CHANGED
@@ -15896,28 +15896,26 @@ var MODE_DEFAULTS = {
15896
15896
  quick: { limit: 3, threshold: 0.5, expansion: false, expansionLimit: 0 },
15897
15897
  deep: { limit: 8, threshold: 0.35, expansion: true, expansionLimit: 3 }
15898
15898
  };
15899
- function computeExpansion(args) {
15900
- const { seeds, sources, searchEngine, threshold, perSeedLimit, totalLimit } = args;
15901
- const seedPaths = new Set(seeds.map((s) => s.path));
15902
- const bestByPath = /* @__PURE__ */ new Map();
15903
- for (const seed of seeds) {
15904
- const source = sources.get(seed.path);
15905
- if (!source || source.embedding.length === 0) continue;
15906
- const neighbors = searchEngine.findNeighbors({
15899
+ function computeRelatedPerSeed(args) {
15900
+ const { seedPaths, sources, searchEngine, threshold, perSeedLimit } = args;
15901
+ const seedSet = new Set(seedPaths);
15902
+ const out = /* @__PURE__ */ new Map();
15903
+ for (const seedPath of seedPaths) {
15904
+ const source = sources.get(seedPath);
15905
+ if (!source || source.embedding.length === 0) {
15906
+ out.set(seedPath, []);
15907
+ continue;
15908
+ }
15909
+ const neighbours = searchEngine.findNeighbors({
15907
15910
  queryVector: source.embedding,
15908
15911
  sources: sources.values(),
15909
15912
  threshold,
15910
- limit: perSeedLimit
15913
+ limit: perSeedLimit + seedSet.size
15911
15914
  });
15912
- for (const n of neighbors) {
15913
- if (seedPaths.has(n.path)) continue;
15914
- const cur = bestByPath.get(n.path);
15915
- if (cur === void 0 || n.similarity > cur) {
15916
- bestByPath.set(n.path, n.similarity);
15917
- }
15918
- }
15915
+ const related = neighbours.filter((n) => !seedSet.has(n.path)).sort((a, b) => b.similarity - a.similarity || a.path.localeCompare(b.path)).slice(0, perSeedLimit).map((n) => ({ path: n.path, expansion_similarity: n.similarity }));
15916
+ out.set(seedPath, related);
15919
15917
  }
15920
- return [...bestByPath.entries()].map(([path10, similarity]) => ({ path: path10, similarity, via_expansion: true })).sort((a, b) => b.similarity - a.similarity || a.path.localeCompare(b.path)).slice(0, totalLimit);
15918
+ return out;
15921
15919
  }
15922
15920
  async function executeRetrieval(input) {
15923
15921
  const { query, mode, sources, embeddingProvider, searchEngine } = input;
@@ -15941,40 +15939,50 @@ async function executeRetrieval(input) {
15941
15939
  limit
15942
15940
  });
15943
15941
  }
15944
- let blockResults;
15945
- if (mode === "deep") {
15946
- blockResults = searchEngine.findBlockNeighbors({
15942
+ const seeds = vectorResults.slice(0, limit);
15943
+ const seedPaths = seeds.map((s) => s.path);
15944
+ const seedPathSet = new Set(seedPaths);
15945
+ const blocksByPath = /* @__PURE__ */ new Map();
15946
+ if (seeds.length > 0) {
15947
+ const seedSources = [...sources.values()].filter((s) => seedPathSet.has(s.path));
15948
+ const rawBlocks = mode === "deep" ? searchEngine.findBlockNeighbors({
15947
15949
  queryVector,
15948
- sources: sources.values(),
15950
+ sources: seedSources,
15949
15951
  threshold,
15950
15952
  limit
15951
- });
15952
- } else if (vectorResults.length > 0) {
15953
- const matchedPaths = new Set(vectorResults.map((r) => r.path));
15954
- const matchedSources = [...sources.values()].filter((s) => matchedPaths.has(s.path));
15955
- blockResults = searchEngine.findBlockNeighbors({
15953
+ }) : searchEngine.findBlockNeighbors({
15956
15954
  queryVector,
15957
- sources: matchedSources,
15955
+ sources: seedSources,
15958
15956
  threshold: 0,
15959
15957
  limit: QUICK_BLOCK_LIMIT
15960
15958
  });
15959
+ for (const block of rawBlocks) {
15960
+ if (!seedPathSet.has(block.path)) continue;
15961
+ const bucket = blocksByPath.get(block.path) ?? [];
15962
+ bucket.push({ heading: block.heading, lines: block.lines, similarity: block.similarity });
15963
+ blocksByPath.set(block.path, bucket);
15964
+ }
15965
+ for (const bucket of blocksByPath.values()) {
15966
+ bucket.sort((a, b) => b.similarity - a.similarity || a.lines[0] - b.lines[0]);
15967
+ }
15961
15968
  }
15962
- const cappedSeeds = vectorResults.slice(0, limit);
15963
- let expansionResults = [];
15964
- if (expansion && expansionLimit > 0 && cappedSeeds.length > 0) {
15965
- expansionResults = computeExpansion({
15966
- seeds: cappedSeeds,
15969
+ let relatedByPath = /* @__PURE__ */ new Map();
15970
+ if (expansion && expansionLimit > 0 && seeds.length > 0) {
15971
+ relatedByPath = computeRelatedPerSeed({
15972
+ seedPaths,
15967
15973
  sources,
15968
15974
  searchEngine,
15969
15975
  threshold,
15970
- perSeedLimit: limit,
15971
- totalLimit: expansionLimit
15976
+ perSeedLimit: expansionLimit
15972
15977
  });
15973
15978
  }
15974
- return {
15975
- results: [...cappedSeeds, ...expansionResults],
15976
- ...blockResults !== void 0 ? { blockResults } : {}
15977
- };
15979
+ const results = seeds.map((seed) => ({
15980
+ path: seed.path,
15981
+ similarity: seed.similarity,
15982
+ blocks: blocksByPath.get(seed.path) ?? [],
15983
+ related: relatedByPath.get(seed.path) ?? []
15984
+ }));
15985
+ return { results };
15978
15986
  }
15979
15987
  function mergeNoteResults(perQuery) {
15980
15988
  const byPath = /* @__PURE__ */ new Map();
@@ -15992,7 +16000,6 @@ function mergeNoteResults(perQuery) {
15992
16000
  if (result.similarity > existing.similarity) {
15993
16001
  existing.similarity = result.similarity;
15994
16002
  }
15995
- existing.matched_queries ??= [];
15996
16003
  if (!existing.matched_queries.includes(query)) {
15997
16004
  existing.matched_queries.push(query);
15998
16005
  }
@@ -16002,34 +16009,6 @@ function mergeNoteResults(perQuery) {
16002
16009
  (a, b) => b.similarity - a.similarity || a.path.localeCompare(b.path)
16003
16010
  );
16004
16011
  }
16005
- function mergeBlockResults(perQuery) {
16006
- const byKey = /* @__PURE__ */ new Map();
16007
- for (const { query, blocks } of perQuery) {
16008
- for (const block of blocks) {
16009
- const key = `${block.path}\0${block.heading}\0${block.lines[0]}-${block.lines[1]}`;
16010
- const existing = byKey.get(key);
16011
- if (!existing) {
16012
- byKey.set(key, {
16013
- path: block.path,
16014
- heading: block.heading,
16015
- lines: block.lines,
16016
- similarity: block.similarity,
16017
- matched_queries: [query]
16018
- });
16019
- continue;
16020
- }
16021
- if (block.similarity > existing.similarity) {
16022
- existing.similarity = block.similarity;
16023
- }
16024
- if (!existing.matched_queries.includes(query)) {
16025
- existing.matched_queries.push(query);
16026
- }
16027
- }
16028
- }
16029
- return [...byKey.values()].sort(
16030
- (a, b) => b.similarity - a.similarity || a.path.localeCompare(b.path)
16031
- );
16032
- }
16033
16012
  async function executeMultiRetrieval(input) {
16034
16013
  const { queries, mode, sources, embeddingProvider, searchEngine } = input;
16035
16014
  const modeConfig = MODE_DEFAULTS[mode];
@@ -16054,57 +16033,77 @@ async function executeMultiRetrieval(input) {
16054
16033
  limit
16055
16034
  });
16056
16035
  }
16057
- let blocks = [];
16058
- if (mode === "deep") {
16059
- blocks = searchEngine.findBlockNeighbors({
16060
- queryVector,
16061
- sources: sources.values(),
16062
- threshold,
16063
- limit
16064
- });
16065
- } else if (neighbors.length > 0) {
16066
- const matched = new Set(neighbors.map((r) => r.path));
16067
- const matchedSources = [...sources.values()].filter((s) => matched.has(s.path));
16068
- blocks = searchEngine.findBlockNeighbors({
16069
- queryVector,
16070
- sources: matchedSources,
16071
- threshold: 0,
16072
- limit: QUICK_BLOCK_LIMIT
16073
- });
16074
- }
16075
- return { query, neighbors, blocks };
16036
+ return { query, queryVector, neighbors };
16076
16037
  })
16077
16038
  );
16078
- const mergedNotes = mergeNoteResults(
16039
+ const merged = mergeNoteResults(
16079
16040
  perQueryOutputs.map(({ query, neighbors }) => ({ query, results: neighbors }))
16080
16041
  );
16081
- const anyBlocks = perQueryOutputs.some(({ blocks }) => blocks.length > 0);
16082
- const mergedBlocks = anyBlocks ? mergeBlockResults(perQueryOutputs.map(({ query, blocks }) => ({ query, blocks }))) : void 0;
16083
- const truncated = mergedNotes.length > limit || (mergedBlocks?.length ?? 0) > limit;
16084
- const cappedNotes = mergedNotes.slice(0, limit);
16085
- const cappedBlocks = mergedBlocks ? mergedBlocks.slice(0, limit) : void 0;
16086
- let expansionResults = [];
16087
- if (expansion && expansionLimit > 0 && cappedNotes.length > 0) {
16088
- const seeds = cappedNotes.map((n) => ({ path: n.path, similarity: n.similarity }));
16089
- const raw = computeExpansion({
16090
- seeds,
16042
+ const truncated = merged.length > limit;
16043
+ const seeds = merged.slice(0, limit);
16044
+ const seedPaths = seeds.map((s) => s.path);
16045
+ const seedPathSet = new Set(seedPaths);
16046
+ const blocksByPath = /* @__PURE__ */ new Map();
16047
+ if (seeds.length > 0) {
16048
+ const seedSources = [...sources.values()].filter((s) => seedPathSet.has(s.path));
16049
+ const rawByKey = /* @__PURE__ */ new Map();
16050
+ for (const { queryVector } of perQueryOutputs) {
16051
+ const raw = mode === "deep" ? searchEngine.findBlockNeighbors({
16052
+ queryVector,
16053
+ sources: seedSources,
16054
+ threshold,
16055
+ limit
16056
+ }) : searchEngine.findBlockNeighbors({
16057
+ queryVector,
16058
+ sources: seedSources,
16059
+ threshold: 0,
16060
+ limit: QUICK_BLOCK_LIMIT
16061
+ });
16062
+ for (const block of raw) {
16063
+ if (!seedPathSet.has(block.path)) continue;
16064
+ const key = `${block.path}\0${block.heading}\0${block.lines[0]}-${block.lines[1]}`;
16065
+ const existing = rawByKey.get(key);
16066
+ if (!existing || block.similarity > existing.similarity) {
16067
+ rawByKey.set(key, {
16068
+ path: block.path,
16069
+ heading: block.heading,
16070
+ lines: block.lines,
16071
+ similarity: block.similarity
16072
+ });
16073
+ }
16074
+ }
16075
+ }
16076
+ for (const block of rawByKey.values()) {
16077
+ const bucket = blocksByPath.get(block.path) ?? [];
16078
+ bucket.push({
16079
+ heading: block.heading,
16080
+ lines: block.lines,
16081
+ similarity: block.similarity
16082
+ });
16083
+ blocksByPath.set(block.path, bucket);
16084
+ }
16085
+ for (const bucket of blocksByPath.values()) {
16086
+ bucket.sort((a, b) => b.similarity - a.similarity || a.lines[0] - b.lines[0]);
16087
+ }
16088
+ }
16089
+ let relatedByPath = /* @__PURE__ */ new Map();
16090
+ if (expansion && expansionLimit > 0 && seeds.length > 0) {
16091
+ relatedByPath = computeRelatedPerSeed({
16092
+ seedPaths,
16091
16093
  sources,
16092
16094
  searchEngine,
16093
16095
  threshold,
16094
- perSeedLimit: limit,
16095
- totalLimit: expansionLimit
16096
+ perSeedLimit: expansionLimit
16096
16097
  });
16097
- expansionResults = raw.map((r) => ({
16098
- path: r.path,
16099
- similarity: r.similarity,
16100
- via_expansion: true
16101
- }));
16102
16098
  }
16103
- return {
16104
- results: [...cappedNotes, ...expansionResults],
16105
- ...cappedBlocks !== void 0 ? { blockResults: cappedBlocks } : {},
16106
- truncated
16107
- };
16099
+ const results = seeds.map((seed) => ({
16100
+ path: seed.path,
16101
+ similarity: seed.similarity,
16102
+ matched_queries: seed.matched_queries,
16103
+ blocks: blocksByPath.get(seed.path) ?? [],
16104
+ related: relatedByPath.get(seed.path) ?? []
16105
+ }));
16106
+ return { results, truncated };
16108
16107
  }
16109
16108
 
16110
16109
  // src/modules/semantic/tools/search-notes.ts
@@ -16185,12 +16184,7 @@ async function runSearchForEntry(entry, input, deps) {
16185
16184
  }
16186
16185
  if (allowed.size === 0) {
16187
16186
  const isMulti = Array.isArray(input.query);
16188
- const isDeep = mode === "deep";
16189
- return {
16190
- results: [],
16191
- ...isDeep ? { blockResults: [] } : {},
16192
- ...isMulti ? { truncated: false } : {}
16193
- };
16187
+ return isMulti ? { results: [], truncated: false } : { results: [] };
16194
16188
  }
16195
16189
  effectiveSources = narrowSources(sources, allowed);
16196
16190
  }
@@ -16208,23 +16202,19 @@ async function runSearchForEntry(entry, input, deps) {
16208
16202
  });
16209
16203
  const candidatePaths = [
16210
16204
  ...output.results.map((r) => r.path),
16211
- ...output.blockResults?.map((b) => b.path) ?? []
16205
+ ...output.results.flatMap((r) => r.related.map((rel) => rel.path))
16212
16206
  ];
16213
16207
  const [existing] = await Promise.all([
16214
16208
  buildExistingPathSet2(entry, candidatePaths),
16215
16209
  graph.ensureFresh()
16216
16210
  ]);
16217
- return {
16218
- results: output.results.filter((r) => existing.has(r.path)).map((r) => ({
16219
- ...r,
16220
- backlink_count: graph.getBacklinkCount(r.path),
16221
- vault: entry.name
16222
- })),
16223
- ...output.blockResults !== void 0 ? {
16224
- blockResults: output.blockResults.filter((b) => existing.has(b.path)).map((b) => ({ ...b, vault: entry.name }))
16225
- } : {},
16226
- truncated: output.truncated
16227
- };
16211
+ const enriched = output.results.filter((r) => existing.has(r.path)).map((r) => ({
16212
+ ...r,
16213
+ related: r.related.filter((rel) => existing.has(rel.path)),
16214
+ backlink_count: graph.getBacklinkCount(r.path),
16215
+ vault: entry.name
16216
+ }));
16217
+ return { results: enriched, truncated: output.truncated };
16228
16218
  } catch (error48) {
16229
16219
  throw wrapDependencyError2(error48, "Failed to search notes", {
16230
16220
  modelKey,
@@ -16245,22 +16235,19 @@ async function runSearchForEntry(entry, input, deps) {
16245
16235
  });
16246
16236
  const candidatePaths = [
16247
16237
  ...output.results.map((r) => r.path),
16248
- ...output.blockResults?.map((b) => b.path) ?? []
16238
+ ...output.results.flatMap((r) => r.related.map((rel) => rel.path))
16249
16239
  ];
16250
16240
  const [existing] = await Promise.all([
16251
16241
  buildExistingPathSet2(entry, candidatePaths),
16252
16242
  graph.ensureFresh()
16253
16243
  ]);
16254
- return {
16255
- results: output.results.filter((r) => existing.has(r.path)).map((r) => ({
16256
- ...r,
16257
- backlink_count: graph.getBacklinkCount(r.path),
16258
- vault: entry.name
16259
- })),
16260
- ...output.blockResults !== void 0 ? {
16261
- blockResults: output.blockResults.filter((b) => existing.has(b.path)).map((b) => ({ ...b, vault: entry.name }))
16262
- } : {}
16263
- };
16244
+ const enriched = output.results.filter((r) => existing.has(r.path)).map((r) => ({
16245
+ ...r,
16246
+ related: r.related.filter((rel) => existing.has(rel.path)),
16247
+ backlink_count: graph.getBacklinkCount(r.path),
16248
+ vault: entry.name
16249
+ }));
16250
+ return { results: enriched };
16264
16251
  } catch (error48) {
16265
16252
  throw wrapDependencyError2(error48, "Failed to search notes", {
16266
16253
  modelKey,
@@ -16283,36 +16270,45 @@ function buildSearchNotesTool(deps) {
16283
16270
  "Search notes by semantic similarity. Best for fuzzy recall, topic exploration, or cross-language matches. Pass short keyword queries (1-4 words), not sentences.",
16284
16271
  "",
16285
16272
  "MODES (pick based on intent):",
16286
- '- "quick" (default) \u2014 specific lookup. Returns up to 3 top notes plus block-level matches scoped to those notes. Use when you want one or two specific notes.',
16287
- '- "deep" \u2014 topic exploration. Returns up to 8 notes plus block-level matches across the whole vault. After the merged top-`limit` results are selected, expansion runs once to pull in semantically related notes; expansion-derived results carry `via_expansion: true`. Use for "tell me about X" or building an overview.',
16273
+ '- "quick" (default) \u2014 specific lookup. Returns up to 3 notes; each note carries `blocks[]` scoped to itself and `related: []`. Use when you want one or two specific notes.',
16274
+ '- "deep" \u2014 topic exploration. Returns up to 8 notes; each note carries `blocks[]` (scoped to itself) and `related[]` (semantically neighbouring notes). Use for "tell me about X" or building an overview.',
16288
16275
  "",
16289
16276
  "PARAMETERS:",
16290
- "- query (required): string, or array of 1-8 strings. Pass an array for synonyms / reformulations / translations \u2014 embedded in batch, merged into one ranked list. Each result carries `matched_queries` (which of your queries hit it). `limit` is the FINAL result count regardless of how many queries are passed \u2014 passing more queries widens coverage but does not increase the result count.",
16277
+ "- query (required): string, or array of 1-8 strings. Pass an array for synonyms / reformulations / translations \u2014 embedded in batch, merged into one ranked list. Each result carries `matched_queries` (which of your queries hit it). `limit` is the FINAL result count regardless of how many queries are passed.",
16291
16278
  '- mode: "quick" | "deep" (default "quick").',
16292
- "- limit: max notes in `results`. Default 3 (quick) / 8 (deep). Override to widen or narrow the result set. Does not affect `blockResults` (quick: capped at 5; deep: capped at mode limit).",
16279
+ "- limit: max notes in `results`. Default 3 (quick) / 8 (deep). Does not affect leaves (`blocks` / `related`).",
16293
16280
  "- threshold: min similarity, 0-1. Default 0.5 (quick) / 0.35 (deep). Raise to 0.6+ to cut weak matches; lower (e.g. 0.3) when nothing comes back.",
16294
16281
  ...registry2.isMulti() ? ["- vault: target a specific vault by name when multiple are registered."] : [],
16295
16282
  "",
16296
- "OUTPUT FIELDS (multi-query):",
16297
- "- matched_queries: which of your queries surfaced this result \u2014 tells you which synonym was load-bearing.",
16298
- "- truncated: true when more unique candidates were merged than fit in `limit`.",
16299
- "- via_expansion: true on results pulled in by post-merge expansion in deep mode (mutually exclusive with matched_queries).",
16283
+ "RESPONSE SHAPE (tree, depth = 2):",
16284
+ "- `results[]` \u2014 top-level direct note matches, ranked by query similarity.",
16285
+ " - `path`, `similarity` (query-similarity), `backlink_count`, `vault`",
16286
+ " - `matched_queries` (only when `query` is an array) \u2014 which of your queries hit this note",
16287
+ " - `blocks[]` \u2014 section-level matches WITHIN this note. Each block has `heading`, `lines: [start, end]`, `similarity` (query-similarity at block level).",
16288
+ " - `related[]` \u2014 expansion neighbours OF this note (deep mode only; empty otherwise). Each item has `path` and `expansion_similarity` (note-to-note, DIFFERENT SCALE from `similarity` \u2014 do not compare them numerically).",
16289
+ "- `truncated` \u2014 top-level, only when `query` is an array; true when merged candidates exceeded `limit`.",
16290
+ "",
16291
+ "INVARIANTS:",
16292
+ "- A `related` item NEVER has a `similarity` field \u2014 only `expansion_similarity`. A direct result NEVER has `expansion_similarity`. Don't conflate them.",
16293
+ "- The same neighbour can appear in `related[]` of multiple direct results (with different `expansion_similarity` values per parent) \u2014 that's by design.",
16294
+ "- `blocks[]` and `related[]` are always present on direct results (possibly empty).",
16295
+ "- After finding a relevant note, call `get_similar_notes` on it for a deeper neighbour profile \u2014 do not infer relationships from `related[]` alone.",
16300
16296
  "",
16301
16297
  "EXAMPLES:",
16302
16298
  '- "where did I write about X?" \u2192 search_notes({query: "X"}) \u2014 quick.',
16303
16299
  '- "what do I know about Y?" \u2192 search_notes({query: "Y", mode: "deep"}).',
16304
- '- multilingual pair: search_notes({query: ["embeddings", "\u0432\u0435\u043A\u0442\u043E\u0440\u043D\u0438\u0439 \u043F\u043E\u0448\u0443\u043A"]}) \u2014 returns one merged list; notes matched by both queries appear with both in matched_queries.',
16305
- '- multilingual deep: search_notes({query: ["optimization", "\u043E\u043F\u0442\u0438\u043C\u0456\u0437\u0430\u0446\u0456\u044F"], mode: "deep"}) \u2014 merged top-`limit` seeds, then expansion once on the merged set.',
16300
+ '- multilingual pair: search_notes({query: ["embeddings", "\u0432\u0435\u043A\u0442\u043E\u0440\u043D\u0438\u0439 \u043F\u043E\u0448\u0443\u043A"]}).',
16301
+ '- multilingual deep: search_notes({query: ["optimization", "\u043E\u043F\u0442\u0438\u043C\u0456\u0437\u0430\u0446\u0456\u044F"], mode: "deep"}).',
16306
16302
  "",
16307
16303
  "PRE-FILTER (filter parameter):",
16308
16304
  "- filter: optional structural narrowing applied BEFORE semantic ranking. Best when vault has many narrative notes that crowd top-K on a niche query.",
16309
16305
  " Shape: { path_prefix?, exclude_path_prefix?, tags?, frontmatter? }. At least one field required.",
16310
16306
  ' - path_prefix: scope to a folder, or array of folders for OR-semantics (e.g. ["Tasks/", "Reflections/"]).',
16311
- ' - exclude_path_prefix: drop notes whose path starts with any of the listed prefixes (e.g. ["Resources/", "Archive/"]). Valid as the sole filter field \u2014 "search the whole vault except these subtrees".',
16307
+ ' - exclude_path_prefix: drop notes whose path starts with any of the listed prefixes (e.g. ["Resources/", "Archive/"]). Valid as the sole filter field.',
16312
16308
  ' - tags: notes that have ANY of these tags (OR within the array; no leading "#").',
16313
16309
  ' - frontmatter: sift filter on frontmatter keys (e.g. { type: "reflection", status: "active" }). Same operator allow-list as query_notes.',
16314
- " Composition: include \u2192 exclude \u2192 tags \u2192 frontmatter \u2192 threshold \u2192 semantic. Use this instead of querying twice and intersecting on the client.",
16315
- '- scoped recall: search_notes({query: "trading lessons", filter: {tags: ["trading"]}}) \u2014 semantic only inside notes tagged trading.',
16310
+ " Composition: include \u2192 exclude \u2192 tags \u2192 frontmatter \u2192 threshold \u2192 semantic.",
16311
+ '- scoped recall: search_notes({query: "trading lessons", filter: {tags: ["trading"]}}).',
16316
16312
  '- carve out noise: search_notes({query: "active thinking", filter: {exclude_path_prefix: ["Resources/", "Archive/"]}, mode: "deep"}).',
16317
16313
  '- scoped multi-query: search_notes({query: ["embeddings","\u0432\u0435\u043A\u0442\u043E\u0440\u043D\u0438\u0439 \u043F\u043E\u0448\u0443\u043A"], filter: {path_prefix: ["Resources/", "Inbox/"]}, mode: "deep"}).',
16318
16314
  ...registry2.isMulti() ? [
@@ -17613,12 +17609,12 @@ If the query is about a known folder, tag, or frontmatter dimension, pass \`filt
17613
17609
  - Use \`limit\` to override the default note count in either mode. Widening \`limit\` widens recall.
17614
17610
 
17615
17611
  ### 3. Use the results
17616
- - \`results\` \u2014 notes ranked by similarity; read the file by path. Each result also carries \`backlink_count\` (vault-wide inbound wikilinks + embeds) \u2014 useful as a tiebreaker when several results share a similar similarity score.
17617
- - \`matched_queries\` (only when \`query\` is an array) \u2014 which of your queries hit this note; lets you spot which synonym was load-bearing
17618
- - \`truncated\` (only when \`query\` is an array) \u2014 true when unique merged candidates exceeded \`limit\`; widen \`limit\` to see more
17619
- - \`via_expansion: true\` (deep mode only) \u2014 marks results pulled in by post-merge expansion; these have no \`matched_queries\`
17620
- - \`blockResults\` \u2014 sections ranked by relevance; use heading + line range to jump to the relevant part
17621
- - After finding a relevant note, call get_similar_notes to discover related content
17612
+ - \`results\` \u2014 top-level notes ranked by similarity. Each result carries \`backlink_count\` (vault-wide inbound wikilinks + embeds) \u2014 useful as a tiebreaker when scores are close.
17613
+ - \`results[].blocks\` \u2014 section-level matches inside this note; use heading + line range to jump to the relevant part. Always present (possibly empty).
17614
+ - \`results[].related\` \u2014 expansion neighbours of this note (deep mode only). Each item has \`path\` and \`expansion_similarity\` \u2014 a DIFFERENT scale from \`similarity\`; do not compare them numerically. Always present (possibly empty). The same neighbour may appear under multiple parents.
17615
+ - \`matched_queries\` (only when \`query\` is an array) \u2014 which of your queries hit this note; lets you spot which synonym was load-bearing.
17616
+ - \`truncated\` (only when \`query\` is an array) \u2014 true when unique merged candidates exceeded \`limit\`; widen \`limit\` to see more.
17617
+ - After finding a relevant note, call \`get_similar_notes\` for a deeper neighbour profile.
17622
17618
 
17623
17619
  ## Routing between operations and semantic
17624
17620