← back to Letsbegin
app/api/processes/analyze/route.ts
147 lines
import { NextRequest, NextResponse } from 'next/server'
import { logGemini } from '@/lib/cost-track'
import * as fs from 'fs'
import * as path from 'path'
// Gemini API configuration (FREE - no credits needed!)
const GEMINI_API_KEY = process.env.GEMINI_API_KEY
if (!GEMINI_API_KEY) {
throw new Error('GEMINI_API_KEY environment variable is required')
}
const GEMINI_ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent'
async function callGemini(prompt: string): Promise<string> {
const response = await fetch(`${GEMINI_ENDPOINT}?key=${GEMINI_API_KEY}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.3, maxOutputTokens: 1500 }
})
})
if (!response.ok) {
const error = await response.text()
throw new Error(`Gemini API error: ${error}`)
}
const data = await response.json()
logGemini(data, { note: 'processes-analyze' })
return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
}
export async function POST(request: NextRequest) {
try {
let body: { projectPath?: string; project?: string }
try {
body = await request.json()
} catch {
return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 })
}
const { projectPath, project } = body
if (!projectPath || !project) {
return NextResponse.json({ error: 'Missing projectPath or project' }, { status: 400 })
}
// Collect error information from various sources
const errorSources: string[] = []
// 1. Check progress.txt for failed stories
const progressFile = path.join(projectPath, 'scripts', 'ralph', 'progress.txt')
if (fs.existsSync(progressFile)) {
const content = fs.readFileSync(progressFile, 'utf-8')
errorSources.push('=== PROGRESS FILE ===')
errorSources.push(content)
errorSources.push('')
}
// 2. Check ralph state file
const stateFile = '/tmp/ralph-state.json'
if (fs.existsSync(stateFile)) {
try {
const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'))
if (state.project === project || state.projectPath === projectPath) {
errorSources.push('=== RALPH STATE ===')
errorSources.push(`Status: ${state.running ? 'Running' : 'Stopped'}`)
errorSources.push(`Story Results: ${JSON.stringify(state.storyResults, null, 2)}`)
if (state.logs?.length) {
errorSources.push('\nRecent Logs:')
errorSources.push(state.logs.slice(-50).join('\n'))
}
errorSources.push('')
}
} catch {}
}
// 3. Check for any error logs in the project
const errorLogPaths = [
path.join(projectPath, 'ralph-error.log'),
path.join(projectPath, 'scripts', 'ralph', 'error.log'),
path.join(projectPath, '.ralph', 'error.log')
]
for (const logPath of errorLogPaths) {
if (fs.existsSync(logPath)) {
errorSources.push(`=== ${path.basename(logPath)} ===`)
const content = fs.readFileSync(logPath, 'utf-8')
// Get last 100 lines
const lines = content.split('\n').slice(-100)
errorSources.push(lines.join('\n'))
errorSources.push('')
}
}
// 4. Check npm/build errors
const buildErrorPaths = [
path.join(projectPath, 'npm-debug.log'),
path.join(projectPath, '.next', 'error.log')
]
for (const logPath of buildErrorPaths) {
if (fs.existsSync(logPath)) {
errorSources.push(`=== ${path.basename(logPath)} ===`)
const content = fs.readFileSync(logPath, 'utf-8')
const lines = content.split('\n').slice(-50)
errorSources.push(lines.join('\n'))
errorSources.push('')
}
}
if (errorSources.length === 0) {
return NextResponse.json({
analysis: `No error logs found for project "${project}".\n\nChecked locations:\n- ${progressFile}\n- ${stateFile}\n- Various error log paths\n\nThe process may have failed without generating error logs, or the logs may have been cleared.`
})
}
// Use Gemini to analyze the errors
const errorContext = errorSources.join('\n')
try {
const aiPrompt = `Analyze these error logs from a Ralph (automated code generator) process that failed. Identify:
1. The root cause of the failure
2. Which user story or step failed
3. Specific error messages and their meaning
4. Recommended fixes
Keep your response concise and actionable.
Error Logs:
${errorContext}`
const analysis = await callGemini(aiPrompt) || 'Unable to generate analysis'
return NextResponse.json({ analysis })
} catch (aiError: any) {
// If AI fails, return raw errors
return NextResponse.json({
analysis: `=== ERROR ANALYSIS (AI Error: ${aiError.message}) ===\n\n${errorContext}`
})
}
} catch (error: any) {
console.error('Analyze error:', error)
return NextResponse.json({ error: error.message }, { status: 500 })
}
}