import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Result from "effect/Result";import * as Schema from "effect/Schema";import { resolvePullRequestAuthorFilter, type PullRequestAction, type PullRequestActor, type PullRequestInvolvement, type PullRequestListFilters, type PullRequestListState, type PullRequestMergeMethod, type PullRequestOmittedFileStat, type PullRequestReaction, type PullRequestReactionContent, type PullRequestReviewCommentDraft, type PullRequestReviewVerdict, type PullRequestReviewerCandidateList, type PullRequestReviewerKind, type PullRequestThreadCommentsResult, type PullRequestUpdateMethod,} from "@t3tools/contracts"; import * as GitHubCli from "../sourceControl/GitHubCli.ts";import * as GitHubGraphQlBudget from "../sourceControl/githubGraphQlBudget.ts";import * as SourceControlRateLimit from "../sourceControl/SourceControlRateLimit.ts";import { ACTOR_AVATARS_GRAPHQL_QUERY, ADD_REACTION_GRAPHQL_MUTATION, buildReviewSubmissionJson, buildReviewerRequestJson, decodeActorAvatarsJson, decodePullRequestActivityJson, decodePullRequestDetailJson, decodePullRequestFilesJson, decodePullRequestListJson, decodePullRequestNodeIdJson, decodePullRequestSearchJson, decodePullRequestStatsJson, decodeReactionSubjectScopeJson, decodeRepositoryAccessJson, decodeReviewerCandidatesJson, decodeReviewDismissalsJson, decodeReviewThreadCommentsJson, decodeReviewThreadsJson, buildPullRequestStatsGraphQlQuery, encodeGraphQlRequestJson, pullRequestSearchGraphQlQuery, PULL_REQUEST_SEARCH_MAX_ROWS, PULL_REQUEST_ACTIVITY_JSON_FIELDS, BASE_COMPARISON_GRAPHQL_QUERY, decodeBaseComparisonJson, PULL_REQUEST_DETAIL_JSON_FIELDS, PULL_REQUEST_LIST_JSON_FIELDS, PULL_REQUEST_NODE_ID_GRAPHQL_QUERY, REACTION_SUBJECT_PULL_REQUEST_GRAPHQL_QUERY, REMOVE_REACTION_GRAPHQL_MUTATION, gitHubReactionContent, REPOSITORY_ACCESS_JSON_FIELDS, RESOLVE_REVIEW_THREAD_GRAPHQL_MUTATION, REVIEWER_CANDIDATES_GRAPHQL_QUERY, REVIEW_THREAD_COMMENTS_GRAPHQL_QUERY, REVIEW_DISMISSALS_GRAPHQL_QUERY, REVIEW_THREAD_REPLY_GRAPHQL_MUTATION, REVIEW_THREADS_GRAPHQL_QUERY, reviewThreadConversation, UNRESOLVE_REVIEW_THREAD_GRAPHQL_MUTATION, UPDATE_ISSUE_COMMENT_GRAPHQL_MUTATION, UPDATE_PULL_REQUEST_GRAPHQL_MUTATION, UPDATE_REVIEW_COMMENT_GRAPHQL_MUTATION, VIEWER_PERMISSIONS_GRAPHQL_QUERY, decodeViewerPermissionsJson, type GitHubBaseComparison, type GitHubPullRequestDetail, type GitHubPullRequestActivity, type GitHubPullRequestListItem, type GitHubPullRequestSearchItem, type GitHubReviewThreadComments, type GitHubRepositoryAccess, type GitHubReviewThreadEntry, type GitHubReviewThreadPage, type GitHubViewerAccess,} from "./gitHubPullRequestJson.ts";import type { ProviderListCursor } from "./PullRequestProvider.ts"; /** * Names the read that produced unusable output, so a failure reports the call it came from * rather than borrowing another operation's message. */export class GitHubPullRequestReadError extends Schema.TaggedErrorClass<GitHubPullRequestReadError>()( "GitHubPullRequestReadError", { command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, cause: Schema.Defect(), },) { get detail(): string { return `GitHub CLI returned an unreadable ${this.operation} response.`; } override get message(): string { return `GitHub CLI failed in ${this.operation}: ${this.detail}`; }} /** Not a decode failure: gh answered, the account it answered for just has no login. */export class GitHubViewerLoginUnavailableError extends Schema.TaggedErrorClass<GitHubViewerLoginUnavailableError>()( "GitHubViewerLoginUnavailableError", { command: Schema.Literal("gh"), cwd: Schema.String, },) { get detail(): string { return "GitHub CLI returned no login for the authenticated account."; } override get message(): string { return `GitHub CLI failed in getViewerLogin: ${this.detail}`; }} /** Not a decode failure: the reader asked to carry on from a cursor this walk never handed out. */export class GitHubDiffCursorError extends Schema.TaggedErrorClass<GitHubDiffCursorError>()( "GitHubDiffCursorError", { command: Schema.Literal("gh"), cwd: Schema.String, },) { get detail(): string { return "The diff cursor was not one this pull request handed out."; } override get message(): string { return `GitHub CLI failed in getPullRequestDiff: ${this.detail}`; }} /** Not a decode failure: the reader named a commit that is not a sha this repository could hold. */export class GitHubDiffCommitError extends Schema.TaggedErrorClass<GitHubDiffCommitError>()( "GitHubDiffCommitError", { command: Schema.Literal("gh"), cwd: Schema.String, },) { get detail(): string { return "The named commit was not a commit sha."; } override get message(): string { return `GitHub CLI failed in getPullRequestDiff: ${this.detail}`; }} /** The revisions read successfully, but cannot name both sides this file needs. */export class GitHubDiffRevisionsUnavailableError extends Schema.TaggedErrorClass<GitHubDiffRevisionsUnavailableError>()( "GitHubDiffRevisionsUnavailableError", { command: Schema.Literal("gh"), cwd: Schema.String, number: Schema.Int, commit: Schema.optional(Schema.String), },) { get detail(): string { return this.commit === undefined ? `Pull request #${this.number} reported no usable base and head revisions.` : `Commit ${this.commit} reported no usable revisions for this file.`; } override get message(): string { return `GitHub CLI failed in getPullRequestDiffFileContents: ${this.detail}`; }} /** A blob exists, but expanding it would be unsafe or would not produce text. */export class GitHubDiffFileContentsUnavailableError extends Schema.TaggedErrorClass<GitHubDiffFileContentsUnavailableError>()( "GitHubDiffFileContentsUnavailableError", { command: Schema.Literal("gh"), cwd: Schema.String, path: Schema.String, reason: Schema.Literals(["oversized", "binary"]), },) { get detail(): string { return this.reason === "oversized" ? `The diff file '${this.path}' exceeds the 1 MB expansion limit.` : `The diff file '${this.path}' is binary.`; } override get message(): string { return `GitHub CLI failed in getPullRequestDiffFileContents: ${this.detail}`; }} /** * Not a decode failure: a repository was named that cannot go into a search or into a GraphQL * document as itself. Every qualifier and every alias below is composed from `owner/name`, so a * name that is not one is refused here rather than escaped into something GitHub might read as a * qualifier of its own. */export class GitHubRepositorySelectorError extends Schema.TaggedErrorClass<GitHubRepositorySelectorError>()( "GitHubRepositorySelectorError", { command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, },) { get detail(): string { return "A repository was named that GitHub cannot address."; } override get message(): string { return `GitHub CLI failed in ${this.operation}: ${this.detail}`; }} /** Not a decode failure: the reader named a subject this pull request never handed out. */export class GitHubSubjectScopeError extends Schema.TaggedErrorClass<GitHubSubjectScopeError>()( "GitHubSubjectScopeError", { command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, },) { get detail(): string { return "The named subject did not belong to the named pull request."; } override get message(): string { return `GitHub CLI failed in ${this.operation}: ${this.detail}`; }} export type GitHubPullRequestCliError = | GitHubCli.GitHubCliError | GitHubPullRequestReadError | GitHubDiffCursorError | GitHubDiffCommitError | GitHubDiffRevisionsUnavailableError | GitHubDiffFileContentsUnavailableError | GitHubRepositorySelectorError | GitHubSubjectScopeError | SourceControlRateLimit.SourceControlRateLimitPausedError | GitHubViewerLoginUnavailableError; /** A large pull request can produce a multi-megabyte patch; past this it is truncated. */const DIFF_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;const DIFF_TIMEOUT_MS = 60_000;/** Pierre expansion is for source files, not blobs large enough to stall a review surface. */const DIFF_FILE_MAX_OUTPUT_BYTES = 1024 * 1024; /** A search-free fallback may scan older rows for local filters, but never the whole repository. */const PULL_REQUEST_FALLBACK_MAX_ROWS = 1_000; /** What the files API serves at most in one response, which is what one slice is made of. */const DIFF_FILES_PAGE_SIZE = 100; /** * Pages of review threads to follow before the conversation is reported as truncated. GitHub * serves a hundred threads a page, so this is a thousand threads — past anything a pull request * a person is reading has, and short of walking a repository-sized conversation forever. */const REVIEW_THREAD_PAGES = 10; export interface GitHubPullRequestListBatch { readonly items: ReadonlyArray<GitHubPullRequestListItem>; readonly truncated: boolean; /** False for a page GitHub would not search, which came back in `gh`'s own order instead. */ readonly continues: boolean;} export interface GitHubPullRequestStat { readonly repository: string; readonly number: number; readonly additions: number; readonly deletions: number;} /** * Aliased lookups per request, and requests at once. Measured over a hundred rows: one request * carrying all hundred takes ~5.2s, four of twenty-five in parallel ~2.1s. */const STAT_ALIASES_PER_REQUEST = 25;const STAT_REQUEST_CONCURRENCY = 4; export interface GitHubPullRequestSearchBatch { /** Rows across every repository asked for, newest update first, each naming its own. */ readonly items: ReadonlyArray<GitHubPullRequestSearchItem>; readonly truncated: boolean;} export interface GitHubPullRequestDiffSlice { readonly patch: string; /** Files in this slice had their hunks withheld, as opposed to there being more slices. */ readonly truncated: boolean; /** Where the next slice starts, or null once the patch is whole. */ readonly nextCursor: string | null; /** GitHub's own counts for the files whose hunks it withheld from this slice. */ readonly omittedFileStats?: ReadonlyArray<PullRequestOmittedFileStat>;} export class GitHubPullRequestCli extends Context.Service< GitHubPullRequestCli, { readonly getViewerLogin: (input: { readonly cwd: string; }) => Effect.Effect<string, GitHubPullRequestCliError>; readonly listPullRequests: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly state: PullRequestListState; readonly involvement: PullRequestInvolvement; readonly viewer: string; readonly limit: number; /** Free text for `--search`, matched as one literal phrase. */ readonly query?: string | undefined; /** Where to carry on from, as a `updated:` qualifier on the same search. */ readonly cursor?: ProviderListCursor | undefined; /** Further narrowings, as qualifiers on the search and as a local pass on the fallback. */ readonly filters?: PullRequestListFilters | undefined; }) => Effect.Effect<GitHubPullRequestListBatch, GitHubPullRequestCliError>; /** * The same listing for a whole host in one search. `limit` is the size of the slice across * all of the repositories rather than per repository, because that is what a search answers: * the newest rows of the lot, which is exactly the page. */ readonly searchPullRequests: (input: { /** Any checkout on the host; the search names its repositories itself. */ readonly cwd: string; readonly host: string; readonly repositories: ReadonlyArray<string>; readonly state: PullRequestListState; readonly involvement: PullRequestInvolvement; readonly viewer: string; readonly limit: number; readonly query?: string | undefined; readonly cursor?: ProviderListCursor | undefined; readonly filters?: PullRequestListFilters | undefined; }) => Effect.Effect<GitHubPullRequestSearchBatch, GitHubPullRequestCliError>; /** The line counts the search leaves out, for rows already on the page. */ readonly listPullRequestStats: (input: { readonly cwd: string; readonly host: string; readonly changeRequests: ReadonlyArray<{ readonly repository: string; readonly number: number; }>; }) => Effect.Effect<ReadonlyArray<GitHubPullRequestStat>, GitHubPullRequestCliError>; readonly getPullRequestDetail: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<GitHubPullRequestDetail, GitHubPullRequestCliError>; /** * How far the branch trails its base, and whether this viewer may update it. Its own read * because the comparison needs the head ref the detail answers with — a fork's branch is not * addressable in the base repository by name alone. */ readonly getPullRequestBaseComparison: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; /** Qualified `owner:branch`, which is the only form a fork's head resolves under. */ readonly headRef: string; /** Manual action checks may use the quota held back from automatic reads. */ readonly allowReserve?: boolean | undefined; }) => Effect.Effect<GitHubBaseComparison, GitHubPullRequestCliError>; readonly getPullRequestActivity: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<GitHubPullRequestActivity, GitHubPullRequestCliError>; readonly getPullRequestDiff: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; /** Absent asks for the first slice; anything else is a cursor a slice handed back. */ readonly cursor?: string | undefined; /** One commit's own changes, rather than everything the pull request carries. */ readonly commit?: string | undefined; }) => Effect.Effect<GitHubPullRequestDiffSlice, GitHubPullRequestCliError>; readonly getPullRequestDiffFileContents: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly commit?: string | undefined; readonly changeType: "change" | "rename-pure" | "rename-changed" | "new" | "deleted"; readonly oldPath: string; readonly newPath: string; }) => Effect.Effect< { readonly oldContents: string; readonly newContents: string }, GitHubPullRequestCliError >; readonly listReviewThreadComments: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<GitHubReviewThreadComments, GitHubPullRequestCliError>; /** One request for a listing's authors, since no `gh` JSON field reports an avatar. */ readonly listActorAvatars: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly ids: ReadonlyArray<string>; }) => Effect.Effect<ReadonlyMap<string, string>, GitHubPullRequestCliError>; readonly getReviewThreadComments: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly threadId: string; readonly cursor: string; }) => Effect.Effect<PullRequestThreadCommentsResult, GitHubPullRequestCliError>; /** One `gh repo view`, which answers what the repository allows and where the viewer stands. */ readonly getRepositoryAccess: (input: { readonly cwd: string; readonly repository: string; readonly host: string; }) => Effect.Effect<GitHubRepositoryAccess, GitHubPullRequestCliError>; /** The viewer's standing on its own, for deciding a write without reading the whole detail. */ readonly getViewerAccess: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; /** Manual action checks may use the quota held back from automatic reads. */ readonly allowReserve?: boolean | undefined; }) => Effect.Effect<GitHubViewerAccess, GitHubPullRequestCliError>; /** Who this pull request may be sent to, and who it has already been sent to. */ readonly listReviewerCandidates: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<PullRequestReviewerCandidateList, GitHubPullRequestCliError>; readonly setReviewerRequest: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly reviewers: ReadonlyArray<{ readonly id: string; readonly kind: PullRequestReviewerKind; }>; /** False deletes the same collection a request posts to, which takes the request back. */ readonly requested: boolean; }) => Effect.Effect<void, GitHubPullRequestCliError>; readonly runPullRequestAction: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly action: PullRequestAction; readonly mergeMethod?: PullRequestMergeMethod; readonly updateMethod?: PullRequestUpdateMethod; }) => Effect.Effect<void, GitHubPullRequestCliError>; readonly commentOnPullRequest: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly body: string; }) => Effect.Effect<void, GitHubPullRequestCliError>; readonly submitReview: (input: { readonly cwd: string; readonly repository: string;Lines 1–500 of 1863. Load more
import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; import { resolvePullRequestAuthorFilter, type PullRequestAction, type PullRequestActor, type PullRequestInvolvement, type PullRequestListFilters, type PullRequestListState, type PullRequestMergeMethod, type PullRequestOmittedFileStat, type PullRequestReaction, type PullRequestReactionContent, type PullRequestReviewCommentDraft, type PullRequestReviewVerdict, type PullRequestReviewerCandidateList, type PullRequestReviewerKind, type PullRequestThreadCommentsResult, type PullRequestUpdateMethod, } from "@t3tools/contracts"; import * as GitHubCli from "../sourceControl/GitHubCli.ts"; import * as GitHubGraphQlBudget from "../sourceControl/githubGraphQlBudget.ts"; import * as SourceControlRateLimit from "../sourceControl/SourceControlRateLimit.ts"; import { ACTOR_AVATARS_GRAPHQL_QUERY, ADD_REACTION_GRAPHQL_MUTATION, buildReviewSubmissionJson, buildReviewerRequestJson, decodeActorAvatarsJson, decodePullRequestActivityJson, decodePullRequestDetailJson, decodePullRequestFilesJson, decodePullRequestListJson, decodePullRequestNodeIdJson, decodePullRequestSearchJson, decodePullRequestStatsJson, decodeReactionSubjectScopeJson, decodeRepositoryAccessJson, decodeReviewerCandidatesJson, decodeReviewDismissalsJson, decodeReviewThreadCommentsJson, decodeReviewThreadsJson, buildPullRequestStatsGraphQlQuery, encodeGraphQlRequestJson, pullRequestSearchGraphQlQuery, PULL_REQUEST_SEARCH_MAX_ROWS, PULL_REQUEST_ACTIVITY_JSON_FIELDS, BASE_COMPARISON_GRAPHQL_QUERY, decodeBaseComparisonJson, PULL_REQUEST_DETAIL_JSON_FIELDS, PULL_REQUEST_LIST_JSON_FIELDS, PULL_REQUEST_NODE_ID_GRAPHQL_QUERY, REACTION_SUBJECT_PULL_REQUEST_GRAPHQL_QUERY, REMOVE_REACTION_GRAPHQL_MUTATION, gitHubReactionContent, REPOSITORY_ACCESS_JSON_FIELDS, RESOLVE_REVIEW_THREAD_GRAPHQL_MUTATION, REVIEWER_CANDIDATES_GRAPHQL_QUERY, REVIEW_THREAD_COMMENTS_GRAPHQL_QUERY, REVIEW_DISMISSALS_GRAPHQL_QUERY, REVIEW_THREAD_REPLY_GRAPHQL_MUTATION, REVIEW_THREADS_GRAPHQL_QUERY, reviewThreadConversation, UNRESOLVE_REVIEW_THREAD_GRAPHQL_MUTATION, UPDATE_ISSUE_COMMENT_GRAPHQL_MUTATION, UPDATE_PULL_REQUEST_GRAPHQL_MUTATION, UPDATE_REVIEW_COMMENT_GRAPHQL_MUTATION, VIEWER_PERMISSIONS_GRAPHQL_QUERY, decodeViewerPermissionsJson, type GitHubBaseComparison, type GitHubPullRequestDetail, type GitHubPullRequestActivity, type GitHubPullRequestListItem, type GitHubPullRequestSearchItem, type GitHubReviewThreadComments, type GitHubRepositoryAccess, type GitHubReviewThreadEntry, type GitHubReviewThreadPage, type GitHubViewerAccess, } from "./gitHubPullRequestJson.ts"; import type { ProviderListCursor } from "./PullRequestProvider.ts"; /** * Names the read that produced unusable output, so a failure reports the call it came from * rather than borrowing another operation's message. */ export class GitHubPullRequestReadError extends Schema.TaggedErrorClass<GitHubPullRequestReadError>()( "GitHubPullRequestReadError", { command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, cause: Schema.Defect(), }, ) { get detail(): string { return `GitHub CLI returned an unreadable ${this.operation} response.`; } override get message(): string { return `GitHub CLI failed in ${this.operation}: ${this.detail}`; } } /** Not a decode failure: gh answered, the account it answered for just has no login. */ export class GitHubViewerLoginUnavailableError extends Schema.TaggedErrorClass<GitHubViewerLoginUnavailableError>()( "GitHubViewerLoginUnavailableError", { command: Schema.Literal("gh"), cwd: Schema.String, }, ) { get detail(): string { return "GitHub CLI returned no login for the authenticated account."; } override get message(): string { return `GitHub CLI failed in getViewerLogin: ${this.detail}`; } } /** Not a decode failure: the reader asked to carry on from a cursor this walk never handed out. */ export class GitHubDiffCursorError extends Schema.TaggedErrorClass<GitHubDiffCursorError>()( "GitHubDiffCursorError", { command: Schema.Literal("gh"), cwd: Schema.String, }, ) { get detail(): string { return "The diff cursor was not one this pull request handed out."; } override get message(): string { return `GitHub CLI failed in getPullRequestDiff: ${this.detail}`; } } /** Not a decode failure: the reader named a commit that is not a sha this repository could hold. */ export class GitHubDiffCommitError extends Schema.TaggedErrorClass<GitHubDiffCommitError>()( "GitHubDiffCommitError", { command: Schema.Literal("gh"), cwd: Schema.String, }, ) { get detail(): string { return "The named commit was not a commit sha."; } override get message(): string { return `GitHub CLI failed in getPullRequestDiff: ${this.detail}`; } } /** The revisions read successfully, but cannot name both sides this file needs. */ export class GitHubDiffRevisionsUnavailableError extends Schema.TaggedErrorClass<GitHubDiffRevisionsUnavailableError>()( "GitHubDiffRevisionsUnavailableError", { command: Schema.Literal("gh"), cwd: Schema.String, number: Schema.Int, commit: Schema.optional(Schema.String), }, ) { get detail(): string { return this.commit === undefined ? `Pull request #${this.number} reported no usable base and head revisions.` : `Commit ${this.commit} reported no usable revisions for this file.`; } override get message(): string { return `GitHub CLI failed in getPullRequestDiffFileContents: ${this.detail}`; } } /** A blob exists, but expanding it would be unsafe or would not produce text. */ export class GitHubDiffFileContentsUnavailableError extends Schema.TaggedErrorClass<GitHubDiffFileContentsUnavailableError>()( "GitHubDiffFileContentsUnavailableError", { command: Schema.Literal("gh"), cwd: Schema.String, path: Schema.String, reason: Schema.Literals(["oversized", "binary"]), }, ) { get detail(): string { return this.reason === "oversized" ? `The diff file '${this.path}' exceeds the 1 MB expansion limit.` : `The diff file '${this.path}' is binary.`; } override get message(): string { return `GitHub CLI failed in getPullRequestDiffFileContents: ${this.detail}`; } } /** * Not a decode failure: a repository was named that cannot go into a search or into a GraphQL * document as itself. Every qualifier and every alias below is composed from `owner/name`, so a * name that is not one is refused here rather than escaped into something GitHub might read as a * qualifier of its own. */ export class GitHubRepositorySelectorError extends Schema.TaggedErrorClass<GitHubRepositorySelectorError>()( "GitHubRepositorySelectorError", { command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, }, ) { get detail(): string { return "A repository was named that GitHub cannot address."; } override get message(): string { return `GitHub CLI failed in ${this.operation}: ${this.detail}`; } } /** Not a decode failure: the reader named a subject this pull request never handed out. */ export class GitHubSubjectScopeError extends Schema.TaggedErrorClass<GitHubSubjectScopeError>()( "GitHubSubjectScopeError", { command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, }, ) { get detail(): string { return "The named subject did not belong to the named pull request."; } override get message(): string { return `GitHub CLI failed in ${this.operation}: ${this.detail}`; } } export type GitHubPullRequestCliError = | GitHubCli.GitHubCliError | GitHubPullRequestReadError | GitHubDiffCursorError | GitHubDiffCommitError | GitHubDiffRevisionsUnavailableError | GitHubDiffFileContentsUnavailableError | GitHubRepositorySelectorError | GitHubSubjectScopeError | SourceControlRateLimit.SourceControlRateLimitPausedError | GitHubViewerLoginUnavailableError; /** A large pull request can produce a multi-megabyte patch; past this it is truncated. */ const DIFF_MAX_OUTPUT_BYTES = 8 * 1024 * 1024; const DIFF_TIMEOUT_MS = 60_000; /** Pierre expansion is for source files, not blobs large enough to stall a review surface. */ const DIFF_FILE_MAX_OUTPUT_BYTES = 1024 * 1024; /** A search-free fallback may scan older rows for local filters, but never the whole repository. */ const PULL_REQUEST_FALLBACK_MAX_ROWS = 1_000; /** What the files API serves at most in one response, which is what one slice is made of. */ const DIFF_FILES_PAGE_SIZE = 100; /** * Pages of review threads to follow before the conversation is reported as truncated. GitHub * serves a hundred threads a page, so this is a thousand threads — past anything a pull request * a person is reading has, and short of walking a repository-sized conversation forever. */ const REVIEW_THREAD_PAGES = 10; export interface GitHubPullRequestListBatch { readonly items: ReadonlyArray<GitHubPullRequestListItem>; readonly truncated: boolean; /** False for a page GitHub would not search, which came back in `gh`'s own order instead. */ readonly continues: boolean; } export interface GitHubPullRequestStat { readonly repository: string; readonly number: number; readonly additions: number; readonly deletions: number; } /** * Aliased lookups per request, and requests at once. Measured over a hundred rows: one request * carrying all hundred takes ~5.2s, four of twenty-five in parallel ~2.1s. */ const STAT_ALIASES_PER_REQUEST = 25; const STAT_REQUEST_CONCURRENCY = 4; export interface GitHubPullRequestSearchBatch { /** Rows across every repository asked for, newest update first, each naming its own. */ readonly items: ReadonlyArray<GitHubPullRequestSearchItem>; readonly truncated: boolean; } export interface GitHubPullRequestDiffSlice { readonly patch: string; /** Files in this slice had their hunks withheld, as opposed to there being more slices. */ readonly truncated: boolean; /** Where the next slice starts, or null once the patch is whole. */ readonly nextCursor: string | null; /** GitHub's own counts for the files whose hunks it withheld from this slice. */ readonly omittedFileStats?: ReadonlyArray<PullRequestOmittedFileStat>; } export class GitHubPullRequestCli extends Context.Service< GitHubPullRequestCli, { readonly getViewerLogin: (input: { readonly cwd: string; }) => Effect.Effect<string, GitHubPullRequestCliError>; readonly listPullRequests: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly state: PullRequestListState; readonly involvement: PullRequestInvolvement; readonly viewer: string; readonly limit: number; /** Free text for `--search`, matched as one literal phrase. */ readonly query?: string | undefined; /** Where to carry on from, as a `updated:` qualifier on the same search. */ readonly cursor?: ProviderListCursor | undefined; /** Further narrowings, as qualifiers on the search and as a local pass on the fallback. */ readonly filters?: PullRequestListFilters | undefined; }) => Effect.Effect<GitHubPullRequestListBatch, GitHubPullRequestCliError>; /** * The same listing for a whole host in one search. `limit` is the size of the slice across * all of the repositories rather than per repository, because that is what a search answers: * the newest rows of the lot, which is exactly the page. */ readonly searchPullRequests: (input: { /** Any checkout on the host; the search names its repositories itself. */ readonly cwd: string; readonly host: string; readonly repositories: ReadonlyArray<string>; readonly state: PullRequestListState; readonly involvement: PullRequestInvolvement; readonly viewer: string; readonly limit: number; readonly query?: string | undefined; readonly cursor?: ProviderListCursor | undefined; readonly filters?: PullRequestListFilters | undefined; }) => Effect.Effect<GitHubPullRequestSearchBatch, GitHubPullRequestCliError>; /** The line counts the search leaves out, for rows already on the page. */ readonly listPullRequestStats: (input: { readonly cwd: string; readonly host: string; readonly changeRequests: ReadonlyArray<{ readonly repository: string; readonly number: number; }>; }) => Effect.Effect<ReadonlyArray<GitHubPullRequestStat>, GitHubPullRequestCliError>; readonly getPullRequestDetail: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<GitHubPullRequestDetail, GitHubPullRequestCliError>; /** * How far the branch trails its base, and whether this viewer may update it. Its own read * because the comparison needs the head ref the detail answers with — a fork's branch is not * addressable in the base repository by name alone. */ readonly getPullRequestBaseComparison: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; /** Qualified `owner:branch`, which is the only form a fork's head resolves under. */ readonly headRef: string; /** Manual action checks may use the quota held back from automatic reads. */ readonly allowReserve?: boolean | undefined; }) => Effect.Effect<GitHubBaseComparison, GitHubPullRequestCliError>; readonly getPullRequestActivity: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<GitHubPullRequestActivity, GitHubPullRequestCliError>; readonly getPullRequestDiff: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; /** Absent asks for the first slice; anything else is a cursor a slice handed back. */ readonly cursor?: string | undefined; /** One commit's own changes, rather than everything the pull request carries. */ readonly commit?: string | undefined; }) => Effect.Effect<GitHubPullRequestDiffSlice, GitHubPullRequestCliError>; readonly getPullRequestDiffFileContents: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly commit?: string | undefined; readonly changeType: "change" | "rename-pure" | "rename-changed" | "new" | "deleted"; readonly oldPath: string; readonly newPath: string; }) => Effect.Effect< { readonly oldContents: string; readonly newContents: string }, GitHubPullRequestCliError >; readonly listReviewThreadComments: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<GitHubReviewThreadComments, GitHubPullRequestCliError>; /** One request for a listing's authors, since no `gh` JSON field reports an avatar. */ readonly listActorAvatars: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly ids: ReadonlyArray<string>; }) => Effect.Effect<ReadonlyMap<string, string>, GitHubPullRequestCliError>; readonly getReviewThreadComments: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly threadId: string; readonly cursor: string; }) => Effect.Effect<PullRequestThreadCommentsResult, GitHubPullRequestCliError>; /** One `gh repo view`, which answers what the repository allows and where the viewer stands. */ readonly getRepositoryAccess: (input: { readonly cwd: string; readonly repository: string; readonly host: string; }) => Effect.Effect<GitHubRepositoryAccess, GitHubPullRequestCliError>; /** The viewer's standing on its own, for deciding a write without reading the whole detail. */ readonly getViewerAccess: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; /** Manual action checks may use the quota held back from automatic reads. */ readonly allowReserve?: boolean | undefined; }) => Effect.Effect<GitHubViewerAccess, GitHubPullRequestCliError>; /** Who this pull request may be sent to, and who it has already been sent to. */ readonly listReviewerCandidates: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; }) => Effect.Effect<PullRequestReviewerCandidateList, GitHubPullRequestCliError>; readonly setReviewerRequest: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly reviewers: ReadonlyArray<{ readonly id: string; readonly kind: PullRequestReviewerKind; }>; /** False deletes the same collection a request posts to, which takes the request back. */ readonly requested: boolean; }) => Effect.Effect<void, GitHubPullRequestCliError>; readonly runPullRequestAction: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly action: PullRequestAction; readonly mergeMethod?: PullRequestMergeMethod; readonly updateMethod?: PullRequestUpdateMethod; }) => Effect.Effect<void, GitHubPullRequestCliError>; readonly commentOnPullRequest: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; readonly body: string; }) => Effect.Effect<void, GitHubPullRequestCliError>; readonly submitReview: (input: { readonly cwd: string; readonly repository: string;