ccguard
Version:
Automated enforcement of net-negative LOC, complexity constraints, and quality standards for Claude code
175 lines • 6.62 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectScanner = void 0;
const fs_1 = require("fs");
const path = __importStar(require("path"));
const glob_1 = require("glob");
const locCounter_1 = require("./locCounter");
class ProjectScanner {
locCounter;
configLoader;
cache = new Map();
CACHE_TTL = 5 * 60 * 1000; // 5 minutes
constructor(configLoader, options = {}) {
this.configLoader = configLoader;
this.locCounter = new locCounter_1.LocCounter({ ignoreEmptyLines: options.ignoreEmptyLines ?? true }, options.formatter);
}
/**
* Scan the project directory and count total LOC
*/
async scan(projectRoot) {
const cacheKey = projectRoot;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
return cached.result;
}
try {
const startTime = Date.now();
const config = this.configLoader.getConfig();
// Get all files in the project
const allFiles = await this.findProjectFiles(projectRoot);
// Filter files based on whitelist patterns and extensions
const filesToScan = this.filterFiles(allFiles, projectRoot);
// Count LOC for each file
let totalLines = 0;
let processedFiles = 0;
for (const file of filesToScan) {
try {
const content = await fs_1.promises.readFile(file, 'utf8');
const lines = this.locCounter.countLines(content);
totalLines += lines;
processedFiles++;
}
catch (err) {
// Skip files that can't be read (permissions, etc)
continue;
}
}
const result = {
totalLines,
fileCount: processedFiles,
timestamp: new Date().toISOString()
};
// Cache the result
this.cache.set(cacheKey, { result, timestamp: Date.now() });
return result;
}
catch (error) {
return {
totalLines: 0,
fileCount: 0,
timestamp: new Date().toISOString(),
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Clear the cache (useful for tests or when files change externally)
*/
clearCache() {
this.cache.clear();
}
/**
* Find all files in the project directory
*/
async findProjectFiles(projectRoot) {
const pattern = path.join(projectRoot, '**/*');
const files = await (0, glob_1.glob)(pattern, {
nodir: true,
follow: false, // Don't follow symlinks to avoid cycles
ignore: [
'**/node_modules/**',
'**/.git/**',
'**/dist/**',
'**/build/**',
'**/coverage/**',
'**/.next/**',
'**/.nuxt/**',
'**/vendor/**'
],
dot: false // Ignore hidden files
});
return files;
}
/**
* Filter files based on whitelist configuration
*/
filterFiles(files, projectRoot) {
const config = this.configLoader.getConfig();
const whitelistPatterns = config.whitelist.patterns;
const whitelistExtensions = config.whitelist.extensions;
return files.filter(file => {
const relativePath = path.relative(projectRoot, file);
const extension = path.extname(file);
// Check if file matches any whitelist pattern
for (const pattern of whitelistPatterns) {
if (this.matchesPattern(relativePath, pattern)) {
return false; // Skip whitelisted files
}
}
// Check if file extension is whitelisted
if (whitelistExtensions.includes(extension)) {
return false; // Skip whitelisted extensions
}
// Skip binary and non-text files
const binaryExtensions = [
'.exe', '.dll', '.so', '.dylib', '.zip', '.tar', '.gz',
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg',
'.mp3', '.mp4', '.avi', '.mov', '.wmv', '.flv',
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.ttf', '.otf', '.woff', '.woff2', '.eot'
];
if (binaryExtensions.includes(extension.toLowerCase())) {
return false;
}
return true;
});
}
/**
* Check if a file path matches a glob pattern
*/
matchesPattern(filePath, pattern) {
// Convert glob pattern to regex
const regexPattern = pattern
.replace(/\*\*/g, '.*')
.replace(/\*/g, '[^/]*')
.replace(/\?/g, '.');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(filePath);
}
}
exports.ProjectScanner = ProjectScanner;
//# sourceMappingURL=projectScanner.js.map