← 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 })
  }
}