/* Shared diagnostic-list capping primitives (RFC-1305): every capped findings list must end with a TRUNC-01 truncation marker naming the true total and the cap — never a bare slice, never a marker borrowing the capped rule's own id. Does not bound message-content truncation inside diagnostic strings (e.g. stderr slices, excerpt shortening) — same defect class, different surface; deferred per RFC-1305 nonGoals. Does not choose caps — call sites keep their own cap values. is always warning-severity regardless of the capped rule's severity opts.total lets early-break collectors report the true filtered count capStrings is the string-envelope adapter for the RFC-1305 sweep's non-Diagnostic emission paths (import.extensions.lint, price.presence.validate, need.markers.validate, dist.faq.validate, dist.content-references.validate, content.idempotency.validate) RFC-1305: initial implementation — capDiagnostics (Diagnostic[] path) + capStrings (string-envelope path), both emitting the shared TRUNC-01 marker. RFC-1305 review-r1: opts.total floored at items.length — an under-counted total can never emit more than cap items silently. */ import type { Diagnostic } from "./diagnostic.ts"; /** Rule id carried by every truncation marker — greppable as one defect class. */ export const TRUNC_RULE_ID = "TRUNC-01"; export interface CapInfo { /** The rule whose findings were capped (e.g. "GRAM-01"). */ cappedRule: string; /** True filtered total — the count that would have been listed. */ total: number; /** The applied cap. */ cap: number; /** Findings hidden by the cap: total - cap. */ hidden: number; } /** * Cap a Diagnostic list: `items.slice(0, cap)` plus — when the true total * exceeds the cap — one trailing TRUNC-01 warning diagnostic carrying * `{ cappedRule, total, cap, hidden }` (RFC-1305). `opts.total` overrides the * total for collectors that break early; it must be the filtered count that * would have been listed, never the raw scan count. */ export function capDiagnostics( items: T[], cap: number, ruleId: string, opts?: { total?: number; detail?: Record }, ): (T | Diagnostic)[] { // Floor at items.length — a caller passing a stale/under-counted opts.total // must never emit more than cap items silently. const total = Math.max(items.length, opts?.total ?? items.length); if (total <= cap) return items; const info: CapInfo = { cappedRule: ruleId, total, cap, hidden: total - cap }; return [ ...items.slice(0, cap), { ruleId: TRUNC_RULE_ID, severity: "warning", message: `${total} findings exceeded cap ${cap}; showing first ${cap} (capped rule ${ruleId}).`, data: { ...opts?.detail, ...info }, }, ]; } /** * String-envelope adapter for commands that emit rendered violation lines * (failResult string lists) rather than Diagnostic objects — appends the same * TRUNC-01 marker as a line so the class stays greppable (RFC-1305). */ export function capStrings( items: string[], cap: number, ruleId: string, opts?: { total?: number }, ): string[] { const total = Math.max(items.length, opts?.total ?? items.length); if (total <= cap) return items; return [ ...items.slice(0, cap), `[${TRUNC_RULE_ID}] ${total} findings exceeded cap ${cap}; showing first ${cap} (capped rule ${ruleId}).`, ]; }