repo-wrapped 0.0.3 → 0.0.4

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.
Files changed (38) hide show
  1. package/dist/commands/generate.js +104 -95
  2. package/dist/constants/chronotypes.js +23 -23
  3. package/dist/constants/colors.js +18 -18
  4. package/dist/constants/index.js +18 -18
  5. package/dist/formatters/index.js +17 -17
  6. package/dist/formatters/timeFormatter.js +28 -29
  7. package/dist/generators/html/templates/achievementsSection.js +42 -43
  8. package/dist/generators/html/templates/commitQualitySection.js +25 -26
  9. package/dist/generators/html/templates/contributionGraph.js +47 -48
  10. package/dist/generators/html/templates/impactSection.js +19 -20
  11. package/dist/generators/html/templates/knowledgeSection.js +86 -87
  12. package/dist/generators/html/templates/streakSection.js +8 -9
  13. package/dist/generators/html/templates/timePatternsSection.js +45 -46
  14. package/dist/generators/html/utils/colorUtils.js +21 -21
  15. package/dist/generators/html/utils/commitMapBuilder.js +23 -24
  16. package/dist/generators/html/utils/dateRangeCalculator.js +56 -57
  17. package/dist/generators/html/utils/developerStatsCalculator.js +28 -29
  18. package/dist/generators/html/utils/scriptLoader.js +15 -16
  19. package/dist/generators/html/utils/styleLoader.js +17 -18
  20. package/dist/generators/html/utils/weekGrouper.js +27 -28
  21. package/dist/index.js +78 -78
  22. package/dist/types/index.js +2 -2
  23. package/dist/utils/achievementDefinitions.js +433 -433
  24. package/dist/utils/achievementEngine.js +169 -170
  25. package/dist/utils/commitQualityAnalyzer.js +367 -368
  26. package/dist/utils/fileHotspotAnalyzer.js +269 -270
  27. package/dist/utils/gitParser.js +136 -126
  28. package/dist/utils/htmlGenerator.js +232 -233
  29. package/dist/utils/impactAnalyzer.js +247 -248
  30. package/dist/utils/knowledgeDistributionAnalyzer.js +373 -374
  31. package/dist/utils/matrixGenerator.js +349 -350
  32. package/dist/utils/slideGenerator.js +170 -171
  33. package/dist/utils/streakCalculator.js +134 -135
  34. package/dist/utils/timePatternAnalyzer.js +304 -305
  35. package/dist/utils/wrappedDisplay.js +124 -115
  36. package/dist/utils/wrappedGenerator.js +376 -377
  37. package/dist/utils/wrappedHtmlGenerator.js +105 -106
  38. package/package.json +10 -10
