UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

100 lines (99 loc) • 5.29 kB
/** * scripts/match — is a fenced block the program ONE call of this turn * submitted? Identity against the record, never evidence for a value. * * Pattern: pure function over the answer and this turn's rows. * Role: core/ layer. Asked inside the ONE judge closure the Route decider * builds (`stages/route.ts` · `judgeEvidence`, through `judge.ts`), * before the names-and-numbers extractor reads the text — so the * draft and the delivered candidate are read alike. Only under * `.namesAndNumbersFromEvidence({ code: 'match' })`. * Emits: N/A. * * ## The match rule: exact, after removing only the wrapper (design § 5.2) * * A block matches a call only when its content (`../evidence/codeRegions.ts`) * equals the program the call SUBMITTED to the tool, byte for byte. Nothing is * transformed: no trailing-space strip, no line-ending rewrite, no Unicode * normalization, no shape, no edit distance — each can change a literal and so * the program (`print(len("""A \nB"""))` prints 5, its copy without the two * spaces 3; `"é"` written with a combining mark is one more character than its * NFC form). The strings are compared as they hold the text (UTF-16 code * units): for well-formed text that is UTF-8 byte equality, and for a lone * surrogate it is stricter, the safe direction. * * - Against the rows of the CURRENT TURN only; earlier turns are counted * (`earlierTurnRows`), never compared, and a miss says "no submission of * this turn", never "no submission". * - A block over `MAX_SCRIPT_BYTES` is `over-size`: no row keeps a program * that long, so it is never compared. * - Two calls that submitted byte-identical programs: the block names the * LATEST, counts the rest (`alsoMatches`) and carries its label. * - No nearest submission is ever named: a near match is a different program. * * WHAT A MATCH YIELDS. The block is script content: it is cut out of the text * the extractor reads, so it yields no value candidates — and it grounds * nothing: the evidence corpus is untouched, so a value in the prose that also * sits in the script still needs a result. A MISS stays in the text and is * checked as prose, exactly as today. * * COST. One linear scan of the answer (`codeRegionsOf`); each of at most * `MAX_CODE_REGIONS` blocks is looked up once among at most `MAX_SCRIPT_ROWS` * programs of at most `MAX_SCRIPT_BYTES` — within the 512 comparisons per * judged answer of design § 4.4. Earlier turns add nothing. */ import { type TurnScripts } from './rows.js'; import type { ScriptBlockVerdict, ScriptsVerdict, UnmatchedReason } from './types.js'; /** One answer, read for its scripts. */ export interface ShownScripts { /** The answer with every MATCHED block cut out — what the extractor reads. */ readonly prose: string; /** The answer with EVERY block cut out — "the prose", for `unsupported[].region`. */ readonly outsideCode: string; readonly verdict: ScriptsVerdict; } /** * Read `answer`'s fenced blocks against this turn's submissions. Pure; the * rows are `rows.ts` · `scriptRowsOfTurn`'s reading (last row per call). */ export declare function readShownScripts(answer: string, turn: TurnScripts, scriptsDropped?: number): ShownScripts; /** * A verdict as detached plain data — field by field, never a reference into * state (a value read back through a TypedScope is a live view, and an event * payload must be plain data). */ export declare function copyScriptsVerdict(verdict: ScriptsVerdict): ScriptsVerdict; /** Every reason a block can be unmatched for. */ export declare const UNMATCHED_REASONS: readonly UnmatchedReason[]; /** * One block verdict's fields, checked as {@link readShownScripts} writes them * — a fresh copy, or `undefined` when any field is one no writer of this * library files: a position or a dispatch that is not a whole number from 1, * a label outside the vocabulary, an exit code its label does not carry * (`label.ts` · `exitCodeFits`), an unmatched block that names a call, a * matched one without its call. Fields beside these (a witness row's `kind`, * `turn`, `iteration`) are not read. */ export declare function blockVerdictOf(value: unknown): ScriptBlockVerdict | undefined; /** One block verdict read back, at its position in the record's `blocks`. */ export interface RecordedBlock { readonly index: number; readonly verdict: ScriptBlockVerdict; } /** A saved `evidence_checked.scripts`, read back. */ export interface RecordedBlocks { /** The verdicts a writer of this library files, at their record position. */ readonly blocks: readonly RecordedBlock[]; /** * What no writer files, counted once each: a verdict that does not read * back, every verdict past `MAX_CODE_REGIONS` (the writer reads no more * blocks), or a `scripts` value that is not `{ blocks: [...] }`. */ readonly unread: number; } /** * A saved `evidence_checked.scripts` — THE reader of the shape for a reader * of a record. `undefined` when the event carries no scripts arm. Bounded by * the writer's cap: no reader iterates more verdicts than a writer files. */ export declare function recordedBlocks(value: unknown): RecordedBlocks | undefined;