import type { GhCheck, GhComment, GhIssue, GhPR, GhReviewThread } from "./gh.js"; export declare const PACKET_PER_FILE_CAP: number; export declare const PACKET_TOTAL_CAP: number; export declare const PACKET_BRIEF_CAP: number; export declare function isNoiseDiffPath(path: string): boolean; export interface DiffSection { path: string; body: string; } /** Split a unified `gh pr diff` output into per-file sections. */ export declare function splitUnifiedDiff(diff: string): DiffSection[]; export interface FilteredDiff { text: string; shown: number; omittedNoise: number; omittedBudget: number; truncatedFiles: number; } /** Noise-filter and budget the diff (per-file + total caps). */ export declare function filterDiffForPacket(diff: string): FilteredDiff; /** CI rollup reduced to pass/fail/pending counts + the failing check names. * Shared by the text packet and the `--json` packet so both report CI * identically. Per-row verdict is `ciStateOf` (pr-state.ts): SUCCESS → * passing; the FAILING set → failing; pending / in-progress / empty / STALE / * unknown → pending; NEUTRAL/SKIPPED set no flag (issue #695). Empty rollup * → reported:false. */ export interface CiSummary { passing: number; failing: number; pending: number; failingChecks: string[]; reported: boolean; } /** ## CI line when every reported check is NEUTRAL/SKIPPED — not * `0 passing · 0 failing · 0 pending`, which reads like a real rollup. */ export declare const CI_NOTHING_VALIDATED = "nothing was validated \u2014 every reported check is NEUTRAL/SKIPPED"; export declare function summarizeChecks(checks: GhCheck[]): CiSummary; /** Lines from PR comments that carry test evidence / health captions. */ export declare function extractEvidenceLines(comments: GhComment[]): string[]; /** One feature from the project map, reduced to what the packet needs. */ export interface FeaturePathsEntry { key: string; name: string; paths: string[]; layer?: string; description?: string; testPriority?: string; } /** Does a feature-map path own a changed file path? * * Two regimes, chosen by whether the map path carries a wildcard: * - **No `*`/`?`** → the original directory-boundary prefix rule: * "src/app/admin" owns "src/app/admin/…" and the exact path itself, but * never "src/app/admin-shop/…". A leading "./" is ignored; empty owns * nothing. Kept byte-identical so the canonical contract's ownsTestVectors * (none of which contain a wildcard) stay green — see contract-parity.test.ts * and the server's reviewcontract.OwnsPath. * - **Wildcard present** → doublestar-equivalent glob matching, matching the * server's featuremap.MatchGlob (featuremap.go:98). * * `[…]` is treated as LITERAL, not as a doublestar character class: every * bracket in this project's map is a Next.js dynamic-route directory * (`…/projects/[project]/_components/**`), which the class reading matches * never. * * The divergence from doublestar here is **DISJOINT, not narrower** — neither * set contains the other (verified against doublestar v4.10.0, PR #480): * * pattern `src/app/[project]/**` doublestar this matcher * ├─ `src/app/[project]/page.tsx` false TRUE ← the real route dir * └─ `src/app/p/page.tsx` TRUE false ← a one-char decoy * * The literal reading is the intended one (map authors write real directory * names), so this stays as-is; the SERVER is what should change. Tracked * separately — do not "fix" this to match doublestar. */ export declare function ownsPath(featurePath: string, filePath: string): boolean; /** The candidate forms of a diff path to try against the map: the raw path, * `/`, and `//`. Mirrors the server's * featuremap.Analyze buildCandidates (featuremap.go:141-161) — mandatory here, * because every path in this project's live map is written repo-prefixed * (`renaiss-shipflow/apps/…`) while `gh pr diff` emits repo-RELATIVE paths. * Without this the matcher fails before globbing is even reached. */ export declare function pathCandidates(path: string, repo?: string): string[]; /** Does this map path own literally any path — i.e. is it a catch-all? * Probed through `ownsPath` + `pathCandidates` so repo-prefixed catch-alls * (`renaiss-shipflow/**`, this project's live `repo-root` entry) are caught * exactly as the matcher sees them. */ export declare function isCatchAllFeaturePath(featurePath: string, repo?: string): boolean; export interface FeatureMatch { /** Feature names owning ≥1 non-noise diff path, map order, deduped. */ touched: string[]; /** The subset of `touched` matched ONLY through a catch-all entry — a match * that carries no information about WHICH feature the diff belongs to. */ catchAll: string[]; } /** Resolve the diff against the map, keeping the catch-all/named distinction that * `touchedFeatures` throws away (issue #645). A single `**` entry otherwise pins * `touched.length` at 1 forever, which silently disarms BOTH packet gates: the * loud-null guard (`touched.length === 0`) and the coverage gate * (`touched.length <= 1`). */ export declare function resolveFeatureMatch(diffPaths: string[], features: FeaturePathsEntry[], repo?: string): FeatureMatch; /** Names of the features whose paths own any non-noise diff path, in * feature-map order, deduped. `repo` (owner/name) enables repo-prefixed * candidates — omit it only when the caller genuinely has no repo. */ export declare function touchedFeatures(diffPaths: string[], features: FeaturePathsEntry[], repo?: string): string[]; /** The loud null result (issue #452, checkbox 2): the map HAS features and the * diff HAS non-noise files, yet nothing matched. Silence there reads exactly * like "this PR touches no feature" — the #407/#431 failure shape, an absent * control that looks like a passing one. */ export declare const FEATURE_MATCH_NULL_WARNING: string; /** The catch-all sibling of the loud null (issue #645). "matched NOTHING" is * literally false when a `**` entry matched, so this says what actually * happened — and names the entry, because the fix is to the MAP, not the diff. */ export declare function featureMatchCatchAllWarning(catchAll: string[]): string; /** Verdict of the feature matcher, as the packet gates see it. * - `matched` — ≥1 NAMED feature owns the diff; the gates work normally. * - `null` — a match was possible and none happened (#452). * - `catch-all` — the only matches came from catch-all entries (#645). Informationally * identical to `null`; it just has a different, honest wording. */ export type FeatureMatchVerdict = "matched" | "null" | "catch-all"; export interface EvidenceCoverage { /** Distinct proofs found: evidence-bearing COMMENTS (one `issue evidence` * bundle posts one comment). Counting matched lines would let one proof * that renders as N caption lines silently satisfy N features. */ evidenceItems: number; /** Set when the PR touches >1 feature with fewer proofs than features. */ warning: string | null; } /** The deterministic layer under the per-feature evidence rule: a PR that * touches more than one feature needs at least one proof per feature. The * semantic judgment (which proof covers which feature) stays with the * reviewer; this only counts — conservatively, by comment. * * `opts.catchAllOnly` (issue #645): the caller resolved the match to catch-all * entries only. The per-feature count is then MEANINGLESS, not satisfied — the * `touched.length <= 1` short-circuit would otherwise report `warning: null` * ("coverage fine") for a PR whose features are entirely unknown. */ export declare function assessEvidenceCoverage(touched: string[], comments: Array<{ body?: string; }>, opts?: { catchAllOnly?: boolean; }): EvidenceCoverage; export declare const DEVIATIONS_HEADING_ALIASES: readonly string[]; /** The "Deviations from brief" section of a PR body — where the worker logs * off-brief pivots (field-guide pattern: deviations must be visible to review, * not buried in commit messages). Empty string when absent OR empty: an empty * section is not a signal, so `hasInterpretationSignal` stays false and the * #190 gate's output is byte-identical to before for such bodies. * * Heading level is NOT significant (issue #424): PR #423 wrote `##`, the old * `^###` matcher missed it, the section vanished from the packet, and the * intent gate opened on green — a merge-path bypass, not a display quirk. * * Where the section ENDS is level-aware, because "any heading ends it" recreates * the very bug at a different level: a log written as `### Deviations` + * `#### Deviation 1` would parse to "" and re-open the gate. Three rules: * 1. a heading at level ≤ the opening level always ends it (a sibling or an * ancestor section — `## Deviations …` then `## Sequencing`); * 2. a DEEPER heading that arrives while the section is still empty opens a * sub-heading-structured log (`#### Deviation 1`, `#### Deviation 2`): it and * its peers are section CONTENT, so only rule 1 can end it. Requires an * opening level ≥ 2 — a PR body writes every section at `##`, so under an * `# Deviations` there is no deeper level left for a sub-log and rule 2 * would swallow the entire body; * 3. otherwise (a flat section that already has content) the first deeper * heading ends it — PR #423's `###### Follow-up a human must decide` is a * new section, not a deviation row. * * Which alias heading wins is "the first one with CONTENT", not "the first one" * — see `findDeviationsSection`. */ export declare function extractDeviations(prBody: string): string; /** Headings that LOOK like a deviation log but aren't one ("### Known deviation * risks"). DISPLAY ONLY — surfaced in the review packet so a human notices * a mis-titled section, and deliberately wired to NOTHING else. It must never * reach `hasInterpretationSignal`, `intentGate` or `mergeDecision`: a fuzzy * matcher that blocks merges is the wider-mouth version of the very bug #424 * fixes. It is a tripwire, not a control. */ export declare function findNearMissDeviationHeadings(prBody: string): string[]; /** Does a PR body carry an intent signal that must be confirmed by the human * reporter before an auto-merge (issue #190)? Three deterministic signals, any * one of which fires — reusing the single deviations parser, never a second one: * 1. the explicit interpretation marker a worker embeds on a deliberate * reinterpretation (contract `markers.interpretationNote`); * 2. an "Interpretation note" heading/callout (the incident shape); * 3. a logged `### Deviations from brief` section (an off-brief pivot). * A body with none of these is not a signal — the intent gate stays a no-op and * automerge's output is byte-identical to before. */ /** The EXPLICIT reinterpretation signals only — the deliberate marker and the * "Interpretation note" callout, NOT a routine deviations log. This is what * gates in `intent-gate: trusted` mode (issue #471): the operator has chosen * to let reviewer-approved conservative deviations merge on green, while a * worker's declared reinterpretation of the ask still parks for them. */ export declare function hasExplicitInterpretationSignal(prBody: string): boolean; export declare function hasInterpretationSignal(prBody: string): boolean; /** The line the packet renders INSTEAD of an unresolved-thread count when the * GraphQL query did not answer (issue #447 slice B). * * `loop-reviewer.md` §0 makes "the packet's External review threads section * shows none unresolved" the approve PRECONDITION. A bare `catch {}` around * `ghReviewThreads` therefore rendered `unresolved: 0` + "none" from a GraphQL * blip — fabricating the exact signal an approval depends on. A count that was * never determined must never be printed as a number, least of all as zero. */ export declare const REVIEW_THREADS_UNAVAILABLE_MARKER = "\u26A0\uFE0F review threads UNAVAILABLE \u2014 unresolved count NOT determined"; /** The line the packet renders INSTEAD of "No linked issue/brief found" when the * linked issue could not be READ (as opposed to not existing). Same contract as * {@link REVIEW_THREADS_UNAVAILABLE_MARKER}: an input that did not answer is * never rendered as an input that is absent — see `specUnavailableWarning`. */ export declare function specUnavailableMarker(issueNumber: number): string; export interface PacketInput { pr: GhPR & { body?: string; }; threads: GhReviewThread[]; diff: string; /** Linked issue (spec/brief source) — null when none could be resolved. */ issue: (GhIssue & { linkKind: string; }) | null; /** Project feature map (paths only) — enables the evidence-coverage check. * Omitted when the map couldn't be fetched; the packet stays quiet then. */ features?: FeaturePathsEntry[]; /** The review-thread query did NOT answer. `threads` is then an empty array * that means "unknown", not "none" — the packet must say so instead of * counting it. */ threadsUnavailable?: boolean; /** The linked issue number whose fetch did NOT answer. `issue` is then null in * a way that means "unknown", not "none" — the packet must say so instead of * rendering the missing-brief warning it uses for a PR that links nothing. */ specUnavailable?: number | null; /** The linked number GitHub ANSWERED about, and answered that it is not a * readable issue here (stale/deleted, or a `Part of #N` naming a PR). * Mutually exclusive with {@link PacketInput.specUnavailable} and NOT a * degradation — nothing went dark. * * It exists because the artifact half was missing (PR #482 round-4 review): * the note was `console.warn`-only, so `issue` and `specUnavailable` both * stayed null and the body fell through to "⚠️ No linked issue/brief found" * — a captured `pr packet > packet.md` told the reviewer the PR links * nothing while it links #N. That is this slice's own thesis violated: every * input the packet failed to obtain is marked IN THE PACKET, never on stderr * alone. Its sibling {@link PacketInput.featureMapNotApplicable} was already * carried into the body; this was the one path without an artifact half. */ specNotReadable?: { number: number; repo: string; } | null; /** Why the feature map was not consulted (null/undefined when it was). Set → * the markdown carries the skip marker, so a piped or captured packet can no * longer look byte-complete while a check silently did not run. */ featureMapSkipCause?: string | null; /** The cross-repo target the coverage check does not apply to (null/undefined * otherwise). Mutually exclusive with {@link PacketInput.featureMapSkipCause} * and deliberately NOT a degradation: nothing was attempted, so the packet * renders a neutral note with no ⚠️ and `degraded` stays empty. Rendering the * failure marker here made a supported `--repo` packet unapprovable on a * healthy API (PR #482 review). */ featureMapNotApplicable?: string | null; /** Target repo as `owner/name`. Feature maps commonly write paths * repo-prefixed while diffs are repo-relative; passing this lets the * matcher try both (see `pathCandidates`). */ repo?: string; } /** Render the full review packet as markdown. */ export declare function buildReviewPacket(input: PacketInput): string; /** Structured form of the review packet — the SAME content `buildReviewPacket` * renders as markdown, as a machine-readable object for `pr packet --json`. * The loop reviewer consumes this: it carries the spec/brief, PR description, * deviations, CI rollup, unresolved threads, evidence + per-feature coverage, * the relevant feature slice, and the noise-filtered budgeted diff. */ export interface ReviewPacketData { pr: { number: number; title: string; headRefName?: string; baseRefName?: string; isDraft?: boolean; mergeable?: string; labels: string[]; }; /** Spec/acceptance brief. `linked:false` carries the missing-brief warning — * the reviewer must NOT infer the spec from the diff. */ spec: { linked: boolean; /** Present (and `true`) only when a linked issue EXISTS but could not be * read. `linked:false` then means "unknown", not "none" — a consumer that * reads it as "this PR links no issue" reproduces the false green. */ unavailable?: boolean; /** Present (and `true`) only when GitHub ANSWERED about the linked number * and the answer was that it is not a readable issue here. `linked:false` * then means "the link is stale / names a PR" — a finding about the LINK, * never a degradation: `degraded` stays absent and `unavailable` is unset, * so a consumer gating on degradation must not block on this. The * machine-readable half of `notReadableNote` (PR #482 round-4 review). */ notReadable?: boolean; /** The issue number that could not be read (set with `unavailable`, and * also with `notReadable`). */ issueNumber?: number; issue?: { number: number; linkKind: string; title: string; body: string; truncated: boolean; }; warning?: string; /** The neutral note rendered for `notReadable` — deliberately NOT `warning`, * which is the field the missing-brief and unavailable-brief findings use. */ notReadableNote?: string; }; prDescription?: { text: string; truncated: boolean; }; /** The "### Deviations from brief" section, when the worker logged one. */ deviations?: string; ci: CiSummary; reviewThreads: { /** `null` — never 0 — when the query did not answer. A consumer that reads * this as a count MUST handle null; reporting zero unresolved threads from * a failed fetch is the false green issue #447 slice B exists to kill. */ unresolved: number | null; /** Present (and `true`) only when the count could not be determined. */ unavailable?: boolean; items: { path: string | null; line: number | null; author: string; body: string; }[]; }; evidence: { lines: string[]; /** Present only when the feature map was supplied AND the PR touches ≥1 feature. */ featuresTouched?: string[]; /** Present (and `true`) only when every name in `featuresTouched` came from a * catch-all map entry (issue #645). `featuresTouched` then identifies NOTHING — * a consumer reading it as "this PR touches feature X" reproduces the false * green. Pairs with a `featureMatchWarning` and a non-null `coverageWarning`. */ featuresTouchedCatchAllOnly?: boolean; /** The multi-feature under-coverage warning (null when covered / N/A). Also * set — never null — on a catch-all-only match, where the per-feature proof * count is unknown rather than satisfied. */ coverageWarning?: string | null; /** The skipped-feature-map marker, mirroring the markdown body — present * only when the per-feature coverage check did not run BECAUSE THE FETCH * FAILED. A degradation: `degraded` carries `shipflow-api` alongside it. */ featureMapSkipped?: string; /** The neutral not-applicable note — present when a cross-repo `--repo` * target means no feature map could apply. NOT a degradation: no ⚠️, and * `degraded` stays absent. A consumer gating on degradation must read * `featureMapSkipped`/`degraded`, never merely "the coverage lines are * missing" (PR #482 review). */ featureMapNotApplicable?: string; /** Set when the map had features and the diff had non-noise files, yet no * NAMED feature matched — the loud null result (issue #452), in either of its * two shapes: nothing matched at all, or only catch-all entries did (#645). * Advisory, NOT a degradation: `degraded` stays absent. */ featureMatchWarning?: string; }; /** The relevant feature slice — omitted when there's no map or nothing touched. */ features?: { touched: { name: string; layer?: string; testPriority?: string; description?: string; }[]; sameLayerNeighbors: string[]; }; diff: FilteredDiff; } export declare function buildReviewPacketData(input: PacketInput): ReviewPacketData; //# sourceMappingURL=packet.d.ts.map