UNPKG

reviewit

Version:

A lightweight command-line tool that spins up a local web server to display Git commit diffs in a GitHub-like Files changed view

141 lines (140 loc) 5.35 kB
import { promises as fs } from 'fs'; import { join, dirname } from 'path'; export class CommentStore { constructor(sessionId) { this.comments = new Map(); this.sessionId = sessionId || Date.now().toString(); this.filePath = join(process.cwd(), '.reviewit', `tmp-comments-${this.sessionId}.json`); } async addComment(file, line, body) { const comment = { id: `${file}:${line}:${Date.now()}`, file, line, body, timestamp: new Date().toISOString(), }; this.comments.set(comment.id, comment); await this.saveToFile(); return comment; } async getComments() { return Array.from(this.comments.values()); } async getCommentsForFile(file) { return Array.from(this.comments.values()).filter((c) => c.file === file); } generatePrompt(comment, diffContent) { const lines = diffContent.split('\n'); // Find the relevant code context around the commented line let relevantLines = []; let foundTargetLine = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Skip chunk headers (start with @@) if (line.startsWith('@@')) continue; // Extract line numbers and content const addMatch = line.match(/^\+(\d+)/); const delMatch = line.match(/^-(\d+)/); const normalMatch = line.match(/^ /); if (addMatch || delMatch || normalMatch) { const lineNumber = addMatch ? parseInt(addMatch[1]) : delMatch ? parseInt(delMatch[1]) : null; // Include lines around the target line (±5 lines context) if (lineNumber && Math.abs(lineNumber - comment.line) <= 5) { relevantLines.push(line); if (lineNumber === comment.line) { foundTargetLine = true; } } else if (!lineNumber && relevantLines.length > 0 && relevantLines.length < 15) { // Include context lines without line numbers if we're building context relevantLines.push(line); } } } // If we didn't find the exact line, include more general context if (!foundTargetLine && relevantLines.length === 0) { const contextStart = Math.max(0, comment.line - 5); const contextEnd = Math.min(lines.length, comment.line + 5); relevantLines = lines.slice(contextStart, contextEnd); } const codeContext = relevantLines.join('\n'); return [ `File: ${comment.file}`, `Line: ${comment.line}`, '', 'Code Context:', '```', codeContext, '```', '', `Comment: ${comment.body}`, ].join('\n'); } generateAllCommentsPrompt(comments, diffFiles) { if (comments.length === 0) { return 'No comments available.'; } const prompts = comments.map((comment, index) => { const targetFile = diffFiles.find((f) => f.path === comment.file); if (!targetFile) { return [ `## Comment ${index + 1}`, `File: ${comment.file}`, `Line: ${comment.line}`, '', 'Code Context:', '```', 'File not found in diff', '```', '', `Comment: ${comment.body}`, ].join('\n'); } let diffContent = ''; for (const chunk of targetFile.chunks) { diffContent += chunk.header + '\n'; for (const line of chunk.lines) { const prefix = line.type === 'add' ? '+' : line.type === 'delete' ? '-' : ' '; diffContent += prefix + line.content + '\n'; } } const individualPrompt = this.generatePrompt(comment, diffContent); return [`## Comment ${index + 1}`, individualPrompt].join('\n'); }); return [ `All Code Review Comments (${comments.length} total)`, '='.repeat(50), '', ...prompts, ].join('\n\n'); } async saveToFile() { try { await fs.mkdir(dirname(this.filePath), { recursive: true }); const data = JSON.stringify(Array.from(this.comments.values()), null, 2); await fs.writeFile(this.filePath, data, 'utf8'); } catch (error) { console.error('Failed to save comments:', error); } } async loadFromFile() { try { const data = await fs.readFile(this.filePath, 'utf8'); const comments = JSON.parse(data); this.comments.clear(); comments.forEach((comment) => { this.comments.set(comment.id, comment); }); } catch (error) { // File doesn't exist or is corrupted, start fresh } } }