/** * Scoring helpers for `eddie_compose_recipe` (issue #1002). * * Extracted from the inline closure in `server.ts` so the ranking rules are * unit-testable. Two behaviors live here: * * 1. **Negation-aware term scoring.** A query term that only appears inside * a negative clause of an entry's intent must not count as a hit. * The motivating bug: `ed-p-marketing-homepage` ("… without a full site * header/footer …") ranked FIRST for the query "a website page with a * site header, primary navigation, main content, and site footer", * beating the templates that actually demonstrate the shell. * * 2. **Page-level intent detection.** "Make a website" must assume the site * shell (site header + main + site footer). When the query is page-level, * the compose_recipe response leads with an explicit shell recommendation * instead of hoping the consumer scrolls to `recipeComponents`. */ import { type TermWeights } from '../text/term-weights.js'; export { queryTerms, STOPWORDS } from '../text/term-match.js'; /** * Count occurrences of `term` in `text` that are NOT inside a negative * clause. Case-insensitive; `text` and `term` are lowercased internally. */ export declare function countPositiveOccurrences(text: string, term: string): number; export interface ScorableEntry { tagName: string; intent: string; displayName: string; /** Selection criteria (#1888), when the entry carries them. */ guidelines?: { useWhen?: string[]; notWhen?: string[]; }; } /** * Score a catalog entry against the query terms. Tag-name matches score * highest (exact identifier, never carries negation), then the intent prose * (negation-aware), then the human display name. Every comparison is on whole * words (#2062); only the tag name keeps partial matching, from the front of a * tag word (see `tagMatches`). * * Pass `weights` (#2080) and each term's contribution — hits and penalty alike — * is multiplied by how rare the word is in the catalog, so an ask is decided by * the words that tell components apart rather than the ones they all share. * Without it every word counts the same, which is what these rules' own unit * tests rely on. */ export declare function scoreEntry(entry: ScorableEntry, terms: string[], weights?: TermWeights): number; /** * Atomic-level tiebreak for compose results: a higher-level component is the * more useful answer to "what do I compose this from?". */ export declare const LEVEL_ORDER: Record; /** * The ordering `eddie_compose_recipe` shows: matching entries only, best score * first, ties broken by atomic level. Exported (#2060) so the tool and the * routing benchmark use one sort instead of the benchmark keeping a copy. */ export declare function rankForCompose(entries: T[], terms: string[], weights?: TermWeights): T[]; /** True when the query intent is page-level rather than component-level. */ export declare function isPageLevelIntent(terms: string[]): boolean; /** * The shell recommendation block that leads every page-level * compose_recipe response. "Make a website" assumes header + footer. */ export declare const SITE_SHELL_RECOMMENDATION: { readonly recommendation: string; readonly components: readonly ["ed-page", "ed-r-site-header", "ed-main", "ed-r-site-footer"]; readonly recipe: "site-shell"; }; //# sourceMappingURL=compose-scoring.d.ts.map