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 <[email protected]>
This commit is contained in:
Sondre Engebråten
2026-02-13 18:25:19 +01:00
co-authored by Claude Opus 4.6
parent d20a62d6fd
commit 65bcb72ca6
8 changed files with 347 additions and 5 deletions
@@ -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<IPCResult<{ remoteUrl: string }>>;
listGitHubOrgs: () => Promise<IPCResult<{ orgs: Array<{ login: string; avatarUrl?: string }> }>>;
// Event Listeners
// Investigation operations (new system)
startInvestigation: (projectId: string, issueNumber: number) => void;
cancelInvestigation: (projectId: string, issueNumber: number) => void;
createTaskFromInvestigation: (projectId: string, issueNumber: number) => Promise<IPCResult<{ specId: string }>>;
dismissIssue: (projectId: string, issueNumber: number, reason: InvestigationDismissReason) => Promise<IPCResult>;
postInvestigationToGitHub: (projectId: string, issueNumber: number) => Promise<IPCResult<{ commentId: number }>>;
getInvestigationSettings: (projectId: string) => Promise<IPCResult<InvestigationSettings>>;
saveInvestigationSettings: (projectId: string, settings: Partial<InvestigationSettings>) => Promise<IPCResult>;
// 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<IPCResult<{ orgs: Array<{ login: string; avatarUrl?: string }> }>> =>
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<IPCResult<{ specId: string }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_CREATE_TASK, projectId, issueNumber),
dismissIssue: (projectId: string, issueNumber: number, reason: InvestigationDismissReason): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_DISMISS, projectId, issueNumber, reason),
postInvestigationToGitHub: (projectId: string, issueNumber: number): Promise<IPCResult<{ commentId: number }>> =>
invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_POST_GITHUB, projectId, issueNumber),
getInvestigationSettings: (projectId: string): Promise<IPCResult<InvestigationSettings>> =>
invokeIpc(IPC_CHANNELS.GITHUB_INVESTIGATION_GET_SETTINGS, projectId),
saveInvestigationSettings: (projectId: string, settings: Partial<InvestigationSettings>): Promise<IPCResult> =>
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 =>
@@ -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: () => () => {},
@@ -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: () => () => {},
+10 -1
View File
@@ -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',
+1
View File
@@ -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';
@@ -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;
@@ -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;
}
+16 -1
View File
@@ -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<IPCResult<{ specId: string }>>;
dismissIssue: (projectId: string, issueNumber: number, reason: InvestigationDismissReason) => Promise<IPCResult>;
postInvestigationToGitHub: (projectId: string, issueNumber: number) => Promise<IPCResult<{ commentId: number }>>;
getInvestigationSettings: (projectId: string) => Promise<IPCResult<InvestigationSettings>>;
saveInvestigationSettings: (projectId: string, settings: Partial<InvestigationSettings>) => Promise<IPCResult>;
// GitHub event listeners (legacy signatures kept for backwards compatibility)
onGitHubInvestigationProgress: (
callback: (projectId: string, status: GitHubInvestigationStatus) => void
) => () => void;