diff --git a/apps/server/src/routes/github/index.ts b/apps/server/src/routes/github/index.ts index bda4d217..57b7cad2 100644 --- a/apps/server/src/routes/github/index.ts +++ b/apps/server/src/routes/github/index.ts @@ -3,16 +3,19 @@ */ import { Router } from 'express'; +import { validatePathParams } from '../../middleware/validate-paths.js'; import { createCheckGitHubRemoteHandler } from './routes/check-github-remote.js'; import { createListIssuesHandler } from './routes/list-issues.js'; import { createListPRsHandler } from './routes/list-prs.js'; +import { createValidateIssueHandler } from './routes/validate-issue.js'; export function createGitHubRoutes(): Router { const router = Router(); - router.post('/check-remote', createCheckGitHubRemoteHandler()); - router.post('/issues', createListIssuesHandler()); - router.post('/prs', createListPRsHandler()); + router.post('/check-remote', validatePathParams('projectPath'), createCheckGitHubRemoteHandler()); + router.post('/issues', validatePathParams('projectPath'), createListIssuesHandler()); + router.post('/prs', validatePathParams('projectPath'), createListPRsHandler()); + router.post('/validate-issue', validatePathParams('projectPath'), createValidateIssueHandler()); return router; } diff --git a/apps/server/src/routes/github/routes/validate-issue.ts b/apps/server/src/routes/github/routes/validate-issue.ts new file mode 100644 index 00000000..3bb7a66e --- /dev/null +++ b/apps/server/src/routes/github/routes/validate-issue.ts @@ -0,0 +1,164 @@ +/** + * POST /validate-issue endpoint - Validate a GitHub issue using Claude SDK + * + * Scans the codebase to determine if an issue is valid, invalid, or needs clarification. + */ + +import type { Request, Response } from 'express'; +import { query } from '@anthropic-ai/claude-agent-sdk'; +import { createLogger } from '@automaker/utils'; +import type { IssueValidationResult } from '@automaker/types'; +import { createSuggestionsOptions } from '../../../lib/sdk-options.js'; +import { + issueValidationSchema, + ISSUE_VALIDATION_SYSTEM_PROMPT, + buildValidationPrompt, +} from './validation-schema.js'; +import { getErrorMessage, logError } from './common.js'; + +const logger = createLogger('IssueValidation'); + +/** + * Request body for issue validation + */ +interface ValidateIssueRequestBody { + projectPath: string; + issueNumber: number; + issueTitle: string; + issueBody: string; + issueLabels?: string[]; +} + +/** + * Creates the handler for validating GitHub issues against the codebase. + * + * Uses Claude SDK with: + * - Read-only tools (Read, Glob, Grep) for codebase analysis + * - JSON schema structured output for reliable parsing + * - System prompt guiding the validation process + */ +export function createValidateIssueHandler() { + return async (req: Request, res: Response): Promise => { + // Declare timeoutId outside try block for proper cleanup + let timeoutId: ReturnType | undefined; + + try { + const { projectPath, issueNumber, issueTitle, issueBody, issueLabels } = + req.body as ValidateIssueRequestBody; + + // Validate required fields + if (!projectPath) { + res.status(400).json({ success: false, error: 'projectPath is required' }); + return; + } + + if (!issueNumber || typeof issueNumber !== 'number') { + res + .status(400) + .json({ success: false, error: 'issueNumber is required and must be a number' }); + return; + } + + if (!issueTitle || typeof issueTitle !== 'string') { + res.status(400).json({ success: false, error: 'issueTitle is required' }); + return; + } + + if (typeof issueBody !== 'string') { + res.status(400).json({ success: false, error: 'issueBody must be a string' }); + return; + } + + logger.info(`Validating issue #${issueNumber}: ${issueTitle}`); + + // Build the prompt + const prompt = buildValidationPrompt(issueNumber, issueTitle, issueBody, issueLabels); + + // Create abort controller with 2 minute timeout for validation + const abortController = new AbortController(); + const VALIDATION_TIMEOUT_MS = 120000; // 2 minutes + timeoutId = setTimeout(() => { + logger.warn(`Validation timeout reached after ${VALIDATION_TIMEOUT_MS}ms`); + abortController.abort(); + }, VALIDATION_TIMEOUT_MS); + + // Create SDK options with structured output and abort controller + const options = createSuggestionsOptions({ + cwd: projectPath, + systemPrompt: ISSUE_VALIDATION_SYSTEM_PROMPT, + abortController, + outputFormat: { + type: 'json_schema', + schema: issueValidationSchema as Record, + }, + }); + + // Execute the query + const stream = query({ prompt, options }); + let validationResult: IssueValidationResult | null = null; + let responseText = ''; + + for await (const msg of stream) { + // Collect assistant text for debugging/fallback + if (msg.type === 'assistant' && msg.message?.content) { + for (const block of msg.message.content) { + if (block.type === 'text') { + responseText += block.text; + } + } + } + + // Extract structured output on success + if (msg.type === 'result' && msg.subtype === 'success') { + const resultMsg = msg as { structured_output?: IssueValidationResult }; + if (resultMsg.structured_output) { + validationResult = resultMsg.structured_output; + logger.debug('Received structured output:', validationResult); + } + } + + // Handle errors + if (msg.type === 'result') { + const resultMsg = msg as { subtype?: string }; + if (resultMsg.subtype === 'error_max_structured_output_retries') { + logger.error('Failed to produce valid structured output after retries'); + throw new Error('Could not produce valid validation output'); + } + } + } + + // Require structured output - no fragile fallback parsing + if (!validationResult) { + logger.error('No structured output received from Claude SDK'); + logger.debug('Raw response text:', responseText); + throw new Error('Validation failed: no structured output received'); + } + + // Clear the timeout since we completed successfully + clearTimeout(timeoutId); + + logger.info(`Issue #${issueNumber} validation complete: ${validationResult.verdict}`); + res.json({ + success: true, + issueNumber, + validation: validationResult, + }); + } catch (error) { + // Clear timeout on error as well (if it was set) + if (timeoutId) { + clearTimeout(timeoutId); + } + + logError(error, `Issue validation failed`); + logger.error('Issue validation error:', error); + + // Check if response already sent + if (!res.headersSent) { + res.status(500).json({ + success: false, + error: getErrorMessage(error), + }); + } + } + }; +} diff --git a/apps/server/src/routes/github/routes/validation-schema.ts b/apps/server/src/routes/github/routes/validation-schema.ts new file mode 100644 index 00000000..50812082 --- /dev/null +++ b/apps/server/src/routes/github/routes/validation-schema.ts @@ -0,0 +1,138 @@ +/** + * Issue Validation Schema and System Prompt + * + * Defines the JSON schema for Claude's structured output and + * the system prompt that guides the validation process. + */ + +/** + * JSON Schema for issue validation structured output. + * Used with Claude SDK's outputFormat option to ensure reliable parsing. + */ +export const issueValidationSchema = { + type: 'object', + properties: { + verdict: { + type: 'string', + enum: ['valid', 'invalid', 'needs_clarification'], + description: 'The validation verdict for the issue', + }, + confidence: { + type: 'string', + enum: ['high', 'medium', 'low'], + description: 'How confident the AI is in its assessment', + }, + reasoning: { + type: 'string', + description: 'Detailed explanation of the verdict', + }, + bugConfirmed: { + type: 'boolean', + description: 'For bug reports: whether the bug was confirmed in the codebase', + }, + relatedFiles: { + type: 'array', + items: { type: 'string' }, + description: 'Files related to the issue found during analysis', + }, + suggestedFix: { + type: 'string', + description: 'Suggested approach to fix or implement the issue', + }, + missingInfo: { + type: 'array', + items: { type: 'string' }, + description: 'Information needed when verdict is needs_clarification', + }, + estimatedComplexity: { + type: 'string', + enum: ['trivial', 'simple', 'moderate', 'complex', 'very_complex'], + description: 'Estimated effort to address the issue', + }, + }, + required: ['verdict', 'confidence', 'reasoning'], + additionalProperties: false, +} as const; + +/** + * System prompt that guides Claude in validating GitHub issues. + * Instructs the model to use read-only tools to analyze the codebase. + */ +export const ISSUE_VALIDATION_SYSTEM_PROMPT = `You are an expert code analyst validating GitHub issues against a codebase. + +Your task is to analyze a GitHub issue and determine if it's valid by scanning the codebase. + +## Validation Process + +1. **Read the issue carefully** - Understand what is being reported or requested +2. **Search the codebase** - Use Glob to find relevant files by pattern, Grep to search for keywords +3. **Examine the code** - Use Read to look at the actual implementation in relevant files +4. **Form your verdict** - Based on your analysis, determine if the issue is valid + +## Verdicts + +- **valid**: The issue describes a real problem that exists in the codebase, or a clear feature request that can be implemented. The referenced files/components exist and the issue is actionable. + +- **invalid**: The issue describes behavior that doesn't exist, references non-existent files or components, is based on a misunderstanding of the code, or the described "bug" is actually expected behavior. + +- **needs_clarification**: The issue lacks sufficient detail to verify. Specify what additional information is needed in the missingInfo field. + +## For Bug Reports, Check: +- Do the referenced files/components exist? +- Does the code match what the issue describes? +- Is the described behavior actually a bug or expected? +- Can you locate the code that would cause the reported issue? + +## For Feature Requests, Check: +- Does the feature already exist? +- Is the implementation location clear? +- Is the request technically feasible given the codebase structure? + +## Response Guidelines + +- **Always include relatedFiles** when you find relevant code +- **Set bugConfirmed to true** only if you can definitively confirm a bug exists in the code +- **Provide a suggestedFix** when you have a clear idea of how to address the issue +- **Use missingInfo** when the verdict is needs_clarification to list what's needed +- **Set estimatedComplexity** to help prioritize: + - trivial: Simple text changes, one-line fixes + - simple: Small changes to one file + - moderate: Changes to multiple files or moderate logic changes + - complex: Significant refactoring or new feature implementation + - very_complex: Major architectural changes or cross-cutting concerns + +Be thorough in your analysis but focus on files that are directly relevant to the issue.`; + +/** + * Build the user prompt for issue validation. + * + * Creates a structured prompt that includes the issue details for Claude + * to analyze against the codebase. + * + * @param issueNumber - The GitHub issue number + * @param issueTitle - The issue title + * @param issueBody - The issue body/description + * @param issueLabels - Optional array of label names + * @returns Formatted prompt string for the validation request + */ +export function buildValidationPrompt( + issueNumber: number, + issueTitle: string, + issueBody: string, + issueLabels?: string[] +): string { + const labelsSection = issueLabels?.length ? `\n\n**Labels:** ${issueLabels.join(', ')}` : ''; + + return `Please validate the following GitHub issue by analyzing the codebase: + +## Issue #${issueNumber}: ${issueTitle} +${labelsSection} + +### Description + +${issueBody || '(No description provided)'} + +--- + +Scan the codebase to verify this issue. Look for the files, components, or functionality mentioned. Determine if this issue is valid, invalid, or needs clarification.`; +} diff --git a/apps/ui/src/components/views/github-issues-view.tsx b/apps/ui/src/components/views/github-issues-view.tsx index 941b5db8..ef760833 100644 --- a/apps/ui/src/components/views/github-issues-view.tsx +++ b/apps/ui/src/components/views/github-issues-view.tsx @@ -1,10 +1,43 @@ import { useState, useEffect, useCallback } from 'react'; -import { CircleDot, Loader2, RefreshCw, ExternalLink, CheckCircle2, Circle, X } from 'lucide-react'; -import { getElectronAPI, GitHubIssue } from '@/lib/electron'; +import { + CircleDot, + Loader2, + RefreshCw, + ExternalLink, + CheckCircle2, + Circle, + X, + Wand2, +} from 'lucide-react'; +import { + getElectronAPI, + GitHubIssue, + IssueValidationResult, + IssueComplexity, +} from '@/lib/electron'; + +/** + * Map issue complexity to feature priority. + * Lower complexity issues get higher priority (1 = high, 2 = medium). + */ +function getFeaturePriority(complexity: IssueComplexity | undefined): number { + switch (complexity) { + case 'trivial': + case 'simple': + return 1; // High priority for easy wins + case 'moderate': + case 'complex': + case 'very_complex': + default: + return 2; // Medium priority for larger efforts + } +} import { useAppStore } from '@/store/app-store'; import { Button } from '@/components/ui/button'; import { Markdown } from '@/components/ui/markdown'; import { cn } from '@/lib/utils'; +import { toast } from 'sonner'; +import { ValidationDialog } from './github-issues-view/validation-dialog'; export function GitHubIssuesView() { const [openIssues, setOpenIssues] = useState([]); @@ -13,6 +46,9 @@ export function GitHubIssuesView() { const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const [selectedIssue, setSelectedIssue] = useState(null); + const [validating, setValidating] = useState(false); + const [validationResult, setValidationResult] = useState(null); + const [showValidationDialog, setShowValidationDialog] = useState(false); const { currentProject } = useAppStore(); const fetchIssues = useCallback(async () => { @@ -57,6 +93,103 @@ export function GitHubIssuesView() { api.openExternalLink(url); }, []); + const handleValidateIssue = useCallback( + async (issue: GitHubIssue) => { + if (!currentProject?.path) { + toast.error('No project selected'); + return; + } + + setValidating(true); + setValidationResult(null); + setShowValidationDialog(true); + + try { + const api = getElectronAPI(); + if (api.github?.validateIssue) { + const result = await api.github.validateIssue(currentProject.path, { + issueNumber: issue.number, + issueTitle: issue.title, + issueBody: issue.body || '', + issueLabels: issue.labels.map((l) => l.name), + }); + + if (result.success) { + setValidationResult(result.validation); + } else { + toast.error(result.error || 'Failed to validate issue'); + setShowValidationDialog(false); + } + } + } catch (err) { + console.error('[GitHubIssuesView] Validation error:', err); + toast.error(err instanceof Error ? err.message : 'Failed to validate issue'); + setShowValidationDialog(false); + } finally { + setValidating(false); + } + }, + [currentProject?.path] + ); + + const handleConvertToTask = useCallback( + async (issue: GitHubIssue, validation: IssueValidationResult) => { + if (!currentProject?.path) { + toast.error('No project selected'); + return; + } + + try { + const api = getElectronAPI(); + if (api.features?.create) { + // Build description from issue body + validation info + const description = [ + `**From GitHub Issue #${issue.number}**`, + '', + issue.body || 'No description provided.', + '', + '---', + '', + '**AI Validation Analysis:**', + validation.reasoning, + validation.suggestedFix ? `\n**Suggested Approach:**\n${validation.suggestedFix}` : '', + validation.relatedFiles?.length + ? `\n**Related Files:**\n${validation.relatedFiles.map((f) => `- \`${f}\``).join('\n')}` + : '', + ] + .filter(Boolean) + .join('\n'); + + const feature = { + id: `issue-${issue.number}-${crypto.randomUUID()}`, + title: issue.title, + description, + category: 'From GitHub', + status: 'backlog' as const, + passes: false, + priority: getFeaturePriority(validation.estimatedComplexity), + model: 'opus' as const, + thinkingLevel: 'none' as const, + branchName: '', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + const result = await api.features.create(currentProject.path, feature); + if (result.success) { + toast.success(`Created task: ${issue.title}`); + } else { + toast.error(result.error || 'Failed to create task'); + } + } + } catch (err) { + console.error('[GitHubIssuesView] Convert to task error:', err); + toast.error(err instanceof Error ? err.message : 'Failed to create task'); + } + }, + [currentProject?.path] + ); + const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString('en-US', { @@ -184,6 +317,19 @@ export function GitHubIssuesView() {
+
)} + + {/* Validation Dialog */} + ); } diff --git a/apps/ui/src/components/views/github-issues-view/validation-dialog.tsx b/apps/ui/src/components/views/github-issues-view/validation-dialog.tsx new file mode 100644 index 00000000..5002d27a --- /dev/null +++ b/apps/ui/src/components/views/github-issues-view/validation-dialog.tsx @@ -0,0 +1,238 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { + CheckCircle2, + XCircle, + AlertCircle, + FileCode, + Lightbulb, + AlertTriangle, + Loader2, + Plus, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import type { + IssueValidationResult, + IssueValidationVerdict, + IssueValidationConfidence, + IssueComplexity, + GitHubIssue, +} from '@/lib/electron'; + +interface ValidationDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + issue: GitHubIssue | null; + validationResult: IssueValidationResult | null; + isValidating: boolean; + onConvertToTask?: (issue: GitHubIssue, validation: IssueValidationResult) => void; +} + +const verdictConfig: Record< + IssueValidationVerdict, + { label: string; color: string; bgColor: string; icon: typeof CheckCircle2 } +> = { + valid: { + label: 'Valid', + color: 'text-green-500', + bgColor: 'bg-green-500/10', + icon: CheckCircle2, + }, + invalid: { + label: 'Invalid', + color: 'text-red-500', + bgColor: 'bg-red-500/10', + icon: XCircle, + }, + needs_clarification: { + label: 'Needs Clarification', + color: 'text-yellow-500', + bgColor: 'bg-yellow-500/10', + icon: AlertCircle, + }, +}; + +const confidenceConfig: Record = { + high: { label: 'High Confidence', color: 'text-green-500' }, + medium: { label: 'Medium Confidence', color: 'text-yellow-500' }, + low: { label: 'Low Confidence', color: 'text-orange-500' }, +}; + +const complexityConfig: Record = { + trivial: { label: 'Trivial', color: 'text-green-500' }, + simple: { label: 'Simple', color: 'text-blue-500' }, + moderate: { label: 'Moderate', color: 'text-yellow-500' }, + complex: { label: 'Complex', color: 'text-orange-500' }, + very_complex: { label: 'Very Complex', color: 'text-red-500' }, +}; + +export function ValidationDialog({ + open, + onOpenChange, + issue, + validationResult, + isValidating, + onConvertToTask, +}: ValidationDialogProps) { + if (!issue) return null; + + const handleConvertToTask = () => { + if (validationResult && onConvertToTask) { + onConvertToTask(issue, validationResult); + onOpenChange(false); + } + }; + + return ( + + + + Issue Validation Result + + #{issue.number}: {issue.title} + + + + {isValidating ? ( +
+ +

Analyzing codebase to validate issue...

+
+ ) : validationResult ? ( +
+ {/* Verdict Badge */} +
+
+ {(() => { + const config = verdictConfig[validationResult.verdict]; + const Icon = config.icon; + return ( + <> +
+ +
+
+

{config.label}

+

+ {confidenceConfig[validationResult.confidence].label} +

+
+ + ); + })()} +
+ {validationResult.estimatedComplexity && ( +
+

Estimated Complexity

+

+ {complexityConfig[validationResult.estimatedComplexity].label} +

+
+ )} +
+ + {/* Bug Confirmed Badge */} + {validationResult.bugConfirmed && ( +
+ + Bug Confirmed in Codebase +
+ )} + + {/* Reasoning */} +
+

+ + Analysis +

+

+ {validationResult.reasoning} +

+
+ + {/* Related Files */} + {validationResult.relatedFiles && validationResult.relatedFiles.length > 0 && ( +
+

+ + Related Files +

+
+ {validationResult.relatedFiles.map((file, index) => ( +
+ {file} +
+ ))} +
+
+ )} + + {/* Suggested Fix */} + {validationResult.suggestedFix && ( +
+

Suggested Approach

+

+ {validationResult.suggestedFix} +

+
+ )} + + {/* Missing Info (for needs_clarification) */} + {validationResult.missingInfo && validationResult.missingInfo.length > 0 && ( +
+

+ + Missing Information +

+
    + {validationResult.missingInfo.map((info, index) => ( +
  • + {info} +
  • + ))} +
+
+ )} +
+ ) : ( +
+ +

No validation result available.

+
+ )} + + + + {validationResult?.verdict === 'valid' && onConvertToTask && ( + + )} + +
+
+ ); +} diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index f5b3e922..66631a9b 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -1,8 +1,26 @@ // Type definitions for Electron IPC API import type { SessionListItem, Message } from '@/types/electron'; import type { ClaudeUsageResponse } from '@/store/app-store'; +import type { + IssueValidationVerdict, + IssueValidationConfidence, + IssueComplexity, + IssueValidationInput, + IssueValidationResult, + IssueValidationResponse, +} from '@automaker/types'; import { getJSON, setJSON, removeItem } from './storage'; +// Re-export issue validation types for use in components +export type { + IssueValidationVerdict, + IssueValidationConfidence, + IssueComplexity, + IssueValidationInput, + IssueValidationResult, + IssueValidationResponse, +}; + export interface FileEntry { name: string; isDirectory: boolean; @@ -156,6 +174,10 @@ export interface GitHubAPI { mergedPRs?: GitHubPR[]; error?: string; }>; + validateIssue: ( + projectPath: string, + issue: IssueValidationInput + ) => Promise; } // Feature Suggestions types @@ -2631,6 +2653,22 @@ function createMockGitHubAPI(): GitHubAPI { mergedPRs: [], }; }, + validateIssue: async (projectPath: string, issue: IssueValidationInput) => { + console.log('[Mock] Validating GitHub issue:', { projectPath, issue }); + // Return a mock validation result + return { + success: true as const, + issueNumber: issue.issueNumber, + validation: { + verdict: 'valid' as const, + confidence: 'medium' as const, + reasoning: + 'This is a mock validation. In production, Claude SDK would analyze the codebase to validate this issue.', + relatedFiles: ['src/components/example.tsx'], + estimatedComplexity: 'moderate' as const, + }, + }; + }, }; } diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index 00b96d6b..2b35c48b 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -24,6 +24,7 @@ import type { GitHubAPI, GitHubIssue, GitHubPR, + IssueValidationInput, } from './electron'; import type { Message, SessionListItem } from '@/types/electron'; import type { Feature, ClaudeUsageResponse } from '@/store/app-store'; @@ -751,6 +752,8 @@ export class HttpApiClient implements ElectronAPI { checkRemote: (projectPath: string) => this.post('/api/github/check-remote', { projectPath }), listIssues: (projectPath: string) => this.post('/api/github/issues', { projectPath }), listPRs: (projectPath: string) => this.post('/api/github/prs', { projectPath }), + validateIssue: (projectPath: string, issue: IssueValidationInput) => + this.post('/api/github/validate-issue', { projectPath, ...issue }), }; // Workspace API diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts index 6e173075..1c0d717c 100644 --- a/libs/types/src/index.ts +++ b/libs/types/src/index.ts @@ -81,3 +81,15 @@ export { THINKING_LEVEL_LABELS, getModelDisplayName, } from './model-display.js'; + +// Issue validation types +export type { + IssueValidationVerdict, + IssueValidationConfidence, + IssueComplexity, + IssueValidationInput, + IssueValidationRequest, + IssueValidationResult, + IssueValidationResponse, + IssueValidationErrorResponse, +} from './issue-validation.js'; diff --git a/libs/types/src/issue-validation.ts b/libs/types/src/issue-validation.ts new file mode 100644 index 00000000..13fe9256 --- /dev/null +++ b/libs/types/src/issue-validation.ts @@ -0,0 +1,78 @@ +/** + * Issue Validation Types + * + * Types for validating GitHub issues against the codebase using Claude SDK. + */ + +/** + * Verdict from issue validation + */ +export type IssueValidationVerdict = 'valid' | 'invalid' | 'needs_clarification'; + +/** + * Confidence level of the validation + */ +export type IssueValidationConfidence = 'high' | 'medium' | 'low'; + +/** + * Complexity estimation for valid issues + */ +export type IssueComplexity = 'trivial' | 'simple' | 'moderate' | 'complex' | 'very_complex'; + +/** + * Issue data for validation (without projectPath) + * Used by UI when calling the validation API + */ +export interface IssueValidationInput { + issueNumber: number; + issueTitle: string; + issueBody: string; + issueLabels?: string[]; +} + +/** + * Full request payload for issue validation endpoint + * Includes projectPath for server-side handling + */ +export interface IssueValidationRequest extends IssueValidationInput { + projectPath: string; +} + +/** + * Result from Claude's issue validation analysis + */ +export interface IssueValidationResult { + /** Whether the issue is valid, invalid, or needs clarification */ + verdict: IssueValidationVerdict; + /** How confident the AI is in its assessment */ + confidence: IssueValidationConfidence; + /** Detailed explanation of the verdict */ + reasoning: string; + /** For bug reports: whether the bug was confirmed in the codebase */ + bugConfirmed?: boolean; + /** Files related to the issue found during analysis */ + relatedFiles?: string[]; + /** Suggested approach to fix or implement */ + suggestedFix?: string; + /** Information that's missing and needed for validation (when verdict = needs_clarification) */ + missingInfo?: string[]; + /** Estimated effort to address the issue */ + estimatedComplexity?: IssueComplexity; +} + +/** + * Successful response from validate-issue endpoint + */ +export interface IssueValidationResponse { + success: true; + issueNumber: number; + validation: IssueValidationResult; +} + +/** + * Error response from validate-issue endpoint + */ +export interface IssueValidationErrorResponse { + success: false; + error: string; +}