refactor(github): organize concrete feature owners (#41601)

This commit is contained in:
Neil
2026-11-12 09:17:26 -08:00
committed by GitHub
parent ff9fd6634b
commit fb8d6f950b
1003 changed files with 4460 additions and 8422 deletions
@@ -0,0 +1,39 @@
import { z } from 'zod'
import { SlugRepo } from '../../rpc-contract/github-repo-target-params'
import { ExpectedRuntimeSourceSchema } from '../../runtime-rpc-envelope'
const methods = new Set([
'github.workItemDetails',
'github.updateIssue',
'github.listLabels',
'github.listAssignableUsers'
])
const exactShape = z.object({
ownerRepo: SlugRepo,
expectedRuntimeSource: ExpectedRuntimeSourceSchema
})
// Only the complete opt-in pair promises enforcement; old independently optional fields stay optional.
export function classifyExactGitHubOperation(method: string, params: unknown, envelope: unknown) {
if (
!methods.has(method) ||
!isRecord(params) ||
!isRecord(envelope) ||
!Object.hasOwn(params, 'ownerRepo') ||
!Object.hasOwn(envelope, 'expectedRuntimeSource')
) {
return null
}
const parsed = exactShape.safeParse({
ownerRepo: params.ownerRepo,
expectedRuntimeSource: envelope.expectedRuntimeSource
})
if (!parsed.success) {
throw new Error('Unable to identify the GitHub operation. Open the item again and try again.')
}
return parsed.data
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
@@ -0,0 +1,43 @@
import type { ClassifiedError } from '../../classified-error'
import { GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN } from './work-items-query-bounds'
export function getWorkItemPageErrorTypes(errors: unknown): ClassifiedError['type'][] {
if (errors === undefined || errors === null) {
return []
}
if (typeof errors !== 'object') {
return ['unknown']
}
const sources = [
['issues', 'issues' in errors ? errors.issues : undefined],
['prs', 'prs' in errors ? errors.prs : undefined]
] as const
return sources.flatMap(([source, error]): ClassifiedError['type'][] => {
if (error === undefined || error === null) {
return []
}
if (typeof error !== 'object' || !('type' in error)) {
return ['unknown']
}
switch (error.type) {
case 'validation_error':
// Only issue search's 1000-result window proves a page limit.
return [
source === 'issues' &&
'message' in error &&
typeof error.message === 'string' &&
GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN.test(error.message)
? 'validation_error'
: 'unknown'
]
case 'permission_denied':
case 'not_found':
case 'issues_disabled':
case 'rate_limited':
case 'network_error':
return [error.type]
default:
return ['unknown']
}
})
}
@@ -0,0 +1,18 @@
export const GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE =
'GitHub work items require a GitHub remote for SSH repositories'
export function isGitHubWorkItemsSshRemoteRequiredError(error: unknown): boolean {
const message =
error instanceof Error
? error.message
: typeof error === 'object' &&
error !== null &&
'message' in error &&
typeof error.message === 'string'
? error.message
: typeof error === 'string'
? error
: ''
return message.includes(GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE)
}
@@ -0,0 +1,83 @@
import type { GitHubRepositoryIdentity as GitHubOwnerRepo } from '../repositories/repository-identity'
import type { GitHubWorkItem, GitHubWorkItemDetails } from './work-item-types'
import { githubRepoIdentityKey } from '../repositories/repository-identity-key'
export type GitHubWorkItemTarget = {
repository: GitHubOwnerRepo
type: 'issue' | 'pr'
number: number
}
export function matchesGitHubWorkItemTarget(
item: Pick<GitHubWorkItem, 'url' | 'prRepo' | 'type' | 'number'> | null | undefined,
target: GitHubWorkItemTarget
): boolean {
if (!item || item.type !== target.type || item.number !== target.number) {
return false
}
try {
const url = new URL(item.url)
const parts = url.pathname.split('/').filter(Boolean)
if (
!['http:', 'https:'].includes(url.protocol) ||
parts.length !== 4 ||
parts[2] !== (target.type === 'pr' ? 'pull' : 'issues') ||
parts[3] !== String(target.number)
) {
return false
}
const repository = { host: url.host, owner: parts[0], repo: parts[1] }
const key = githubRepoIdentityKey(repository)
return (
key === githubRepoIdentityKey(target.repository) &&
(!item.prRepo || githubRepoIdentityKey(item.prRepo) === key)
)
} catch {
return false
}
}
export class GitHubWorkItemTargetError extends Error {
constructor(target: GitHubWorkItemTarget) {
const { owner, repo } = target.repository
super(
`Couldn't load ${owner}/${repo}#${target.number} from this server. Try again or open the ${target.type === 'pr' ? 'pull request' : 'issue'} on GitHub.`
)
}
}
export function requireGitHubWorkItemTargetDetails(
details: GitHubWorkItemDetails | null,
target: GitHubWorkItemTarget
): GitHubWorkItemDetails {
if (!details || !matchesGitHubWorkItemTarget(details.item, target)) {
throw new GitHubWorkItemTargetError(target)
}
return details
}
export const GITHUB_EXACT_OPERATION_UNSUPPORTED =
'Update Orca on the executing server to edit this item or load its repository options.'
export function githubWorkItemTargetKey(target: GitHubWorkItemTarget): string {
return [githubRepoIdentityKey(target.repository), target.type, target.number].join('\0')
}
export function parseGitHubItemRepository(url: string): GitHubOwnerRepo | null {
try {
const parsed = new URL(url)
const parts = parsed.pathname.split('/').filter(Boolean)
return ['https:', 'http:'].includes(parsed.protocol) && parts.length >= 2
? { host: parsed.host, owner: parts[0], repo: parts[1] }
: null
} catch {
return null
}
}
export function resolveGitHubWorkItemTarget(
item: Pick<GitHubWorkItem, 'url' | 'prRepo' | 'type' | 'number'>
): GitHubWorkItemTarget | null {
const repository = item.prRepo ?? parseGitHubItemRepository(item.url)
return repository ? { repository, type: item.type, number: item.number } : null
}
@@ -0,0 +1,106 @@
import type { ClassifiedError } from '../../classified-error'
import type { ExecutionHostId } from '../../execution-host'
import type { PRCheckDetail } from '../checks/check-types'
import type { GitHubIssueTimelineItem, PRComment } from '../comments/comment-types'
import type {
GitHubAssignableUser,
GitHubPRFile,
GitHubPRMergeMethodSettings,
GitHubPRReviewSummary,
PRMergeableState,
PRReviewDecision,
ProviderCheckSummary
} from '../pull-requests/pull-request-types'
import type {
GitHubRepositoryIdentity as GitHubOwnerRepo,
GitHubRepositoryIdentity
} from '../repositories/repository-identity'
export type GitHubWorkItem = {
id: string
type: 'issue' | 'pr'
number: number
title: string
state: 'open' | 'closed' | 'merged' | 'draft'
url: string
labels: string[]
updatedAt: string
author: string | null
// Use API avatars for GHE logins absent on github.com (#8784). gh-pr-view omits them; UI falls back through login URL to initials.
authorAvatarUrl?: string
branchName?: string
baseRefName?: string
// Head SHA enables cached check-run requests instead of per-row gh pr checks.
headSha?: string
prRepo?: GitHubRepositoryIdentity
additions?: number
deletions?: number
changedFiles?: number
/** Issue-only; older hosts omit it. */
commentCount?: number
reviewDecision?: PRReviewDecision | null
reviewRequests?: GitHubAssignableUser[]
latestReviews?: GitHubPRReviewSummary[]
assignees?: GitHubAssignableUser[]
checksSummary?: ProviderCheckSummary
mergeable?: PRMergeableState
autoMergeEnabled?: boolean
autoMergeAllowed?: boolean | null
mergeQueueRequired?: boolean | null
mergeMethodSettings?: GitHubPRMergeMethodSettings
mergeStateStatus?: string | null
maintainerCanModify?: boolean
// Fork heads use refs/pull/<N>/head for creation and a separate push target in resolvePrBase.
isCrossRepository?: boolean
/** Renderer fetches/optimistic stubs stamp origin repo for cross-repo pills and open-in-browser routing. */
repoId: string
/** Repo ids can repeat across hosts; stamp the producing host so actions cannot target a same-id twin. */
repoHostId?: ExecutionHostId
}
export type GitHubWorkItemDetails = {
// Main lacks Orca Repo.id; renderer stamps it when storing details.
item: Omit<GitHubWorkItem, 'repoId'>
body: string
bodyImageUrls?: Record<string, string>
comments: PRComment[]
/** Issue-only provider activity such as assignment, references, project moves, and state changes. */
timelineItems?: GitHubIssueTimelineItem[]
/** Only set for PRs. Head/base SHAs used by the Files tab to fetch per-file content. */
headSha?: string
baseSha?: string
/** GraphQL node ID required by GitHub's file-viewed mutations. Only set for PRs. */
pullRequestId?: string
checks?: PRCheckDetail[]
files?: GitHubPRFile[]
/** Only set for PRs. True when the file fetch failed (rate limit, auth,
* unresolved remote) rather than the PR genuinely having no changed files. */
filesUnavailable?: boolean
participants?: GitHubAssignableUser[]
/** Logins of current assignees. Only set for issues. */
assignees?: string[]
}
/** Piggyback issue/PR sources on Tasks refresh to avoid another IPC round trip. Per-source failures preserve successful items;
* render items alongside error banners (e.g. private upstream 403), never discard the other side’s success. */
export type ListWorkItemsResult<T> = {
items: T[]
sources: {
/** Affirmative other-forge identity; absent on unresolved or legacy results. */
ineligible?: true
issues: GitHubOwnerRepo | null
prs: GitHubOwnerRepo | null
/** Raw origin independent of preferences; compare candidates without inferring from effective PR source.
* Absent from older hosts' results: unavailable evidence, distinct from a present unresolved `null`. */
originCandidate?: GitHubOwnerRepo | null
/** Raw upstream independent of preferences; selector visibility/tooltips need it even when effective issues source is origin. */
upstreamCandidate?: GitHubOwnerRepo | null
}
errors?: {
issues?: ClassifiedError
prs?: ClassifiedError
}
/** Present only when requested upstream was missing and origin was used; renderer shows a once-per-session toast.
* Optional literal true preserves legacy consumers and forbids erroneous explicit false writes. */
issueSourceFellBack?: true
}
@@ -0,0 +1,16 @@
import { isClipboardTextByteLengthOverLimit } from '../../clipboard-text'
export const GITHUB_WORK_ITEMS_QUERY_MAX_BYTES = 8 * 1024
/**
* The Search API's free-text 422 wording is the only signal separating a
* permanently unreachable page (past the first 1000 matches) from a transient failure.
*/
export const GITHUB_SEARCH_RESULT_WINDOW_ERROR_PATTERN = /first 1000 search results/i
export function isGitHubWorkItemsQueryTooLarge(
query: string,
maxBytes = GITHUB_WORK_ITEMS_QUERY_MAX_BYTES
): boolean {
return isClipboardTextByteLengthOverLimit(query, maxBytes)
}