Files
Aperant/apps/frontend/src/preload/api/modules/github-api.ts
T
Andy b4e6b2fe43 auto-claude: 182-implement-pagination-and-filtering-for-github-pr-l (#1654)
* auto-claude: subtask-1-1 - Add GITHUB_PR_LIST_MORE IPC channel constant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-1-2 - Add listMorePRs IPC handler for pagination

- Add GITHUB_PR_LIST_MORE handler that accepts cursor parameter
- Update PRListResult interface to include endCursor field
- Update existing GITHUB_PR_LIST handler to also return endCursor
- Both handlers use GraphQL pagination with cursor-based navigation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-1-3 - Update PRListResult type to include endCursor field

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-1-4 - Add listMorePRs method to GitHubAPI interface

Add listMorePRs method for cursor-based pagination to the GitHubAPI
interface and createGitHubAPI implementation in preload. Also update
browser-mock.ts to include the new method for type consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-2-1 - Add sortBy option to PRFilterState interface and DEFAULT_FILTERS

- Added PRSortOption type with 'newest' | 'oldest' | 'largest' options
- Added sortBy field to PRFilterState interface
- Set default sortBy to 'newest' in DEFAULT_FILTERS

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-2-2 - Add sorting logic to filteredPRs useMemo in usePRF

* auto-claude: subtask-2-4 - Add loadMore function, endCursor state, and isLoadingMore state

- Add isLoadingMore state to track pagination loading
- Add endCursor state to track pagination cursor
- Add loadMore function for cursor-based pagination
- Update UseGitHubPRsResult interface with new properties
- Store endCursor from fetchPRs API response
- Reset endCursor when project changes
- Batch preload review results for newly loaded PRs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-3-1 - Add sort dropdown to PRFilterBar using FilterDropdown

- Add SortDropdown component with single-select behavior for sorting PRs
- Add SORT_OPTIONS constant with newest/oldest/largest options
- Add onSortChange prop to PRFilterBarProps interface
- Import ArrowUpDown, Clock, FileCode icons from lucide-react
- Import PRSortOption type from usePRFiltering hook
- Update GitHubPRs.tsx to pass setSortBy as onSortChange prop
- Add i18n translations for sort labels (en/fr)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-3-2 - Add onLoadMore and isLoadingMore props to PRList component

Add pagination props to PRListProps interface:
- onLoadMore: Optional callback to load more PRs when hasMore is true
- isLoadingMore: Optional boolean to track loading state for pagination

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-3-3 - Replace status indicator text with Load More button

- Add Load More button component to PRList when hasMore is true
- Show loading spinner with "Loading..." text when isLoadingMore is true
- Keep "All PRs loaded" text when all PRs are displayed
- Add prReview.loadMore and prReview.loadingMore translation keys
- Import Button and Loader2 components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-3-4 - Wire up new props in GitHubPRs.tsx parent component

- Add loadMore and isLoadingMore to useGitHubPRs destructuring
- Pass loadMore as onLoadMore prop to PRList component
- Pass isLoadingMore to PRList component for loading state
- setSortBy already wired to PRFilterBar's onSortChange

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-4-1 - Add English translation keys for sortBy, sortNewest, sortOldest, sortLargest, loadMore, loadingMore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* auto-claude: subtask-4-2 - Add French translation keys for sortBy, sortNewest

Adds French translations for pagination and sorting UI elements:
- sort.sortBy: "Trier par"
- sort.sortNewest: "Plus récent"
- sort.sortOldest: "Plus ancien"
- sort.sortLargest: "Plus grand"
- pagination.loadMore: "Charger plus"
- pagination.loadingMore: "Chargement..."

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix ruff formatting and Windows dataclass import error

- Apply ruff formatting to parallel_orchestrator_reviewer.py (3 long lines)
- Apply ruff formatting to pydantic_models.py (Field on single line)
- Fix Windows test collection error: register module in sys.modules before
  exec_module so dataclass decorator can find it

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix PR review findings: dedup mapping, race conditions, unused i18n keys

- Extract shared mapGraphQLPRToData helper to eliminate duplicated PR
  mapping logic between listPRs and listMorePRs handlers
- Add staleness checks to loadMore using generation counter and
  projectId ref to prevent race conditions with refresh and project
  switching
- Reset isLoadingMore on project change to prevent stuck loading state
- Remove unused common.sort.* and common.pagination.* translation keys
  from en/fr locale files (code uses prReview.* keys instead)
- Align endCursor type to string | null in preload PRListResult
- Preserve sortBy preference when clearing filters for consistency
  with hasActiveFilters

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix PR review findings: dedup PR handlers, stable sort order

- Extract fetchPRsFromGraphQL helper to deduplicate listPRs/listMorePRs handlers
- Add secondary sort key (createdAt) to 'largest' sort for stable ordering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix PR review findings: deduplication and keyboard navigation

- Add deduplication by PR number when appending paginated PRs to prevent
  duplicates if a PR shifts position between pagination requests
- Add keyboard navigation to SortDropdown (ArrowUp/Down, Enter, Space, Escape)
  matching the pattern used in FilterDropdown for accessibility consistency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix follow-up review findings: pagination, sort, keyboard UX

- Preserve pagination state on failure response to allow retry
- Pre-compute timestamps before sorting to avoid Date object creation
- Add scrollIntoView for keyboard-focused items in FilterDropdown
- Focus current selection when SortDropdown opens for better keyboard UX

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Test User <test@example.com>
2026-02-04 14:06:49 +01:00

758 lines
27 KiB
TypeScript

import { IPC_CHANNELS } from '../../../shared/constants';
import type {
GitHubRepository,
GitHubIssue,
GitHubSyncStatus,
GitHubImportResult,
GitHubInvestigationStatus,
GitHubInvestigationResult,
IPCResult,
VersionSuggestion,
PaginatedIssuesResult
} from '../../../shared/types';
import { createIpcListener, invokeIpc, sendIpc, IpcListenerCleanup } from './ipc-utils';
/**
* Auto-fix configuration
*/
export interface AutoFixConfig {
enabled: boolean;
labels: string[];
requireHumanApproval: boolean;
botToken?: string;
model: string;
thinkingLevel: string;
}
/**
* Auto-fix queue item
*/
export interface AutoFixQueueItem {
issueNumber: number;
repo: string;
status: 'pending' | 'analyzing' | 'creating_spec' | 'building' | 'qa_review' | 'pr_created' | 'completed' | 'failed';
specId?: string;
prNumber?: number;
error?: string;
createdAt: string;
updatedAt: string;
}
/**
* Auto-fix progress status
*/
export interface AutoFixProgress {
phase: 'checking' | 'fetching' | 'analyzing' | 'batching' | 'creating_spec' | 'building' | 'qa_review' | 'creating_pr' | 'complete';
issueNumber: number;
progress: number;
message: string;
}
/**
* Issue batch for grouped fixing
*/
export interface IssueBatch {
batchId: string;
repo: string;
primaryIssue: number;
issues: Array<{
issueNumber: number;
title: string;
similarityToPrimary: number;
}>;
commonThemes: string[];
status: 'pending' | 'analyzing' | 'creating_spec' | 'building' | 'qa_review' | 'pr_created' | 'completed' | 'failed';
specId?: string;
prNumber?: number;
error?: string;
createdAt: string;
updatedAt: string;
}
/**
* Batch progress status
*/
export interface BatchProgress {
phase: 'analyzing' | 'batching' | 'creating_specs' | 'complete';
progress: number;
message: string;
totalIssues: number;
batchCount: number;
}
/**
* Analyze preview progress (proactive workflow)
*/
export interface AnalyzePreviewProgress {
phase: 'analyzing' | 'complete';
progress: number;
message: string;
}
/**
* Proposed batch from analyze-preview
*/
export interface ProposedBatch {
primaryIssue: number;
issues: Array<{
issueNumber: number;
title: string;
labels: string[];
similarityToPrimary: number;
}>;
issueCount: number;
commonThemes: string[];
validated: boolean;
confidence: number;
reasoning: string;
theme: string;
}
/**
* Analyze preview result (proactive batch workflow)
*/
export interface AnalyzePreviewResult {
success: boolean;
totalIssues: number;
analyzedIssues: number;
alreadyBatched: number;
proposedBatches: ProposedBatch[];
singleIssues: Array<{
issueNumber: number;
title: string;
labels: string[];
}>;
message: string;
error?: string;
}
/**
* Workflow run awaiting approval (for fork PRs)
*/
export interface WorkflowAwaitingApproval {
id: number;
name: string;
html_url: string;
workflow_name: string;
}
/**
* Workflows awaiting approval result
*/
export interface WorkflowsAwaitingApprovalResult {
awaiting_approval: number;
workflow_runs: WorkflowAwaitingApproval[];
can_approve: boolean;
error?: string;
}
// Re-export PaginatedIssuesResult from shared types for API consumers
export type { PaginatedIssuesResult };
/**
* GitHub Integration API operations
*/
export interface GitHubAPI {
// Operations
getGitHubRepositories: (projectId: string) => Promise<IPCResult<GitHubRepository[]>>;
getGitHubIssues: (
projectId: string,
state?: 'open' | 'closed' | 'all',
page?: number,
fetchAll?: boolean
) => Promise<IPCResult<PaginatedIssuesResult>>;
getGitHubIssue: (projectId: string, issueNumber: number) => Promise<IPCResult<GitHubIssue>>;
getIssueComments: (projectId: string, issueNumber: number) => Promise<IPCResult<any[]>>;
checkGitHubConnection: (projectId: string) => Promise<IPCResult<GitHubSyncStatus>>;
investigateGitHubIssue: (projectId: string, issueNumber: number, selectedCommentIds?: number[]) => void;
importGitHubIssues: (projectId: string, issueNumbers: number[]) => Promise<IPCResult<GitHubImportResult>>;
createGitHubRelease: (
projectId: string,
version: string,
releaseNotes: string,
options?: { draft?: boolean; prerelease?: boolean }
) => Promise<IPCResult<{ url: string }>>;
/** AI-powered version suggestion based on commits since last release */
suggestReleaseVersion: (projectId: string) => Promise<IPCResult<VersionSuggestion>>;
// OAuth operations (gh CLI)
checkGitHubCli: () => Promise<IPCResult<{ installed: boolean; version?: string }>>;
checkGitHubAuth: () => Promise<IPCResult<{ authenticated: boolean; username?: string }>>;
startGitHubAuth: () => Promise<IPCResult<{ success: boolean; message?: string }>>;
getGitHubToken: () => Promise<IPCResult<{ token: string }>>;
getGitHubUser: () => Promise<IPCResult<{ username: string; name?: string }>>;
listGitHubUserRepos: () => Promise<IPCResult<{ repos: Array<{ fullName: string; description: string | null; isPrivate: boolean }> }>>;
// OAuth event listener - receives device code immediately when extracted
onGitHubAuthDeviceCode: (
callback: (data: { deviceCode: string; authUrl: string; browserOpened: boolean }) => void
) => IpcListenerCleanup;
// OAuth event listener - notifies when GitHub account changes (via gh auth login)
onGitHubAuthChanged: (
callback: (data: { oldUsername: string | null; newUsername: string }) => void
) => IpcListenerCleanup;
// Repository detection and management
detectGitHubRepo: (projectPath: string) => Promise<IPCResult<string>>;
getGitHubBranches: (repo: string, token: string) => Promise<IPCResult<string[]>>;
createGitHubRepo: (
repoName: string,
options: { description?: string; isPrivate?: boolean; projectPath: string; owner?: string }
) => Promise<IPCResult<{ fullName: string; url: string }>>;
addGitRemote: (
projectPath: string,
repoFullName: string
) => Promise<IPCResult<{ remoteUrl: string }>>;
listGitHubOrgs: () => Promise<IPCResult<{ orgs: Array<{ login: string; avatarUrl?: string }> }>>;
// Event Listeners
onGitHubInvestigationProgress: (
callback: (projectId: string, status: GitHubInvestigationStatus) => void
) => IpcListenerCleanup;
onGitHubInvestigationComplete: (
callback: (projectId: string, result: GitHubInvestigationResult) => void
) => IpcListenerCleanup;
onGitHubInvestigationError: (
callback: (projectId: string, error: string) => void
) => IpcListenerCleanup;
// Auto-fix operations
getAutoFixConfig: (projectId: string) => Promise<AutoFixConfig | null>;
saveAutoFixConfig: (projectId: string, config: AutoFixConfig) => Promise<boolean>;
getAutoFixQueue: (projectId: string) => Promise<AutoFixQueueItem[]>;
checkAutoFixLabels: (projectId: string) => Promise<number[]>;
checkNewIssues: (projectId: string) => Promise<Array<{number: number}>>;
startAutoFix: (projectId: string, issueNumber: number) => void;
// Batch auto-fix operations
batchAutoFix: (projectId: string, issueNumbers?: number[]) => void;
getBatches: (projectId: string) => Promise<IssueBatch[]>;
// Auto-fix event listeners
onAutoFixProgress: (
callback: (projectId: string, progress: AutoFixProgress) => void
) => IpcListenerCleanup;
onAutoFixComplete: (
callback: (projectId: string, result: AutoFixQueueItem) => void
) => IpcListenerCleanup;
onAutoFixError: (
callback: (projectId: string, error: { issueNumber: number; error: string }) => void
) => IpcListenerCleanup;
// Batch auto-fix event listeners
onBatchProgress: (
callback: (projectId: string, progress: BatchProgress) => void
) => IpcListenerCleanup;
onBatchComplete: (
callback: (projectId: string, batches: IssueBatch[]) => void
) => IpcListenerCleanup;
onBatchError: (
callback: (projectId: string, error: { error: string }) => void
) => IpcListenerCleanup;
// Analyze & Group Issues (proactive batch workflow)
analyzeIssuesPreview: (projectId: string, issueNumbers?: number[], maxIssues?: number) => void;
approveBatches: (projectId: string, approvedBatches: ProposedBatch[]) => Promise<{ success: boolean; batches?: IssueBatch[]; error?: string }>;
// Analyze preview event listeners
onAnalyzePreviewProgress: (
callback: (projectId: string, progress: AnalyzePreviewProgress) => void
) => IpcListenerCleanup;
onAnalyzePreviewComplete: (
callback: (projectId: string, result: AnalyzePreviewResult) => void
) => IpcListenerCleanup;
onAnalyzePreviewError: (
callback: (projectId: string, error: { error: string }) => void
) => IpcListenerCleanup;
// PR operations (fetches up to 100 open PRs at once - GitHub GraphQL limit)
listPRs: (projectId: string) => Promise<PRListResult>;
/** Load more PRs using cursor-based pagination */
listMorePRs: (projectId: string, cursor: string) => Promise<PRListResult>;
getPR: (projectId: string, prNumber: number) => Promise<PRData | null>;
runPRReview: (projectId: string, prNumber: number) => void;
cancelPRReview: (projectId: string, prNumber: number) => Promise<boolean>;
postPRReview: (projectId: string, prNumber: number, selectedFindingIds?: string[], options?: { forceApprove?: boolean }) => Promise<boolean>;
deletePRReview: (projectId: string, prNumber: number) => Promise<boolean>;
postPRComment: (projectId: string, prNumber: number, body: string) => Promise<boolean>;
mergePR: (projectId: string, prNumber: number, mergeMethod?: 'merge' | 'squash' | 'rebase') => Promise<boolean>;
assignPR: (projectId: string, prNumber: number, username: string) => Promise<boolean>;
markReviewPosted: (projectId: string, prNumber: number) => Promise<boolean>;
getPRReview: (projectId: string, prNumber: number) => Promise<PRReviewResult | null>;
getPRReviewsBatch: (projectId: string, prNumbers: number[]) => Promise<Record<number, PRReviewResult | null>>;
// Follow-up review operations
checkNewCommits: (projectId: string, prNumber: number) => Promise<NewCommitsCheck>;
checkMergeReadiness: (projectId: string, prNumber: number) => Promise<MergeReadiness>;
updatePRBranch: (projectId: string, prNumber: number) => Promise<{ success: boolean; error?: string }>;
runFollowupReview: (projectId: string, prNumber: number) => void;
// PR logs
getPRLogs: (projectId: string, prNumber: number) => Promise<PRLogs | null>;
// Workflow approval (for fork PRs)
getWorkflowsAwaitingApproval: (projectId: string, prNumber: number) => Promise<WorkflowsAwaitingApprovalResult>;
approveWorkflow: (projectId: string, runId: number) => Promise<boolean>;
// PR event listeners
onPRReviewProgress: (
callback: (projectId: string, progress: PRReviewProgress) => void
) => IpcListenerCleanup;
onPRReviewComplete: (
callback: (projectId: string, result: PRReviewResult) => void
) => IpcListenerCleanup;
onPRReviewError: (
callback: (projectId: string, error: { prNumber: number; error: string }) => void
) => IpcListenerCleanup;
}
/**
* PR data from GitHub API
*/
export interface PRData {
number: number;
title: string;
body: string;
state: string;
author: { login: string };
headRefName: string;
baseRefName: string;
additions: number;
deletions: number;
changedFiles: number;
assignees: Array<{ login: string }>;
files: Array<{
path: string;
additions: number;
deletions: number;
status: string;
}>;
createdAt: string;
updatedAt: string;
htmlUrl: string;
}
/**
* PR list result with pagination info
*/
export interface PRListResult {
prs: PRData[];
hasNextPage: boolean; // True if more PRs exist beyond the 100 limit
endCursor?: string | null; // Cursor for fetching next page (null if no more pages)
}
/**
* PR review finding
*/
export interface PRReviewFinding {
id: string;
severity: 'critical' | 'high' | 'medium' | 'low';
category: 'security' | 'quality' | 'style' | 'test' | 'docs' | 'pattern' | 'performance';
title: string;
description: string;
file: string;
line: number;
endLine?: number;
suggestedFix?: string;
fixable: boolean;
}
/**
* PR review result
*/
export interface PRReviewResult {
prNumber: number;
repo: string;
success: boolean;
findings: PRReviewFinding[];
summary: string;
overallStatus: 'approve' | 'request_changes' | 'comment';
reviewId?: number;
reviewedAt: string;
error?: string;
// Follow-up review fields
reviewedCommitSha?: string;
reviewedFileBlobs?: Record<string, string>; // filename → blob SHA for rebase-resistant follow-ups
isFollowupReview?: boolean;
previousReviewId?: number;
resolvedFindings?: string[];
unresolvedFindings?: string[];
newFindingsSinceLastReview?: string[];
// Track if findings have been posted to GitHub (enables follow-up review)
hasPostedFindings?: boolean;
postedFindingIds?: string[];
postedAt?: string;
}
/**
* Result of checking for new commits since last review
*/
export interface NewCommitsCheck {
hasNewCommits: boolean;
newCommitCount: number;
lastReviewedCommit?: string;
currentHeadCommit?: string;
/** Whether new commits happened AFTER findings were posted (for "Ready for Follow-up" status) */
hasCommitsAfterPosting?: boolean;
/** Whether new commits touch files that had findings (requires verification) */
hasOverlapWithFindings?: boolean;
/** Files from new commits that overlap with finding files */
overlappingFiles?: string[];
/** Whether this appears to be a merge from base branch (develop/main) */
isMergeFromBase?: boolean;
}
/**
* Lightweight merge readiness check result
* Used for real-time validation of AI verdict freshness
*/
export interface MergeReadiness {
/** PR is in draft mode */
isDraft: boolean;
/** GitHub's mergeable status */
mergeable: 'MERGEABLE' | 'CONFLICTING' | 'UNKNOWN';
/** Branch is behind base branch (out of date) */
isBehind: boolean;
/** Simplified CI status */
ciStatus: 'passing' | 'failing' | 'pending' | 'none';
/** List of blockers that contradict a "ready to merge" verdict */
blockers: string[];
}
/**
* Review progress status
*/
export interface PRReviewProgress {
phase: 'fetching' | 'analyzing' | 'generating' | 'posting' | 'complete';
prNumber: number;
progress: number;
message: string;
}
/**
* PR review log entry type
*/
export type PRLogEntryType = 'text' | 'tool_start' | 'tool_end' | 'phase_start' | 'phase_end' | 'error' | 'success' | 'info';
/**
* PR review log phase
*/
export type PRLogPhase = 'context' | 'analysis' | 'synthesis';
/**
* Single log entry in PR review
*/
export interface PRLogEntry {
timestamp: string;
type: PRLogEntryType;
content: string;
phase: PRLogPhase;
source?: string; // e.g., 'Context', 'AI', 'Orchestrator', 'ParallelFollowup'
detail?: string; // Expandable detail content
collapsed?: boolean;
}
/**
* Phase log containing entries
*/
export interface PRPhaseLog {
phase: PRLogPhase;
status: 'pending' | 'active' | 'completed' | 'failed';
started_at: string | null;
completed_at: string | null;
entries: PRLogEntry[];
}
/**
* Complete PR review logs
*/
export interface PRLogs {
pr_number: number;
repo: string;
created_at: string;
updated_at: string;
is_followup: boolean;
phases: {
context: PRPhaseLog;
analysis: PRPhaseLog;
synthesis: PRPhaseLog;
};
}
/**
* Creates the GitHub Integration API implementation
*/
export const createGitHubAPI = (): GitHubAPI => ({
// Operations
getGitHubRepositories: (projectId: string): Promise<IPCResult<GitHubRepository[]>> =>
invokeIpc(IPC_CHANNELS.GITHUB_GET_REPOSITORIES, projectId),
getGitHubIssues: (
projectId: string,
state?: 'open' | 'closed' | 'all',
page?: number,
fetchAll?: boolean
): Promise<IPCResult<PaginatedIssuesResult>> =>
invokeIpc(IPC_CHANNELS.GITHUB_GET_ISSUES, projectId, state, page, fetchAll),
getGitHubIssue: (projectId: string, issueNumber: number): Promise<IPCResult<GitHubIssue>> =>
invokeIpc(IPC_CHANNELS.GITHUB_GET_ISSUE, projectId, issueNumber),
getIssueComments: (projectId: string, issueNumber: number): Promise<IPCResult<any[]>> =>
invokeIpc(IPC_CHANNELS.GITHUB_GET_ISSUE_COMMENTS, projectId, issueNumber),
checkGitHubConnection: (projectId: string): Promise<IPCResult<GitHubSyncStatus>> =>
invokeIpc(IPC_CHANNELS.GITHUB_CHECK_CONNECTION, projectId),
investigateGitHubIssue: (projectId: string, issueNumber: number, selectedCommentIds?: number[]): void =>
sendIpc(IPC_CHANNELS.GITHUB_INVESTIGATE_ISSUE, projectId, issueNumber, selectedCommentIds),
importGitHubIssues: (projectId: string, issueNumbers: number[]): Promise<IPCResult<GitHubImportResult>> =>
invokeIpc(IPC_CHANNELS.GITHUB_IMPORT_ISSUES, projectId, issueNumbers),
createGitHubRelease: (
projectId: string,
version: string,
releaseNotes: string,
options?: { draft?: boolean; prerelease?: boolean }
): Promise<IPCResult<{ url: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_CREATE_RELEASE, projectId, version, releaseNotes, options),
suggestReleaseVersion: (projectId: string): Promise<IPCResult<VersionSuggestion>> =>
invokeIpc(IPC_CHANNELS.RELEASE_SUGGEST_VERSION, projectId),
// OAuth operations (gh CLI)
checkGitHubCli: (): Promise<IPCResult<{ installed: boolean; version?: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_CHECK_CLI),
checkGitHubAuth: (): Promise<IPCResult<{ authenticated: boolean; username?: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_CHECK_AUTH),
startGitHubAuth: (): Promise<IPCResult<{ success: boolean; message?: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_START_AUTH),
getGitHubToken: (): Promise<IPCResult<{ token: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_GET_TOKEN),
getGitHubUser: (): Promise<IPCResult<{ username: string; name?: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_GET_USER),
listGitHubUserRepos: (): Promise<IPCResult<{ repos: Array<{ fullName: string; description: string | null; isPrivate: boolean }> }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_LIST_USER_REPOS),
// OAuth event listener - receives device code immediately when extracted (during auth process)
onGitHubAuthDeviceCode: (
callback: (data: { deviceCode: string; authUrl: string; browserOpened: boolean }) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTH_DEVICE_CODE, callback),
// OAuth event listener - notifies when GitHub account changes (via gh auth login)
onGitHubAuthChanged: (
callback: (data: { oldUsername: string | null; newUsername: string }) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTH_CHANGED, callback),
// Repository detection and management
detectGitHubRepo: (projectPath: string): Promise<IPCResult<string>> =>
invokeIpc(IPC_CHANNELS.GITHUB_DETECT_REPO, projectPath),
getGitHubBranches: (repo: string, token: string): Promise<IPCResult<string[]>> =>
invokeIpc(IPC_CHANNELS.GITHUB_GET_BRANCHES, repo, token),
createGitHubRepo: (
repoName: string,
options: { description?: string; isPrivate?: boolean; projectPath: string; owner?: string }
): Promise<IPCResult<{ fullName: string; url: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_CREATE_REPO, repoName, options),
addGitRemote: (
projectPath: string,
repoFullName: string
): Promise<IPCResult<{ remoteUrl: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_ADD_REMOTE, projectPath, repoFullName),
listGitHubOrgs: (): Promise<IPCResult<{ orgs: Array<{ login: string; avatarUrl?: string }> }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_LIST_ORGS),
// Event Listeners
onGitHubInvestigationProgress: (
callback: (projectId: string, status: GitHubInvestigationStatus) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_INVESTIGATION_PROGRESS, callback),
onGitHubInvestigationComplete: (
callback: (projectId: string, result: GitHubInvestigationResult) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_INVESTIGATION_COMPLETE, callback),
onGitHubInvestigationError: (
callback: (projectId: string, error: string) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_INVESTIGATION_ERROR, callback),
// Auto-fix operations
getAutoFixConfig: (projectId: string): Promise<AutoFixConfig | null> =>
invokeIpc(IPC_CHANNELS.GITHUB_AUTOFIX_GET_CONFIG, projectId),
saveAutoFixConfig: (projectId: string, config: AutoFixConfig): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_AUTOFIX_SAVE_CONFIG, projectId, config),
getAutoFixQueue: (projectId: string): Promise<AutoFixQueueItem[]> =>
invokeIpc(IPC_CHANNELS.GITHUB_AUTOFIX_GET_QUEUE, projectId),
checkAutoFixLabels: (projectId: string): Promise<number[]> =>
invokeIpc(IPC_CHANNELS.GITHUB_AUTOFIX_CHECK_LABELS, projectId),
checkNewIssues: (projectId: string): Promise<Array<{number: number}>> =>
invokeIpc(IPC_CHANNELS.GITHUB_AUTOFIX_CHECK_NEW, projectId),
startAutoFix: (projectId: string, issueNumber: number): void =>
sendIpc(IPC_CHANNELS.GITHUB_AUTOFIX_START, projectId, issueNumber),
// Batch auto-fix operations
batchAutoFix: (projectId: string, issueNumbers?: number[]): void =>
sendIpc(IPC_CHANNELS.GITHUB_AUTOFIX_BATCH, projectId, issueNumbers),
getBatches: (projectId: string): Promise<IssueBatch[]> =>
invokeIpc(IPC_CHANNELS.GITHUB_AUTOFIX_GET_BATCHES, projectId),
// Auto-fix event listeners
onAutoFixProgress: (
callback: (projectId: string, progress: AutoFixProgress) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_PROGRESS, callback),
onAutoFixComplete: (
callback: (projectId: string, result: AutoFixQueueItem) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_COMPLETE, callback),
onAutoFixError: (
callback: (projectId: string, error: { issueNumber: number; error: string }) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_ERROR, callback),
// Batch auto-fix event listeners
onBatchProgress: (
callback: (projectId: string, progress: BatchProgress) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_BATCH_PROGRESS, callback),
onBatchComplete: (
callback: (projectId: string, batches: IssueBatch[]) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_BATCH_COMPLETE, callback),
onBatchError: (
callback: (projectId: string, error: { error: string }) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_BATCH_ERROR, callback),
// Analyze & Group Issues (proactive batch workflow)
analyzeIssuesPreview: (projectId: string, issueNumbers?: number[], maxIssues?: number): void =>
sendIpc(IPC_CHANNELS.GITHUB_AUTOFIX_ANALYZE_PREVIEW, projectId, issueNumbers, maxIssues),
approveBatches: (projectId: string, approvedBatches: ProposedBatch[]): Promise<{ success: boolean; batches?: IssueBatch[]; error?: string }> =>
invokeIpc(IPC_CHANNELS.GITHUB_AUTOFIX_APPROVE_BATCHES, projectId, approvedBatches),
// Analyze preview event listeners
onAnalyzePreviewProgress: (
callback: (projectId: string, progress: AnalyzePreviewProgress) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_ANALYZE_PREVIEW_PROGRESS, callback),
onAnalyzePreviewComplete: (
callback: (projectId: string, result: AnalyzePreviewResult) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_ANALYZE_PREVIEW_COMPLETE, callback),
onAnalyzePreviewError: (
callback: (projectId: string, error: { error: string }) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_AUTOFIX_ANALYZE_PREVIEW_ERROR, callback),
// PR operations
// Fetches up to 100 open PRs at once (GitHub GraphQL limit)
listPRs: (projectId: string): Promise<PRListResult> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_LIST, projectId),
// Load more PRs using cursor-based pagination
listMorePRs: (projectId: string, cursor: string): Promise<PRListResult> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_LIST_MORE, projectId, cursor),
getPR: (projectId: string, prNumber: number): Promise<PRData | null> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_GET, projectId, prNumber),
runPRReview: (projectId: string, prNumber: number): void =>
sendIpc(IPC_CHANNELS.GITHUB_PR_REVIEW, projectId, prNumber),
cancelPRReview: (projectId: string, prNumber: number): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_REVIEW_CANCEL, projectId, prNumber),
postPRReview: (projectId: string, prNumber: number, selectedFindingIds?: string[], options?: { forceApprove?: boolean }): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_POST_REVIEW, projectId, prNumber, selectedFindingIds, options),
deletePRReview: (projectId: string, prNumber: number): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_DELETE_REVIEW, projectId, prNumber),
postPRComment: (projectId: string, prNumber: number, body: string): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_POST_COMMENT, projectId, prNumber, body),
mergePR: (projectId: string, prNumber: number, mergeMethod: 'merge' | 'squash' | 'rebase' = 'squash'): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_MERGE, projectId, prNumber, mergeMethod),
assignPR: (projectId: string, prNumber: number, username: string): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_ASSIGN, projectId, prNumber, username),
markReviewPosted: (projectId: string, prNumber: number): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_MARK_REVIEW_POSTED, projectId, prNumber),
getPRReview: (projectId: string, prNumber: number): Promise<PRReviewResult | null> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_GET_REVIEW, projectId, prNumber),
getPRReviewsBatch: (projectId: string, prNumbers: number[]): Promise<Record<number, PRReviewResult | null>> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_GET_REVIEWS_BATCH, projectId, prNumbers),
// Follow-up review operations
checkNewCommits: (projectId: string, prNumber: number): Promise<NewCommitsCheck> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_CHECK_NEW_COMMITS, projectId, prNumber),
checkMergeReadiness: (projectId: string, prNumber: number): Promise<MergeReadiness> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_CHECK_MERGE_READINESS, projectId, prNumber),
updatePRBranch: (projectId: string, prNumber: number): Promise<{ success: boolean; error?: string }> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_UPDATE_BRANCH, projectId, prNumber),
runFollowupReview: (projectId: string, prNumber: number): void =>
sendIpc(IPC_CHANNELS.GITHUB_PR_FOLLOWUP_REVIEW, projectId, prNumber),
// PR logs
getPRLogs: (projectId: string, prNumber: number): Promise<PRLogs | null> =>
invokeIpc(IPC_CHANNELS.GITHUB_PR_GET_LOGS, projectId, prNumber),
// Workflow approval (for fork PRs)
getWorkflowsAwaitingApproval: (projectId: string, prNumber: number): Promise<WorkflowsAwaitingApprovalResult> =>
invokeIpc(IPC_CHANNELS.GITHUB_WORKFLOWS_AWAITING_APPROVAL, projectId, prNumber),
approveWorkflow: (projectId: string, runId: number): Promise<boolean> =>
invokeIpc(IPC_CHANNELS.GITHUB_WORKFLOW_APPROVE, projectId, runId),
// PR event listeners
onPRReviewProgress: (
callback: (projectId: string, progress: PRReviewProgress) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_PR_REVIEW_PROGRESS, callback),
onPRReviewComplete: (
callback: (projectId: string, result: PRReviewResult) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_PR_REVIEW_COMPLETE, callback),
onPRReviewError: (
callback: (projectId: string, error: { prNumber: number; error: string }) => void
): IpcListenerCleanup =>
createIpcListener(IPC_CHANNELS.GITHUB_PR_REVIEW_ERROR, callback)
});