repo-wrapped 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/generate.js +104 -95
- package/dist/constants/chronotypes.js +23 -23
- package/dist/constants/colors.js +18 -18
- package/dist/constants/index.js +18 -18
- package/dist/formatters/index.js +17 -17
- package/dist/formatters/timeFormatter.js +28 -29
- package/dist/generators/html/templates/achievementsSection.js +42 -43
- package/dist/generators/html/templates/commitQualitySection.js +25 -26
- package/dist/generators/html/templates/contributionGraph.js +64 -48
- package/dist/generators/html/templates/impactSection.js +19 -20
- package/dist/generators/html/templates/knowledgeSection.js +86 -87
- package/dist/generators/html/templates/streakSection.js +8 -9
- package/dist/generators/html/templates/timePatternsSection.js +45 -46
- package/dist/generators/html/utils/colorUtils.js +61 -21
- package/dist/generators/html/utils/commitMapBuilder.js +23 -24
- package/dist/generators/html/utils/dateRangeCalculator.js +56 -57
- package/dist/generators/html/utils/developerStatsCalculator.js +28 -29
- package/dist/generators/html/utils/scriptLoader.js +15 -16
- package/dist/generators/html/utils/styleLoader.js +17 -18
- package/dist/generators/html/utils/weekGrouper.js +27 -28
- package/dist/index.js +78 -78
- package/dist/types/index.js +2 -2
- package/dist/utils/achievementDefinitions.js +433 -433
- package/dist/utils/achievementEngine.js +169 -170
- package/dist/utils/commitQualityAnalyzer.js +367 -368
- package/dist/utils/fileHotspotAnalyzer.js +269 -270
- package/dist/utils/gitParser.js +136 -126
- package/dist/utils/htmlGenerator.js +245 -233
- package/dist/utils/impactAnalyzer.js +247 -248
- package/dist/utils/knowledgeDistributionAnalyzer.js +380 -374
- package/dist/utils/matrixGenerator.js +369 -350
- package/dist/utils/slideGenerator.js +170 -171
- package/dist/utils/streakCalculator.js +134 -135
- package/dist/utils/timePatternAnalyzer.js +304 -305
- package/dist/utils/wrappedDisplay.js +124 -115
- package/dist/utils/wrappedGenerator.js +376 -377
- package/dist/utils/wrappedHtmlGenerator.js +105 -106
- package/package.json +10 -10
|
@@ -1,248 +1,247 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.analyzeImpact =
|
|
4
|
-
// File weight patterns based on criticality
|
|
5
|
-
const FILE_WEIGHTS = [
|
|
6
|
-
{ pattern: /^src\/(core|lib)\//, weight: 1.5, category: 'core' },
|
|
7
|
-
{ pattern: /^(src|lib)\/.*\.(ts|js|tsx|jsx)$/, weight: 1.0, category: 'feature' },
|
|
8
|
-
{ pattern: /(test|spec|__tests__)\/|\.test\.|\.spec\./, weight: 0.8, category: 'test' },
|
|
9
|
-
{ pattern: /\.(config|rc)\.|\.env|tsconfig|webpack|vite|eslint|prettier/, weight: 0.6, category: 'config' },
|
|
10
|
-
{ pattern: /\.md$|^docs\/|^README/, weight: 0.4, category: 'docs' },
|
|
11
|
-
{ pattern: /package\.json|\.lock$|yarn\.lock|pnpm-lock/, weight: 0.3, category: 'config' },
|
|
12
|
-
];
|
|
13
|
-
// Commit type weights based on conventional commits
|
|
14
|
-
const TYPE_WEIGHTS = {
|
|
15
|
-
feat: 1.5,
|
|
16
|
-
fix: 1.2,
|
|
17
|
-
refactor: 1.0,
|
|
18
|
-
perf: 1.3,
|
|
19
|
-
test: 0.9,
|
|
20
|
-
docs: 0.5,
|
|
21
|
-
chore: 0.4,
|
|
22
|
-
style: 0.3,
|
|
23
|
-
ci: 0.4,
|
|
24
|
-
build: 0.5,
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Get file weight and category based on path
|
|
28
|
-
*/
|
|
29
|
-
function getFileInfo(filePath) {
|
|
30
|
-
const normalizedPath = filePath.toLowerCase().replace(/\\/g, '/');
|
|
31
|
-
for (const { pattern, weight, category } of FILE_WEIGHTS) {
|
|
32
|
-
if (pattern.test(normalizedPath)) {
|
|
33
|
-
return { weight, category };
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
// Default for unknown files
|
|
37
|
-
return { weight: 0.7, category: 'feature' };
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Extract commit type from conventional commit message
|
|
41
|
-
*/
|
|
42
|
-
function getCommitType(message) {
|
|
43
|
-
const match = message.match(/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?:/i);
|
|
44
|
-
return match ? match[1].toLowerCase() : null;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Calculate recency factor (recent changes weighted higher)
|
|
48
|
-
*/
|
|
49
|
-
function getRecencyFactor(commitDate, now = new Date()) {
|
|
50
|
-
const daysSinceCommit = Math.floor((now.getTime() - commitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
51
|
-
// Exponential decay over 90 days
|
|
52
|
-
if (daysSinceCommit <= 7)
|
|
53
|
-
return 1.0;
|
|
54
|
-
if (daysSinceCommit <= 30)
|
|
55
|
-
return 0.9;
|
|
56
|
-
if (daysSinceCommit <= 60)
|
|
57
|
-
return 0.7;
|
|
58
|
-
if (daysSinceCommit <= 90)
|
|
59
|
-
return 0.5;
|
|
60
|
-
return 0.3;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Extract files changed from commit message (simplified - assumes file paths in message)
|
|
64
|
-
* In a real implementation, this would come from git diff
|
|
65
|
-
*/
|
|
66
|
-
function extractFilesFromCommit(commit) {
|
|
67
|
-
// For now, return empty - in a real implementation we'd parse git log --name-only
|
|
68
|
-
// This is a placeholder that allows the analyzer to work with existing data
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Analyze impact of commits
|
|
73
|
-
*/
|
|
74
|
-
function analyzeImpact(commits, changedFiles) {
|
|
75
|
-
if (commits.length === 0) {
|
|
76
|
-
return getEmptyImpactAnalysis();
|
|
77
|
-
}
|
|
78
|
-
const now = new Date();
|
|
79
|
-
const fileImpacts = new Map();
|
|
80
|
-
let coreContributions = 0;
|
|
81
|
-
let featureWork = 0;
|
|
82
|
-
let maintenanceWork = 0;
|
|
83
|
-
let documentationWork = 0;
|
|
84
|
-
let totalRawScore = 0;
|
|
85
|
-
let recentScore = 0; // Last 30 days
|
|
86
|
-
let olderScore = 0; // 30-90 days ago
|
|
87
|
-
// If we have file change data from fileHotspotAnalyzer, use it
|
|
88
|
-
if (changedFiles && changedFiles.size > 0) {
|
|
89
|
-
changedFiles.forEach((data, filePath) => {
|
|
90
|
-
const { weight, category } = getFileInfo(filePath);
|
|
91
|
-
const impactScore = data.count * weight;
|
|
92
|
-
fileImpacts.set(filePath, {
|
|
93
|
-
score: impactScore,
|
|
94
|
-
count: data.count,
|
|
95
|
-
lastChanged: data.lastChanged,
|
|
96
|
-
category
|
|
97
|
-
});
|
|
98
|
-
totalRawScore += impactScore;
|
|
99
|
-
// Categorize contributions
|
|
100
|
-
switch (category) {
|
|
101
|
-
case 'core':
|
|
102
|
-
coreContributions += impactScore;
|
|
103
|
-
break;
|
|
104
|
-
case 'test':
|
|
105
|
-
case 'config':
|
|
106
|
-
maintenanceWork += impactScore;
|
|
107
|
-
break;
|
|
108
|
-
case 'docs':
|
|
109
|
-
documentationWork += impactScore;
|
|
110
|
-
break;
|
|
111
|
-
default:
|
|
112
|
-
featureWork += impactScore;
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
// Process commits for type-based weighting and recency
|
|
117
|
-
commits.forEach(commit => {
|
|
118
|
-
const commitDate = new Date(commit.date);
|
|
119
|
-
const commitType = getCommitType(commit.message);
|
|
120
|
-
const typeWeight = commitType ? (TYPE_WEIGHTS[commitType] || 1.0) : 1.0;
|
|
121
|
-
const recencyFactor = getRecencyFactor(commitDate, now);
|
|
122
|
-
const daysSinceCommit = Math.floor((now.getTime() - commitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
123
|
-
const baseScore = typeWeight * recencyFactor;
|
|
124
|
-
if (daysSinceCommit <= 30) {
|
|
125
|
-
recentScore += baseScore;
|
|
126
|
-
}
|
|
127
|
-
else if (daysSinceCommit <= 90) {
|
|
128
|
-
olderScore += baseScore;
|
|
129
|
-
}
|
|
130
|
-
// Additional category scoring based on commit type
|
|
131
|
-
if (commitType === 'feat') {
|
|
132
|
-
featureWork += typeWeight;
|
|
133
|
-
}
|
|
134
|
-
else if (commitType === 'fix' || commitType === 'refactor') {
|
|
135
|
-
maintenanceWork += typeWeight;
|
|
136
|
-
}
|
|
137
|
-
else if (commitType === 'docs') {
|
|
138
|
-
documentationWork += typeWeight;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
// Normalize overall score to 0-100
|
|
142
|
-
const maxPossibleScore = commits.length * 1.5 * 1.0; // Max type weight * max recency
|
|
143
|
-
const normalizedScore = Math.min(100, Math.round((totalRawScore + recentScore) / Math.max(maxPossibleScore, 1) * 100));
|
|
144
|
-
// Get top 10 impact files
|
|
145
|
-
const topImpactFiles = Array.from(fileImpacts.entries())
|
|
146
|
-
.sort((a, b) => b[1].score - a[1].score)
|
|
147
|
-
.slice(0, 10)
|
|
148
|
-
.map(([path, data]) => ({
|
|
149
|
-
path,
|
|
150
|
-
impactScore: Math.round(data.score * 10) / 10,
|
|
151
|
-
changeCount: data.count,
|
|
152
|
-
lastChanged: data.lastChanged,
|
|
153
|
-
category: data.category
|
|
154
|
-
}));
|
|
155
|
-
// Determine trend
|
|
156
|
-
let impactTrend = 'stable';
|
|
157
|
-
if (recentScore > olderScore * 1.2) {
|
|
158
|
-
impactTrend = 'increasing';
|
|
159
|
-
}
|
|
160
|
-
else if (recentScore < olderScore * 0.8) {
|
|
161
|
-
impactTrend = 'decreasing';
|
|
162
|
-
}
|
|
163
|
-
// Generate insights
|
|
164
|
-
const insights = generateInsights({
|
|
165
|
-
coreContributions,
|
|
166
|
-
featureWork,
|
|
167
|
-
maintenanceWork,
|
|
168
|
-
documentationWork,
|
|
169
|
-
topImpactFiles,
|
|
170
|
-
impactTrend,
|
|
171
|
-
totalCommits: commits.length
|
|
172
|
-
});
|
|
173
|
-
// Normalize breakdown scores
|
|
174
|
-
const breakdownTotal = coreContributions + featureWork + maintenanceWork + documentationWork || 1;
|
|
175
|
-
return {
|
|
176
|
-
overallScore: normalizedScore,
|
|
177
|
-
scoreBreakdown: {
|
|
178
|
-
coreContributions: Math.round((coreContributions / breakdownTotal) * 100),
|
|
179
|
-
featureWork: Math.round((featureWork / breakdownTotal) * 100),
|
|
180
|
-
maintenanceWork: Math.round((maintenanceWork / breakdownTotal) * 100),
|
|
181
|
-
documentationWork: Math.round((documentationWork / breakdownTotal) * 100),
|
|
182
|
-
},
|
|
183
|
-
topImpactFiles,
|
|
184
|
-
impactTrend,
|
|
185
|
-
insights
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeImpact = analyzeImpact;
|
|
4
|
+
// File weight patterns based on criticality
|
|
5
|
+
const FILE_WEIGHTS = [
|
|
6
|
+
{ pattern: /^src\/(core|lib)\//, weight: 1.5, category: 'core' },
|
|
7
|
+
{ pattern: /^(src|lib)\/.*\.(ts|js|tsx|jsx)$/, weight: 1.0, category: 'feature' },
|
|
8
|
+
{ pattern: /(test|spec|__tests__)\/|\.test\.|\.spec\./, weight: 0.8, category: 'test' },
|
|
9
|
+
{ pattern: /\.(config|rc)\.|\.env|tsconfig|webpack|vite|eslint|prettier/, weight: 0.6, category: 'config' },
|
|
10
|
+
{ pattern: /\.md$|^docs\/|^README/, weight: 0.4, category: 'docs' },
|
|
11
|
+
{ pattern: /package\.json|\.lock$|yarn\.lock|pnpm-lock/, weight: 0.3, category: 'config' },
|
|
12
|
+
];
|
|
13
|
+
// Commit type weights based on conventional commits
|
|
14
|
+
const TYPE_WEIGHTS = {
|
|
15
|
+
feat: 1.5,
|
|
16
|
+
fix: 1.2,
|
|
17
|
+
refactor: 1.0,
|
|
18
|
+
perf: 1.3,
|
|
19
|
+
test: 0.9,
|
|
20
|
+
docs: 0.5,
|
|
21
|
+
chore: 0.4,
|
|
22
|
+
style: 0.3,
|
|
23
|
+
ci: 0.4,
|
|
24
|
+
build: 0.5,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get file weight and category based on path
|
|
28
|
+
*/
|
|
29
|
+
function getFileInfo(filePath) {
|
|
30
|
+
const normalizedPath = filePath.toLowerCase().replace(/\\/g, '/');
|
|
31
|
+
for (const { pattern, weight, category } of FILE_WEIGHTS) {
|
|
32
|
+
if (pattern.test(normalizedPath)) {
|
|
33
|
+
return { weight, category };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Default for unknown files
|
|
37
|
+
return { weight: 0.7, category: 'feature' };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract commit type from conventional commit message
|
|
41
|
+
*/
|
|
42
|
+
function getCommitType(message) {
|
|
43
|
+
const match = message.match(/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?:/i);
|
|
44
|
+
return match ? match[1].toLowerCase() : null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Calculate recency factor (recent changes weighted higher)
|
|
48
|
+
*/
|
|
49
|
+
function getRecencyFactor(commitDate, now = new Date()) {
|
|
50
|
+
const daysSinceCommit = Math.floor((now.getTime() - commitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
51
|
+
// Exponential decay over 90 days
|
|
52
|
+
if (daysSinceCommit <= 7)
|
|
53
|
+
return 1.0;
|
|
54
|
+
if (daysSinceCommit <= 30)
|
|
55
|
+
return 0.9;
|
|
56
|
+
if (daysSinceCommit <= 60)
|
|
57
|
+
return 0.7;
|
|
58
|
+
if (daysSinceCommit <= 90)
|
|
59
|
+
return 0.5;
|
|
60
|
+
return 0.3;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Extract files changed from commit message (simplified - assumes file paths in message)
|
|
64
|
+
* In a real implementation, this would come from git diff
|
|
65
|
+
*/
|
|
66
|
+
function extractFilesFromCommit(commit) {
|
|
67
|
+
// For now, return empty - in a real implementation we'd parse git log --name-only
|
|
68
|
+
// This is a placeholder that allows the analyzer to work with existing data
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Analyze impact of commits
|
|
73
|
+
*/
|
|
74
|
+
function analyzeImpact(commits, changedFiles) {
|
|
75
|
+
if (commits.length === 0) {
|
|
76
|
+
return getEmptyImpactAnalysis();
|
|
77
|
+
}
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const fileImpacts = new Map();
|
|
80
|
+
let coreContributions = 0;
|
|
81
|
+
let featureWork = 0;
|
|
82
|
+
let maintenanceWork = 0;
|
|
83
|
+
let documentationWork = 0;
|
|
84
|
+
let totalRawScore = 0;
|
|
85
|
+
let recentScore = 0; // Last 30 days
|
|
86
|
+
let olderScore = 0; // 30-90 days ago
|
|
87
|
+
// If we have file change data from fileHotspotAnalyzer, use it
|
|
88
|
+
if (changedFiles && changedFiles.size > 0) {
|
|
89
|
+
changedFiles.forEach((data, filePath) => {
|
|
90
|
+
const { weight, category } = getFileInfo(filePath);
|
|
91
|
+
const impactScore = data.count * weight;
|
|
92
|
+
fileImpacts.set(filePath, {
|
|
93
|
+
score: impactScore,
|
|
94
|
+
count: data.count,
|
|
95
|
+
lastChanged: data.lastChanged,
|
|
96
|
+
category
|
|
97
|
+
});
|
|
98
|
+
totalRawScore += impactScore;
|
|
99
|
+
// Categorize contributions
|
|
100
|
+
switch (category) {
|
|
101
|
+
case 'core':
|
|
102
|
+
coreContributions += impactScore;
|
|
103
|
+
break;
|
|
104
|
+
case 'test':
|
|
105
|
+
case 'config':
|
|
106
|
+
maintenanceWork += impactScore;
|
|
107
|
+
break;
|
|
108
|
+
case 'docs':
|
|
109
|
+
documentationWork += impactScore;
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
featureWork += impactScore;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Process commits for type-based weighting and recency
|
|
117
|
+
commits.forEach(commit => {
|
|
118
|
+
const commitDate = new Date(commit.date);
|
|
119
|
+
const commitType = getCommitType(commit.message);
|
|
120
|
+
const typeWeight = commitType ? (TYPE_WEIGHTS[commitType] || 1.0) : 1.0;
|
|
121
|
+
const recencyFactor = getRecencyFactor(commitDate, now);
|
|
122
|
+
const daysSinceCommit = Math.floor((now.getTime() - commitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
123
|
+
const baseScore = typeWeight * recencyFactor;
|
|
124
|
+
if (daysSinceCommit <= 30) {
|
|
125
|
+
recentScore += baseScore;
|
|
126
|
+
}
|
|
127
|
+
else if (daysSinceCommit <= 90) {
|
|
128
|
+
olderScore += baseScore;
|
|
129
|
+
}
|
|
130
|
+
// Additional category scoring based on commit type
|
|
131
|
+
if (commitType === 'feat') {
|
|
132
|
+
featureWork += typeWeight;
|
|
133
|
+
}
|
|
134
|
+
else if (commitType === 'fix' || commitType === 'refactor') {
|
|
135
|
+
maintenanceWork += typeWeight;
|
|
136
|
+
}
|
|
137
|
+
else if (commitType === 'docs') {
|
|
138
|
+
documentationWork += typeWeight;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// Normalize overall score to 0-100
|
|
142
|
+
const maxPossibleScore = commits.length * 1.5 * 1.0; // Max type weight * max recency
|
|
143
|
+
const normalizedScore = Math.min(100, Math.round((totalRawScore + recentScore) / Math.max(maxPossibleScore, 1) * 100));
|
|
144
|
+
// Get top 10 impact files
|
|
145
|
+
const topImpactFiles = Array.from(fileImpacts.entries())
|
|
146
|
+
.sort((a, b) => b[1].score - a[1].score)
|
|
147
|
+
.slice(0, 10)
|
|
148
|
+
.map(([path, data]) => ({
|
|
149
|
+
path,
|
|
150
|
+
impactScore: Math.round(data.score * 10) / 10,
|
|
151
|
+
changeCount: data.count,
|
|
152
|
+
lastChanged: data.lastChanged,
|
|
153
|
+
category: data.category
|
|
154
|
+
}));
|
|
155
|
+
// Determine trend
|
|
156
|
+
let impactTrend = 'stable';
|
|
157
|
+
if (recentScore > olderScore * 1.2) {
|
|
158
|
+
impactTrend = 'increasing';
|
|
159
|
+
}
|
|
160
|
+
else if (recentScore < olderScore * 0.8) {
|
|
161
|
+
impactTrend = 'decreasing';
|
|
162
|
+
}
|
|
163
|
+
// Generate insights
|
|
164
|
+
const insights = generateInsights({
|
|
165
|
+
coreContributions,
|
|
166
|
+
featureWork,
|
|
167
|
+
maintenanceWork,
|
|
168
|
+
documentationWork,
|
|
169
|
+
topImpactFiles,
|
|
170
|
+
impactTrend,
|
|
171
|
+
totalCommits: commits.length
|
|
172
|
+
});
|
|
173
|
+
// Normalize breakdown scores
|
|
174
|
+
const breakdownTotal = coreContributions + featureWork + maintenanceWork + documentationWork || 1;
|
|
175
|
+
return {
|
|
176
|
+
overallScore: normalizedScore,
|
|
177
|
+
scoreBreakdown: {
|
|
178
|
+
coreContributions: Math.round((coreContributions / breakdownTotal) * 100),
|
|
179
|
+
featureWork: Math.round((featureWork / breakdownTotal) * 100),
|
|
180
|
+
maintenanceWork: Math.round((maintenanceWork / breakdownTotal) * 100),
|
|
181
|
+
documentationWork: Math.round((documentationWork / breakdownTotal) * 100),
|
|
182
|
+
},
|
|
183
|
+
topImpactFiles,
|
|
184
|
+
impactTrend,
|
|
185
|
+
insights
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Generate meaningful insights from impact data
|
|
190
|
+
*/
|
|
191
|
+
function generateInsights(data) {
|
|
192
|
+
const insights = [];
|
|
193
|
+
const total = data.coreContributions + data.featureWork + data.maintenanceWork + data.documentationWork || 1;
|
|
194
|
+
// Core contributions insight
|
|
195
|
+
const corePercentage = (data.coreContributions / total) * 100;
|
|
196
|
+
if (corePercentage > 30) {
|
|
197
|
+
insights.push(`Strong focus on core modules — ${corePercentage.toFixed(0)}% of impact from critical paths`);
|
|
198
|
+
}
|
|
199
|
+
// Feature work insight
|
|
200
|
+
const featurePercentage = (data.featureWork / total) * 100;
|
|
201
|
+
if (featurePercentage > 50) {
|
|
202
|
+
insights.push(`Feature-focused development — ${featurePercentage.toFixed(0)}% of contributions are new functionality`);
|
|
203
|
+
}
|
|
204
|
+
// Maintenance insight
|
|
205
|
+
const maintenancePercentage = (data.maintenanceWork / total) * 100;
|
|
206
|
+
if (maintenancePercentage > 40) {
|
|
207
|
+
insights.push(`Strong maintenance focus — keeping the codebase healthy`);
|
|
208
|
+
}
|
|
209
|
+
// Documentation insight
|
|
210
|
+
const docPercentage = (data.documentationWork / total) * 100;
|
|
211
|
+
if (docPercentage < 5 && data.totalCommits > 20) {
|
|
212
|
+
insights.push(`Documentation contributions below average — consider adding READMEs or inline docs`);
|
|
213
|
+
}
|
|
214
|
+
else if (docPercentage > 15) {
|
|
215
|
+
insights.push(`Good documentation practices — ${docPercentage.toFixed(0)}% of work includes docs`);
|
|
216
|
+
}
|
|
217
|
+
// Trend insight
|
|
218
|
+
if (data.impactTrend === 'increasing') {
|
|
219
|
+
insights.push(`Impact trending upward — recent contributions are higher value`);
|
|
220
|
+
}
|
|
221
|
+
else if (data.impactTrend === 'decreasing') {
|
|
222
|
+
insights.push(`Recent activity lower than historical average`);
|
|
223
|
+
}
|
|
224
|
+
// Top files insight
|
|
225
|
+
if (data.topImpactFiles.length > 0) {
|
|
226
|
+
const topFile = data.topImpactFiles[0];
|
|
227
|
+
insights.push(`Highest impact: ${topFile.path.split('/').pop()} (${topFile.changeCount} changes)`);
|
|
228
|
+
}
|
|
229
|
+
return insights.slice(0, 5); // Limit to 5 insights
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Return empty analysis for no commits
|
|
233
|
+
*/
|
|
234
|
+
function getEmptyImpactAnalysis() {
|
|
235
|
+
return {
|
|
236
|
+
overallScore: 0,
|
|
237
|
+
scoreBreakdown: {
|
|
238
|
+
coreContributions: 0,
|
|
239
|
+
featureWork: 0,
|
|
240
|
+
maintenanceWork: 0,
|
|
241
|
+
documentationWork: 0,
|
|
242
|
+
},
|
|
243
|
+
topImpactFiles: [],
|
|
244
|
+
impactTrend: 'stable',
|
|
245
|
+
insights: ['No commit data available for analysis']
|
|
246
|
+
};
|
|
247
|
+
}
|