/** * skillMatch — the DATA-matcher domain for skill-graph start rules (SG-A). * * One module owns everything a matcher is and does, so the three consumers can * never drift apart: * * • what a matcher IS — {@link SkillMatch} (author form) and * {@link SkillMatchData} (serializable description); * • how it RUNS — {@link compileMatch}: ONE compilation returns the * predicate that routes AND the data that describes it; * • how two matchers RELATE — {@link compareMatchers}: what the check-up can * honestly prove (`overlapping-rules` / * `rules-shadowed-by-order`); * • how it DRAWS — {@link mermaidMatchCaption}: the entry-edge caption * `toMermaid()` uses when the author gave no label. * * Deliberately engine-type-free (the predicate context is structural — * `{ userMessage }` — which the engine's `InjectionContext` satisfies), so * `skillGraphCheckup.ts` can import it and keep its own "pure over strings" * law intact. Imported by `skillGraph.ts` (compile + caption) and * `skillGraphCheckup.ts` (data type + compare); imports nothing. */ /** * A DATA matcher on a start rule — the declarative alternative to a `when` * predicate. The two SYNC leaf members: * * • a `RegExp` — tested against the user's message (`ctx.userMessage`). The * stateful `g`/`y` flags are dropped at compile time (a sticky regex would * alternate its answer across identical messages); the stored provenance * carries the flags that actually run. * • `{ keywords: [...] }` — case-insensitive; the rule matches when ANY keyword * is present. Whole-word where sensible: a `\b` anchor is applied at each edge * of the keyword that is an ASCII word character, so `refund` does not match * "refunds", while `visa card` matches as a phrase and `v2?` matches literally. * * Being DATA is the point: a predicate is opaque code the library can only run, * while a matcher can be COMPARED by the check-up (`overlapping-rules`, * `rules-shadowed-by-order`), CAPTIONED by `toMermaid()` on the entry edge, and * STORED on the compiled skill's provenance (`metadata.skillGraph.match`, as * serializable {@link SkillMatchData}). * * The third member (SG-C) is the INTENT matcher: * • `{ intent, examples }` — one sentence naming the intent ("customer wants * a refund") plus ≥1 real user phrasings. Unlike the other two it compiles * to NO message predicate: it cannot answer synchronously, so the entry * carrying it compiles cursor-gated and the matching happens in the * turn-start cascade (the RouteTurn stage), judged by the graph's * configured classifier (`.classify(scorer)` / `start.classify`). A graph * declaring one without a classifier is refused at build. * * The fourth member (9.20.0) is the CONJUNCTION: * • `{ all: [...] }` — the rule matches only when EVERY member matches ("zone * AND audit-shaped" as data, instead of a lookahead regex nobody can read * back). Members are the SYNC data arms only — RegExp or `{ keywords }`: * an intent inside `all` is refused at build (intents are judged by the * classifier at turn start; they cannot compose into a synchronous AND — * declare a separate intent rule instead). A nested `all` is FLATTENED * (AND is associative, so the flattened form runs identically and the * stored data describes exactly what runs — the same normalize-to-truth * rule that drops a regex's stateful flags). An empty `all` is refused: * it has nothing to check, so it could only surprise. * * Extensible by design: each non-RegExp member is an object discriminated by its * own required key, so a future matcher is a new arm — not a reshape. A shape * that is none of these is refused at build time, naming the forms that ARE * supported. */ export type SkillMatch = RegExp | { readonly keywords: readonly string[]; } | { readonly intent: string; readonly examples: readonly string[]; } | { readonly all: readonly SkillMatch[]; }; /** * The serializable description of a DATA matcher on a start rule (`match:` on * `start.rules` / `SkillEntryOptions.match`) — what the check-up compares, what * `toMermaid()` captions, and what rides the compiled skill's provenance. Pure * strings by design (it has to survive `structuredClone`): a `RegExp` matcher is * stored as its `source` + the `flags` that actually run. * * A discriminated union so a future member (e.g. an intent matcher) is a new * `kind` arm, not a reshape. * * The `'all'` arm carries the conjunction's members as `parts`. Compilation * GUARANTEES the parts are the leaf sync arms only (`'regex'` / `'keywords'`): * an intent member is refused, and a nested `all` is flattened before the data * is stored — so every consumer (the check-up's part comparison, the mermaid * caption) reads a flat AND of leaves, never a tree. */ export type SkillMatchData = { readonly kind: 'regex'; readonly source: string; readonly flags: string; } | { readonly kind: 'keywords'; readonly keywords: readonly string[]; } | { readonly kind: 'intent'; readonly intent: string; readonly examples: readonly string[]; } | { readonly kind: 'all'; readonly parts: readonly SkillMatchData[]; }; /** The one field a compiled matcher reads. Structural on purpose — the engine's * `InjectionContext` satisfies it, and this module never has to import it. */ export interface MatchableContext { readonly userMessage: string; } /** * The EVIDENCE a data matcher routed on (9.28.0) — the actual text out of the * USER MESSAGE that made the rule true. Recorded on the routing verdict * (`turn_routed.witness`, `cursorMove.witness`) so the record can say *because * the message said "…"* instead of only *a rule matched*. * * Three honesty rules, all enforced at capture: * • the text comes from `ctx.userMessage` and nothing else — never a tool * result, never system/assistant prose (a matcher only ever reads the user * message, so this is a property of where it is captured, not a filter); * • it is BOUNDED to {@link WITNESS_MAX_CHARS} characters, ellipsis included * — a `/[\s\S]+/` rule must not paste the whole message into every record; * • runs of whitespace collapse to one space and the ends are trimmed, so a * multi-line match stays one quotable clause. That is the ONLY edit made to * the matched substring, and it is the reason `text` is described as the * matched text rather than the matched bytes. * * `keyword` is present only for the `{ keywords }` arm: WHICH declared keyword * hit, beside the text it hit on (they differ in case, and `text` may carry the * phrase's real spacing). * * A `when` predicate produces NO witness (opaque code — the library cannot know * what convinced it), and neither does an intent match (a scorer's evidence is * its scores, already recorded). Absent is the honest answer there. */ export interface RouteWitness { readonly text: string; readonly keyword?: string; } /** The witness bound — 80 characters, ellipsis included. A record is evidence, * not a transcript. */ export declare const WITNESS_MAX_CHARS = 80; /** * Compile a data matcher into its predicate + its serializable description — ONE * compilation, so the predicate that routes and the data the check-up compares can * never describe different matchers. Refuses a shape it cannot honor (a bare * string, an empty keyword list, an intent without examples), naming the forms * that ARE supported — accepting one and matching nothing would be the silent kind * of wrong. * * The INTENT arm compiles to `predicate: undefined` — it cannot answer * synchronously (a classifier judges it in the turn-start cascade), so the entry * carrying it is cursor-gated instead of rule-gated. `skillGraph.ts` is the only * caller that accepts a predicate-less compile, and it refuses `match: { intent }` * when no classifier is configured. * * The third member of the returned triple is `witness` (9.28.0): the SAME * compilation's evidence extractor — given a context the predicate accepted, it * returns {@link RouteWitness} (the matched text, bounded). It is a separate * function on purpose, so the hot path stays exactly what it was (a boolean * `.test`) and the evidence is paid for ONCE per turn, on the rule that actually * won. The intent arm has no witness (nothing matched synchronously). */ export declare function compileMatch(match: SkillMatch, where: string): { predicate: ((ctx: MatchableContext) => boolean) | undefined; data: SkillMatchData; witness?: (ctx: MatchableContext) => RouteWitness | undefined; }; /** * Compare two DATA matchers, earlier vs later (declaration order). Returns only * what is PROVABLE: * • `'shadows'` — every message the later matches, the earlier matches too, so * the later can never be chosen (identical regex; earlier keywords ⊇ later's); * • `'overlaps'` — a witness class of messages matches both (a shared keyword), * but each rule also has messages of its own; * • `undefined` — nothing provable here (different regex sources, a regex vs a * keyword list): say nothing rather than guess. * `why` is the human clause the check-up splices into its problem message. * * The `'all'` arm (9.20.0) keeps the same law — claim only what part-coverage * proves. An `all` rule matches a SUBSET of each of its parts' messages, so: * • a plain rule EARLIER that provably covers ONE part of a later `all` rule * shadows it (every message the `all` matches satisfies that part, which * the earlier already matches); * • an `all` rule EARLIER shadows a later matcher when EVERY earlier part is * provably covered by some later part — the later only ADDS constraints * (identical part sets are the simplest case). * "Provably covers" is same-kind only: an identical regex, or a keyword * superset. No `'overlaps'` is ever claimed for a pair involving `all`: a * conjunction can be unsatisfiable (`all: [/^a/, /^b/]` matches nothing), so a * witness message cannot be asserted from the data — and the ordinary * specific-rule-first layout (the `all` rule above its plain fallback) is a * DESIGN, not a problem to warn about. */ export declare function compareMatchers(earlier: SkillMatchData, later: SkillMatchData): { relation: 'shadows' | 'overlaps'; why: string; } | undefined; /** * Caption an entry edge with its data matcher, escaped for a mermaid `|…|` label * (`|` would end the label early; `"` would open a string — both become mermaid * entities). Keywords list at most three, then a count, so a wide router stays * readable; an `all` conjunction joins its parts with ` AND ` (parts are always * leaves — compilation flattened any nesting). Used only when the author gave no * explicit `label` — an explicit label always wins, byte-for-byte, exactly as * before. */ export declare function mermaidMatchCaption(m: SkillMatchData): string; /** * The UNESCAPED caption — one grammar for naming a matcher, shared by the * mermaid label (which escapes it once, at the top) and by prose that quotes a * matcher back to its author (`skillExamples.ts`'s messages). Recursion for the * `all` arm happens here so the escaping stays a single outer pass. */ export declare function plainMatchCaption(m: SkillMatchData): string; //# sourceMappingURL=skillMatch.d.ts.map