diff --git a/.jules/bolt.md b/.jules/bolt.md index 68b59a4..a57ecce 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,6 @@ ## 2024-05-18 - Optimize asynchronous file operations and child processes in `services/ReviewGenerator.js` **Learning:** `generateSplitReviews` and `generateUnifiedReview` previously executed Git operations (e.g. `GitService.getDiffForFile`) and file write operations (`fs.writeFileSync`) sequentially within a `for...of` loop. For large PRs or repos with many changed files, this sequential execution significantly bottlenecks review generation time. -**Action:** Replace `for...of` loops over `includedFiles` with an array of Promises processed via `Promise.all`. This allows Git diffs to be requested and files to be written in parallel, substantially cutting down end-to-end execution time for large payloads. It is critical to use asynchronous I/O (`fs.promises.writeFile`) instead of synchronous alternatives. \ No newline at end of file +**Action:** Replace `for...of` loops over `includedFiles` with an array of Promises processed via `Promise.all`. This allows Git diffs to be requested and files to be written in parallel, substantially cutting down end-to-end execution time for large payloads. It is critical to use asynchronous I/O (`fs.promises.writeFile`) instead of synchronous alternatives. +## 2026-03-04 - Parallelize independent Git CLI commands +**Learning:** Sequential execution of Git CLI commands (via `child_process.execFile`) adds unnecessary latency to endpoints like `/api/staged-files` and `/api/health`. These commands are independent and can be safely parallelized. +**Action:** Always look for opportunities to run independent asynchronous operations concurrently using `Promise.all` to reduce response times, especially for operations that wrap CLI calls. diff --git a/server.js b/server.js index c60c8c9..27da3c4 100644 --- a/server.js +++ b/server.js @@ -281,21 +281,27 @@ app.get('/api/health', handleAsyncRoute(async (req, res) => { let stagedCount = 0; let unstagedCount = 0; - try { - const stagedFiles = await GitService.execute('diff-cached-names'); - stagedCount = stagedFiles.trim() ? stagedFiles.trim().split('\n').filter(f => f.length > 0).length : 0; - } catch (error) { - console.warn('Failed to get staged files count:', error.message); + // Run git commands in parallel + const [stagedFilesOutput, unstagedFilesOutput] = await Promise.all([ + GitService.execute('diff-cached-names').catch(error => { + console.warn('Failed to get staged files count:', error.message); + return ''; + }), + GitService.execute('status-porcelain').catch(error => { + console.warn('Failed to get unstaged files count:', error.message); + return ''; + }) + ]); + + if (stagedFilesOutput) { + stagedCount = stagedFilesOutput.trim() ? stagedFilesOutput.trim().split('\n').filter(f => f.length > 0).length : 0; } - try { - const unstagedFiles = await GitService.execute('status-porcelain'); - const unstagedLines = unstagedFiles.trim().split('\n').filter(line => + if (unstagedFilesOutput) { + const unstagedLines = unstagedFilesOutput.trim().split('\n').filter(line => line.length > 0 && (line.startsWith(' M') || line.startsWith('??')) ); unstagedCount = unstagedLines.length; - } catch (error) { - console.warn('Failed to get unstaged files count:', error.message); } const totalChanges = stagedCount + unstagedCount; @@ -329,13 +335,18 @@ app.get('/api/summary', handleAsyncRoute(async (req, res) => { app.get('/api/staged-files', handleAsyncRoute(async (req, res) => { try { - const output = await GitService.execute('diff-cached-names'); + // Run git commands in parallel to optimize response time + const [output, statusOutput, deletedOutput] = await Promise.all([ + GitService.execute('diff-cached-names'), + GitService.execute('diff-cached', ['--name-status']).catch(() => ''), + GitService.execute('status-porcelain').catch(() => '') + ]); + const files = output.trim() ? output.trim().split('\n').filter(f => f.length > 0) : []; // Get file statuses to determine if files are deleted let fileStatuses = {}; - try { - const statusOutput = await GitService.execute('diff-cached', ['--name-status']); + if (statusOutput) { const statusLines = statusOutput.trim().split('\n').filter(line => line.length > 0); statusLines.forEach(line => { const [status, filename] = line.split('\t'); @@ -343,20 +354,15 @@ app.get('/api/staged-files', handleAsyncRoute(async (req, res) => { fileStatuses[filename] = status; } }); - } catch (error) { - // Ignore error, fileStatuses will remain empty } // Check for unstaged deleted files let deletedFiles = []; - try { - const deletedOutput = await GitService.execute('status-porcelain'); + if (deletedOutput) { const deletedLines = deletedOutput.trim().split('\n').filter(line => line.length > 0 && (line.startsWith(' D') || line.startsWith('AD')) ); deletedFiles = deletedLines.map(line => line.substring(line.startsWith('AD') ? 3 : 3)); - } catch (error) { - // Ignore error, deletedFiles will remain empty } res.json({