Files
orca/src/main/github/issues.ts
T
OrcaWinandOrcaWin 4687e90a16 [comment audit] Continue main-process comment cleanup (#32946)
* [comment audit] Shorten daemon, hook, and emulator comments [skip ci]

* [comment audit] Shorten VM, conversation, SQLite, and theme-import comments [skip ci]

* [comment audit] Tighten Git execution and source-control comment contracts [skip ci]

* [comment audit] Clarify repository probes and maintenance comments [skip ci]

* [comment audit] Shorten status, diff-cache, and checkout comments [skip ci]

* [comment audit] Clarify worktree lifecycle and WSL routing comments [skip ci]

* [comment audit] Shorten forge identity and review-refresh comments [skip ci]

* [comment audit] Tighten hosted-review client comment contracts [skip ci]

* [comment audit] Clarify GitLab compatibility and review comments [skip ci]

* [comment audit] Tighten hook ownership and watchdog rationale [skip ci]

* [comment audit] Condense host and integration compatibility comments [skip ci]

* [comment audit] Clarify IPC authority and remote-routing rationale [skip ci]

* [comment audit] Finalize file-import and containment comment cleanup

* [comment audit] Fix stale Jira type imports blocking full CI

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 16:27:37 -07:00

137 lines
4.2 KiB
TypeScript

import type { ClassifiedError } from '../../shared/classified-error'
import type { IssueInfo } from '../../shared/github/pull-request-types'
import type { IssueSourcePreference } from '../../shared/repo-types'
import { mapIssueInfo } from './mappers'
import type { LocalGitExecOptions } from './gh-utils'
import {
getIssueGitHubApiRepository,
resolveGitHubRepoExecution,
resolveIssueGitHubApiRepositorySource
} from './github-api-repository'
// prettier-ignore
import { ghExecFileAsync, acquire, release, classifyListIssuesError } from './gh-utils'
// Keep successful-empty and failed listings distinguishable; fallback toasts belong to the Tasks envelope.
export type IssueListResult = {
items: IssueInfo[]
error?: ClassifiedError
}
/** Linked issue numbers keep heuristic source resolution; changing list preference must not retarget existing links. */
export async function getIssue(
repoPath: string,
issueNumber: number,
connectionId?: string | null,
localGitOptions: LocalGitExecOptions = {}
): Promise<IssueInfo | null> {
const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution(
repoPath,
() => getIssueGitHubApiRepository(repoPath, connectionId, localGitOptions),
connectionId,
localGitOptions
)
// Without local cwd, unresolved connection-backed requests could target gh's default repository.
if (connectionId && !ownerRepo) {
return null
}
await acquire()
try {
if (ownerRepo) {
const { stdout } = await ghExecFileAsync(
[
'api',
'--cache',
'300s',
`repos/${ownerRepo.owner}/${ownerRepo.repo}/issues/${issueNumber}`
],
ghOptions
)
const data = JSON.parse(stdout)
return mapIssueInfo(data)
}
// Local compatibility path relies on gh repository discovery.
const { stdout } = await ghExecFileAsync(
['issue', 'view', String(issueNumber), '--json', 'number,title,state,url,labels,body'],
ghOptions
)
const data = JSON.parse(stdout)
return mapIssueInfo(data)
} catch {
return null
} finally {
release()
}
}
/** Return classified failures separately so unavailable repositories cannot look like empty backlogs. */
export async function listIssues(
repoPath: string,
limit = 20,
preference?: IssueSourcePreference,
connectionId?: string | null,
localGitOptions: LocalGitExecOptions = {}
): Promise<IssueListResult> {
const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution(
repoPath,
async () =>
(
await resolveIssueGitHubApiRepositorySource(
repoPath,
preference,
connectionId,
localGitOptions
)
).source,
connectionId,
localGitOptions
)
// Refuse unresolved remote identities rather than listing gh's default repository.
if (connectionId && !ownerRepo) {
return {
items: [],
error: {
type: 'not_found',
message: 'Could not resolve GitHub owner/repo for this repository'
}
}
}
await acquire()
try {
if (ownerRepo) {
const { stdout } = await ghExecFileAsync(
[
'api',
'--cache',
'120s',
`repos/${ownerRepo.owner}/${ownerRepo.repo}/issues?per_page=${limit}&state=open&sort=updated&direction=desc`
],
ghOptions
)
const data = JSON.parse(stdout) as Record<string, unknown>[]
// GitHub REST issues includes PRs; exclude them from this issue-only result.
return {
items: data
.filter((d) => !('pull_request' in d))
.map((d) => mapIssueInfo(d as Parameters<typeof mapIssueInfo>[0]))
}
}
// Local compatibility path relies on gh repository discovery.
const { stdout } = await ghExecFileAsync(
['issue', 'list', '--json', 'number,title,state,url,labels', '--limit', String(limit)],
ghOptions
)
const data = JSON.parse(stdout) as unknown[]
return {
items: data.map((d) => mapIssueInfo(d as Parameters<typeof mapIssueInfo>[0]))
}
} catch (err) {
const stderr = err instanceof Error ? err.message : String(err)
return {
items: [],
error: classifyListIssuesError(stderr)
}
} finally {
release()
}
}