import { type AuditCandidate, type ClearanceEvidence } from "./pr-state.js"; export { _setExec, _resetExec } from "./sh.js"; export declare function ghInstalled(): boolean; export declare function ghAuthStatus(): "logged-in" | "logged-out"; export declare function ghAuthToken(): string; /** Runs `gh auth login` interactively (inheriting stdio). Returns true on success. */ export declare function ghAuthLogin(): boolean; export interface GhIssue { number: number; title: string; body: string; state: string; labels: { name: string; }[]; assignees: { login: string; }[]; url: string; createdAt: string; authorAssociation?: string; /** Set when the enrichment call FAILED, so an undefined association means "we * could not tell", not "GitHub said CONTRIBUTOR". Both stay gated — this only * makes the two tellable apart in logs. */ associationLookupFailed?: boolean; } export declare function ghIssueView(repo: string, number: number): GhIssue; /** Why a {@link ghIssueView} read failed — the only two answers that matter to a * caller deciding whether a dependency went dark. * * `"not-an-issue"` means GitHub **answered** and the answer was that this * number is not a readable issue here (deleted/transferred, or a `Part of #N` * naming a PR — `gh issue view` refuses those). Nothing is unavailable. * `"unavailable"` means we did **not** get such an answer: an outage, a 5xx, a * timeout, a rate limit, a network failure — or wording this classifier does * not recognise. */ export type IssueReadFailure = "not-an-issue" | "unavailable"; /** Classify a thrown {@link ghIssueView} failure — from THAT call's own error, * never from a second call against a different transport. * * This is the round-4 fix for PR #482's third false green. Round 3 classified * by probing {@link ghIssueOrPrState} (`gh api repos/../issues/N`, REST) after * `gh issue view --json` (GraphQL) threw. On a GraphQL-only outage the brief * fetch failed while REST still answered, so a REAL outage was classified as a * stale link: no degradation marker, no `spec.unavailable`, and the packet * blamed the author for a brief GitHub had simply not served. A probe of a * dependency that did not fail proves nothing about the one that did. * * It also fixes the complementary hole in that probe: `ghIssueOrPrState` * collapses an ANSWERED 404 into the same `null` as a connection failure (a * deliberate fail-safe for its own caller, issue #337), so a deleted-issue link * still degraded — only the PR-number case was ever rescued. GitHub's own * "could not resolve" answer distinguishes the two, and it arrives on the * failing call, so `ghIssueOrPrState` and issue #337's caller are untouched. * * Pure over the error value: unit-testable without a subprocess. `execSync` * folds the child's stderr into both `e.message` and `e.stderr`; read both, so * a caller that pipes stderr differently still classifies the same. */ export declare function classifyIssueReadFailure(e: unknown): IssueReadFailure; /** * Lifecycle state of an issue OR pull request — the REST issues endpoint * covers both (every PR is an issue), so one call handles either dependency * kind, cross-repo included. Returns null when unreadable (no access, deleted * repo, network error) so callers fail safe to "still waiting" (issue #337). * * That null is LOSSY on purpose and must stay that way: it folds an answered * 404 into a connection failure so a dependency whose state we cannot read is * never reported as closed. It is therefore useless as an "is GitHub up?" * probe — see {@link classifyIssueReadFailure}, which is what `pr packet` uses * instead of calling this. */ export declare function ghIssueOrPrState(repo: string, number: number): "open" | "closed" | null; /** Label every ShipFlow-created issue wears, so agent-filed issues are * distinguishable from human-filed ones at a glance and filterable. */ export declare const VIA_SHIPFLOW_LABEL: "via-shipflow"; /** Create an issue. Deliberately takes NO assignees — see * {@link ghIssueAddAssignees}. * * `--assignee` used to be a parameter here, and it is the reason PR #688's * reviewer measured one `issue create` producing TWO issues server-side. * MEASURED against live GitHub (not inferred): `gh issue create --assignee * ` CREATES the issue first and THEN fails * `replaceActorsForAssignable` — exit 1, stderr `GraphQL: Could not resolve to * a user or bot with the login '…'`, stdout EMPTY, so the caller cannot even * learn the URL of what it just filed. Any caller that treats that exit as * "nothing was created" files a duplicate. The flag therefore does not come * back: assignment is a SEPARATE mutation on an issue that already exists, so * its failure can never cost, duplicate, or orphan the filing. */ export declare function ghIssueCreate(repo: string, title: string, body: string, labels?: string[]): { url: string; number: number; }; /** Assign logins to an issue that ALREADY EXISTS — the separate, retry-free * half of the create/assign split (see {@link ghIssueCreate}). * * Deliberately NOT `{ stdio: "ignore" }` (unlike `ghIssueAddLabels`): ignoring * fd 2 makes Node leave `err.stderr` null, and gh's reason for refusing an * assignee is the only thing the caller can put in front of an operator. * * MEASURED shapes, live: an unresolvable login exits 1 with `failed to update * : GraphQL: Could not resolve to a user or bot with the login '…' * (replaceActorsForAssignable)` on stderr; a REAL user without push access * exits **0**, prints the issue URL, and silently drops the assignee — which * is why the caller must VERIFY rather than trust the exit code * ({@link ghIssueAssignees}). */ export declare function ghIssueAddAssignees(repo: string, number: number, logins: string[]): void; /** The logins GitHub says are ACTUALLY assigned to an issue. Narrow read * (`--json assignees` only) because it exists solely to catch the exit-0 * silent drop above — a `--json assignees` refusal is cheaper to explain than * a full {@link ghIssueView} payload. */ export declare function ghIssueAssignees(repo: string, number: number): string[]; export declare function ghIssueList(repo: string, state?: string, limit?: number, assignee?: string): GhIssue[]; /** The filters `gh issue list` (and the GitHub issues API) support. */ export interface GhIssueFilters { state?: string; labels?: string[]; assignee?: string; author?: string; mention?: string; milestone?: string; search?: string; limit?: number; } /** Issue with the detail fields the export needs on top of GhIssue. */ export interface GhIssueDetail extends GhIssue { author: { login: string; } | null; milestone: { title: string; } | null; updatedAt: string; closedAt: string | null; } /** `gh issue list` with the full GitHub filter set and detail fields. */ export declare function ghIssueListFiltered(repo: string, f?: GhIssueFilters): GhIssueDetail[]; export interface GhPRCreateResult { url: string; number: number; } export declare function ghPRCreate(args: { repo: string; title?: string; body: string; base?: string; head?: string; draft?: boolean; }): GhPRCreateResult; export type MergeMethod = "squash" | "merge" | "rebase"; /** The repo's allowed merge methods, from its settings. Best-effort: an * unreadable repo (rate limit, perms) reports all three allowed so the caller * falls back to today's behavior rather than refusing to merge. */ export declare function ghRepoMergeMethods(repo: string): { squash: boolean; merge: boolean; rebase: boolean; }; /** Pick the merge method: the preference if the repo allows it, else the first * allowed of squash → merge → rebase (issue #494 — repos that disallow squash * made every automerge fail with a hardcoded --squash). Pure; tested. */ export declare function chooseMergeMethod(preferred: MergeMethod, allowed: { squash: boolean; merge: boolean; rebase: boolean; }): MergeMethod; /** Raw `gh pr checks` rows (name\tstate\t…), one per check; [] when the PR * has no checks yet or the read fails — callers treat empty as PENDING, * never green (issue #608). */ /** Own open PRs, ascending PR number — the --all-ready sweep order. */ export declare function ghOwnOpenPRs(repo: string, author: string): { number: number; isDraft: boolean; }[]; export declare function ghPRCheckLines(repo: string, number: number): string[]; export declare function ghPRMerge(repo: string, number: number, mode?: MergeMethod, deleteBranch?: boolean): { mergedSha: string; headBranch: string; }; export interface GhReview { author: { login: string; } | null; state: string; submittedAt: string; } export interface GhComment { author: { login: string; } | null; createdAt: string; body: string; /** Node id (`IC_kwDO…`). Free — `gh pr list --json comments` and * `gh issue list --json comments` both return it inside the comment object, * so no field has to be added to `PR_FIELDS` and no extra call is made. * The reporter-correction detector (issue #442) embeds it in the * `rework-from` marker so "this comment was already acted on" is an EXACT * test rather than a timestamp comparison. Optional so an older gh (or a * hand-built test fixture) simply falls back to the ordering check. */ id?: string; /** Permalink to the comment — carried onto the inbox row so the orchestrator * can quote a reporter correction without a second API call. */ url?: string; /** OWNER | MEMBER | COLLABORATOR | CONTRIBUTOR | NONE — per COMMENT, unlike * `GhPR.authorAssociation`, which gh only exposes via GraphQL. This one ships * in the same `comments` payload for free. It is the ONLY discriminator that * works for "could this person's word settle the PR's intent": the loop and * the human comment under the SAME login, so an author-login filter is * useless here (verified on PR #401 — every comment is one MEMBER account). * Undefined is UNTRUSTED, never trusted. */ authorAssociation?: string; } export interface GhCheck { name?: string; status?: string; conclusion?: string; state?: string; } export interface GhPR { number: number; title: string; body?: string; headRefName: string; baseRefName?: string; url: string; isDraft: boolean; reviewDecision: string; mergeable?: string; author?: { login?: string; }; isCrossRepository?: boolean; /** Diagnostic, NOT a trust input — the earlier wording here implied otherwise, * but `headTrust()` never consults it: the fork gate is `isCrossRepository`, * a boolean no repository name can spoof. Queried so a `fork-head` refusal * can name WHOSE fork was refused — the login is the only human-readable * identifier `gh pr list` gives for the head repo. `ghCompareHead` now also * reads it to owner-qualify a cross-repo head for the compare API (PR #531 * review) — still not a trust input: it only decides WHICH ref we compare, * and an absent owner yields null (block), never a permissive fallback. */ headRepositoryOwner?: { login?: string; }; authorAssociation?: string; /** Set when the `authorAssociation` enrichment call FAILED, so an undefined * association means "we could not tell", not "GitHub said CONTRIBUTOR". * Both stay untrusted — this only makes the two tellable apart. */ associationLookupFailed?: boolean; labels?: { name: string; }[]; /** Current head commit SHA. `isApproved` binds `shipflow-approved` to this * (issue #637). Optional: missing/unreadable fails closed on the label path. */ headRefOid?: string; reviews: GhReview[]; comments: GhComment[]; statusCheckRollup: GhCheck[]; closingIssuesReferences: { number: number; title: string; }[]; createdAt?: string; updatedAt: string; } /** Open PRs authored by the current gh user. */ export declare function ghPRListMine(repo: string, limit?: number): GhPR[]; /** GitHub caps `first` on EVERY GraphQL connection at 100. Asking for more is * not clamped server-side — the whole query fails with `EXCESSIVE_PAGINATION`, * `data.repository` comes back null, and `gh` exits non-zero (PR #450 review, * verified live). So a window wider than this must be walked with `after:`, * never requested in one shot. */ export declare const GH_GRAPHQL_PAGE_MAX = 100; /** `authorAssociation` per open PR number. GraphQL-only: `gh pr list --json` * does NOT expose the field (verified against gh's field list), so the sweep's * trust check needs this second call. Paginated and newest-first — see * `ghAuthorAssociations`. Throws on failure — the caller must fail CLOSED * rather than treat "unknown association" as trusted. */ export declare function ghPRAuthorAssociations(repo: string, limit?: number): Map; /** `authorAssociation` per OPEN ISSUE number. GraphQL-only, exactly like the PR * side: `gh issue list --json` does NOT expose the field, so the intake gate * (issue #448) needs this second call. Paginated and newest-first — see * `ghAuthorAssociations`. Throws on failure — the caller decides what an * unreadable association means (it must not read as "inside the code org"). * Open-only, like `ghPRAuthorAssociations`: the underlying query is * `states:OPEN`, which is why `ghIssueListWithAssociations` takes no `state`. */ export declare function ghIssueAuthorAssociations(repo: string, limit?: number): Map; /** OPEN issues enriched with `authorAssociation` so the intake gate can run * BEFORE the loop claims anything. A failed enrichment leaves the association * undefined and stamps `associationLookupFailed`, which the gate reads as * outside the org — it still fails CLOSED, but only IN MEMORY for that pass: * `issue next` must not PERSIST `needs-reporter-approval` off a lookup it never * read (PR #450 review). Mirrors `ghPRListAll`'s loud failure — a silent empty * enrichment made a one-off outage read as a real GitHub verdict. * * **Open-only by construction — there is deliberately no `state` parameter.** * `ghAuthorAssociations` queries `states:OPEN`, so a `state: "closed"`/`"all"` * listing would return issues the association map cannot contain: every one of * them would resolve to `undefined` and, under `isOutsideCodeOrg`'s fail-closed * rule, read as filed outside the code org. Constraining the signature makes * that mismatch unrepresentable rather than merely undocumented * (PR #450 review). */ export declare function ghIssueListWithAssociations(repo: string, limit?: number, assignee?: string): GhIssue[]; /** Every open PR regardless of author — the repo-wide conflict sweep (issue #393), * enriched with `authorAssociation` so the head-trust filter can run BEFORE any * branch is checked out. If the enrichment call fails the association stays * undefined, which `headTrust()` treats as untrusted (fail closed). */ export declare function ghPRListAll(repo: string, limit?: number): GhPR[]; /** The authenticated GitHub login (for "is this comment mine?" checks). */ export interface GhUser { login: string; id: number; name: string; email: string; } /** The authenticated GitHub account (login/id/name/public email). */ export declare function ghUser(): GhUser; /** The email to commit with: the account's public email, else the GitHub * noreply form — which forges always match to the account. */ export declare function ghMatchedEmail(u: GhUser): string; export declare function ghCurrentLogin(): string; /** Open issues carrying a given label, newest-updated first, with comments. */ export declare function ghIssueListByLabel(repo: string, label: string, limit?: number): (GhIssue & { comments: GhComment[]; })[]; /** Issue numbers that some OPEN PR is the work for (any author) — closing * keywords AND `Part of #N` slice links (`linkedIssueNumbers`). An issue * with such a PR has work genuinely in flight, so its in-progress label is * NOT stale even when the claim TTL has lapsed (issue #216, #634). */ export declare function ghOpenPRClosingIssues(repo: string): Set; /** Raw unified diff for a PR (used by the review packet and by `pr diff`). * * SERVER-SIDE, and that is the whole point (issue #407). `gh pr diff --repo` * asks GitHub what the PR changes; it resolves nothing from the cwd — not HEAD, * not a local base ref, not the index. A caller standing in a **detached** * worktree, or one whose local `main` is 191 commits stale, gets the same bytes * as a caller on the PR branch. The `security-review` skill's ambient * `git diff ...HEAD` does not have that property: in * `.claude/worktrees/shipflow-loop` (detached at a fixed sha) it diffs a commit * against itself, captures zero bytes, and still emits a fully-formed CLEAN * verdict. Never swap this for local git to "save a network call". */ export declare function ghPRDiffText(repo: string, number: number): string; /** The paths a PR changes, per GitHub's REST **files** endpoint. * * The INDEPENDENT witness in the scan-attestation gate (issue #407) — and the * independence is the whole point, so this is deliberately NOT * `gh pr diff --name-only`. That flag queries no file list: `gh` regexes * `diff --git` headers back out of the very diff bytes {@link ghPRDiffText} * already fetched. Two readings of ONE response can never disagree, so the * census would empty in lockstep with the capture and `pr diff`'s * `files === 0 && names > 0` guard could not fire on the exact failure it * exists to catch (PR #484 review — found independently by gemini and codex). * * `/pulls//files` is a different endpoint with a different response shape, * so "the capture is empty" and "the PR changes nothing" become * distinguishable. `--paginate` because that endpoint pages at 100: an * unpaginated read would under-count a large PR and turn a correct attestation * into a "mismatch" refusal. * * Server-side also means it sees files the local index never had: a PR adding * brand-new files lists them here whether or not the caller's worktree has ever * fetched the branch. */ export declare function ghPRChangedFiles(repo: string, number: number): string[]; /** The most recent MERGED PR whose head was `branch`, or null when none (open, * never PR'd, or closed-unmerged). Feeds the merged-branch GC (issue #455): * `headRefOid` is the tip the PR actually merged, which the planner compares * against the local branch tip so unpushed work is never deleted. */ export declare function ghPRMergedByHead(repo: string, branch: string): { number: number; headRefOid: string; } | null; /** * The ref to hand the compare API as `head`. GitHub resolves `base...head` * INSIDE the base repo, so a fork PR's bare `headRefName` is wrong twice over * (PR #531 review): usually it 404s because the branch does not exist upstream, * and when the fork's branch shares a name with an upstream one — `main` is the * common case — it silently compares `main...main`, returns `behind_by: 0`, and * waves a stale fork head straight through the freshness gate. Cross-repo heads * must be owner-qualified as `owner:branch` (REST "Compare two commits"). * * Returns null when the head is cross-repo and GitHub reports no head-repo owner * (deleted fork): there is no ref we could compare, and inventing one is exactly * the fail-open this gate exists to prevent. * * `isCrossRepository` is the primary signal; the owner comparison is a fallback * for an older `gh` that omits it. Either one saying "fork" means fork. */ export declare function ghCompareHead(repo: string, pr: Pick): string | null; /** How stale a PR head is against its base. `behindBy` is null when we could not * find out — which always BLOCKS the merge. `unresolvable` splits that null in * two: a transient probe failure (retry next tick) vs. a comparison that can * never succeed by waiting, which the loop must escalate once instead of * re-polling forever (PR #531 review). */ export interface PRFreshness { behindBy: number | null; unresolvable?: boolean; } /** * How many commits the PR's head is BEHIND its base, via the compare API — the * only authoritative source: without strict branch protection GitHub reports a * behind branch's mergeStateStatus as CLEAN, so that field cannot gate a merge * (issue #530). Callers must treat a null `behindBy` as a blocker — never merge * on unknown freshness (the same fail-closed posture as the review-thread probe). * * A 404 from compare is PERMANENT, not transient: the ref pair does not resolve * (deleted head branch, deleted fork, or a token that cannot see the head repo). * Re-running the identical call next tick cannot change that answer, so it comes * back `unresolvable` — still blocking, but routed to one escalation instead of * an endless re-poll. Every other failure (network, rate limit, gh blip) stays * retryable. */ export declare function ghPRFreshness(repo: string, pr: GhPR): PRFreshness; /** When the PR head's latest commit was committed, ISO 8601 (issue #687). * * Dedicated GraphQL — not `GhPR.updatedAt` and not `PR_FIELDS`. `updatedAt` * rewinds on every comment and review, which would reset the settle window * on the very signal we are waiting for. * * THROWS on transport failure, GraphQL `errors`, a null repository, or a * missing/unparseable `committedDate`. A swallowed miss would look like "no * head"; the caller fails closed. */ export declare function ghPRLastHeadAt(repo: string, number: number): string; /** One PR with the full field set (CI, reviews, labels, mergeable, base). */ export declare function ghPRView(repo: string, number: number): GhPR; /** Current head SHA for `pr approve`'s bind (issue #637). Null if gh blips * or the field is empty — the caller validates 40-hex and fails closed. * Dedicated `--json headRefOid` so approve does not pull the full PR_FIELDS * payload. */ export declare function ghPRHeadOid(repo: string, number: number): string | null; /** The palette color for a label name, or undefined for labels ShipFlow * doesn't own (repo-specific names stay whatever color the repo chose). */ export declare function labelColorFor(name: string): string | undefined; export declare function ghEnsureLabel(repo: string, name: string, color?: string, description?: string): void; export declare function ghIssueAddLabels(repo: string, number: number, labels: string[]): void; export declare function ghIssueRemoveLabel(repo: string, number: number, label: string): void; export declare function ghIssueComment(repo: string, number: number, body: string): void; export interface LabelRemoval { actor: string; actorIsBot: boolean; actorKnown: boolean; createdAt: string; } /** One read of an issue's timeline, carrying every signal the gates need from * it — so asking for both costs ONE paginated call, not two. */ export interface IssueTimelineSignals { /** Every `unlabeled` event for the requested label, oldest-first. */ removals: LabelRemoval[]; /** `created_at` of every title rename (`renamed` in REST, `RenamedTitleEvent` * in GraphQL), oldest-first; "" when the timeline omitted the timestamp. * * Renames are collected because `Issue.lastEditedAt` tracks **body edits * only** — a title change leaves it untouched (verified live: `golang/go` * #80581 renamed after its last body edit; `cli/cli` #13924 and this repo's * #460/#447/#440/#435/#402 all renamed with `lastEditedAt: null`). Binding * approval to `lastEditedAt` alone therefore left approve-then-swap open * THROUGH THE TITLE, which on a thin-bodied issue is the whole spec * (PR #450 review round 6). */ renamedAt: string[]; } /** Every `unlabeled` timeline event for `label`, with whether the actor was a * bot and WHEN it happened. Oldest-first (gh's native timeline order). * * The actor matters because the intent gate can no longer treat a bare removal * as a clearance (issue #411): a maintainer removing the label in the GitHub * UI is the documented human override, while a bot removal is only a clearance * when the server also recorded an audit comment for it. Best-effort — any * gh/API failure returns [] (fail toward re-checking the signal, never toward * a silent merge). * * `createdAt` (ISO 8601, or "" when the timeline omitted it) is what the intake * gate compares the issue's content changes against, so an approval binds to * the content that was approved (PR #450 review round 5). */ export declare function ghLabelRemovals(repo: string, number: number, label: string): LabelRemoval[]; /** `ghLabelRemovals` + title renames from the SAME paginated timeline read. * Best-effort in one piece: any gh/API failure returns empty arrays, which the * intake gate reads as "no approval on record" and withholds. */ export declare function ghIssueTimelineSignals(repo: string, number: number, label: string): IssueTimelineSignals; /** Audit-comment candidates read over REST — the ONE source that can tell the * ShipFlow App apart from a person or another bot (issue #537). * * WHY NOT `ghIssueComments`. That function shells `gh issue view --json * comments`, i.e. GraphQL, whose comment author exposes ONLY `login` — no * botness bit, and a Bot's login there carries NO `[bot]` suffix (measured on * PR #489, gh 2.95.0: login `renaissshipflow`, association `NONE`). The audit * check's author rule therefore could never fire, and the #411 clearance path * was dead from the day it shipped. REST carries `user.type`, which is what * makes the record attributable. * * WHY `ghIssueComments` IS NOT SIMPLY RE-POINTED AT REST. Its `id` is a GraphQL * node id (`IC_…`) consumed by `ghUpdateIssueComment` (the `updateIssueComment` * mutation) and by `reworkAttemptsOn`. REST returns numeric ids, which that * mutation rejects — `issue escalate --update` would break silently. Two * readers, two id spaces, on purpose. * * Best-effort: [] on any gh failure. The caller turns that into 0 audit * comments, which fails toward RE-ARMING the gate. */ export declare function ghIntentGateAuditCandidates(repo: string, number: number): AuditCandidate[]; /** When an issue's **body** was last edited, ISO 8601, or `null` when it has * never been edited. GraphQL-only: `gh issue view --json` exposes no such field. * * BODY ONLY — a title change is a `RenamedTitleEvent` and leaves this field * untouched (round 6). Content binding therefore needs `renamedAt` from * `ghIssueTimelineSignals` alongside this; neither alone covers an edit. * * **THROWS on any failure — deliberately.** `null` here means "never edited", * which the intake gate reads as "the approved content is still the content". * A swallowed error returning `null` would therefore make an unreadable lookup * indistinguishable from a clean issue and re-open the approve-then-swap hole * this exists to close (PR #450 review round 5). The caller fails closed. */ export declare function ghIssueLastEditedAt(repo: string, number: number): string | null; /** How many of a PR's comments ARE the intent-gate clearance audit record — the * artifact the server stamps on every `needs-reporter-review` removal. * * Author-checked, not just body-matched: the marker literal ships in the repo's * own contract file, so counting it wherever it appears let any comment quoting * that hunk disarm the gate permanently (PR #441 review). * Best-effort: 0 on any gh failure, which fails toward re-arming the gate. */ export declare function ghIntentGateAuditCount(repo: string, number: number): number; /** WHEN this PR's intent gate was last genuinely CLEARED, ISO 8601 — or * `undefined` when no accepted audit record exists (or the read failed). * * The anchor `gateOpenedAt` (pr-state.ts) needs and could not have (issue * #650). That function scans `pr.comments`, which is GRAPHQL and carries no * botness bit at all, so the App's own clearance record can never be accepted * there — the #537 blind spot, at a second call site #537 did not re-point. * Same author check as `ghIntentGateAuditCount`, on the same REST candidates; * this one keeps the winning record's DATE instead of counting. * * NEWEST accepted record wins: a gate can be armed, cleared, and re-armed, and * only the last clearance bounds the arming that is standing now. * * Best-effort: `undefined` on any gh failure or unparseable date, which leaves * the caller on the pre-#650 GraphQL-only anchor — an anchor that can only be * too OLD, i.e. a gate that escalates early rather than one that never fires. * ONE `gh api` call, and the caller must only make it for a GATED PR. */ export declare function ghIntentGateLastClearedAt(repo: string, number: number): string | undefined; /** The evidence `intentGateEverCleared` (pr-state.ts) decides from. Kept here, * not there, so the decision itself stays pure and unit-testable. */ export declare function ghIntentGateClearance(repo: string, number: number, label: string): ClearanceEvidence; /** The issue's author login — the escalate command's owner-of-last-resort. * Best-effort: null on any gh failure rather than blocking the escalation. */ export declare function ghIssueAuthor(repo: string, number: number): string | null; export interface GhIssueComment { id: string; body: string; viewerDidAuthor: boolean; /** Who wrote it. Carried so a body-matched artifact can be author-checked * before it is trusted (PR #441) — `gh issue view --json comments` returns * both of these already, at no extra call. */ authorLogin: string; authorAssociation: string; } /** All comments on an issue, oldest-first (gh's native order). * * ⚠️ GraphQL, and therefore BLIND TO BOTNESS (issue #537). `author` carries * ONLY `login`, and a Bot's login here has NO `[bot]` suffix — measured on the * live PR #489 audit comment, whose REST identity is * `renaissshipflow[bot]`/`Bot` and whose GraphQL identity is plain * `renaissshipflow`. Do NOT point the intent-gate audit check back at this * function: that is precisely the bug that left the #411 clearance path dead. * `ghIntentGateAuditCandidates` (REST) is the reader for that decision; this * one stays GraphQL because its `id` is a node id the update mutation needs. */ export declare function ghIssueComments(repo: string, number: number): GhIssueComment[]; /** Edit an existing issue comment in place (id = node id from ghIssueComments). */ export declare function ghUpdateIssueComment(commentId: string, body: string): void; export interface GhReviewThread { id: string; isResolved: boolean; path: string; line: number | null; author: string; body: string; /** First-comment createdAt — settle clock (issue #687). */ submittedAt?: string; } /** Create a formal PR review with inline comments in one call — the CLI * counterpart of the server's CreatePRReview (issue #96). Posts via the REST * reviews endpoint with a JSON body on stdin so multi-line finding bodies * survive intact. Event is always COMMENT (advisory). */ export declare function ghCreateReview(repo: string, number: number, payload: { event: string; body: string; comments: { path: string; line: number; side: string; body: string; }[]; }): void; export declare function ghReviewThreads(repo: string, number: number): GhReviewThread[]; /** Mark a review thread resolved (after the loop has addressed it). Best-effort. */ export declare function ghResolveReviewThread(threadId: string): void; //# sourceMappingURL=gh.d.ts.map