From 65bcb72ca655f9246fd763c7d6b50874fc5ff9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Engebr=C3=A5ten?= Date: Fri, 13 Feb 2026 18:25:19 +0100 Subject: [PATCH] feat(issues): add investigation type definitions, IPC channels, and preload bridge (F1) Create investigation.ts with 18 types/interfaces for the AI investigation system. Add 7 new IPC channels, update ElectronAPI with new method declarations, add preload bridge implementations, deprecate old GitHubInvestigationResult/Status types, and add mock stubs for browser dev mode. Co-Authored-By: Claude Opus 4.6 --- .../src/preload/api/modules/github-api.ts | 68 +++++- .../frontend/src/renderer/lib/browser-mock.ts | 12 + .../renderer/lib/mocks/integration-mock.ts | 9 + apps/frontend/src/shared/constants/ipc.ts | 11 +- apps/frontend/src/shared/types/index.ts | 1 + .../frontend/src/shared/types/integrations.ts | 8 + .../src/shared/types/investigation.ts | 226 ++++++++++++++++++ apps/frontend/src/shared/types/ipc.ts | 17 +- 8 files changed, 347 insertions(+), 5 deletions(-) create mode 100644 apps/frontend/src/shared/types/investigation.ts diff --git a/apps/frontend/src/preload/api/modules/github-api.ts b/apps/frontend/src/preload/api/modules/github-api.ts index ec1e075a..60090122 100644 --- a/apps/frontend/src/preload/api/modules/github-api.ts +++ b/apps/frontend/src/preload/api/modules/github-api.ts @@ -10,7 +10,11 @@ import type { VersionSuggestion, PaginatedIssuesResult, PRStatusUpdate, - PollingMetadata + PollingMetadata, + InvestigationProgress, + InvestigationResult, + InvestigationDismissReason, + InvestigationSettings } from '../../../shared/types'; import type { EnrichmentFile, IssueEnrichment, WorkflowState, Resolution } from '../../../shared/types/enrichment'; import type { LabelSyncConfig, LabelSyncResult } from '../../../shared/types/label-sync'; @@ -225,7 +229,27 @@ export interface GitHubAPI { ) => Promise>; listGitHubOrgs: () => Promise }>>; - // Event Listeners + // Investigation operations (new system) + startInvestigation: (projectId: string, issueNumber: number) => void; + cancelInvestigation: (projectId: string, issueNumber: number) => void; + createTaskFromInvestigation: (projectId: string, issueNumber: number) => Promise>; + dismissIssue: (projectId: string, issueNumber: number, reason: InvestigationDismissReason) => Promise; + postInvestigationToGitHub: (projectId: string, issueNumber: number) => Promise>; + getInvestigationSettings: (projectId: string) => Promise>; + saveInvestigationSettings: (projectId: string, settings: Partial) => Promise; + + // Investigation event listeners (new system) + onInvestigationProgress: ( + callback: (projectId: string, progress: InvestigationProgress) => void + ) => IpcListenerCleanup; + onInvestigationComplete: ( + callback: (projectId: string, result: InvestigationResult) => void + ) => IpcListenerCleanup; + onInvestigationError: ( + callback: (projectId: string, error: string) => void + ) => IpcListenerCleanup; + + // Legacy Event Listeners (deprecated — use investigation methods above) onGitHubInvestigationProgress: ( callback: (projectId: string, status: GitHubInvestigationStatus) => void ) => IpcListenerCleanup; @@ -682,7 +706,45 @@ export const createGitHubAPI = (): GitHubAPI => ({ listGitHubOrgs: (): Promise }>> => invokeIpc(IPC_CHANNELS.GITHUB_LIST_ORGS), - // Event Listeners + // Investigation operations (new system) + startInvestigation: (projectId: string, issueNumber: number): void => + sendIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_START, projectId, issueNumber), + + cancelInvestigation: (projectId: string, issueNumber: number): void => + sendIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_CANCEL, projectId, issueNumber), + + createTaskFromInvestigation: (projectId: string, issueNumber: number): Promise> => + invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_CREATE_TASK, projectId, issueNumber), + + dismissIssue: (projectId: string, issueNumber: number, reason: InvestigationDismissReason): Promise => + invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_DISMISS, projectId, issueNumber, reason), + + postInvestigationToGitHub: (projectId: string, issueNumber: number): Promise> => + invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_POST_GITHUB, projectId, issueNumber), + + getInvestigationSettings: (projectId: string): Promise> => + invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_GET_SETTINGS, projectId), + + saveInvestigationSettings: (projectId: string, settings: Partial): Promise => + invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_SAVE_SETTINGS, projectId, settings), + + // Investigation event listeners (new system) + onInvestigationProgress: ( + callback: (projectId: string, progress: InvestigationProgress) => void + ): IpcListenerCleanup => + createIpcListener(IPC_CHANNELS.GITHUB_INVESTIGATION_PROGRESS, callback), + + onInvestigationComplete: ( + callback: (projectId: string, result: InvestigationResult) => void + ): IpcListenerCleanup => + createIpcListener(IPC_CHANNELS.GITHUB_INVESTIGATION_COMPLETE, callback), + + onInvestigationError: ( + callback: (projectId: string, error: string) => void + ): IpcListenerCleanup => + createIpcListener(IPC_CHANNELS.GITHUB_INVESTIGATION_ERROR, callback), + + // Legacy Event Listeners (deprecated — use investigation methods above) onGitHubInvestigationProgress: ( callback: (projectId: string, status: GitHubInvestigationStatus) => void ): IpcListenerCleanup => diff --git a/apps/frontend/src/renderer/lib/browser-mock.ts b/apps/frontend/src/renderer/lib/browser-mock.ts index b8a21068..9be6b5a8 100644 --- a/apps/frontend/src/renderer/lib/browser-mock.ts +++ b/apps/frontend/src/renderer/lib/browser-mock.ts @@ -190,6 +190,18 @@ const browserMockAPI: ElectronAPI = { listGitHubOrgs: async () => ({ success: true, data: { orgs: [] } }), onGitHubAuthDeviceCode: () => () => {}, onGitHubAuthChanged: () => () => {}, + // Investigation operations (new system) + startInvestigation: () => {}, + cancelInvestigation: () => {}, + createTaskFromInvestigation: async () => ({ success: true, data: { specId: '' } }), + dismissIssue: async () => ({ success: true }), + postInvestigationToGitHub: async () => ({ success: true, data: { commentId: 0 } }), + getInvestigationSettings: async () => ({ success: true, data: { autoCreateTasks: false, autoStartTasks: false, pipelineMode: 'full' as const, autoPostToGitHub: false, autoCloseIssues: false, maxParallelInvestigations: 3, labelIncludeFilter: [], labelExcludeFilter: [] } }), + saveInvestigationSettings: async () => ({ success: true }), + onInvestigationProgress: () => () => {}, + onInvestigationComplete: () => () => {}, + onInvestigationError: () => () => {}, + // Legacy investigation listeners onGitHubInvestigationProgress: () => () => {}, onGitHubInvestigationComplete: () => () => {}, onGitHubInvestigationError: () => () => {}, diff --git a/apps/frontend/src/renderer/lib/mocks/integration-mock.ts b/apps/frontend/src/renderer/lib/mocks/integration-mock.ts index 3e5c0a9f..34e48ca8 100644 --- a/apps/frontend/src/renderer/lib/mocks/integration-mock.ts +++ b/apps/frontend/src/renderer/lib/mocks/integration-mock.ts @@ -135,6 +135,15 @@ export const integrationMock = { } }), + // Investigation operations (new system) + startInvestigation: () => {}, + cancelInvestigation: () => {}, + createTaskFromInvestigation: async () => ({ success: true, data: { specId: '' } }), + dismissIssue: async () => ({ success: true }), + postInvestigationToGitHub: async () => ({ success: true, data: { commentId: 0 } }), + getInvestigationSettings: async () => ({ success: true, data: { autoCreateTasks: false, autoStartTasks: false, pipelineMode: 'full' as const, autoPostToGitHub: false, autoCloseIssues: false, maxParallelInvestigations: 3, labelIncludeFilter: [], labelExcludeFilter: [] } }), + saveInvestigationSettings: async () => ({ success: true }), + onGitHubInvestigationProgress: () => () => {}, onGitHubInvestigationComplete: () => () => {}, onGitHubInvestigationError: () => () => {}, diff --git a/apps/frontend/src/shared/constants/ipc.ts b/apps/frontend/src/shared/constants/ipc.ts index 40e42dc5..2023abe7 100644 --- a/apps/frontend/src/shared/constants/ipc.ts +++ b/apps/frontend/src/shared/constants/ipc.ts @@ -266,7 +266,16 @@ export const IPC_CHANNELS = { GITHUB_AUTH_DEVICE_CODE: 'github:authDeviceCode', GITHUB_AUTH_CHANGED: 'github:authChanged', // Event: GitHub auth state changed (account swap) - // GitHub events (main -> renderer) + // GitHub Investigation operations (renderer -> main) + GITHUB_INVESTIGATION_START: 'github:investigation:start', + GITHUB_INVESTIGATION_CANCEL: 'github:investigation:cancel', + GITHUB_INVESTIGATION_CREATE_TASK: 'github:investigation:createTask', + GITHUB_INVESTIGATION_DISMISS: 'github:investigation:dismiss', + GITHUB_INVESTIGATION_POST_GITHUB: 'github:investigation:postGitHub', + GITHUB_INVESTIGATION_GET_SETTINGS: 'github:investigation:getSettings', + GITHUB_INVESTIGATION_SAVE_SETTINGS: 'github:investigation:saveSettings', + + // GitHub Investigation events (main -> renderer) GITHUB_INVESTIGATION_PROGRESS: 'github:investigationProgress', GITHUB_INVESTIGATION_COMPLETE: 'github:investigationComplete', GITHUB_INVESTIGATION_ERROR: 'github:investigationError', diff --git a/apps/frontend/src/shared/types/index.ts b/apps/frontend/src/shared/types/index.ts index f40b2748..5795ead4 100644 --- a/apps/frontend/src/shared/types/index.ts +++ b/apps/frontend/src/shared/types/index.ts @@ -18,6 +18,7 @@ export * from './changelog'; export * from './insights'; export * from './roadmap'; export * from './integrations'; +export * from './investigation'; export * from './app-update'; export * from './cli'; export * from './pr-status'; diff --git a/apps/frontend/src/shared/types/integrations.ts b/apps/frontend/src/shared/types/integrations.ts index 856222e6..ff69d28a 100644 --- a/apps/frontend/src/shared/types/integrations.ts +++ b/apps/frontend/src/shared/types/integrations.ts @@ -130,6 +130,10 @@ export interface GitHubImportResult { tasks?: import('./task').Task[]; } +/** + * @deprecated Use `InvestigationResult` from `./investigation` instead. + * Kept for backwards compatibility with existing code that references this type. + */ export interface GitHubInvestigationResult { success: boolean; issueNumber: number; @@ -144,6 +148,10 @@ export interface GitHubInvestigationResult { error?: string; } +/** + * @deprecated Use `InvestigationProgress` from `./investigation` instead. + * Kept for backwards compatibility with existing code that references this type. + */ export interface GitHubInvestigationStatus { phase: 'idle' | 'fetching' | 'analyzing' | 'creating_task' | 'complete' | 'error'; issueNumber?: number; diff --git a/apps/frontend/src/shared/types/investigation.ts b/apps/frontend/src/shared/types/investigation.ts new file mode 100644 index 00000000..d649d775 --- /dev/null +++ b/apps/frontend/src/shared/types/investigation.ts @@ -0,0 +1,226 @@ +/** + * AI Issue Investigation types + * + * Defines the type contracts for the investigation system that replaces + * the old triage/enrichment workflow. Investigation runs 4 specialist + * agents in parallel to analyze GitHub issues. + */ + +// ============================================ +// Investigation State Machine +// ============================================ + +/** + * Derived investigation state for an issue. + * Computed from investigation data + linked task status, never manually set. + */ +export type InvestigationState = + | 'new' + | 'investigating' + | 'findings_ready' + | 'resolved' + | 'failed' + | 'task_created' + | 'building' + | 'done'; + +/** + * The 4 specialist agent types that run during investigation. + */ +export type InvestigationAgentType = 'root_cause' | 'impact' | 'fix_advisor' | 'reproducer'; + +// ============================================ +// Agent Output Types +// ============================================ + +/** + * Code reference found by an investigation agent. + */ +export interface CodeReference { + file: string; + line?: number; + endLine?: number; + snippet?: string; + description: string; +} + +/** + * Base output from any specialist agent. + */ +export interface InvestigationAgentResult { + agentType: InvestigationAgentType; + summary: string; + findings: string[]; + codeReferences: CodeReference[]; + confidence: number; // 0-100 +} + +/** + * Root Cause Analyzer output — traces the bug/issue to its source. + */ +export interface RootCauseAnalysis extends InvestigationAgentResult { + agentType: 'root_cause'; + rootCause: string; + codePaths: string[]; + relatedIssues?: number[]; +} + +/** + * Impact Assessor output — determines blast radius. + */ +export interface ImpactAssessment extends InvestigationAgentResult { + agentType: 'impact'; + severity: 'critical' | 'high' | 'medium' | 'low'; + affectedComponents: string[]; + userImpact: string; + riskIfUnfixed: string; +} + +/** + * Fix Advisor output — suggests concrete fix approaches. + */ +export interface FixAdvice extends InvestigationAgentResult { + agentType: 'fix_advisor'; + suggestedApproaches: Array<{ + title: string; + description: string; + filesToModify: string[]; + complexity: 'simple' | 'standard' | 'complex'; + risks: string[]; + }>; + recommendedApproach: number; // index into suggestedApproaches + patternsToFollow: string[]; +} + +/** + * Reproducer output — reproducibility and test coverage. + */ +export interface ReproductionAnalysis extends InvestigationAgentResult { + agentType: 'reproducer'; + reproducible: boolean | 'unknown'; + reproductionSteps?: string[]; + existingTests: string[]; + testGaps: string[]; + suggestedTests: string[]; +} + +// ============================================ +// Investigation Report +// ============================================ + +/** + * Linked PR detected during investigation. + */ +export interface LinkedPR { + number: number; + title: string; + state: 'open' | 'closed' | 'merged'; + url: string; +} + +/** + * AI-suggested label for the issue. + */ +export interface SuggestedLabel { + name: string; + reason: string; + accepted?: boolean; // user accept/reject state +} + +/** + * Complete investigation report combining all 4 agent outputs. + */ +export interface InvestigationReport { + rootCause: RootCauseAnalysis; + impact: ImpactAssessment; + fixAdvice: FixAdvice; + reproduction: ReproductionAnalysis; + summary: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + suggestedLabels: SuggestedLabel[]; + likelyResolved: boolean; + linkedPRs: LinkedPR[]; + timestamp: string; +} + +// ============================================ +// Investigation Progress & Result +// ============================================ + +/** + * Per-agent status within an investigation. + */ +export interface AgentStatus { + agentType: InvestigationAgentType; + status: 'pending' | 'running' | 'completed' | 'failed'; + progress: number; // 0-100 + message?: string; +} + +/** + * Real-time progress update during investigation. + */ +export interface InvestigationProgress { + issueNumber: number; + phase: string; + progress: number; // 0-100 + message: string; + agentStatuses: AgentStatus[]; + startedAt: string; +} + +/** + * Final investigation result delivered on completion. + */ +export interface InvestigationResult { + issueNumber: number; + report: InvestigationReport; + specId?: string; + worktreePath?: string; + githubCommentId?: number; + completedAt: string; +} + +// ============================================ +// Dismiss & Settings +// ============================================ + +/** + * Reason for dismissing an issue. + */ +export type InvestigationDismissReason = 'wont_fix' | 'duplicate' | 'cannot_reproduce' | 'out_of_scope'; + +/** + * Pipeline mode for investigation-created tasks. + */ +export type InvestigationPipelineMode = 'full' | 'skip_to_planning' | 'minimal'; + +/** + * Investigation settings (subsection of GitHub settings). + */ +export interface InvestigationSettings { + autoCreateTasks: boolean; + autoStartTasks: boolean; + pipelineMode: InvestigationPipelineMode; + autoPostToGitHub: boolean; + autoCloseIssues: boolean; + maxParallelInvestigations: number; + labelIncludeFilter: string[]; + labelExcludeFilter: string[]; +} + +// ============================================ +// Batch Staging +// ============================================ + +/** + * An item in the batch staging area (auto-create review). + */ +export interface BatchStagingItem { + issueNumber: number; + issueTitle: string; + report: InvestigationReport; + specId: string; + approved?: boolean; + createdAt: string; +} diff --git a/apps/frontend/src/shared/types/ipc.ts b/apps/frontend/src/shared/types/ipc.ts index b1fc2c4b..358cbf3d 100644 --- a/apps/frontend/src/shared/types/ipc.ts +++ b/apps/frontend/src/shared/types/ipc.ts @@ -138,6 +138,12 @@ import type { GitLabMRReviewProgress, GitLabNewCommitsCheck } from './integrations'; +import type { + InvestigationProgress, + InvestigationResult, + InvestigationDismissReason, + InvestigationSettings +} from './investigation'; import type { APIProfile, ProfilesFile, TestConnectionResult, DiscoverModelsResult } from './profile'; // ============================================ @@ -533,7 +539,16 @@ export interface ElectronAPI { callback: (data: { deviceCode: string; authUrl: string; browserOpened: boolean }) => void ) => () => void; - // GitHub event listeners + // GitHub Investigation operations (new system) + startInvestigation: (projectId: string, issueNumber: number) => void; + cancelInvestigation: (projectId: string, issueNumber: number) => void; + createTaskFromInvestigation: (projectId: string, issueNumber: number) => Promise>; + dismissIssue: (projectId: string, issueNumber: number, reason: InvestigationDismissReason) => Promise; + postInvestigationToGitHub: (projectId: string, issueNumber: number) => Promise>; + getInvestigationSettings: (projectId: string) => Promise>; + saveInvestigationSettings: (projectId: string, settings: Partial) => Promise; + + // GitHub event listeners (legacy signatures kept for backwards compatibility) onGitHubInvestigationProgress: ( callback: (projectId: string, status: GitHubInvestigationStatus) => void ) => () => void;