UNPKG

ccguard

Version:

Automated enforcement of net-negative LOC, complexity constraints, and quality standards for Claude code

145 lines 5.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BashLocTracker = void 0; const fs_1 = require("fs"); const path_1 = require("path"); const locCounter_1 = require("./locCounter"); const bashCommandParser_1 = require("./bashCommandParser"); class BashLocTracker { locCounter; parser; constructor(options = {}) { this.locCounter = new locCounter_1.LocCounter(options); this.parser = new bashCommandParser_1.BashCommandParser(); } /** * Calculate LOC change for a bash command * This requires checking file state before the command executes */ async calculateBashChange(input, preOperationCheck = true) { const operations = this.parser.parseCommand(input); let totalAdded = 0; let totalRemoved = 0; for (const operation of operations) { for (const path of operation.paths) { const absolutePath = this.resolveFilePath(path, input.cwd); switch (operation.type) { case 'delete': if (preOperationCheck) { // Count lines in file that will be deleted const lines = this.countFileLines(absolutePath); if (lines > 0) { totalRemoved += lines; } } break; case 'create': // New files start empty, so no immediate LOC change // The actual content will be tracked in subsequent operations break; case 'overwrite': if (preOperationCheck) { // Count current lines (will be removed) const currentLines = this.countFileLines(absolutePath); if (currentLines > 0) { totalRemoved += currentLines; } // Note: We can't know what will be written until after the operation // This is a limitation when doing pre-operation validation } break; case 'modify': // Append operations add lines but don't remove // We can't know how many lines will be added in pre-check break; } } } return { linesAdded: totalAdded, linesRemoved: totalRemoved, netChange: totalAdded - totalRemoved }; } /** * Calculate LOC change after bash command execution * Used in snapshot mode to compare before/after states */ async calculatePostBashChange(input, fileStatesBefore, fileStatesAfter) { const operations = this.parser.parseCommand(input); let totalAdded = 0; let totalRemoved = 0; // Get all affected paths const affectedPaths = new Set(); for (const operation of operations) { for (const path of operation.paths) { const absolutePath = this.resolveFilePath(path, input.cwd); affectedPaths.add(absolutePath); } } // Calculate changes for each affected file for (const path of affectedPaths) { const linesBefore = fileStatesBefore.get(path) ?? 0; const linesAfter = fileStatesAfter.get(path) ?? 0; if (linesBefore > 0 && linesAfter === 0) { // File was deleted totalRemoved += linesBefore; } else if (linesBefore === 0 && linesAfter > 0) { // File was created totalAdded += linesAfter; } else if (linesBefore > 0 && linesAfter > 0) { // File was modified if (linesAfter > linesBefore) { totalAdded += (linesAfter - linesBefore); } else if (linesBefore > linesAfter) { totalRemoved += (linesBefore - linesAfter); } } } return { linesAdded: totalAdded, linesRemoved: totalRemoved, netChange: totalAdded - totalRemoved }; } /** * Get files that would be affected by a bash command */ getAffectedFiles(input) { const operations = this.parser.parseCommand(input); const files = []; for (const operation of operations) { for (const path of operation.paths) { const absolutePath = this.resolveFilePath(path, input.cwd); files.push(absolutePath); } } return [...new Set(files)]; // Remove duplicates } countFileLines(filePath) { try { if (!(0, fs_1.existsSync)(filePath)) { return 0; } const content = (0, fs_1.readFileSync)(filePath, 'utf-8'); return this.locCounter.countLines(content); } catch { return 0; } } resolveFilePath(filePath, cwd) { // Handle absolute paths if (filePath.startsWith('/')) { return filePath; } // Resolve relative paths const basePath = cwd || process.cwd(); return (0, path_1.resolve)(basePath, filePath); } } exports.BashLocTracker = BashLocTracker; //# sourceMappingURL=bashLocTracker.js.map