mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 08:02:21 +00:00
The GitLab, GitHub, Jira and Linear integration modules, their two IPC registrars, and the shared GitHub project types each carried a file-level `eslint-disable max-lines` and ran 351-614 counted lines against a 300-line budget. AGENTS.md calls for splitting rather than suppressing, and config/max-lines-baseline.txt is a shrink-only ratchet, so this removes all eight suppressions and prunes their entries (341 -> 333). Pure move, no behavior change. Each client is cut along the seam it already had: per-operation modules for the issue APIs (create / update / comment / field options), and for Jira the request queue, site credential store, authenticated request, and site identity. The two IPC registrars keep their own handlers and delegate the rest to per-domain sub-registrars, so they remain real entry points rather than re-export shims. The IPC surface is proved intact rather than assumed: comparing (method, channel) multisets between HEAD and the split gives 52 registrations across 52 distinct channels on both sides. Provider-neutrality is preserved -- GitLab and GitHub keep separate, parallel module layouts rather than being merged behind a shared abstraction. Verified: oxlint clean, ratchet passes, typecheck clean, full unit suite green (the one remaining failure is a pre-existing load flake in an untouched file, green when re-run serially), no new runtime import cycles among 744 modules, and no lint suppression added anywhere.
78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
import type { GitHubCommentResult, PRComment } from '../../shared/github/comment-types'
|
|
import type { LocalGitExecOptions, OwnerRepo } from './gh-utils'
|
|
import { getIssueGitHubApiRepository, resolveGitHubRepoExecution } from './github-api-repository'
|
|
import { acquire, classifyGhError, ghExecFileAsync, release } from './gh-utils'
|
|
|
|
/**
|
|
* Add a comment to an existing GitHub issue.
|
|
*
|
|
* Why this path doesn't take a preference (mirrors `getIssue` / `updateIssue`):
|
|
* a comment is posted against an issue number already bound to a worktree or
|
|
* surfaced from a prior read. Routing through the live per-repo preference
|
|
* would let a user read upstream#N, toggle the selector to origin, and have
|
|
* their reply silently post on origin#N — a different issue entirely. That
|
|
* is the same silent-source-switch class of wrongness #1186 / the parent
|
|
* design doc guard against. List and create paths honor preference;
|
|
* mutations stay on the heuristic `getIssueOwnerRepo`.
|
|
*/
|
|
export async function addIssueComment(
|
|
repoPath: string,
|
|
issueNumber: number,
|
|
body: string,
|
|
connectionId?: string | null,
|
|
ownerRepoOverride?: OwnerRepo | null,
|
|
localGitOptions: LocalGitExecOptions = {}
|
|
): Promise<GitHubCommentResult> {
|
|
const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution(
|
|
repoPath,
|
|
ownerRepoOverride ??
|
|
(() => getIssueGitHubApiRepository(repoPath, connectionId, localGitOptions)),
|
|
connectionId,
|
|
localGitOptions
|
|
)
|
|
if (!ownerRepo) {
|
|
return { ok: false, error: 'Could not resolve GitHub owner/repo for this repository' }
|
|
}
|
|
await acquire()
|
|
try {
|
|
const { stdout } = await ghExecFileAsync(
|
|
[
|
|
'api',
|
|
'-X',
|
|
'POST',
|
|
`repos/${ownerRepo.owner}/${ownerRepo.repo}/issues/${issueNumber}/comments`,
|
|
'--raw-field',
|
|
`body=${body}`
|
|
],
|
|
ghOptions
|
|
)
|
|
const data = JSON.parse(stdout) as {
|
|
id?: number
|
|
node_id?: string | null
|
|
user: { login: string; avatar_url: string; type?: string } | null
|
|
body?: string
|
|
created_at?: string
|
|
html_url?: string
|
|
}
|
|
if (typeof data.id !== 'number' || !Number.isSafeInteger(data.id) || data.id < 1) {
|
|
return { ok: false, error: 'Unexpected response from GitHub' }
|
|
}
|
|
const comment: PRComment = {
|
|
id: data.id,
|
|
reactionSubjectId: data.node_id?.trim() || undefined,
|
|
author: data.user?.login ?? 'You',
|
|
authorAvatarUrl: data.user?.avatar_url ?? '',
|
|
body: data.body ?? body,
|
|
createdAt: data.created_at ?? new Date().toISOString(),
|
|
url: data.html_url ?? '',
|
|
isBot: data.user?.type === 'Bot'
|
|
}
|
|
return { ok: true, comment }
|
|
} catch (err) {
|
|
const stderr = err instanceof Error ? err.message : String(err)
|
|
return { ok: false, error: classifyGhError(stderr).message }
|
|
} finally {
|
|
release()
|
|
}
|
|
}
|