import type { TranscriptTextMessage } from './messages.js'; import type { ActionNarration } from '@auden.to/protocol'; /** * Characters of adjacent assistant prose carried per action. * * **Measured, not guessed** — the plan's third open question asks for exactly * that. Measured over a real 180-line Claude Code transcript (53 tool calls, 12 * distinct prose carriers): * * | Window | Carriers captured whole | * |---|---| * | 120 | 49% | * | 160 | 83% | * | 200 | 83% | * | 600 | 83% | * * The distribution is bimodal, which is what makes the choice easy: eleven * carriers run 38–148 characters ("Now the CLI-side extraction. The critical * rule: select on content-block type, never role."), and the twelfth is 2,488 — * a long written-out report. Nothing lies between. So every window from 148 to * 600 buys identical coverage, and the only thing a larger one does is carry * more of the single bulk message, which is the case a bound exists for. * * 200 is the smallest round number above the largest short carrier, with * headroom, and an order of magnitude under `MAX_PROSE_CHARS` (2,000). * * **Rejected: the first sentence.** It measured p50=38 characters and it breaks * on the prose it has to handle — "Design review verdict:" truncates at a colon * inside bold markers, and "Plan and row read." drops the clause carrying the * actual intent. It is smaller and it loses the thing being synced. * * **Honest limits of the measurement:** one session, one agent, agent-authored. * It says what Claude Code's assistant prose looks like, not what every agent's * does. Widening this is cheap and reversible; the number is here, alone, to be * overruled by a second corpus. */ export declare const ASSISTANT_PROSE_WINDOW = 200; /** * Everything an action may carry from the transcript — two fields, and the * class table is why there are not three. * * **Re-exported from the wire schema rather than declared here.** `CLAUDE.md` * forbids a hand-declared twin of a Valibot-derived type, and this is the case * that rule exists for: a local copy lets this builder grow a field the server * rejects, or keep one the schema drops, and the mismatch compiles cleanly on * both sides. Deriving it means a tightening of `ActionNarrationSchema` fails * *here*, at the choke point, which is the only place that can act on it. */ export type { ActionNarration } from '@auden.to/protocol'; /** The minimum an action must expose to be narrated. */ export type NarratableAction = { id: string; toolName: string; }; /** * How "adjacent" is resolved — and it is a measurement, not a reading of the * word. * * In a real Claude Code transcript, assistant text and `tool_use` are separate * entries — 52 `assistant|tool_use` and 11 `assistant|text`, with no entry * containing both — so interpreting "adjacent" as same-message yields prose for * 0 of 52 tool calls. It is the nearest **preceding** assistant text, and one * carrier then serves several consecutive calls (53 calls over 12 carriers in * that transcript). That is also why `ActionEntry.id` is the join key rather * than adjacency: adjacency cannot tell those calls apart, and the plan says so * at `:18`. * * Because the walk below is over an ordered block sequence rather than over * per-message buckets, a format that *does* interleave text and `tool_use` * inside one message resolves correctly too: each call takes the text most * recently seen before it, not the concatenation of the message's text. Getting * that wrong would attach prose written *after* a call to it, which is the * confidently-wrong failure this module treats as worse than silence. */ /** * Pair each action with the narration in effect when it ran. * * **Alignment is by prefix, and it fails closed.** The transcript records * `tool_use` blocks; the run log records `ActionEntry`s. Neither carries the * other's identifier — `ActionEntry.id` is a `randomUUID()` minted by the hook * (`../hook-logger.ts:336-346`), not the transcript's `toolu_…` — so the two * sequences are matched by position, walking both and requiring the tool names * to agree at every step. **At the first disagreement this stops**, and every * remaining action is returned unnarrated. * * Stopping rather than resynchronizing is deliberate. A clever realignment * would attach one turn's prose to a different turn's tool call, and a * confidently mislabelled piece of evidence is worse for a grader than none — * the grader has no way to tell it is wrong, and the verdict it produces looks * exactly as trustworthy as a correct one. Degrading to today's behaviour is a * visible, safe failure. */ export declare function narrateActions(messages: readonly TranscriptTextMessage[], actions: readonly NarratableAction[]): Map; //# sourceMappingURL=narration.d.ts.map