/** * Deterministic "sibling surface" signal for PR reviews — the omission-frontier attack. * * The weakest miss shape in the 2026-07 cross-repo study was omission: a bug that * is what ISN'T in the diff, so no reviewer (human, Sonnet, or Kimi) reliably finds * it. Two concrete cases motivate this module: * - guzzle #3740: adds the `on_trailers` request option, wired through * `CurlFactory.php` only — the sibling `StreamHandler.php` (same directory, * same "handler" family) silently ignores it. Both Sonnet and Kimi missed it. * - gin #3081: adds `binding/toml.go`, whose decode function ends with a * duplicated `decoder.Decode(obj)` instead of the `return validate(obj)` every * sibling binding (`json.go`, `xml.go`, `yaml.go`, ...) uses. * * Both are structural facts about a directory of same-extension "family" files, * not something an LLM needs to reason its way to — so, mirroring * `stale-literal-signals.ts` / `doc-claims-signals.ts`, this module precomputes * two directions and hands them to the agent as a `` block: * * - Direction A ("unmirrored addition"): a feature-shaped literal/identifier the * diff ADDED to one family member, absent from an untouched sibling. * - Direction B ("family-pattern divergence"): a call-shaped identifier most * untouched siblings share, absent from the changed/new file entirely. * * Threshold notes (tuned against the real fixtures, not just the illustrative * "200-file directory" anti-example): * - Real same-extension families run bigger than a tidy top-level guess: guzzle's * `src/Handler/` has 14 members, gin's `binding/` has 16. The family-size gate * exists to exclude genuinely unrelated bulk directories (rack's `lib/rack/` * has 42), not to exclude realistic package directories — capped at 20. * - Direction A's corpus-rarity check counts occurrences OUTSIDE every file this * PR touched, not merely outside F. `on_trailers` appears in 44 chunks outside * `CurlFactory.php` alone — but every one of those chunks is in a file the PR * ALSO changed (RequestOptions.php, Client.php, docs, tests): expected fan-out * of wiring one new feature through several files, not pre-existing generic * vocabulary. Outside the whole changed-file set, the count is 0. * - Direction B's "shared by siblings" is a MAJORITY, not unanimous: gin's real * `validate(` call appears in 9 of 13 untouched siblings, not all 13 (a few * unrelated files like `any.go` never call it). Requiring unanimity would * silently kill the positive. * * A second, independent family axis covers a gap the same-directory definition * cannot see: MIRROR DIRECTORIES. reqwest's `blocking/request.rs` and * `async_impl/request.rs` are the real sibling axis for "applied to one variant, * forgot the other" bugs (reqwest #916/#1550), but they live in different * directories, so the same-dir family never pairs them. A mirror sibling for a * changed `/.` is `/.` where dirA and dirB * share at least two same-basename+extension source files (the mirror-evidence * gate — one coincidental shared name like `utils.rs` must not create a family) * and dirB actually contains the counterpart file itself. Qualifying mirror * directories are capped at 3 per changed file: a basename like `index.ts` * shared across dozens of directories is noise, not a mirror relationship, so * exceeding the cap discards the mirror family for that file entirely rather * than truncating to the first 3. Mirror siblings feed both directions exactly * like same-dir siblings, except Direction B with a SINGLE mirror sibling (the * common case — most mirror relationships are 1:1 pairs, not larger clusters) * swaps the cross-file majority-share rule for a within-file repetition rule: * the identifier must occur at least twice in that one sibling, compensating * for a family too small for "majority" to mean anything. Rendered entries * that come from this axis are labeled "mirror sibling" so the reviewer can * tell the relationship apart from a same-directory one. * * Provenance: ported from PR #744 (`feat/sibling-surface-signals`), parked * as draft/YAGNI after a blind re-screen found the module non-regressive * and discovery-effective but with no *measured* detection lift — the * study's only omission-shaped miss (guzzle#3740) turned out to be a * *disclosed* limitation, so the mechanism declining it was correct * judgment, not a gap. The 2026-07-15 omission-pass design review * (`.wip/omission-pass-design.md`) revisited that verdict: a mislabeled * fixture is not proof the mechanism fails, so this ships as a plain * main-pass signal rather than staying parked for a dedicated evaluation * that may never materialize. Module logic and tests are unchanged from * #744 (mirror-directory extension included); only the wiring changed — * see `system-prompt.ts` for the `incomplete-handling` rule gate this * revival adds. */ import type { SignalContext } from './signal-context.js'; export type SiblingSurfaceDirection = 'unmirrored-addition' | 'family-pattern-divergence'; export interface SiblingSurfaceEntry { direction: SiblingSurfaceDirection; /** Display form: quoted for a string literal, bare for an identifier/call. */ display: string; /** The file the entry is about: where it was added (A) or where it's missing (B). */ file: string; /** New-file line the identifier was added on. Direction A only. */ line?: number; /** Direction A: untouched siblings LACKING it. Direction B: untouched siblings SHARING it. */ siblings: string[]; /** True when `siblings` are mirror-directory siblings (cross-dir, same basename+ext), not same-directory family members. */ isMirror: boolean; } /** * Precompute sibling-surface entries for the review context. Returns [] when * there's no repo index to scan against. Exposed for testing. */ export declare function extractSiblingSurfaces(context: SignalContext): SiblingSurfaceEntry[]; /** * Render sibling-surface entries as a `` block. Returns '' * when there are no entries so callers can append unconditionally — a fixture * with no signal must render byte-identical prompts. */ export declare function renderSiblingSurfaces(entries: SiblingSurfaceEntry[]): string; /** * Build the `` section for the agent's initial message. * Returns '' when there is no repo index or no entries survive the scan. */ export declare function renderSiblingSurfacesSection(context: SignalContext): string; //# sourceMappingURL=sibling-surface-signals.d.ts.map