ccguard
Version:
Automated enforcement of net-negative LOC, complexity constraints, and quality standards for Claude code
150 lines • 5.27 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitIgnoreParser = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
class GitIgnoreParser {
patterns;
rootDir;
constructor(rootDir) {
this.rootDir = rootDir;
this.patterns = [];
this.loadPatterns();
}
loadPatterns() {
// Add default patterns first
this.addDefaultPatterns();
// Then load .gitignore (so it can override defaults)
this.loadGitignoreFile(path_1.default.join(this.rootDir, '.gitignore'));
}
loadGitignoreFile(gitignorePath) {
if (!fs_1.default.existsSync(gitignorePath)) {
return;
}
try {
const content = fs_1.default.readFileSync(gitignorePath, 'utf-8');
const lines = content.split('\n');
for (const line of lines) {
const trimmed = line.trim();
// Skip empty lines and comments
if (!trimmed || trimmed.startsWith('#')) {
continue;
}
this.addPattern(trimmed);
}
}
catch (error) {
console.error(`Error reading .gitignore at ${gitignorePath}:`, error);
}
}
addDefaultPatterns() {
// Always ignore these
const defaults = [
'node_modules',
'.git',
'dist',
'build',
'.DS_Store',
'.env',
'.env.local',
'coverage',
'.nyc_output',
'.vscode',
'.idea',
];
for (const pattern of defaults) {
this.addPattern(pattern);
}
}
addPattern(pattern) {
let negated = false;
let directory = false;
let workingPattern = pattern;
// Handle negation
if (workingPattern.startsWith('!')) {
negated = true;
workingPattern = workingPattern.slice(1);
}
// Handle directory-only patterns
if (workingPattern.endsWith('/')) {
directory = true;
workingPattern = workingPattern.slice(0, -1);
}
// Convert gitignore pattern to regex
const regex = this.gitignoreToRegex(workingPattern);
this.patterns.push({ pattern: regex, negated, directory });
}
gitignoreToRegex(pattern) {
// Escape regex special characters except * and ?
let regex = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
// Handle ** (matches any number of directories)
regex = regex.replace(/\*\*/g, '___DOUBLE_STAR___');
// Handle * (matches anything except /)
regex = regex.replace(/\*/g, '[^/]*');
// Handle ? (matches any single character except /)
regex = regex.replace(/\?/g, '[^/]');
// Replace back ** placeholder
regex = regex.replace(/___DOUBLE_STAR___/g, '.*');
// If pattern doesn't start with /, it can match anywhere
if (!pattern.startsWith('/')) {
regex = `(^|/)${regex}`;
}
else {
// Remove leading / and anchor to start
regex = `^${regex.slice(1)}`;
}
// Add end anchor if pattern doesn't end with *
if (!pattern.endsWith('*')) {
regex = `${regex}($|/)`;
}
return new RegExp(regex);
}
isIgnored(filePath) {
// Convert to relative path from root
const relativePath = path_1.default.relative(this.rootDir, filePath);
// Never include files outside the root directory
if (relativePath.startsWith('..')) {
return true;
}
let ignored = false;
// Check each pattern in order (later patterns can override earlier ones)
for (const { pattern, negated } of this.patterns) {
if (pattern.test(relativePath)) {
ignored = !negated;
}
}
return ignored;
}
// Get all files in a directory that are not ignored
getAllFiles(dir = this.rootDir) {
const files = [];
const walkDir = (currentDir) => {
try {
const entries = fs_1.default.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path_1.default.join(currentDir, entry.name);
if (this.isIgnored(fullPath)) {
continue;
}
if (entry.isDirectory()) {
walkDir(fullPath);
}
else if (entry.isFile()) {
files.push(fullPath);
}
}
}
catch (error) {
// Skip directories we can't read
console.debug(`Skipping unreadable directory: ${currentDir}`);
}
};
walkDir(dir);
return files;
}
}
exports.GitIgnoreParser = GitIgnoreParser;
//# sourceMappingURL=GitIgnoreParser.js.map