@@ -1,377 +1,376 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateYearWrapped = void 0;
4
- const child_process_1 = require("child_process");
5
- const achievementEngine_js_1 = require("./achievementEngine.js");
6
- const commitQualityAnalyzer_js_1 = require("./commitQualityAnalyzer.js");
7
- const fileHotspotAnalyzer_js_1 = require("./fileHotspotAnalyzer.js");
8
- const gitParser_js_1 = require("./gitParser.js");
9
- const streakCalculator_js_1 = require("./streakCalculator.js");
10
- const timePatternAnalyzer_js_1 = require("./timePatternAnalyzer.js");
11
- function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
12
- // Define year boundaries
13
- const startDate = new Date(`${year}-01-01`);
14
- const endDate = new Date(`${year}-12-31`);
15
- // Fetch all commits for the year
16
- const commits = (0, gitParser_js_1.parseGitCommits)(repoPath).filter(commit => {
17
- const commitDate = new Date(commit.date);
18
- return commitDate >= startDate && commitDate <= endDate;
19
- });
20
- if (commits.length === 0) {
21
- throw new Error(`No commits found for year ${year}`);
22
- }
23
- // Calculate all metrics using existing utilities
24
- const streakData = (0, streakCalculator_js_1.calculateStreaks)(commits, startDate, endDate);
25
- const timePattern = (0, timePatternAnalyzer_js_1.analyzeTimePatterns)(commits, startDate, endDate);
26
- const commitQuality = (0, commitQualityAnalyzer_js_1.analyzeCommitQuality)(commits);
27
- const fileHotspots = (0, fileHotspotAnalyzer_js_1.analyzeFileHotspots)(repoPath, startDate, endDate);
28
- // Create AnalysisData for achievement checking
29
- const analysisData = {
30
- commits,
31
- totalCommits: commits.length,
32
- streakData,
33
- timePattern,
34
- commitQuality,
35
- fileHotspots,
36
- dateRange: {
37
- start: startDate,
38
- end: endDate
39
- }
40
- };
41
- const achievementProgress = (0, achievementEngine_js_1.checkAchievements)(analysisData);
42
- // Calculate overview
43
- const overview = {
44
- totalCommits: commits.length,
45
- activeDays: streakData.totalActiveDays,
46
- totalDays: streakData.totalDays,
47
- activePercentage: streakData.activeDayPercentage,
48
- avgCommitsPerDay: commits.length / streakData.totalActiveDays
49
- };
50
- // Calculate impact (lines added/removed)
51
- const impact = calculateImpact(commits, repoPath, year);
52
- // Extract timing insights
53
- const timing = {
54
- chronotype: mapChronotype(timePattern.chronotype),
55
- peakHour: timePattern.peakHour.hour,
56
- peakDay: timePattern.peakDay.dayOfWeek,
57
- mostProductiveMonth: findMostProductiveMonth(commits),
58
- mostProductiveWeek: findMostProductiveWeek(commits)
59
- };
60
- // Extract streaks
61
- const streaks = {
62
- longest: {
63
- days: streakData.longestStreak.days,
64
- start: streakData.longestStreak.startDate,
65
- end: streakData.longestStreak.endDate
66
- },
67
- current: {
68
- days: streakData.currentStreak.days,
69
- isActive: streakData.currentStreak.isActive
70
- },
71
- totalStreaks: streakData.streaks.length
72
- };
73
- // Extract quality metrics
74
- const quality = {
75
- overallScore: commitQuality.overallScore,
76
- conventionalPercentage: commitQuality.conventionalCommits.adherence,
77
- avgMessageLength: commitQuality.subjectQuality.avgLength,
78
- withBody: commitQuality.bodyQuality.withBody
79
- };
80
- // Work distribution
81
- const workDistribution = {
82
- features: commitQuality.typeDistribution.features,
83
- fixes: commitQuality.typeDistribution.fixes,
84
- docs: commitQuality.typeDistribution.docs,
85
- refactors: commitQuality.typeDistribution.refactors,
86
- tests: commitQuality.typeDistribution.tests,
87
- other: commitQuality.typeDistribution.chores + commitQuality.typeDistribution.other
88
- };
89
- // Collaboration
90
- const collaboration = analyzeCollaboration(commits, repoPath);
91
- // Achievements
92
- const achievements = {
93
- earned: achievementProgress.achievements
94
- .filter(a => a.isUnlocked)
95
- .map(a => a.name),
96
- total: achievementProgress.unlockedCount
97
- };
98
- // Fun facts
99
- const funFacts = findFunFacts(commits, timePattern);
100
- // Get developer name
101
- const developer = getDeveloperName(repoPath);
102
- // Compare with previous year if requested
103
- let comparison;
104
- if (includeComparison) {
105
- try {
106
- comparison = compareWithPreviousYear(year, repoPath);
107
- }
108
- catch (error) {
109
- // Previous year data not available
110
- comparison = undefined;
111
- }
112
- }
113
- return {
114
- year,
115
- developer,
116
- overview,
117
- impact,
118
- timing,
119
- streaks,
120
- quality,
121
- workDistribution,
122
- collaboration,
123
- achievements,
124
- funFacts,
125
- comparison
126
- };
127
- }
128
- exports.generateYearWrapped = generateYearWrapped;
129
- function calculateImpact(commits, repoPath, year) {
130
- try {
131
- // Get git log with numstat for the year
132
- const output = (0, child_process_1.execSync)(`git log --since="${year}-01-01" --until="${year}-12-31" --numstat --format="%H" --no-merges`, { cwd: repoPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
133
- let linesAdded = 0;
134
- let linesRemoved = 0;
135
- const filesChanged = new Set();
136
- const fileChangeCount = {};
137
- const lines = output.trim().split('\n');
138
- for (const line of lines) {
139
- if (line.match(/^[0-9a-f]{40}$/))
140
- continue; // Skip commit hashes
141
- if (line.trim() === '')
142
- continue;
143
- const parts = line.split('\t');
144
- if (parts.length !== 3)
145
- continue;
146
- const [added, removed, file] = parts;
147
- // Skip binary files
148
- if (added === '-' || removed === '-')
149
- continue;
150
- linesAdded += parseInt(added, 10) || 0;
151
- linesRemoved += parseInt(removed, 10) || 0;
152
- filesChanged.add(file);
153
- fileChangeCount[file] = (fileChangeCount[file] || 0) + 1;
154
- }
155
- // Get top 3 most changed files
156
- const mostChangedFiles = Object.entries(fileChangeCount)
157
- .sort(([, a], [, b]) => b - a)
158
- .slice(0, 3)
159
- .map(([file]) => file);
160
- return {
161
- linesAdded,
162
- linesRemoved,
163
- netLines: linesAdded - linesRemoved,
164
- filesChanged: filesChanged.size,
165
- mostChangedFiles
166
- };
167
- }
168
- catch (error) {
169
- return {
170
- linesAdded: 0,
171
- linesRemoved: 0,
172
- netLines: 0,
173
- filesChanged: 0,
174
- mostChangedFiles: []
175
- };
176
- }
177
- }
178
- function mapChronotype(type) {
179
- const mapping = {
180
- 'early-bird': 'morning',
181
- 'balanced': 'day',
182
- 'night-owl': 'evening',
183
- 'vampire': 'night'
184
- };
185
- return mapping[type] || 'day';
186
- }
187
- function findMostProductiveMonth(commits) {
188
- const monthCounts = {};
189
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
190
- commits.forEach(commit => {
191
- const date = new Date(commit.date);
192
- const monthKey = months[date.getMonth()];
193
- monthCounts[monthKey] = (monthCounts[monthKey] || 0) + 1;
194
- });
195
- const topMonth = Object.entries(monthCounts)
196
- .sort(([, a], [, b]) => b - a)[0];
197
- return topMonth ? topMonth[0] : 'Jan';
198
- }
199
- function findMostProductiveWeek(commits) {
200
- if (commits.length === 0)
201
- return new Date();
202
- // Group commits by week
203
- const weekCounts = {};
204
- commits.forEach(commit => {
205
- const date = new Date(commit.date);
206
- const weekStart = getWeekStart(date);
207
- const key = weekStart.toISOString().split('T')[0];
208
- if (!weekCounts[key]) {
209
- weekCounts[key] = { count: 0, date: weekStart };
210
- }
211
- weekCounts[key].count++;
212
- });
213
- const topWeek = Object.values(weekCounts)
214
- .sort((a, b) => b.count - a.count)[0];
215
- return topWeek ? topWeek.date : new Date(commits[0].date);
216
- }
217
- function getWeekStart(date) {
218
- const d = new Date(date);
219
- const day = d.getDay();
220
- const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Adjust for Monday start
221
- return new Date(d.setDate(diff));
222
- }
223
- function analyzeCollaboration(commits, repoPath) {
224
- const authors = new Set();
225
- const authorDays = {};
226
- commits.forEach(commit => {
227
- authors.add(commit.author);
228
- if (!authorDays[commit.author]) {
229
- authorDays[commit.author] = new Set();
230
- }
231
- authorDays[commit.author].add(commit.date);
232
- });
233
- const currentUser = getDeveloperName(repoPath);
234
- const otherAuthors = Array.from(authors).filter(a => a !== currentUser);
235
- // Find top collaborator (most shared commit days)
236
- let topCollaborator = null;
237
- if (otherAuthors.length > 0 && authorDays[currentUser]) {
238
- const userDays = authorDays[currentUser];
239
- otherAuthors.forEach(author => {
240
- const sharedDays = Array.from(authorDays[author] || [])
241
- .filter(day => userDays.has(day)).length;
242
- if (!topCollaborator || sharedDays > topCollaborator.sharedDays) {
243
- topCollaborator = { name: author, sharedDays };
244
- }
245
- });
246
- }
247
- // Calculate solo percentage
248
- const totalDays = authorDays[currentUser]?.size || 0;
249
- const soloDays = Array.from(authorDays[currentUser] || []).filter(day => {
250
- return otherAuthors.every(author => !authorDays[author]?.has(day));
251
- }).length;
252
- const soloPercentage = totalDays > 0 ? (soloDays / totalDays) * 100 : 100;
253
- return {
254
- uniqueContributors: otherAuthors.length,
255
- topCollaborator,
256
- soloPercentage
257
- };
258
- }
259
- function findFunFacts(commits, timePattern) {
260
- const holidayCommits = [];
261
- let midnightCommits = 0;
262
- let weekendCommits = 0;
263
- const dayCommitCounts = {};
264
- commits.forEach(commit => {
265
- const date = new Date(commit.date);
266
- const dayKey = commit.date;
267
- dayCommitCounts[dayKey] = (dayCommitCounts[dayKey] || 0) + 1;
268
- // Check for holidays
269
- const month = date.getMonth() + 1;
270
- const day = date.getDate();
271
- if (month === 12 && day === 25)
272
- holidayCommits.push('Christmas');
273
- if (month === 1 && day === 1)
274
- holidayCommits.push('New Year');
275
- if (month === 7 && day === 4)
276
- holidayCommits.push('Independence Day');
277
- if (month === 10 && day === 31)
278
- holidayCommits.push('Halloween');
279
- // Weekend commits
280
- const dayOfWeek = date.getDay();
281
- if (dayOfWeek === 0 || dayOfWeek === 6) {
282
- weekendCommits++;
283
- }
284
- });
285
- // Count midnight commits from time pattern
286
- if (timePattern.hourly) {
287
- const midnight = timePattern.hourly.find((h) => h.hour === 0);
288
- midnightCommits = midnight ? midnight.commitCount : 0;
289
- }
290
- // Find busiest day
291
- const busiestDayEntry = Object.entries(dayCommitCounts)
292
- .sort(([, a], [, b]) => b - a)[0];
293
- const busiestDay = busiestDayEntry
294
- ? { date: new Date(busiestDayEntry[0]), commits: busiestDayEntry[1] }
295
- : null;
296
- // Longest session from timePattern
297
- const longestSession = timePattern.timingStats?.longestSession || null;
298
- return {
299
- holidayCommits: Array.from(new Set(holidayCommits)),
300
- midnightCommits,
301
- weekendCommits,
302
- longestSession,
303
- busiestDay
304
- };
305
- }
306
- function getDeveloperName(repoPath) {
307
- try {
308
- const name = (0, child_process_1.execSync)('git config user.name', {
309
- cwd: repoPath,
310
- encoding: 'utf-8'
311
- }).trim();
312
- return name || 'Developer';
313
- }
314
- catch {
315
- return 'Developer';
316
- }
317
- }
318
- function compareWithPreviousYear(year, repoPath) {
319
- // Generate wrapped for current and previous year
320
- const currentYearData = generateYearWrapped(year, repoPath, false);
321
- const previousYearData = generateYearWrapped(year - 1, repoPath, false);
322
- // Extract stats
323
- const currentStats = {
324
- commits: currentYearData.overview.totalCommits,
325
- streak: currentYearData.streaks.longest.days,
326
- quality: currentYearData.quality.overallScore,
327
- collaboration: currentYearData.collaboration.uniqueContributors
328
- };
329
- const previousStats = {
330
- commits: previousYearData.overview.totalCommits,
331
- streak: previousYearData.streaks.longest.days,
332
- quality: previousYearData.quality.overallScore,
333
- collaboration: previousYearData.collaboration.uniqueContributors
334
- };
335
- // Calculate changes
336
- const calculateChange = (current, previous) => {
337
- const value = current - previous;
338
- const percentage = previous > 0 ? (value / previous) * 100 : 0;
339
- let trend = 'stable';
340
- if (Math.abs(percentage) >= 5) {
341
- trend = value > 0 ? 'up' : 'down';
342
- }
343
- return { value, percentage, trend };
344
- };
345
- const changes = {
346
- commits: calculateChange(currentStats.commits, previousStats.commits),
347
- streak: calculateChange(currentStats.streak, previousStats.streak),
348
- quality: calculateChange(currentStats.quality, previousStats.quality),
349
- collaboration: calculateChange(currentStats.collaboration, previousStats.collaboration)
350
- };
351
- // Generate insights
352
- const insights = [];
353
- if (changes.commits.trend === 'up') {
354
- insights.push(`${Math.abs(changes.commits.percentage).toFixed(0)}% more commits than last year!`);
355
- }
356
- else if (changes.commits.trend === 'down') {
357
- insights.push('Fewer commits, but quality over quantity!');
358
- }
359
- if (changes.quality.trend === 'up') {
360
- insights.push('Commit quality improved significantly! 📈');
361
- }
362
- if (changes.streak.trend === 'up') {
363
- insights.push(`Streak improved by ${changes.streak.value} days!`);
364
- }
365
- if (changes.collaboration.trend === 'up') {
366
- insights.push('Expanded collaboration network! 🤝');
367
- }
368
- if (insights.length === 0) {
369
- insights.push('Consistent performance year over year!');
370
- }
371
- return {
372
- currentYear: currentStats,
373
- previousYear: previousStats,
374
- changes,
375
- insights
376
- };
377
- }
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateYearWrapped = generateYearWrapped;
4
+ const child_process_1 = require("child_process");
5
+ const achievementEngine_js_1 = require("./achievementEngine.js");
6
+ const commitQualityAnalyzer_js_1 = require("./commitQualityAnalyzer.js");
7
+ const fileHotspotAnalyzer_js_1 = require("./fileHotspotAnalyzer.js");
8
+ const gitParser_js_1 = require("./gitParser.js");
9
+ const streakCalculator_js_1 = require("./streakCalculator.js");
10
+ const timePatternAnalyzer_js_1 = require("./timePatternAnalyzer.js");
11
+ function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
12
+ // Define year boundaries
13
+ const startDate = new Date(`${year}-01-01`);
14
+ const endDate = new Date(`${year}-12-31`);
15
+ // Fetch all commits for the year
16
+ const commits = (0, gitParser_js_1.parseGitCommits)(repoPath).filter(commit => {
17
+ const commitDate = new Date(commit.date);
18
+ return commitDate >= startDate && commitDate <= endDate;
19
+ });
20
+ if (commits.length === 0) {
21
+ throw new Error(`No commits found for year ${year}`);
22
+ }
23
+ // Calculate all metrics using existing utilities
24
+ const streakData = (0, streakCalculator_js_1.calculateStreaks)(commits, startDate, endDate);
25
+ const timePattern = (0, timePatternAnalyzer_js_1.analyzeTimePatterns)(commits, startDate, endDate);
26
+ const commitQuality = (0, commitQualityAnalyzer_js_1.analyzeCommitQuality)(commits);
27
+ const fileHotspots = (0, fileHotspotAnalyzer_js_1.analyzeFileHotspots)(repoPath, startDate, endDate);
28
+ // Create AnalysisData for achievement checking
29
+ const analysisData = {
30
+ commits,
31
+ totalCommits: commits.length,
32
+ streakData,
33
+ timePattern,
34
+ commitQuality,
35
+ fileHotspots,
36
+ dateRange: {
37
+ start: startDate,
38
+ end: endDate
39
+ }
40
+ };
41
+ const achievementProgress = (0, achievementEngine_js_1.checkAchievements)(analysisData);
42
+ // Calculate overview
43
+ const overview = {
44
+ totalCommits: commits.length,
45
+ activeDays: streakData.totalActiveDays,
46
+ totalDays: streakData.totalDays,
47
+ activePercentage: streakData.activeDayPercentage,
48
+ avgCommitsPerDay: commits.length / streakData.totalActiveDays
49
+ };
50
+ // Calculate impact (lines added/removed)
51
+ const impact = calculateImpact(commits, repoPath, year);
52
+ // Extract timing insights
53
+ const timing = {
54
+ chronotype: mapChronotype(timePattern.chronotype),
55
+ peakHour: timePattern.peakHour.hour,
56
+ peakDay: timePattern.peakDay.dayOfWeek,
57
+ mostProductiveMonth: findMostProductiveMonth(commits),
58
+ mostProductiveWeek: findMostProductiveWeek(commits)
59
+ };
60
+ // Extract streaks
61
+ const streaks = {
62
+ longest: {
63
+ days: streakData.longestStreak.days,
64
+ start: streakData.longestStreak.startDate,
65
+ end: streakData.longestStreak.endDate
66
+ },
67
+ current: {
68
+ days: streakData.currentStreak.days,
69
+ isActive: streakData.currentStreak.isActive
70
+ },
71
+ totalStreaks: streakData.streaks.length
72
+ };
73
+ // Extract quality metrics
74
+ const quality = {
75
+ overallScore: commitQuality.overallScore,
76
+ conventionalPercentage: commitQuality.conventionalCommits.adherence,
77
+ avgMessageLength: commitQuality.subjectQuality.avgLength,
78
+ withBody: commitQuality.bodyQuality.withBody
79
+ };
80
+ // Work distribution
81
+ const workDistribution = {
82
+ features: commitQuality.typeDistribution.features,
83
+ fixes: commitQuality.typeDistribution.fixes,
84
+ docs: commitQuality.typeDistribution.docs,
85
+ refactors: commitQuality.typeDistribution.refactors,
86
+ tests: commitQuality.typeDistribution.tests,
87
+ other: commitQuality.typeDistribution.chores + commitQuality.typeDistribution.other
88
+ };
89
+ // Collaboration
90
+ const collaboration = analyzeCollaboration(commits, repoPath);
91
+ // Achievements
92
+ const achievements = {
93
+ earned: achievementProgress.achievements
94
+ .filter(a => a.isUnlocked)
95
+ .map(a => a.name),
96
+ total: achievementProgress.unlockedCount
97
+ };
98
+ // Fun facts
99
+ const funFacts = findFunFacts(commits, timePattern);
100
+ // Get developer name
101
+ const developer = getDeveloperName(repoPath);
102
+ // Compare with previous year if requested
103
+ let comparison;
104
+ if (includeComparison) {
105
+ try {
106
+ comparison = compareWithPreviousYear(year, repoPath);
107
+ }
108
+ catch (error) {
109
+ // Previous year data not available
110
+ comparison = undefined;
111
+ }
112
+ }
113
+ return {
114
+ year,
115
+ developer,
116
+ overview,
117
+ impact,
118
+ timing,
119
+ streaks,
120
+ quality,
121
+ workDistribution,
122
+ collaboration,
123
+ achievements,
124
+ funFacts,
125
+ comparison
126
+ };
127
+ }
128
+ function calculateImpact(commits, repoPath, year) {
129
+ try {
130
+ // Get git log with numstat for the year
131
+ const output = (0, child_process_1.execSync)(`git log --since="${year}-01-01" --until="${year}-12-31" --numstat --format="%H" --no-merges`, { cwd: repoPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
132
+ let linesAdded = 0;
133
+ let linesRemoved = 0;
134
+ const filesChanged = new Set();
135
+ const fileChangeCount = {};
136
+ const lines = output.trim().split('\n');
137
+ for (const line of lines) {
138
+ if (line.match(/^[0-9a-f]{40}$/))
139
+ continue; // Skip commit hashes
140
+ if (line.trim() === '')
141
+ continue;
142
+ const parts = line.split('\t');
143
+ if (parts.length !== 3)
144
+ continue;
145
+ const [added, removed, file] = parts;
146
+ // Skip binary files
147
+ if (added === '-' || removed === '-')
148
+ continue;
149
+ linesAdded += parseInt(added, 10) || 0;
150
+ linesRemoved += parseInt(removed, 10) || 0;
151
+ filesChanged.add(file);
152
+ fileChangeCount[file] = (fileChangeCount[file] || 0) + 1;
153
+ }
154
+ // Get top 3 most changed files
155
+ const mostChangedFiles = Object.entries(fileChangeCount)
156
+ .sort(([, a], [, b]) => b - a)
157
+ .slice(0, 3)
158
+ .map(([file]) => file);
159
+ return {
160
+ linesAdded,
161
+ linesRemoved,
162
+ netLines: linesAdded - linesRemoved,
163
+ filesChanged: filesChanged.size,
164
+ mostChangedFiles
165
+ };
166
+ }
167
+ catch (error) {
168
+ return {
169
+ linesAdded: 0,
170
+ linesRemoved: 0,
171
+ netLines: 0,
172
+ filesChanged: 0,
173
+ mostChangedFiles: []
174
+ };
175
+ }
176
+ }
177
+ function mapChronotype(type) {
178
+ const mapping = {
179
+ 'early-bird': 'morning',
180
+ 'balanced': 'day',
181
+ 'night-owl': 'evening',
182
+ 'vampire': 'night'
183
+ };
184
+ return mapping[type] || 'day';
185
+ }
186
+ function findMostProductiveMonth(commits) {
187
+ const monthCounts = {};
188
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
189
+ commits.forEach(commit => {
190
+ const date = new Date(commit.date);
191
+ const monthKey = months[date.getMonth()];
192
+ monthCounts[monthKey] = (monthCounts[monthKey] || 0) + 1;
193
+ });
194
+ const topMonth = Object.entries(monthCounts)
195
+ .sort(([, a], [, b]) => b - a)[0];
196
+ return topMonth ? topMonth[0] : 'Jan';
197
+ }
198
+ function findMostProductiveWeek(commits) {
199
+ if (commits.length === 0)
200
+ return new Date();
201
+ // Group commits by week
202
+ const weekCounts = {};
203
+ commits.forEach(commit => {
204
+ const date = new Date(commit.date);
205
+ const weekStart = getWeekStart(date);
206
+ const key = weekStart.toISOString().split('T')[0];
207
+ if (!weekCounts[key]) {
208
+ weekCounts[key] = { count: 0, date: weekStart };
209
+ }
210
+ weekCounts[key].count++;
211
+ });
212
+ const topWeek = Object.values(weekCounts)
213
+ .sort((a, b) => b.count - a.count)[0];
214
+ return topWeek ? topWeek.date : new Date(commits[0].date);
215
+ }
216
+ function getWeekStart(date) {
217
+ const d = new Date(date);
218
+ const day = d.getDay();
219
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Adjust for Monday start
220
+ return new Date(d.setDate(diff));
221
+ }
222
+ function analyzeCollaboration(commits, repoPath) {
223
+ const authors = new Set();
224
+ const authorDays = {};
225
+ commits.forEach(commit => {
226
+ authors.add(commit.author);
227
+ if (!authorDays[commit.author]) {
228
+ authorDays[commit.author] = new Set();
229
+ }
230
+ authorDays[commit.author].add(commit.date);
231
+ });
232
+ const currentUser = getDeveloperName(repoPath);
233
+ const otherAuthors = Array.from(authors).filter(a => a !== currentUser);
234
+ // Find top collaborator (most shared commit days)
235
+ let topCollaborator = null;
236
+ if (otherAuthors.length > 0 && authorDays[currentUser]) {
237
+ const userDays = authorDays[currentUser];
238
+ otherAuthors.forEach(author => {
239
+ const sharedDays = Array.from(authorDays[author] || [])
240
+ .filter(day => userDays.has(day)).length;
241
+ if (!topCollaborator || sharedDays > topCollaborator.sharedDays) {
242
+ topCollaborator = { name: author, sharedDays };
243
+ }
244
+ });
245
+ }
246
+ // Calculate solo percentage
247
+ const totalDays = authorDays[currentUser]?.size || 0;
248
+ const soloDays = Array.from(authorDays[currentUser] || []).filter(day => {
249
+ return otherAuthors.every(author => !authorDays[author]?.has(day));
250
+ }).length;
251
+ const soloPercentage = totalDays > 0 ? (soloDays / totalDays) * 100 : 100;
252
+ return {
253
+ uniqueContributors: otherAuthors.length,
254
+ topCollaborator,
255
+ soloPercentage
256
+ };
257
+ }
258
+ function findFunFacts(commits, timePattern) {
259
+ const holidayCommits = [];
260
+ let midnightCommits = 0;
261
+ let weekendCommits = 0;
262
+ const dayCommitCounts = {};
263
+ commits.forEach(commit => {
264
+ const date = new Date(commit.date);
265
+ const dayKey = commit.date;
266
+ dayCommitCounts[dayKey] = (dayCommitCounts[dayKey] || 0) + 1;
267
+ // Check for holidays
268
+ const month = date.getMonth() + 1;
269
+ const day = date.getDate();
270
+ if (month === 12 && day === 25)
271
+ holidayCommits.push('Christmas');
272
+ if (month === 1 && day === 1)
273
+ holidayCommits.push('New Year');
274
+ if (month === 7 && day === 4)
275
+ holidayCommits.push('Independence Day');
276
+ if (month === 10 && day === 31)
277
+ holidayCommits.push('Halloween');
278
+ // Weekend commits
279
+ const dayOfWeek = date.getDay();
280
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
281
+ weekendCommits++;
282
+ }
283
+ });
284
+ // Count midnight commits from time pattern
285
+ if (timePattern.hourly) {
286
+ const midnight = timePattern.hourly.find((h) => h.hour === 0);
287
+ midnightCommits = midnight ? midnight.commitCount : 0;
288
+ }
289
+ // Find busiest day
290
+ const busiestDayEntry = Object.entries(dayCommitCounts)
291
+ .sort(([, a], [, b]) => b - a)[0];
292
+ const busiestDay = busiestDayEntry
293
+ ? { date: new Date(busiestDayEntry[0]), commits: busiestDayEntry[1] }
294
+ : null;
295
+ // Longest session from timePattern
296
+ const longestSession = timePattern.timingStats?.longestSession || null;
297
+ return {
298
+ holidayCommits: Array.from(new Set(holidayCommits)),
299
+ midnightCommits,
300
+ weekendCommits,
301
+ longestSession,
302
+ busiestDay
303
+ };
304
+ }
305
+ function getDeveloperName(repoPath) {
306
+ try {
307
+ const name = (0, child_process_1.execSync)('git config user.name', {
308
+ cwd: repoPath,
309
+ encoding: 'utf-8'
310
+ }).trim();
311
+ return name || 'Developer';
312
+ }
313
+ catch {
314
+ return 'Developer';
315
+ }
316
+ }
317
+ function compareWithPreviousYear(year, repoPath) {
318
+ // Generate wrapped for current and previous year
319
+ const currentYearData = generateYearWrapped(year, repoPath, false);
320
+ const previousYearData = generateYearWrapped(year - 1, repoPath, false);
321
+ // Extract stats
322
+ const currentStats = {
323
+ commits: currentYearData.overview.totalCommits,
324
+ streak: currentYearData.streaks.longest.days,
325
+ quality: currentYearData.quality.overallScore,
326
+ collaboration: currentYearData.collaboration.uniqueContributors
327
+ };
328
+ const previousStats = {
329
+ commits: previousYearData.overview.totalCommits,
330
+ streak: previousYearData.streaks.longest.days,
331
+ quality: previousYearData.quality.overallScore,
332
+ collaboration: previousYearData.collaboration.uniqueContributors
333
+ };
334
+ // Calculate changes
335
+ const calculateChange = (current, previous) => {
336
+ const value = current - previous;
337
+ const percentage = previous > 0 ? (value / previous) * 100 : 0;
338
+ let trend = 'stable';
339
+ if (Math.abs(percentage) >= 5) {
340
+ trend = value > 0 ? 'up' : 'down';
341
+ }
342
+ return { value, percentage, trend };
343
+ };
344
+ const changes = {
345
+ commits: calculateChange(currentStats.commits, previousStats.commits),
346
+ streak: calculateChange(currentStats.streak, previousStats.streak),
347
+ quality: calculateChange(currentStats.quality, previousStats.quality),
348
+ collaboration: calculateChange(currentStats.collaboration, previousStats.collaboration)
349
+ };
350
+ // Generate insights
351
+ const insights = [];
352
+ if (changes.commits.trend === 'up') {
353
+ insights.push(`${Math.abs(changes.commits.percentage).toFixed(0)}% more commits than last year!`);
354
+ }
355
+ else if (changes.commits.trend === 'down') {
356
+ insights.push('Fewer commits, but quality over quantity!');
357
+ }
358
+ if (changes.quality.trend === 'up') {
359
+ insights.push('Commit quality improved significantly! 📈');
360
+ }
361
+ if (changes.streak.trend === 'up') {
362
+ insights.push(`Streak improved by ${changes.streak.value} days!`);
363
+ }
364
+ if (changes.collaboration.trend === 'up') {
365
+ insights.push('Expanded collaboration network! 🤝');
366
+ }
367
+ if (insights.length === 0) {
368
+ insights.push('Consistent performance year over year!');
369
+ }
370
+ return {
371
+ currentYear: currentStats,
372
+ previousYear: previousStats,
373
+ changes,
374
+ insights
375
+ };
376
+ }