Something went wrong. Try again.
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071/** * DeterministicCompactor — the pure pipeline surface behind the `Compactor` contract * (ARCHITECTURE §11, saga gy5n0k.6). * * Assembles gy5n0k.1–.5 behind the three-method `Compactor` interface: * - `normalize` → gy5n0k.1 `normalize` (SessionEntry[] → NormalizedBlock[]) * - `buildSections` → gy5n0k.4 `buildSections` (runs the .2 extractors, plumbs ctx.codeIndex) * - `formatSummary` → gy5n0k.5 `compile` (formatSummary + mergePrevious + HANDOFF_PREAMBLE) * * Pure pipeline ONLY — no `shouldCompact`. The *need* for compaction is a separate seam * (`CompactionTrigger`, gy5n0k.3, decision Q2): the trigger decides when/where to cut, * this class renders the summary for whatever front slice it's handed. Swapping a * `Compactor` impl never touches the trigger, and vice versa. * * The compaction orchestration (findCutPoint → normalize → buildSections → formatSummary * → `CompactionEntry`) lives in `AgentSession` (gy5n0k.7), NOT here — this class carries no * I/O, no session coupling, and no knowledge of the live window. It is a thin, deterministic * adapter over the free-function pipeline so a consumer can ask for "the compactor" in one * call and so `CompactionEntry.compactor` records `"deterministic"`. * * Unit-testable in isolation; see `tests/core/compactor/compactor.test.ts`. */import type { SessionEntry } from '@/types/session-entry';import type { NormalizedBlock } from '@/types/normalized-block';import type { Compactor, SectionData } from '@/contracts/compactor';import type { ExtractorContext } from '@/contracts/extractor';import { normalize } from './normalize';import { buildSections } from './build-sections';import { compile } from './compile';
/** * Name recorded on `CompactionEntry.compactor` so a session log identifies which impl * produced a given summary. Stable across compactions within a session; distinct from any * future LLM-backed compactor's name. */export const DETERMINISTIC_COMPACTOR_NAME = 'deterministic';
/** * Default `Compactor` impl: a thin adapter over the gy5n0k.1–.5 free-function pipeline. * * `formatSummary` delegates to `compile` (not the bare `formatSummary` free fn) so the * returned string is the full handoff document — preamble + merged sections + line-wrap — * exactly what `.7` stores on `CompactionEntry.summary` and prepends to the next turn's * system prompt. When `previousSummary` is omitted, `compile` emits the first-pass summary * (no merge); when present, it merges stable sections + re-caps volatile ones + re-strips * any legacy preamble baked into `prev` (gy5n0k.5). */export class DeterministicCompactor implements Compactor { /** Recorded on `CompactionEntry.compactor`. */ readonly name = DETERMINISTIC_COMPACTOR_NAME;
/** Flatten the JSONL row stream; pair tool_call ↔ tool_result via a shared look-ahead index. */ normalize(entries: SessionEntry[]): NormalizedBlock[] { return normalize(entries); }
/** Run the .2 extractors + brief/outstanding-context (.4) over the normalized stream. */ buildSections(blocks: NormalizedBlock[], ctx: ExtractorContext): SectionData { return buildSections(blocks, ctx); }
/** * Render the section doc — `formatSummary` + `mergePrevious` + `HANDOFF_PREAMBLE` + line * wrap (gy5n0k.5 `compile`). The returned string is the handoff summary `.7` stores and * prepends; it already carries the `This summary captures work done before…` preamble. */ formatSummary(sections: SectionData, previousSummary?: string): string { return compile(sections, previousSummary); }}