UNPKG

ccguard

Version:

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

175 lines 6.62 kB
"use strict"; 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