refakts
Version:
TypeScript refactoring tool built for AI coding agents to perform precise refactoring operations via command line instead of requiring complete code regeneration.
110 lines • 5.05 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeFrequencyCheck = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
exports.changeFrequencyCheck = {
name: 'changeFrequency',
check: async (sourceDir) => {
const changeIssues = await analyzeChangeFrequency();
const filteredIssues = await filterRecentlyFixedIssues(changeIssues);
return filteredIssues.map(toQualityIssue);
},
getGroupDefinition: (groupKey) => {
if (groupKey === 'changeFrequency')
return {
title: 'OPEN-CLOSED PRINCIPLE VIOLATIONS',
description: 'Files changing frequently suggest design should be extensible without modification.',
actionGuidance: 'Consider introducing abstractions, dependency injection, or plugin patterns to improve the design. Suggest the improvement to the user starting with the ⚠️ emoji a detailed explanation of your design suggestion.',
requiresUserConsultation: true
};
if (groupKey === 'cohesiveChange')
return {
title: 'ABSTRACTION LEAKAGE',
description: 'Files changing together suggest concerns not properly encapsulated.',
actionGuidance: 'Consider extracting shared abstractions or reducing coupling. Suggest the improvement to the user starting with the ⚠️ emoji a detailed explanation of your design suggestion.',
requiresUserConsultation: true
};
return undefined;
}
};
const analyzeChangeFrequency = async () => {
try {
const recentlyChangedFiles = await getRecentlyChangedFiles();
if (recentlyChangedFiles.length === 0)
return [];
const fileChanges = await analyzeFileChangeFrequency(recentlyChangedFiles);
const cohesiveChanges = await analyzeCohesiveChanges(recentlyChangedFiles);
return [...fileChanges, ...cohesiveChanges];
}
catch (error) {
return [];
}
};
const getRecentlyChangedFiles = async () => {
const { stdout } = await execAsync('git log --oneline --name-only -2');
return stdout.split('\n')
.filter(line => line.startsWith('src/'))
.filter((file, index, array) => array.indexOf(file) === index);
};
const analyzeFileChangeFrequency = async (recentlyChangedFiles) => {
const { stdout } = await execAsync('git log --oneline --name-only -100 | grep "^src/" | sort | uniq -c | sort -nr');
return stdout.split('\n')
.filter(line => line.trim())
.map(line => line.trim().split(/\s+/))
.filter(([count, file]) => parseInt(count) >= 10 && recentlyChangedFiles.includes(file))
.map(([count, file]) => `${file} changed ${count} times in last 100 commits`);
};
const analyzeCohesiveChanges = async (recentlyChangedFiles) => {
const { stdout } = await execAsync('git log --oneline --name-only -100');
const commits = stdout.split('\n\n').filter(Boolean);
const filePairs = generateFilePairs(commits, recentlyChangedFiles);
const frequentPairs = countFilePairs(filePairs);
return Array.from(frequentPairs.entries())
.filter(([, count]) => count >= 10)
.map(([pair, count]) => `[${pair}] change together ${count} times`);
};
const generateFilePairs = (commits, recentlyChangedFiles) => {
const pairs = [];
commits.forEach(commit => {
const files = commit.split('\n').slice(1).filter(f => f.startsWith('src/'));
for (let i = 0; i < files.length; i++) {
for (let j = i + 1; j < files.length; j++) {
if (recentlyChangedFiles.includes(files[i]) || recentlyChangedFiles.includes(files[j])) {
const pair = [files[i], files[j]].sort().join(', ');
pairs.push(pair);
}
}
}
});
return pairs;
};
const countFilePairs = (pairs) => {
const counts = new Map();
pairs.forEach(pair => counts.set(pair, (counts.get(pair) || 0) + 1));
return counts;
};
const filterRecentlyFixedIssues = async (issues) => {
const recentCommits = await getRecentCommitMessages(3);
const hasQualityFix = recentCommits.some(isQualityFixCommit);
return hasQualityFix ? [] : issues;
};
const getRecentCommitMessages = async (count) => {
try {
const { stdout } = await execAsync(`git log --oneline -${count} --pretty=format:"%s"`);
return stdout.split('\n').filter(Boolean);
}
catch (error) {
return [];
}
};
const isQualityFixCommit = (message) => {
const keywords = ['refactor', 'quality', 'extract', 'simplify', 'cleanup', 'structure'];
return keywords.some(keyword => message.toLowerCase().includes(keyword));
};
const toQualityIssue = (issue) => ({
type: issue.includes('change together') ? 'cohesiveChange' : 'changeFrequency',
message: issue
});
//# sourceMappingURL=change-frequency-check.js.map