UNPKG

scai

Version:

> AI-powered CLI tool for commit messages **and** pull request reviews — using local models.

152 lines (151 loc) 6.75 kB
import chalk from "chalk"; export const preserveCodeModule = { name: "preserveCodeModule", description: "Ensure code matches original exactly, preserving comments with clear before/after output", async run(input) { const { originalContent, content, filepath } = input; if (!originalContent) throw new Error("Requires `originalContent`."); const syntax = { singleLine: ["//"], multiLine: [{ start: "/*", end: "*/" }, { start: "/**", end: "*/" }] }; // --- Normalize line endings --- const normalize = (txt) => txt.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); const origLines = normalize(originalContent).split("\n"); const newLines = normalize(content).split("\n"); // --- Classify line --- let inBlockComment = false; let blockLines = []; const classifyLine = (line) => { const trimmed = line.trimStart(); // --- Single-line comment --- for (const s of syntax.singleLine) { if (trimmed.startsWith(s)) return "comment"; } // --- Multi-line comment --- for (const { start, end } of syntax.multiLine) { if (!inBlockComment) { if (trimmed.startsWith(start) && trimmed.includes(end)) { return "comment"; // entire block on one line } if (trimmed.startsWith(start)) { inBlockComment = true; blockLines = [line]; return ""; // wait until block ends } } else { blockLines.push(line); if (trimmed.includes(end)) { inBlockComment = false; return "comment"; // end of block } return ""; // inside block } } return "code"; }; // --- Helper: collect comment blocks into map --- function collectCommentsMap(lines) { const map = new Map(); let commentBuffer = []; for (const line of lines) { const type = classifyLine(line); if (type === "comment") { // Collect full comment line commentBuffer.push(line); } else if (type === "code") { // Flush buffer when hitting code if (commentBuffer.length > 0) { const key = line.trim().toLowerCase(); if (!map.has(key)) map.set(key, new Set()); const commentBlock = commentBuffer.map(l => l.trimEnd()).join("\n").toLowerCase(); map.get(key).add(commentBlock); commentBuffer = []; } } } // Flush remaining comments at EOF if (commentBuffer.length > 0) { const key = ""; if (!map.has(key)) map.set(key, new Set()); const commentBlock = commentBuffer.map(l => l.trimEnd()).join("\n").toLowerCase(); map.get(key).add(commentBlock); } return map; } // --- Step 1: Collect comments --- const modelComments = collectCommentsMap(newLines); // model first const origComments = collectCommentsMap(origLines); // original // --- Step 2: Remove duplicates --- for (const [key, commentSet] of modelComments.entries()) { if (origComments.has(key)) { commentSet.forEach(c => { origComments.get(key).delete(c.trim().toLowerCase()); }); if (origComments.get(key).size === 0) origComments.delete(key); } } // --- Step 3: Build fixed lines with model comments inserted above original --- const fixedLines = []; for (const origLine of origLines) { const key = origLine.trim().toLowerCase(); // Insert model comment blocks if any if (modelComments.has(key)) { modelComments.get(key).forEach(block => { const lines = block.split("\n"); for (const line of lines) { if (!fixedLines.includes(line)) { fixedLines.push(line); console.log(chalk.blue("Inserted comment:"), line.trim()); } else { console.log(chalk.gray("Skipped duplicate comment:"), line.trim()); } } }); } fixedLines.push(origLine); } // --- Logging for debugging --- console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (original) ===")); origLines.forEach((line, i) => { const type = classifyLine(line); const colored = type === "code" ? chalk.green(line) : type === "comment" ? chalk.yellow(line) : chalk.gray(line); // "" means middle of block console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`); }); console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (model) ===")); newLines.forEach((line, i) => { const type = classifyLine(line); const colored = type === "code" ? chalk.green(line) : type === "comment" ? chalk.yellow(line) : chalk.gray(line); console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`); }); console.log(chalk.bold.blue("\n=== FIXED CONTENT ===")); fixedLines.forEach((line, i) => { // classifyLine might not be ideal here since fixedLines are final // so we treat anything starting with a comment marker as "comment" const trimmed = line.trimStart(); const type = syntax.singleLine.some(s => trimmed.startsWith(s)) || syntax.multiLine.some(({ start }) => trimmed.startsWith(start)) ? "comment" : "code"; const colored = type === "code" ? chalk.green(line) : chalk.yellow(line); console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`); }); return { content: fixedLines.join("\n"), filepath }; } };