/** * Shared brief helpers — evidence assembly, ref validation, JSON parsing. * * Every brief tool follows the same skeleton: * 1. Assemble numbered evidence from the caller's inputs * 2. Prompt the Deep tier with the numbered evidence + a structured * JSON schema the model must fill in * 3. Validate model-cited evidence_refs against real evidence ids * 4. Honestly flag weak briefs via coverage_notes * * This module centralizes (1), (3), and the generic pieces of (4) so * brief handlers stay focused on their prompt and their output shape. */ import { type CorpusHit } from "../../corpus/searcher.js"; import type { RunContext } from "../../runContext.js"; import { type EvidenceItem } from "./evidence.js"; export interface AssembleEvidenceInput { /** Raw log text, sliced into numbered line-range windows. */ log_text?: string; /** Unified-diff text, split on `diff --git` markers into per-file items. */ diff_text?: string; /** File paths loaded server-side (Claude does not preload). */ source_paths?: string[]; /** Optional corpus; queried via hybrid mode when `corpus_query` is non-empty. */ corpus?: string; /** Already-resolved corpus query. Handlers decide their own fallback. */ corpus_query?: string; /** Per-file chunk cap when loading source_paths. */ per_file_max_chars?: number; /** How many chunks to pull from the corpus. */ corpus_top_k?: number; /** * Minimum retrieval score for a corpus chunk to survive into the * assembled evidence. Corpus hits with `score < threshold` are dropped * before the model sees them. Absent → no filtering (current behavior). * This is the brief-side analogue of `min_top_score` on corpus_answer. */ corpus_min_evidence_score?: number; } export interface AssembledEvidence { evidence: EvidenceItem[]; corpus_used: { name: string; chunks_used: number; } | null; /** Corpus hits kept for callers that want to inspect them beyond evidence snapshotting. */ corpus_hits: CorpusHit[]; /** * Coverage notes generated during evidence assembly (e.g. dropped-by- * threshold counts). Handlers should merge these into their final * `coverage_notes` so the operator sees them in the brief. */ assembly_notes: string[]; } /** * Walk the input sources in a deterministic order (log → diff → paths → * corpus) and return a single numbered evidence list. Ids are "e1", * "e2", ... — assigned in that order so the model can cite by index. */ export declare function assembleEvidence(input: AssembleEvidenceInput, ctx: RunContext): Promise; /** * Strip model-supplied evidence_refs that don't match any real evidence id. * Dedupes while preserving first-mention order. Returns the valid subset * and a count of stripped refs so callers can surface it in warnings. */ export declare function normalizeRefs(refs: unknown, validIds: Set): { valid: string[]; stripped: number; }; /** Normalize a free-form confidence string to the closed set. */ export declare function normalizeConfidence(c: unknown): "high" | "medium" | "low"; /** * Tolerant JSON parse: returns an empty object if the model didn't follow * the contract. NOT fence-tolerant — model-facing callers should prefer * `parseModelJsonObject` below (cloud models fence their JSON). */ export declare function parseJsonObject(raw: string): Record; /** * JSON.parse with markdown-fence tolerance — the model-output parse * primitive (Phase 3b, Slice A; promoted from verifyClaims' parseJurorJson). * * Observed live (F1 dogfood jury, 2026-07-06): glm-5.2 and kimi-k2.7 on * Ollama Cloud wrap replies in ```json fences even under `format:"json"` — * the cloud side doesn't grammar-enforce every model (deepseek emits raw). * Every runTool-driven parser is cloud-reachable (always was under * cloud-primary; F2 added per-call escalation), so this is the shared fix. * * Contract: direct parse FIRST (the overwhelmingly common case, and it * keeps raw JSON that legitimately contains a fence inside a string value * from being mis-extracted), then the FIRST fenced block, else RETHROW the * ORIGINAL error. Bespoke callers (extract's unparseable path, classify's * abstain, triage's empty fallback) swap `JSON.parse(raw.trim())` for this * one-for-one and keep their catch blocks byte-equivalent. */ export declare function parseModelJson(raw: string): unknown; /** * Fence-tolerant successor to `parseJsonObject` for model-facing callers: * `parseModelJson` + object narrowing, `{}` when the reply isn't parseable * as a JSON object at all. Never throws. */ export declare function parseModelJsonObject(raw: string): Record; /** Pick a string field off a loose object, else default to empty. */ export declare function readString(obj: Record, key: string): string; /** Pick an array field off a loose object, else default to empty array. */ export declare function readArray(obj: Record, key: string): unknown[]; /** * Pick an array field that callers iterate as objects, dropping non-object * entries (null / numbers / strings / nested arrays). * * Most brief / refactor parsers iterate `for (const entry of readArray(...))` * and immediately cast `entry as { field?: unknown }` then access fields. * Models occasionally return arrays with `null` or stray strings sprinkled * in; `null.field` throws TypeError uncaught and crashes the whole tool * call. This helper keeps loops crash-safe by filtering down to the only * shape the call sites actually handle. Callers that legitimately want * mixed-type arrays (e.g. `uncited_fragments: ["a", "b"]`) keep using * `readArray` and do their own per-entry type check. */ export declare function readObjectArray(obj: Record, key: string): Record[]; //# sourceMappingURL=common.d.ts.map