/** * Node Search — term matching and ranking for `search_nodes` * * The original matcher lowercased the WHOLE query and asked whether that one * literal string appeared inside `name` or `description`. That meant a person * typing the words in any order, or typing words that live in different fields, * got nothing back — while the node sat in the graph. Measured 2026-08-02 * against the live wheel: * * search_nodes("veritas") → 2 (literal phrase present) * search_nodes("MMOT review surface") → 1 (contiguous in description) * search_nodes("veritas stc") → 0 ← both words in the NAME * search_nodes("veritas stc surface service") → 0 ← all four words present * search_nodes("ServiceFacet") → 0 ← metadata was never read * * Registering infrastructure in the wheel is only worth doing if it can be found * again. So the query is now split into terms and each node is scored across * name + description + metadata values. * * The matching contract, stated so it cannot drift: * * 1. **Primary tier — every term must be found.** A node is returned when all * of the query's terms appear somewhere in its searchable text. This is AND * *across* fields rather than AND *within one* field, which is the whole * defect: "veritas stc" (name) + "service" (description/metadata) is one * node, not three failed lookups. * 2. **Fallback tier — only when the primary tier is empty.** Nodes matching at * least one term are returned, ranked. This keeps one mistyped or extra word * from turning a good query into a void, without flooding: the fallback is * never mixed into a result set that already has full matches. * 3. **A query whose terms match nothing returns nothing.** There is no * "closest node" consolation prize — a search that always answers is a * search that cannot be trusted. * * Ranking puts the strongest evidence first: an exact name, then the literal * phrase in the name, then per-term hits weighted by field (name > description > * metadata) with whole-word hits beating substrings. Ties break on recency so * the order is stable rather than insertion-dependent. * * @see mcp/src/tools/discovery.ts — the `search_nodes` tool * @see mcp/src/http-store.ts — server-backed store (the live wheel path) * @see mcp/src/jsonl-store.ts — local JSONL store */ /** The shape `search_nodes` ranks. Structural only — both stores satisfy it. */ export interface SearchableNode { id: string; name: string; description?: string; metadata?: Record; created_at?: string; updated_at?: string; } /** * Split text into comparable terms. * * Anything that is not a letter or digit separates. That is what lets * "veritas stc surface" reach `veritas-stc-surface`, and what lets a pasted * identifier be typed back as plain words. */ export declare function tokenize(text: string): string[]; /** * Match and rank nodes against a natural multi-word query. * * Returns best-first. An empty/whitespace-only query returns nothing rather * than the entire graph — asking nothing is not asking for everything. */ export declare function rankNodes(nodes: T[], query: string): T[]; //# sourceMappingURL=node-search.d.ts.map