mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
306 lines • 12.3 kB
JavaScript
import fs from 'fs-extra';
import * as path from 'path';
import simpleGit from 'simple-git';
import * as glob from 'glob';
export class PatternAnalyzer {
projectPath;
patternsFile;
patterns;
git;
constructor(projectPath) {
this.projectPath = projectPath;
this.patternsFile = path.join(projectPath, '.mira', 'patterns', 'history.json');
this.patterns = new Map();
this.git = simpleGit(projectPath);
this.loadPatterns();
}
loadPatterns() {
try {
if (fs.existsSync(this.patternsFile)) {
const data = fs.readJsonSync(this.patternsFile);
for (const pattern of data.patterns || []) {
this.patterns.set(pattern.id, {
...pattern,
lastSeen: new Date(pattern.lastSeen)
});
}
}
}
catch (error) {
console.error('Error loading patterns:', error);
}
}
savePatterns() {
const dir = path.dirname(this.patternsFile);
fs.ensureDirSync(dir);
const data = {
patterns: Array.from(this.patterns.values()),
lastUpdated: new Date().toISOString()
};
fs.writeJsonSync(this.patternsFile, data, { spaces: 2 });
}
async analyzeGitPatterns() {
const patterns = [];
try {
// Analyze commit patterns
const log = await this.git.log({ maxCount: 100 });
const commitPatterns = this.analyzeCommitMessages([...log.all]);
patterns.push(...commitPatterns);
// Analyze file change patterns
const filePatterns = await this.analyzeFileChangePatterns();
patterns.push(...filePatterns);
// Analyze development cycle patterns
const cyclePatterns = await this.analyzeDevelopmentCycles([...log.all]);
patterns.push(...cyclePatterns);
}
catch (error) {
console.error('Error analyzing git patterns:', error);
}
// Update stored patterns
for (const pattern of patterns) {
this.patterns.set(pattern.id, pattern);
}
this.savePatterns();
return patterns;
}
analyzeCommitMessages(commits) {
const patterns = [];
const prefixCount = {};
const timeOfDayCount = {};
for (const commit of commits) {
// Analyze commit prefixes
const prefix = commit.message.split(':')[0].toLowerCase();
if (prefix.length < 20) {
prefixCount[prefix] = (prefixCount[prefix] || 0) + 1;
}
// Analyze commit times
const hour = new Date(commit.date).getHours();
const timeOfDay = hour < 6 ? 'night' : hour < 12 ? 'morning' : hour < 18 ? 'afternoon' : 'evening';
timeOfDayCount[timeOfDay] = (timeOfDayCount[timeOfDay] || 0) + 1;
}
// Create patterns from analysis
for (const [prefix, count] of Object.entries(prefixCount)) {
if (count >= 3) {
patterns.push({
id: `commit-prefix-${prefix}`,
type: 'commit',
description: `Frequent use of "${prefix}" commit prefix`,
frequency: count,
lastSeen: new Date(),
data: { prefix, count }
});
}
}
// Most active time pattern
const mostActiveTime = Object.entries(timeOfDayCount)
.sort((a, b) => b[1] - a[1])[0];
if (mostActiveTime) {
patterns.push({
id: 'commit-time-pattern',
type: 'commit',
description: `Most commits during ${mostActiveTime[0]}`,
frequency: mostActiveTime[1],
lastSeen: new Date(),
data: { timeOfDay: mostActiveTime[0], distribution: timeOfDayCount }
});
}
return patterns;
}
async analyzeFileChangePatterns() {
const patterns = [];
try {
const status = await this.git.status();
const modifiedFiles = status.modified;
// Analyze frequently modified files
const fileModCount = {};
// Get file modification history
for (const file of modifiedFiles) {
const log = await this.git.log({ file, maxCount: 20 });
fileModCount[file] = log.total;
}
// Create patterns for frequently modified files
for (const [file, count] of Object.entries(fileModCount)) {
if (count >= 5) {
patterns.push({
id: `file-hotspot-${file.replace(/[\/\\]/g, '-')}`,
type: 'file_change',
description: `${file} is frequently modified`,
frequency: count,
lastSeen: new Date(),
data: { file, modificationCount: count }
});
}
}
}
catch (error) {
console.error('Error analyzing file patterns:', error);
}
return patterns;
}
async analyzeDevelopmentCycles(commits) {
const patterns = [];
// Analyze commit frequency patterns
const commitsByDay = {};
const commitsByDayOfWeek = {};
for (const commit of commits) {
const date = new Date(commit.date);
const dayKey = date.toISOString().split('T')[0];
const dayOfWeek = date.getDay();
commitsByDay[dayKey] = (commitsByDay[dayKey] || 0) + 1;
commitsByDayOfWeek[dayOfWeek] = (commitsByDayOfWeek[dayOfWeek] || 0) + 1;
}
// Find most productive day of week
const mostProductiveDay = Object.entries(commitsByDayOfWeek)
.sort((a, b) => b[1] - a[1])[0];
if (mostProductiveDay) {
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
patterns.push({
id: 'productive-day-pattern',
type: 'development_cycle',
description: `Most productive on ${days[parseInt(mostProductiveDay[0])]}s`,
frequency: mostProductiveDay[1],
lastSeen: new Date(),
data: { dayOfWeek: parseInt(mostProductiveDay[0]), distribution: commitsByDayOfWeek }
});
}
// Analyze burst patterns
const sortedDays = Object.entries(commitsByDay).sort((a, b) => a[0].localeCompare(b[0]));
let currentBurst = 0;
let maxBurst = 0;
for (let i = 1; i < sortedDays.length; i++) {
const prevDate = new Date(sortedDays[i - 1][0]);
const currDate = new Date(sortedDays[i][0]);
const daysDiff = (currDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24);
if (daysDiff === 1) {
currentBurst++;
maxBurst = Math.max(maxBurst, currentBurst);
}
else {
currentBurst = 0;
}
}
if (maxBurst >= 3) {
patterns.push({
id: 'burst-development-pattern',
type: 'development_cycle',
description: `Tends to work in bursts of ${maxBurst}+ consecutive days`,
frequency: maxBurst,
lastSeen: new Date(),
data: { maxConsecutiveDays: maxBurst }
});
}
return patterns;
}
async analyzeCodePatterns() {
const patterns = [];
try {
// Analyze code structure patterns
const codePatterns = await this.analyzeCodeStructure();
patterns.push(...codePatterns);
// Analyze import patterns
const importPatterns = await this.analyzeImportPatterns();
patterns.push(...importPatterns);
}
catch (error) {
console.error('Error analyzing code patterns:', error);
}
return patterns;
}
async analyzeCodeStructure() {
const patterns = [];
const extensions = ['.js', '.ts', '.py', '.java', '.cs', '.cpp'];
for (const ext of extensions) {
const files = glob.sync(`**/*${ext}`, {
cwd: this.projectPath,
ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**']
});
if (files.length >= 5) {
// Analyze common patterns in these files
let totalLines = 0;
let filesWithClasses = 0;
let filesWithFunctions = 0;
for (const file of files.slice(0, 20)) { // Sample first 20 files
try {
const content = fs.readFileSync(path.join(this.projectPath, file), 'utf-8');
const lines = content.split('\n');
totalLines += lines.length;
if (content.match(/class\s+\w+/))
filesWithClasses++;
if (content.match(/function\s+\w+|const\s+\w+\s*=\s*\(|def\s+\w+/))
filesWithFunctions++;
}
catch (error) {
// Skip files that can't be read
}
}
const avgLines = Math.round(totalLines / Math.min(files.length, 20));
patterns.push({
id: `code-structure-${ext}`,
type: 'file_change',
description: `${ext} files average ${avgLines} lines`,
frequency: files.length,
lastSeen: new Date(),
data: {
extension: ext,
fileCount: files.length,
avgLines,
classUsage: filesWithClasses,
functionUsage: filesWithFunctions
}
});
}
}
return patterns;
}
async analyzeImportPatterns() {
const patterns = [];
const importCounts = {};
// Analyze JavaScript/TypeScript imports
const jsFiles = glob.sync('**/*.{js,ts,jsx,tsx}', {
cwd: this.projectPath,
ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**']
});
for (const file of jsFiles.slice(0, 50)) { // Sample first 50 files
try {
const content = fs.readFileSync(path.join(this.projectPath, file), 'utf-8');
const imports = content.match(/import\s+.*?from\s+['"](.+?)['"]/g) || [];
for (const imp of imports) {
const match = imp.match(/from\s+['"](.+?)['"]/);
if (match) {
const module = match[1];
const baseModule = module.startsWith('.') ? 'relative' : module.split('/')[0];
importCounts[baseModule] = (importCounts[baseModule] || 0) + 1;
}
}
}
catch (error) {
// Skip files that can't be read
}
}
// Create patterns for frequently used imports
for (const [module, count] of Object.entries(importCounts)) {
if (count >= 10 && module !== 'relative') {
patterns.push({
id: `import-pattern-${module}`,
type: 'file_change',
description: `Frequently imports ${module}`,
frequency: count,
lastSeen: new Date(),
data: { module, count }
});
}
}
return patterns;
}
getPatterns() {
return Array.from(this.patterns.values());
}
getPatternById(id) {
return this.patterns.get(id);
}
recordPattern(pattern) {
this.patterns.set(pattern.id, pattern);
this.savePatterns();
}
}
//# sourceMappingURL=PatternAnalyzer.js.map