diff --git a/.automaker/feature_list.json b/.automaker/feature_list.json index fda8794b..da003be0 100644 --- a/.automaker/feature_list.json +++ b/.automaker/feature_list.json @@ -262,5 +262,17 @@ "summary": "Added git diff panel for in-progress and waiting approval features. Created GitDiffPanel component with themed syntax highlighting. Modified: git-diff-panel.tsx (new), agent-output-modal.tsx, worktree-manager.js, auto-mode-service.js, main.js, preload.js, electron.d.ts. The panel shows changed files with +/- stats and expandable unified diff view using CSS theme variables.", "model": "opus", "thinkingLevel": "ultrathink" + }, + { + "id": "feature-1765370691119-yz43i6m6a", + "category": "Core", + "description": "i want you to run 'npx tsc --noEmi' on our app and fix / get rid of typescript errors that are left ", + "steps": [], + "status": "verified", + "imagePaths": [], + "skipTests": false, + "summary": "Fixed all TypeScript errors in the codebase. Modified: src/app/api/claude/test/route.ts (rewrote to use fetch instead of missing @anthropic-ai/sdk), src/lib/electron.ts (fixed saveImageToTemp signature, removed duplicate Window declaration), src/types/electron.d.ts (added deleteFile, resumeFeature, contextExists, analyzeProject methods), src/store/app-store.ts (added FileTreeNode, ProjectAnalysis types and analysis state/actions), src/components/views/analysis-view.tsx (added type annotations for implicit any), src/hooks/use-electron-agent.ts (fixed return type to include queue properties). Created: src/components/ui/badge.tsx (new component required by chat-history.tsx).", + "model": "opus", + "thinkingLevel": "ultrathink" } ] \ No newline at end of file diff --git a/app/src/app/api/claude/test/route.ts b/app/src/app/api/claude/test/route.ts index d7e85ca5..36a46e3a 100644 --- a/app/src/app/api/claude/test/route.ts +++ b/app/src/app/api/claude/test/route.ts @@ -1,6 +1,11 @@ -import Anthropic from "@anthropic-ai/sdk"; import { NextRequest, NextResponse } from "next/server"; +interface AnthropicResponse { + content?: Array<{ type: string; text?: string }>; + model?: string; + error?: { message?: string }; +} + export async function POST(request: NextRequest) { try { const { apiKey } = await request.json(); @@ -15,31 +20,60 @@ export async function POST(request: NextRequest) { ); } - // Create Anthropic client with the provided key - const anthropic = new Anthropic({ - apiKey: effectiveApiKey, + // Send a simple test prompt to the Anthropic API + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": effectiveApiKey, + "anthropic-version": "2023-06-01", + }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: 100, + messages: [ + { + role: "user", + content: "Respond with exactly: 'Claude API connection successful!' and nothing else.", + }, + ], + }), }); - // Send a simple test prompt - const response = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", - max_tokens: 100, - messages: [ - { - role: "user", - content: "Respond with exactly: 'Claude SDK connection successful!' and nothing else.", - }, - ], - }); + if (!response.ok) { + const errorData = (await response.json()) as AnthropicResponse; + const errorMessage = errorData.error?.message || `HTTP ${response.status}`; + + if (response.status === 401) { + return NextResponse.json( + { success: false, error: "Invalid API key. Please check your Anthropic API key." }, + { status: 401 } + ); + } + + if (response.status === 429) { + return NextResponse.json( + { success: false, error: "Rate limit exceeded. Please try again later." }, + { status: 429 } + ); + } + + return NextResponse.json( + { success: false, error: `API error: ${errorMessage}` }, + { status: response.status } + ); + } + + const data = (await response.json()) as AnthropicResponse; // Check if we got a valid response - if (response.content && response.content.length > 0) { - const textContent = response.content.find((block) => block.type === "text"); - if (textContent && textContent.type === "text") { + if (data.content && data.content.length > 0) { + const textContent = data.content.find((block) => block.type === "text"); + if (textContent && textContent.type === "text" && textContent.text) { return NextResponse.json({ success: true, message: `Connection successful! Response: "${textContent.text}"`, - model: response.model, + model: data.model, }); } } @@ -47,33 +81,11 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: true, message: "Connection successful! Claude responded.", - model: response.model, + model: data.model, }); } catch (error: unknown) { console.error("Claude API test error:", error); - // Handle specific Anthropic API errors - if (error instanceof Anthropic.AuthenticationError) { - return NextResponse.json( - { success: false, error: "Invalid API key. Please check your Anthropic API key." }, - { status: 401 } - ); - } - - if (error instanceof Anthropic.RateLimitError) { - return NextResponse.json( - { success: false, error: "Rate limit exceeded. Please try again later." }, - { status: 429 } - ); - } - - if (error instanceof Anthropic.APIError) { - return NextResponse.json( - { success: false, error: `API error: ${error.message}` }, - { status: error.status || 500 } - ); - } - const errorMessage = error instanceof Error ? error.message : "Failed to connect to Claude API"; diff --git a/app/src/components/ui/badge.tsx b/app/src/components/ui/badge.tsx new file mode 100644 index 00000000..9ec9a1a0 --- /dev/null +++ b/app/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/app/src/components/views/analysis-view.tsx b/app/src/components/views/analysis-view.tsx index 4a4d3005..48f4e07d 100644 --- a/app/src/components/views/analysis-view.tsx +++ b/app/src/components/views/analysis-view.tsx @@ -326,8 +326,8 @@ export function AnalysisView() { const analyzeStructure = () => { const structure: string[] = []; const topLevelDirs = projectAnalysis.fileTree - .filter((n) => n.isDirectory) - .map((n) => n.name); + .filter((n: FileTreeNode) => n.isDirectory) + .map((n: FileTreeNode) => n.name); for (const dir of topLevelDirs) { structure.push(` `); @@ -350,14 +350,14 @@ export function AnalysisView() { ${Object.entries(projectAnalysis.filesByExtension) - .filter(([ext]) => + .filter(([ext]: [string, number]) => ["ts", "tsx", "js", "jsx", "py", "go", "rs", "java", "cpp", "c"].includes( ext ) ) - .sort((a, b) => b[1] - a[1]) + .sort((a: [string, number], b: [string, number]) => b[1] - a[1]) .slice(0, 5) - .map(([ext, count]) => ` `) + .map(([ext, count]: [string, number]) => ` `) .join("\n")} @@ -375,10 +375,10 @@ ${analyzeStructure()} ${Object.entries(projectAnalysis.filesByExtension) - .sort((a, b) => b[1] - a[1]) + .sort((a: [string, number], b: [string, number]) => b[1] - a[1]) .slice(0, 10) .map( - ([ext, count]) => + ([ext, count]: [string, number]) => ` ` @@ -465,11 +465,11 @@ ${Object.entries(projectAnalysis.filesByExtension) const detectFeatures = () => { const extensions = projectAnalysis.filesByExtension; const topLevelDirs = projectAnalysis.fileTree - .filter((n) => n.isDirectory) - .map((n) => n.name.toLowerCase()); + .filter((n: FileTreeNode) => n.isDirectory) + .map((n: FileTreeNode) => n.name.toLowerCase()); const topLevelFiles = projectAnalysis.fileTree - .filter((n) => !n.isDirectory) - .map((n) => n.name.toLowerCase()); + .filter((n: FileTreeNode) => !n.isDirectory) + .map((n: FileTreeNode) => n.name.toLowerCase()); // Check for test directories and files const hasTests = @@ -840,7 +840,7 @@ ${Object.entries(projectAnalysis.filesByExtension)
{node.isDirectory && isExpanded && node.children && (
- {node.children.map((child) => renderNode(child, depth + 1))} + {node.children.map((child: FileTreeNode) => renderNode(child, depth + 1))}
)} @@ -964,9 +964,9 @@ ${Object.entries(projectAnalysis.filesByExtension)
{Object.entries(projectAnalysis.filesByExtension) - .sort((a, b) => b[1] - a[1]) + .sort((a: [string, number], b: [string, number]) => b[1] - a[1]) .slice(0, 15) - .map(([ext, count]) => ( + .map(([ext, count]: [string, number]) => (
{ext.startsWith("(") ? ext : `.${ext}`} @@ -1107,7 +1107,7 @@ ${Object.entries(projectAnalysis.filesByExtension) data-testid="analysis-file-tree" >
- {projectAnalysis.fileTree.map((node) => renderNode(node))} + {projectAnalysis.fileTree.map((node: FileTreeNode) => renderNode(node))}
diff --git a/app/src/hooks/use-electron-agent.ts b/app/src/hooks/use-electron-agent.ts index f187c37a..f7262219 100644 --- a/app/src/hooks/use-electron-agent.ts +++ b/app/src/hooks/use-electron-agent.ts @@ -266,7 +266,8 @@ export function useElectronAgent({ setIsProcessing(false); setError(event.error); if (event.message) { - setMessages((prev) => [...prev, event.message]); + const errorMessage = event.message; + setMessages((prev) => [...prev, errorMessage]); } break; } @@ -400,5 +401,8 @@ export function useElectronAgent({ stopExecution, clearHistory, error, + queuedMessages, + isQueueProcessing: isProcessingQueue, + clearMessageQueue: clearQueue, }; } diff --git a/app/src/lib/electron.ts b/app/src/lib/electron.ts index 1efabeaf..1ed578f3 100644 --- a/app/src/lib/electron.ts +++ b/app/src/lib/electron.ts @@ -78,7 +78,7 @@ export interface ElectronAPI { deleteFile: (filePath: string) => Promise; trashItem?: (filePath: string) => Promise; getPath: (name: string) => Promise; - saveImageToTemp?: (data: string, filename: string, mimeType: string) => Promise; + saveImageToTemp?: (data: string, filename: string, mimeType: string, projectPath?: string) => Promise; autoMode?: AutoModeAPI; checkClaudeCli?: () => Promise<{ success: boolean; @@ -132,12 +132,8 @@ export interface ElectronAPI { git?: GitAPI; } -declare global { - interface Window { - electronAPI?: ElectronAPI; - isElectron?: boolean; - } -} +// Note: Window interface is declared in @/types/electron.d.ts +// Do not redeclare here to avoid type conflicts // Mock data for web development const mockFeatures = [ @@ -386,12 +382,13 @@ export const getElectronAPI = (): ElectronAPI => { }, // Save image to temp directory - saveImageToTemp: async (data: string, filename: string, mimeType: string) => { - // Generate a mock temp file path + saveImageToTemp: async (data: string, filename: string, mimeType: string, projectPath?: string) => { + // Generate a mock temp file path - use projectPath if provided const timestamp = Date.now(); - const ext = mimeType.split("/")[1] || "png"; const safeName = filename.replace(/[^a-zA-Z0-9.-]/g, "_"); - const tempFilePath = `/tmp/automaker-images/${timestamp}_${safeName}`; + const tempFilePath = projectPath + ? `${projectPath}/.automaker/images/${timestamp}_${safeName}` + : `/tmp/automaker-images/${timestamp}_${safeName}`; // Store the image data in mock file system for testing mockFileSystem[tempFilePath] = data; diff --git a/app/src/store/app-store.ts b/app/src/store/app-store.ts index c8700dc7..243dadb6 100644 --- a/app/src/store/app-store.ts +++ b/app/src/store/app-store.ts @@ -126,6 +126,24 @@ export interface Feature { branchName?: string; // Name of the feature branch } +// File tree node for project analysis +export interface FileTreeNode { + name: string; + path: string; + isDirectory: boolean; + extension?: string; + children?: FileTreeNode[]; +} + +// Project analysis result +export interface ProjectAnalysis { + fileTree: FileTreeNode[]; + totalFiles: number; + totalDirectories: number; + filesByExtension: Record; + analyzedAt: string; +} + export interface AppState { // Project state projects: Project[]; @@ -173,6 +191,10 @@ export interface AppState { // AI Profiles aiProfiles: AIProfile[]; + + // Project Analysis + projectAnalysis: ProjectAnalysis | null; + isAnalyzing: boolean; } export interface AutoModeActivity { @@ -267,6 +289,11 @@ export interface AppActions { removeAIProfile: (id: string) => void; reorderAIProfiles: (oldIndex: number, newIndex: number) => void; + // Project Analysis actions + setProjectAnalysis: (analysis: ProjectAnalysis | null) => void; + setIsAnalyzing: (analyzing: boolean) => void; + clearAnalysis: () => void; + // Reset reset: () => void; } @@ -351,6 +378,8 @@ const initialState: AppState = { defaultSkipTests: false, // Default to TDD mode (tests enabled) useWorktrees: false, // Default to disabled (worktree feature is experimental) aiProfiles: DEFAULT_AI_PROFILES, + projectAnalysis: null, + isAnalyzing: false, }; export const useAppStore = create()( @@ -713,6 +742,11 @@ export const useAppStore = create()( set({ aiProfiles: profiles }); }, + // Project Analysis actions + setProjectAnalysis: (analysis) => set({ projectAnalysis: analysis }), + setIsAnalyzing: (analyzing) => set({ isAnalyzing: analyzing }), + clearAnalysis: () => set({ projectAnalysis: null }), + // Reset reset: () => set(initialState), }), diff --git a/app/src/types/electron.d.ts b/app/src/types/electron.d.ts index 1ff9ac79..aaf99776 100644 --- a/app/src/types/electron.d.ts +++ b/app/src/types/electron.d.ts @@ -241,6 +241,24 @@ export interface AutoModeAPI { error?: string; }>; + resumeFeature: (projectPath: string, featureId: string) => Promise<{ + success: boolean; + passes?: boolean; + error?: string; + }>; + + contextExists: (projectPath: string, featureId: string) => Promise<{ + success: boolean; + exists?: boolean; + error?: string; + }>; + + analyzeProject: (projectPath: string) => Promise<{ + success: boolean; + message?: string; + error?: string; + }>; + followUpFeature: (projectPath: string, featureId: string, prompt: string, imagePaths?: string[]) => Promise<{ success: boolean; passes?: boolean; @@ -302,6 +320,10 @@ export interface ElectronAPI { }; error?: string; }>; + deleteFile: (filePath: string) => Promise<{ + success: boolean; + error?: string; + }>; // App APIs getPath: (name: string) => Promise;