UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

586 lines (580 loc) 20.3 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import * as fs from "fs/promises"; import * as path from "path"; import { logger } from "../../../core/monitoring/logger.js"; import { FrameManager } from "../../../core/context/index.js"; import { sessionManager } from "../../../core/session/index.js"; class RalphDebugger { frameManager; activeSessions = /* @__PURE__ */ new Map(); config; constructor(config) { this.config = { enableRealTimeMonitoring: true, captureDetailedTrace: true, generateVisualization: true, exportFormat: "html", maxTraceDepth: 50, ...config }; logger.info("Ralph debugger initialized", this.config); } async initialize() { try { await sessionManager.initialize(); const session = await sessionManager.getOrCreateSession({}); if (session.database) { this.frameManager = new FrameManager(session.database, session.projectId); } logger.info("Debugger initialized successfully"); } catch (error) { logger.error("Failed to initialize debugger", error); throw error; } } /** * Start debugging a Ralph loop */ async startDebugSession(loopId, ralphDir) { logger.info("Starting debug session", { loopId, ralphDir }); const session = { id: `debug-${Date.now()}`, loopId, ralphDir, startTime: Date.now(), iterations: [], contextFlow: [], performance: { iterationTimes: [], memoryUsage: [], contextSizes: [], averageIterationTime: 0, peakMemory: 0, contextEfficiency: 0 }, realTimeMonitoring: this.config.enableRealTimeMonitoring }; this.activeSessions.set(loopId, session); if (this.config.enableRealTimeMonitoring) { await this.startRealTimeMonitoring(session); } return session; } /** * Generate comprehensive debug report */ async generateDebugReport(loopId) { const session = this.activeSessions.get(loopId); if (!session) { throw new Error(`No debug session found for loop ${loopId}`); } logger.info("Generating debug report", { loopId }); const report = { sessionId: session.id, loopId, generatedAt: Date.now(), summary: await this.generateSummary(session), iterationAnalysis: await this.analyzeIterations(session), contextAnalysis: await this.analyzeContextFlow(session), performanceAnalysis: await this.analyzePerformance(session), visualization: this.config.generateVisualization ? await this.generateVisualization(session) : void 0, recommendations: await this.generateRecommendations(session), exportPath: "" }; const exportPath = await this.exportReport(report); report.exportPath = exportPath; logger.info("Debug report generated", { loopId, exportPath }); return report; } /** * Create visual timeline of loop execution */ async generateLoopTimeline(loopId) { const session = this.activeSessions.get(loopId); if (!session) { throw new Error(`No debug session found for loop ${loopId}`); } const timeline = { title: `Ralph Loop Timeline: ${loopId}`, startTime: session.startTime, iterations: session.iterations.map((iter) => ({ iteration: iter.iteration, startTime: iter.startTime, endTime: iter.endTime, duration: iter.endTime - iter.startTime, success: iter.success, changes: iter.changes?.length || 0, errors: iter.errors?.length || 0, contextSize: iter.contextSize, phase: iter.phase })), totalDuration: session.performance.iterationTimes.reduce((sum, time) => sum + time, 0) }; const html = await this.generateTimelineHTML(timeline); const timelinePath = path.join(".ralph-debug", `timeline-${loopId}.html`); await fs.mkdir(path.dirname(timelinePath), { recursive: true }); await fs.writeFile(timelinePath, html); return timelinePath; } /** * Create context flow diagram */ async generateContextFlowDiagram(loopId) { const session = this.activeSessions.get(loopId); if (!session) { throw new Error(`No debug session found for loop ${loopId}`); } const diagram = { id: `context-flow-${loopId}`, nodes: [], edges: [], metrics: { totalNodes: 0, totalEdges: 0, avgContextSize: 0, maxContextSize: 0 } }; for (let i = 0; i < session.iterations.length; i++) { const iteration = session.iterations[i]; diagram.nodes.push({ id: `iter-${iteration.iteration}`, type: "iteration", label: `Iteration ${iteration.iteration}`, size: iteration.contextSize || 100, color: iteration.success ? "#4CAF50" : "#F44336", metadata: { duration: iteration.endTime - iteration.startTime, changes: iteration.changes?.length || 0, errors: iteration.errors?.length || 0 } }); if (i < session.iterations.length - 1) { diagram.edges.push({ id: `edge-${i}-${i + 1}`, from: `iter-${iteration.iteration}`, to: `iter-${session.iterations[i + 1].iteration}`, type: "sequence", weight: iteration.contextSize || 1 }); } } diagram.metrics = { totalNodes: diagram.nodes.length, totalEdges: diagram.edges.length, avgContextSize: session.performance.contextSizes.length > 0 ? session.performance.contextSizes.reduce((sum, size) => sum + size, 0) / session.performance.contextSizes.length : 0, maxContextSize: Math.max(...session.performance.contextSizes) }; return diagram; } /** * Real-time monitoring of loop execution */ async startRealTimeMonitoring(session) { const monitoringInterval = setInterval(async () => { try { await this.captureIterationTrace(session); await this.updatePerformanceMetrics(session); } catch (error) { logger.error("Monitoring error", error); } }, 1e3); session.monitoringInterval = monitoringInterval; } /** * Capture detailed trace of current iteration */ async captureIterationTrace(session) { try { const statePath = path.join(session.ralphDir, "state.json"); const iterationPath = path.join(session.ralphDir, "iteration.txt"); let currentState = {}; let currentIteration = 0; try { const stateData = await fs.readFile(statePath, "utf8"); currentState = JSON.parse(stateData); const iterData = await fs.readFile(iterationPath, "utf8"); currentIteration = parseInt(iterData.trim()) || 0; } catch { return; } const lastTrace = session.iterations[session.iterations.length - 1]; if (lastTrace?.iteration === currentIteration) { return; } const trace = { iteration: currentIteration, startTime: Date.now(), endTime: Date.now(), // Will be updated when iteration completes phase: this.determineIterationPhase(session.ralphDir), contextSize: await this.calculateContextSize(session.ralphDir), success: false, // Will be updated changes: [], errors: [], memoryUsage: process.memoryUsage().heapUsed, stackTrace: this.captureStackTrace() }; session.iterations.push(trace); } catch (error) { logger.debug("Failed to capture iteration trace", error); } } /** * Update performance metrics */ async updatePerformanceMetrics(session) { const currentMemory = process.memoryUsage().heapUsed; session.performance.memoryUsage.push(currentMemory); session.performance.peakMemory = Math.max(session.performance.peakMemory, currentMemory); const contextSize = await this.calculateContextSize(session.ralphDir); session.performance.contextSizes.push(contextSize); if (session.performance.iterationTimes.length > 0) { session.performance.averageIterationTime = session.performance.iterationTimes.reduce((sum, time) => sum + time, 0) / session.performance.iterationTimes.length; } if (session.performance.contextSizes.length > 0) { const avgContextSize = session.performance.contextSizes.reduce((sum, size) => sum + size, 0) / session.performance.contextSizes.length; session.performance.contextEfficiency = Math.max(0, 1 - avgContextSize / 1e4); } } /** * Generate executive summary */ async generateSummary(session) { const totalIterations = session.iterations.length; const successfulIterations = session.iterations.filter((i) => i.success).length; const totalDuration = session.performance.iterationTimes.reduce((sum, time) => sum + time, 0); return { loopId: session.loopId, totalIterations, successfulIterations, successRate: totalIterations > 0 ? successfulIterations / totalIterations : 0, totalDuration, averageIterationTime: session.performance.averageIterationTime, peakMemoryUsage: session.performance.peakMemory, contextEfficiency: session.performance.contextEfficiency, status: totalIterations > 0 && session.iterations[session.iterations.length - 1].success ? "completed" : "in_progress" }; } /** * Analyze iteration patterns */ async analyzeIterations(session) { if (session.iterations.length === 0) return { patterns: [], insights: [] }; const patterns = []; const insights = []; const durations = session.iterations.map((i) => i.endTime - i.startTime); const avgDuration = durations.reduce((sum, d) => sum + d, 0) / durations.length; if (durations.some((d) => d > avgDuration * 2)) { patterns.push("Variable iteration times detected"); insights.push("Some iterations took significantly longer than average - investigate bottlenecks"); } const consecutiveFailures = this.findConsecutiveFailures(session.iterations); if (consecutiveFailures.length > 2) { patterns.push("Multiple consecutive failures detected"); insights.push("Consider adjusting approach or criteria after consecutive failures"); } if (session.performance.contextSizes.length > 1) { const contextGrowth = session.performance.contextSizes[session.performance.contextSizes.length - 1] - session.performance.contextSizes[0]; if (contextGrowth > 1e3) { patterns.push("Significant context growth"); insights.push("Context is growing rapidly - consider context pruning strategies"); } } return { patterns, insights }; } /** * Analyze context flow */ async analyzeContextFlow(session) { return { avgContextSize: session.performance.contextSizes.length > 0 ? session.performance.contextSizes.reduce((sum, size) => sum + size, 0) / session.performance.contextSizes.length : 0, maxContextSize: Math.max(...session.performance.contextSizes), contextGrowthRate: this.calculateGrowthRate(session.performance.contextSizes), efficiency: session.performance.contextEfficiency }; } /** * Analyze performance metrics */ async analyzePerformance(session) { return { memoryEfficiency: this.calculateMemoryEfficiency(session.performance.memoryUsage), iterationEfficiency: session.performance.averageIterationTime, resourceUtilization: { cpu: "N/A", // Would need CPU monitoring memory: session.performance.peakMemory, context: session.performance.contextEfficiency } }; } /** * Generate visualization HTML */ async generateVisualization(session) { const htmlContent = await this.generateVisualizationHTML(session); const vizPath = path.join(".ralph-debug", `visualization-${session.loopId}.html`); await fs.mkdir(path.dirname(vizPath), { recursive: true }); await fs.writeFile(vizPath, htmlContent); return { id: `viz-${session.loopId}`, type: "interactive_timeline", htmlPath: vizPath, data: { iterations: session.iterations, performance: session.performance, contextFlow: session.contextFlow }, metadata: { generatedAt: Date.now(), format: "html", interactive: true } }; } /** * Generate recommendations */ async generateRecommendations(session) { const recommendations = []; if (session.performance.averageIterationTime > 3e4) { recommendations.push("Consider breaking down complex tasks into smaller iterations"); } if (session.performance.contextEfficiency < 0.7) { recommendations.push("Optimize context management - consider using context budgeting"); } const successRate = session.iterations.filter((i) => i.success).length / Math.max(1, session.iterations.length); if (successRate < 0.5) { recommendations.push("Low success rate detected - review task criteria and approach"); } if (session.performance.peakMemory > 500 * 1024 * 1024) { recommendations.push("High memory usage detected - investigate memory leaks"); } return recommendations; } /** * Export report in specified format */ async exportReport(report) { const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"); const filename = `ralph-debug-${report.loopId}-${timestamp}`; let content; let extension; switch (this.config.exportFormat) { case "json": content = JSON.stringify(report, null, 2); extension = "json"; break; case "markdown": content = this.generateMarkdownReport(report); extension = "md"; break; case "html": default: content = this.generateHTMLReport(report); extension = "html"; break; } const exportPath = path.join(".ralph-debug", `${filename}.${extension}`); await fs.mkdir(path.dirname(exportPath), { recursive: true }); await fs.writeFile(exportPath, content); return exportPath; } // Helper methods determineIterationPhase(ralphDir) { return "working"; } async calculateContextSize(ralphDir) { try { const feedbackPath = path.join(ralphDir, "feedback.txt"); const feedback = await fs.readFile(feedbackPath, "utf8"); return feedback.length; } catch { return 0; } } captureStackTrace() { const stack = new Error().stack || ""; return stack.split("\n").slice(1, 6).join("\n"); } findConsecutiveFailures(iterations) { const failures = []; let currentStreak = 0; for (const iteration of iterations) { if (!iteration.success) { currentStreak++; } else { if (currentStreak > 0) { failures.push(currentStreak); } currentStreak = 0; } } if (currentStreak > 0) { failures.push(currentStreak); } return failures; } calculateGrowthRate(sizes) { if (sizes.length < 2) return 0; const first = sizes[0]; const last = sizes[sizes.length - 1]; return first > 0 ? (last - first) / first : 0; } calculateMemoryEfficiency(memoryUsage) { if (memoryUsage.length < 2) return 1; const min = Math.min(...memoryUsage); const max = Math.max(...memoryUsage); return max > 0 ? min / max : 1; } generateTimelineHTML(timeline) { return ` <!DOCTYPE html> <html> <head> <title>${timeline.title}</title> <script src="https://d3js.org/d3.v7.min.js"></script> <style> body { font-family: Arial, sans-serif; margin: 20px; } .timeline { margin: 20px 0; } .iteration { margin: 10px 0; padding: 10px; border-left: 4px solid #ccc; } .success { border-left-color: #4CAF50; } .failure { border-left-color: #F44336; } </style> </head> <body> <h1>${timeline.title}</h1> <div class="timeline"> ${timeline.iterations.map((iter) => ` <div class="iteration ${iter.success ? "success" : "failure"}"> <h3>Iteration ${iter.iteration}</h3> <p>Duration: ${iter.duration}ms</p> <p>Changes: ${iter.changes} | Errors: ${iter.errors}</p> <p>Context Size: ${iter.contextSize}</p> </div> `).join("")} </div> </body> </html> `; } generateVisualizationHTML(session) { return ` <!DOCTYPE html> <html> <head> <title>Ralph Loop Visualization - ${session.loopId}</title> <script src="https://d3js.org/d3.v7.min.js"></script> <style> body { font-family: Arial, sans-serif; margin: 20px; } .chart { margin: 20px 0; } .metric { display: inline-block; margin: 10px; padding: 10px; border: 1px solid #ccc; } </style> </head> <body> <h1>Ralph Loop Debug Visualization</h1> <div id="metrics"> <div class="metric"> <h3>Iterations</h3> <p>${session.iterations.length}</p> </div> <div class="metric"> <h3>Avg Time</h3> <p>${Math.round(session.performance.averageIterationTime)}ms</p> </div> <div class="metric"> <h3>Context Efficiency</h3> <p>${Math.round(session.performance.contextEfficiency * 100)}%</p> </div> </div> <div id="timeline" class="chart"></div> <script> // D3.js visualization code would go here console.log('Visualization data:', ${JSON.stringify(session)}); </script> </body> </html> `; } generateMarkdownReport(report) { return ` # Ralph Loop Debug Report **Loop ID:** ${report.loopId} **Generated:** ${new Date(report.generatedAt).toLocaleString()} ## Summary - **Total Iterations:** ${report.summary.totalIterations} - **Success Rate:** ${Math.round(report.summary.successRate * 100)}% - **Total Duration:** ${report.summary.totalDuration}ms - **Average Iteration Time:** ${Math.round(report.summary.averageIterationTime)}ms ## Performance Analysis - **Peak Memory:** ${Math.round(report.performanceAnalysis.resourceUtilization.memory / 1024 / 1024)}MB - **Context Efficiency:** ${Math.round(report.performanceAnalysis.resourceUtilization.context * 100)}% ## Recommendations ${report.recommendations.map((r) => `- ${r}`).join("\n")} `; } generateHTMLReport(report) { return ` <!DOCTYPE html> <html> <head> <title>Ralph Debug Report - ${report.loopId}</title> <style> body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; } .summary { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; } .metric { display: inline-block; margin: 10px; padding: 15px; background: white; border-radius: 4px; } .recommendations { background: #e3f2fd; padding: 15px; border-radius: 4px; } </style> </head> <body> <h1>Ralph Loop Debug Report</h1> <div class="summary"> <h2>Executive Summary</h2> <div class="metric"> <h3>${report.summary.totalIterations}</h3> <p>Total Iterations</p> </div> <div class="metric"> <h3>${Math.round(report.summary.successRate * 100)}%</h3> <p>Success Rate</p> </div> <div class="metric"> <h3>${Math.round(report.summary.averageIterationTime)}ms</h3> <p>Avg Iteration Time</p> </div> </div> <div class="recommendations"> <h2>Recommendations</h2> <ul> ${report.recommendations.map((r) => `<li>${r}</li>`).join("")} </ul> </div> </body> </html> `; } /** * Stop debug session and cleanup */ async stopDebugSession(loopId) { const session = this.activeSessions.get(loopId); if (!session) return; if (session.monitoringInterval) { clearInterval(session.monitoringInterval); } await this.generateDebugReport(loopId); this.activeSessions.delete(loopId); logger.info("Debug session stopped", { loopId }); } } const ralphDebugger = new RalphDebugger(); export { RalphDebugger, ralphDebugger }; //# sourceMappingURL=ralph-debugger.js.map