/** * skillIntent — the intent side of the turn-start routing cascade (SG-C): * candidate projection, duplicate-example normalization, the graph's * `TurnRoutingPlan` (what the agent's RouteTurn stage consumes), and the * leave-one-out `checkupIntents` audit. * * One module owns how a declared `{ intent, examples }` becomes a scoreable * {@link IntentCandidate}, so the router, the menu, the audit and the record * can never project the same declaration differently. Pure over the entry * declarations `skillGraph.ts` hands it; imports no engine machinery beyond * the injection-context type. */ import type { InjectionContext } from './types.js'; import type { RouteWitness, SkillMatchData } from './skillMatch.js'; import type { IntentCandidate, IntentScorer } from './intentScorer.js'; import { type RoutingPolicy } from './routingPolicy.js'; import type { GraphCheckup, GraphProblem } from './skillGraphCheckup.js'; /** The slice of an entry declaration this module reads — structural, so * `skillGraph.ts`'s module-private `EntryDecl` satisfies it unchanged. */ export interface IntentEntryDecl { readonly id: string; readonly when?: (ctx: InjectionContext) => boolean; readonly match?: SkillMatchData; /** The data matcher's EVIDENCE extractor (9.28.0), compiled beside `when` — * called only on the rule that wins the turn. Absent for a `when` rule. */ readonly witness?: (ctx: InjectionContext) => RouteWitness | undefined; } /** * The graph's turn-routing surface — everything the agent's RouteTurn stage * needs from the graph, projected ONCE at build. Present on every flat graph * (a decision `tree()` routes by predicate and has no turn start to route); * `scorer` is present only when `.classify()` / `start.classify` configured. */ export interface TurnRoutingPlan { /** The configured classifier — tier 2 over declared intents. */ readonly scorer?: IntentScorer; /** The resolved tie policy the verdicts are judged under (recorded). */ readonly policy: RoutingPolicy; /** Every entry id, declaration order — the `unmatched` menu. */ readonly entryIds: readonly string[]; /** Tier 1: the first NON-intent conditional entry whose predicate passes, * declaration order — its id, plus the `witness` evidence when the rule was * DATA and it yielded quotable text (9.28.0; a `when` rule has none). * Unconditional entries are defaults, not rules — they never beat an * inherited cursor. A throwing predicate is a no-match (dev-warned), the * same law the cursor resolver applies. */ firstRuleMatch(ctx: InjectionContext): { readonly id: string; readonly witness?: RouteWitness; } | undefined; /** Tier-2 candidates: the declared intent entries, minus `exclude`. */ intentCandidates(exclude?: ReadonlySet): readonly IntentCandidate[]; /** The incumbent as a candidate: its declared intent when it is an intent * entry, else its description (never fabricated — `id` is a graph node). */ incumbentCandidate(id: string): IntentCandidate; } /** Build the plan. `describeFor` resolves a skill id to its description (the * incumbent's fallback intent text). */ export declare function buildTurnRoutingPlan(input: { readonly entries: readonly IntentEntryDecl[]; readonly describeFor: (id: string) => string | undefined; readonly policy: RoutingPolicy; readonly scorer?: IntentScorer; }): TurnRoutingPlan; /** Case/whitespace-normalize an example for the duplicate check — the SAME * string under two intents is scorer luck, whatever its casing. */ export declare function normalizeExample(example: string): string; /** * The one PROVABLE intent-pair fact, decidable from the data alone: the same * (normalized) example declared under two intents. Whichever wins is scorer * luck, so it is named — both ids and the string. Everything else about two * example sets needs the configured scorer ({@link runCheckupIntents}). */ export declare function findDuplicateIntentExamples(entries: readonly IntentEntryDecl[]): readonly GraphProblem[]; /** * The leave-one-out intent audit behind `graph.checkupIntents()` — run each * declared example through the CONFIGURED scorer (the router that will * actually run; auditing with a different scorer would prove nothing about * production) against all intent candidates, with the example's own intent * represented by its REMAINING examples. An example whose top-1 is a * different intent, or whose top-vs-own pairwise margin is inside * `nearTieMargin`, is an `overlapping-intents` warning naming the example, * both intents, both numbers, and the fix. * * Honesty boundaries, stated in the messages: only the configured scorer's * view is checked; `when` predicates are opaque and never claimed checked; * tier-1 regex/keyword rules that fire BEFORE tier 2 are named as unaudited * shadowers when present. On an `llmClassifier` graph this costs one model * call per example — the caller's docstring says so. */ export declare function runCheckupIntents(input: { readonly entries: readonly IntentEntryDecl[]; readonly scorer: IntentScorer; readonly policy: RoutingPolicy; readonly signal?: AbortSignal; }): Promise;