import type { ParsedMemoryItem } from '../db/store.js'; export declare function humanizeMemoryKind(kind: string): string; /** Marker for an item in an unresolved contest. 12 chars → 3 tokens; AC caps it at 4. */ export declare const CONTESTED_MARKER = " [contested]"; /** * A contest is recorded on `notes.conflict`, but `ParsedMemoryItem` carries no * such column — the signal survives projection only as the `Conflict: true` * line `buildNoteMemoryText` writes into the item text. Reading it back out is * forced rather than chosen: a real column needs a migration, and this release * spends its single `SCHEMA_VERSION` bump elsewhere. * * Two guards keep the sniff honest, both load-bearing: * * Only notes have a conflict column, so an episode or branch snapshot whose * captured stdout/stderr happens to carry the phrase is not in a contest — and * would otherwise be silently reordered by `groupContestedAdjacent` while * `renderMemoryLine` never marks it, leaving a reorder with no visible cause. * * The match is line-exact because `buildNoteMemoryText` always emits this as * its own line. A substring match makes a note that merely *discusses* the flag * ("insertNote sets conflict: true on both sides") render as contested — and * nothing could ever clear it, since `cortex_resolve` clears the column while * the marker is read from text. */ export declare function isContested(item: ParsedMemoryItem): boolean; /** * Reorders results so both sides of a contest read together, pulling later * counterparts up to sit directly behind the highest-ranked side. * * Promoting the counterpart is the whole point, so items *do* move ahead of * where they ranked — only rank 0 is fixed. Two consequences follow and both * are intended: a budget that trims from the bottom now follows display order, * so a contested counterpart can be kept while a higher-ranked uncontested item * is dropped; and ranking metrics stay safe only because this runs in `recall`, * never inside `retrieveMemory`. * * Pairs on `(scope_key, subject)` because contradiction detection is * scope-keyed — the same subject on another branch is a different conversation, * not the other half of this one. */ export declare function groupContestedAdjacent(items: T[]): T[]; /** * Contested grouping confined to each run of equal kind, for surfaces that sort * by kind first. `brief` orders by `KIND_PRIORITY` and `state` renders * kind-headed sections; grouping across a kind boundary there would drag a * contested insight up into the decisions and destroy the primary ordering. * Within a bucket it costs nothing, and same-kind contests are the common case * — detection's prior is always a decision, so decisions contest decisions. * A cross-kind pair stays split on those surfaces; only `recall`, which is a * flat score-ordered list, can seat both sides together unconditionally. */ export declare function groupContestedWithinKind(items: T[]): T[]; /** * Prefix for the rejected-alternatives continuation line. The two-space indent * subordinates it to the decision it belongs to. */ export declare const ALREADY_REJECTED_PREFIX = " already rejected: "; /** * The `already rejected:` line for an item, or null when it carries none. * * `notes.alternatives` is a real column, but `ParsedMemoryItem` carries no such * field — the value survives projection only as the `Alternatives: a, b` line * `buildNoteMemoryText` writes. Reading it back out of the text is forced rather * than chosen, for the same reason as `isContested`: a real field needs a * migration, and this release spends its single `SCHEMA_VERSION` bump elsewhere. * * Three guards keep the read honest, and all three are load-bearing. * * **Kind.** Only notes have an alternatives column. An episode carries captured * stdout/stderr in its text, so a build log line starting `Alternatives:` would * otherwise render a rejection list nobody wrote. * * **Line-exact, never a substring.** Three locked eval suites seed decision text * whose *content* contains the word mid-sentence — "… after renewal. * Alternatives: client cookie rotation rejected …". A substring match renders * those alternatives a second time and pushes `output_tokens` positive on two * of them at once. * * **Trailer-only, and the first one there.** A line-exact match anywhere in the * text is not enough: a note whose *content* contains its own `Alternatives:` * line fabricates a rejection list for a note whose column is `NULL`, and * nothing could ever clear it. `noteTrailerLines` restricts the search to the * lines the projection actually appended. Taking the first match inside that * trailer — rather than the last — matters because an alternative containing a * newline splits into extra `Alternatives:` lines, and the *later* ones are * attacker-controlled content that would otherwise replace the real list * wholesale rather than merely truncating it. * * One residual case survives and is bounded: a content line landing *last*, * with no trailer after it, is byte-identical to what a subject-less * `note:insight` legitimately projects. It is not separable from the text alone. * It does not reach a `decision`, `intent`, `blocker` or `focus` note, because * `insertNote` requires a subject for those and the `Subject:` line therefore * always lands after the content — which is exactly the ordering this scan * keys on. */ export declare function renderedAlternatives(item: ParsedMemoryItem): string | null; export declare function renderMemorySnippet(text: string, maxLines?: number, maxChars?: number): string; export declare function formatMemoryTimestamp(createdAt: string): string | null; /** Compact relative age: 'today', 'Nd ago', or the ISO date past 30 days. */ export declare function formatAgeLabel(createdAt: string): string | null; /** One-word trust summary of an item's file references. */ export declare function describeValidity(item: ParsedMemoryItem): string; export declare function renderMemoryLine(item: ParsedMemoryItem, maxLines?: number): string; //# sourceMappingURL=render.d.ts.map