ccguard
Version:
Automated enforcement of net-negative LOC, complexity constraints, and quality standards for Claude code
145 lines • 5.63 kB
JavaScript
;
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