ccguard
Version:
Automated enforcement of net-negative LOC, complexity constraints, and quality standards for Claude code
112 lines • 4.18 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigLoader = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const schemas_1 = require("../contracts/schemas");
const zod_1 = require("zod");
class ConfigLoader {
configPath;
static DEFAULT_CONFIG = {
enforcement: {
mode: 'session-wide',
strategy: 'cumulative',
ignoreEmptyLines: true,
limitType: 'hard', // Default to hard limit for backward compatibility
},
whitelist: {
patterns: [],
extensions: [],
},
thresholds: {
allowedPositiveLines: 0,
},
};
config;
constructor(configPath) {
this.configPath = configPath;
this.config = this.loadConfig();
}
findConfigFile() {
const configNames = ['.ccguard.config.json', 'ccguard.config.json'];
// Start from current directory and walk up
let currentDir = process.cwd();
while (currentDir !== path_1.default.parse(currentDir).root) {
for (const configName of configNames) {
const configPath = path_1.default.join(currentDir, configName);
if (fs_1.default.existsSync(configPath)) {
return configPath;
}
}
currentDir = path_1.default.dirname(currentDir);
}
return null;
}
loadConfig() {
const configPath = this.configPath ?? this.findConfigFile();
if (!configPath || !fs_1.default.existsSync(configPath)) {
return ConfigLoader.DEFAULT_CONFIG;
}
try {
const rawConfig = fs_1.default.readFileSync(configPath, 'utf-8');
const parsedConfig = JSON.parse(rawConfig);
// Validate and apply defaults
const validated = schemas_1.GuardConfigSchema.parse(parsedConfig);
return validated;
}
catch (error) {
if (error instanceof zod_1.z.ZodError) {
console.error(`Invalid config at ${configPath}:`, error.errors);
}
else if (error instanceof SyntaxError) {
console.error(`Invalid JSON in config file ${configPath}`);
}
else {
console.error(`Error loading config from ${configPath}:`, error);
}
return ConfigLoader.DEFAULT_CONFIG;
}
}
getConfig() {
return this.config;
}
isFileWhitelisted(filePath) {
const { patterns, extensions } = this.config.whitelist;
// Check extension whitelist
if (extensions.length > 0) {
const ext = path_1.default.extname(filePath).toLowerCase();
if (extensions.includes(ext)) {
return true;
}
}
// Check pattern whitelist (simple glob matching)
if (patterns.length > 0) {
const normalizedPath = path_1.default.normalize(filePath);
for (const pattern of patterns) {
if (this.matchPattern(normalizedPath, pattern)) {
return true;
}
}
}
return false;
}
matchPattern(filePath, pattern) {
// Simple glob matching - supports * and **
const regexPattern = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except * and ?
.replace(/\*\*/g, '___DOUBLE_STAR___') // Temporary placeholder
.replace(/\*/g, '[^/]*') // Single * matches anything except /
.replace(/\?/g, '.') // ? matches any single character
.replace(/___DOUBLE_STAR___/g, '.*'); // ** matches anything including /
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(filePath);
}
reloadConfig() {
this.config = this.loadConfig();
}
}
exports.ConfigLoader = ConfigLoader;
//# sourceMappingURL=ConfigLoader.js.map