PRININIT / lib /src /file-filter.js
yaminXwin's picture
feat: implement confidence-based comment routing, severity calibration, token budget truncation, and false-positive suppression rules
4d0586c
Raw
History Blame Contribute Delete
7.07 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFileCategory = exports.shouldSkipLLM = exports.filterFilesForLLM = exports.logTokenSavings = exports.generateConsolidatedRecommendations = exports.generateRecommendation = exports.classifyFiles = exports.classifyFile = exports.LOCKFILES = exports.IGNORED_PATTERNS = void 0;
exports.IGNORED_PATTERNS = [
/node_modules\//,
/dist\//,
/build\//,
/\.next\//,
/coverage\//,
/\.min\.js$/,
/\.bundle\.js$/,
/\.generated\./,
/vendor\//,
/\.map$/,
/\.snap$/,
/\.lock$/,
/\.md$/, // Skip markdown files unless explicitly requested
/__pycache__\//,
/\.pyc$/,
/\.egg-info\//,
/target\//, // Rust/Java build output
/out\//, // TypeScript/JavaScript build output
/\.cache\//,
/\.vscode\//,
/\.idea\//,
/\.DS_Store$/,
/Thumbs\.db$/
];
exports.LOCKFILES = [
'package-lock.json',
'pnpm-lock.yaml',
'yarn.lock',
'Cargo.lock',
'poetry.lock',
'requirements.txt',
'Pipfile.lock',
'go.sum',
'composer.lock'
];
const TOKENS_PER_LINE = 4;
const TOKENS_PER_KB = 150;
const classifyFile = (filename) => {
const lowerFilename = filename.toLowerCase();
if (lowerFilename.includes('node_modules/')) {
return {
filename,
category: 'node_modules',
skipLLM: true,
reason: 'node_modules directory should not be committed to Git'
};
}
const isLockfile = exports.LOCKFILES.some(lockfile => {
const lockfileLower = lockfile.toLowerCase();
return (lowerFilename.endsWith(lockfileLower) ||
lowerFilename.includes(lockfileLower));
});
if (isLockfile) {
return {
filename,
category: 'lockfile',
skipLLM: true,
reason: 'Dependency lockfile - skipping deep AI analysis'
};
}
for (const pattern of exports.IGNORED_PATTERNS) {
if (pattern.test(filename)) {
return {
filename,
category: 'generated',
skipLLM: true,
reason: 'Generated or build artifact'
};
}
}
return {
filename,
category: 'source',
skipLLM: false
};
};
exports.classifyFile = classifyFile;
const classifyFiles = (filenames) => {
const classifications = [];
const metrics = {
skippedFiles: 0,
skippedTokensEstimate: 0,
categories: {}
};
for (const filename of filenames) {
const classification = (0, exports.classifyFile)(filename);
classifications.push(classification);
if (classification.skipLLM) {
metrics.skippedFiles++;
// Track by category
metrics.categories[classification.category] =
(metrics.categories[classification.category] || 0) + 1;
// Estimate tokens saved (rough estimate based on file path length)
// In a real implementation, this would use actual file size
const estimatedTokens = Math.max(1000, filename.length * TOKENS_PER_LINE);
metrics.skippedTokensEstimate += estimatedTokens;
}
}
return { classifications, metrics };
};
exports.classifyFiles = classifyFiles;
const generateRecommendation = (classification) => {
if (!classification.skipLLM) {
return null;
}
switch (classification.category) {
case 'node_modules':
return `## 💡 Recommendation
\`node_modules/\` was detected in this pull request.
This directory should usually not be committed to Git.
### Suggested Fix
Add this to \`.gitignore\`:
\`\`\`gitignore
node_modules/
\`\`\`
### AI Agent Prompt
Remove committed node_modules files and update .gitignore to exclude them permanently.`;
case 'lockfile':
return `## ℹ️ Optimization Suggestion
Large dependency lockfile changes detected.
Consider separating dependency updates into dedicated PRs to reduce review noise and improve review clarity.`;
case 'generated':
return `## 💡 Recommendation
Generated/build artifacts were detected and skipped from AI review to improve review performance and reduce unnecessary noise.`;
default:
return `## ℹ️ Information
File \`${classification.filename}\` was skipped from AI review: ${classification.reason || 'No specific reason provided'}`;
}
};
exports.generateRecommendation = generateRecommendation;
const generateConsolidatedRecommendations = (classifications) => {
const recommendations = [];
const seenCategories = new Set();
// Group by category to avoid duplicate recommendations
const byCategory = new Map();
for (const classification of classifications) {
if (classification.skipLLM) {
if (!byCategory.has(classification.category)) {
byCategory.set(classification.category, []);
}
byCategory.get(classification.category).push(classification);
}
}
// Generate one recommendation per category
for (const [category, files] of byCategory.entries()) {
if (seenCategories.has(category))
continue;
seenCategories.add(category);
const representative = files[0];
const recommendation = (0, exports.generateRecommendation)(representative);
if (recommendation) {
// Add file count if multiple files in this category
let finalRecommendation = recommendation;
if (files.length > 1) {
finalRecommendation = recommendation.replace(/was detected/, `(${files.length} files) were detected`);
}
recommendations.push(finalRecommendation);
}
}
return recommendations;
};
exports.generateConsolidatedRecommendations = generateConsolidatedRecommendations;
const logTokenSavings = (metrics) => {
console.log('REVIEW_OPTIMIZATION', {
skippedFiles: metrics.skippedFiles,
skippedTokensEstimate: metrics.skippedTokensEstimate,
reason: 'File classification filtering',
categories: metrics.categories
});
// Human-readable summary
if (metrics.skippedFiles > 0) {
const tokenK = (metrics.skippedTokensEstimate / 1000).toFixed(0);
console.log(`Skipped ${metrics.skippedFiles} generated/irrelevant files`);
console.log(`Estimated token savings: ${tokenK}k`);
}
};
exports.logTokenSavings = logTokenSavings;
const filterFilesForLLM = (filenames) => {
return filenames.filter(filename => !(0, exports.classifyFile)(filename).skipLLM);
};
exports.filterFilesForLLM = filterFilesForLLM;
const shouldSkipLLM = (filename) => {
return (0, exports.classifyFile)(filename).skipLLM;
};
exports.shouldSkipLLM = shouldSkipLLM;
const getFileCategory = (filename) => {
return (0, exports.classifyFile)(filename).category;
};
exports.getFileCategory = getFileCategory;
//# sourceMappingURL=file-filter.js.map