repo-wrapped 0.0.2 โ†’ 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 +99 -77
  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 -125
  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,350 +1,349 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.generateCommitMatrix = void 0;
7
- const chalk_1 = __importDefault(require("chalk"));
8
- const date_fns_1 = require("date-fns");
9
- const constants_1 = require("../constants");
10
- const achievementEngine_1 = require("./achievementEngine");
11
- const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
12
- const fileHotspotAnalyzer_1 = require("./fileHotspotAnalyzer");
13
- const streakCalculator_1 = require("./streakCalculator");
14
- const timePatternAnalyzer_1 = require("./timePatternAnalyzer");
15
- function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, repoUrl) {
16
- // Get today or end of year if it's a past year
17
- const now = new Date();
18
- const isCurrentYear = year === now.getFullYear();
19
- // Calculate the end date (today if current year, or end of December for past years)
20
- const endDate = isCurrentYear
21
- ? now
22
- : new Date(year, 11, 31);
23
- // Calculate start date (N months back from end date)
24
- const startDate = (0, date_fns_1.startOfMonth)((0, date_fns_1.subMonths)(endDate, monthsToShow - 1));
25
- // Get the full week range (start on Sunday, end on Saturday)
26
- const weekStartDate = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 0 });
27
- const weekEndDate = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 0 });
28
- // Create a map of date string -> commit count
29
- const commitMap = new Map();
30
- commits.forEach(commit => {
31
- const commitDate = new Date(commit.date);
32
- if (commitDate >= startDate && commitDate <= endDate) {
33
- const dateKey = (0, date_fns_1.format)(commitDate, 'yyyy-MM-dd');
34
- commitMap.set(dateKey, (commitMap.get(dateKey) || 0) + 1);
35
- }
36
- });
37
- const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
38
- // Calculate streaks
39
- const streakData = (0, streakCalculator_1.calculateStreaks)(commits, startDate, endDate);
40
- // Analyze time patterns
41
- const timePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(commits, startDate, endDate);
42
- // Analyze commit quality
43
- const commitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits);
44
- // Analyze file hotspots
45
- const fileHotspots = (0, fileHotspotAnalyzer_1.analyzeFileHotspots)(repoPath, startDate, endDate);
46
- // Prepare analysis data for achievements
47
- const analysisData = {
48
- commits,
49
- totalCommits,
50
- streakData,
51
- timePattern,
52
- commitQuality,
53
- fileHotspots,
54
- dateRange: { start: startDate, end: endDate }
55
- };
56
- // Check achievements
57
- const achievementProgress = (0, achievementEngine_1.checkAchievements)(analysisData);
58
- return formatGitHubStyle(commitMap, weekStartDate, weekEndDate, startDate, endDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl);
59
- }
60
- exports.generateCommitMatrix = generateCommitMatrix;
61
- function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate, dataEndDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl) {
62
- const days = (0, date_fns_1.eachDayOfInterval)({ start: weekStartDate, end: weekEndDate });
63
- const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
64
- // Group days into weeks
65
- const weeks = [];
66
- let currentWeek = [];
67
- days.forEach(day => {
68
- currentWeek.push(day);
69
- if (currentWeek.length === 7) {
70
- weeks.push(currentWeek);
71
- currentWeek = [];
72
- }
73
- });
74
- // Build the output with header
75
- let output = '\n';
76
- // Add repository info if available
77
- if (repoName) {
78
- output += chalk_1.default.bold.cyan(` ${repoName}\n`);
79
- if (repoUrl) {
80
- output += chalk_1.default.dim(` ${repoUrl}\n`);
81
- }
82
- output += '\n';
83
- }
84
- // Add stats
85
- output += chalk_1.default.dim(' ') + chalk_1.default.white(`${totalCommits} contributions in the last ${(0, date_fns_1.format)(dataStartDate, 'MMM yyyy')} - ${(0, date_fns_1.format)(dataEndDate, 'MMM yyyy')}\n\n`);
86
- // Build month headers
87
- let monthHeader = ' ';
88
- let currentMonth = '';
89
- weeks.forEach((week, weekIndex) => {
90
- const firstDayOfWeek = week[0];
91
- const monthName = (0, date_fns_1.format)(firstDayOfWeek, 'MMM');
92
- if (monthName !== currentMonth) {
93
- monthHeader += chalk_1.default.bold(monthName.padEnd(4));
94
- currentMonth = monthName;
95
- }
96
- else {
97
- monthHeader += ' ';
98
- }
99
- });
100
- output += monthHeader + '\n';
101
- // Day rows with better formatting
102
- for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
103
- // Only show Mon, Wed, Fri labels
104
- const label = [1, 3, 5].includes(dayOfWeek)
105
- ? chalk_1.default.dim(dayLabels[dayOfWeek].padEnd(5))
106
- : ' ';
107
- let row = label + ' ';
108
- weeks.forEach(week => {
109
- const day = week[dayOfWeek];
110
- if (day) {
111
- const dateKey = (0, date_fns_1.format)(day, 'yyyy-MM-dd');
112
- const count = commitMap.get(dateKey) || 0;
113
- const isInRange = day >= dataStartDate && day <= dataEndDate;
114
- if (isInRange) {
115
- row += formatCell(count) + ' ';
116
- }
117
- else {
118
- row += chalk_1.default.dim('โ– ') + ' ';
119
- }
120
- }
121
- else {
122
- row += ' ';
123
- }
124
- });
125
- output += row + '\n';
126
- }
127
- // Add legend with better spacing
128
- output += '\n';
129
- output += chalk_1.default.dim(' Learn how we count contributions\n');
130
- output += chalk_1.default.dim(' Less ') +
131
- formatCell(0) + ' ' +
132
- formatCell(1) + ' ' +
133
- formatCell(4) + ' ' +
134
- formatCell(7) + ' ' +
135
- formatCell(10) + ' ' +
136
- chalk_1.default.dim('More\n');
137
- // Streak & Momentum Section
138
- output += '\n';
139
- output += chalk_1.default.bold.cyan('๐Ÿ“Š Streak & Momentum\n');
140
- output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
141
- const currentStreakEmoji = streakData.currentStreak.isActive ? '๐Ÿ”ฅ' : '๐Ÿ’ค';
142
- const currentStreakText = streakData.currentStreak.days > 0
143
- ? `${streakData.currentStreak.days} day${streakData.currentStreak.days > 1 ? 's' : ''}`
144
- : 'No active streak';
145
- output += `${currentStreakEmoji} Current Streak: ${chalk_1.default.bold.green(currentStreakText)}`;
146
- if (streakData.currentStreak.isActive && streakData.currentStreak.days > 0) {
147
- output += chalk_1.default.dim(` - ${(0, streakCalculator_1.getStreakMotivation)(streakData.currentStreak.days, true)}`);
148
- }
149
- output += '\n';
150
- const longestStreakText = streakData.longestStreak.days > 0
151
- ? `${streakData.longestStreak.days} day${streakData.longestStreak.days > 1 ? 's' : ''} (${(0, date_fns_1.format)(streakData.longestStreak.startDate, 'MMM d')} - ${(0, date_fns_1.format)(streakData.longestStreak.endDate, 'MMM d, yyyy')})`
152
- : 'No streaks yet';
153
- output += `๐Ÿ† Longest Streak: ${chalk_1.default.bold.yellow(longestStreakText)}\n`;
154
- const activePercentage = streakData.activeDayPercentage.toFixed(1);
155
- output += `๐Ÿ“ˆ Active Days: ${chalk_1.default.bold.cyan(streakData.totalActiveDays)}/${streakData.totalDays} (${activePercentage}%)\n`;
156
- // Time & Productivity Patterns Section
157
- output += '\n';
158
- output += chalk_1.default.bold.cyan('โฐ Productivity Patterns\n');
159
- output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
160
- // Peak hour
161
- const peakHourLabel = timePattern.peakHour.hour === 0 ? '12:00 AM' :
162
- timePattern.peakHour.hour < 12 ? `${timePattern.peakHour.hour}:00 AM` :
163
- timePattern.peakHour.hour === 12 ? '12:00 PM' :
164
- `${timePattern.peakHour.hour - 12}:00 PM`;
165
- output += `โฑ๏ธ Peak Hour: ${chalk_1.default.bold.green(peakHourLabel)} (${timePattern.peakHour.commitCount} commits)\n`;
166
- // Chronotype
167
- const chronotypeLabel = (0, timePatternAnalyzer_1.getChronotypeLabel)(timePattern.chronotype);
168
- const chronotypeDesc = (0, timePatternAnalyzer_1.getChronotypeDescription)(timePattern.chronotype);
169
- output += `${chronotypeLabel} ${chalk_1.default.dim(`(${timePattern.chronotypeConfidence.toFixed(0)}% confidence)`)}\n`;
170
- if (chronotypeDesc) {
171
- output += chalk_1.default.dim(` ${chronotypeDesc}\n`);
172
- }
173
- // Work-life balance
174
- const stars = 'โญ'.repeat(timePattern.workLifeBalance.score);
175
- output += `โš–๏ธ Work-Life Balance: ${stars} ${chalk_1.default.dim(`(${timePattern.workLifeBalance.score}/5)`)}\n`;
176
- output += chalk_1.default.dim(` Weekday: ${timePattern.workLifeBalance.weekdayCommits} | Weekend: ${timePattern.workLifeBalance.weekendCommits}\n`);
177
- // Consistency
178
- output += `๐Ÿ“Š Consistency: ${chalk_1.default.bold.yellow(timePattern.consistency.score.toFixed(1))}/10 ${chalk_1.default.dim(`(${timePattern.consistency.regularity})`)}\n`;
179
- // Burnout warnings
180
- if (timePattern.burnoutRisk.level !== 'low') {
181
- output += `\n${chalk_1.default.yellow('โš ๏ธ Burnout Risk: ' + timePattern.burnoutRisk.level.toUpperCase())}\n`;
182
- timePattern.burnoutRisk.indicators.forEach((indicator) => {
183
- output += chalk_1.default.dim(` โ€ข ${indicator}\n`);
184
- });
185
- }
186
- // Commit Quality Section
187
- output += '\n';
188
- output += chalk_1.default.bold.cyan('๐Ÿ“ Commit Quality\n');
189
- output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
190
- const rating = (0, commitQualityAnalyzer_1.getQualityRating)(commitQuality.overallScore);
191
- const level = (0, commitQualityAnalyzer_1.getQualityLevel)(commitQuality.overallScore);
192
- output += `๐Ÿ“Š Overall Score: ${chalk_1.default.bold.green(commitQuality.overallScore.toFixed(1))}/10 ${rating} ${chalk_1.default.dim(`(${level})`)}\n`;
193
- output += `โœ… Convention: ${chalk_1.default.bold.cyan(commitQuality.conventionalCommits.adherence.toFixed(1))}%`;
194
- if (commitQuality.conventionalCommits.adherence < 70) {
195
- output += chalk_1.default.yellow(' โš ๏ธ');
196
- }
197
- else {
198
- output += chalk_1.default.green(' โœจ');
199
- }
200
- output += '\n';
201
- // Subject quality
202
- output += `๐Ÿ“ Subject Quality: ${chalk_1.default.bold.yellow(commitQuality.subjectQuality.score.toFixed(1))}/10 `;
203
- output += chalk_1.default.dim(`(avg ${commitQuality.subjectQuality.avgLength} chars)\n`);
204
- // Body quality
205
- output += `๐Ÿ“„ Body Quality: ${chalk_1.default.bold.yellow(commitQuality.bodyQuality.score.toFixed(1))}/10 `;
206
- output += chalk_1.default.dim(`(${commitQuality.bodyQuality.withBody}/${commitQuality.totalCommits} with body)\n`);
207
- // Top commit types
208
- if (Object.keys(commitQuality.conventionalCommits.types).length > 0) {
209
- output += `\n${chalk_1.default.dim('Top Commit Types:')}\n`;
210
- const sortedTypes = Object.entries(commitQuality.conventionalCommits.types)
211
- .sort(([, a], [, b]) => b - a)
212
- .slice(0, 3);
213
- sortedTypes.forEach(([type, count]) => {
214
- output += chalk_1.default.dim(` ${type}: ${count}\n`);
215
- });
216
- }
217
- // Improvements
218
- if (commitQuality.improvements && commitQuality.improvements.length > 0) {
219
- output += `\n${chalk_1.default.yellow('๐Ÿ’ก Tips for Improvement:')}\n`;
220
- commitQuality.improvements.slice(0, 2).forEach((tip) => {
221
- output += chalk_1.default.dim(` โ€ข ${tip}\n`);
222
- });
223
- }
224
- // Code hygiene metrics (if any)
225
- const totalHygiene = commitQuality.codeHygiene.quickFixes +
226
- commitQuality.codeHygiene.workInProgress +
227
- commitQuality.codeHygiene.oops +
228
- commitQuality.codeHygiene.vague;
229
- if (totalHygiene > 0) {
230
- output += `\n${chalk_1.default.dim('๐Ÿงน Code Hygiene Indicators:\n')}`;
231
- if (commitQuality.codeHygiene.quickFixes > 0) {
232
- output += chalk_1.default.dim(` Quick fixes (typos/spelling): ${commitQuality.codeHygiene.quickFixes}\n`);
233
- }
234
- if (commitQuality.codeHygiene.workInProgress > 0) {
235
- output += chalk_1.default.dim(` Work in progress: ${commitQuality.codeHygiene.workInProgress}\n`);
236
- }
237
- if (commitQuality.codeHygiene.oops > 0) {
238
- output += chalk_1.default.dim(` Correction commits: ${commitQuality.codeHygiene.oops}\n`);
239
- }
240
- if (commitQuality.codeHygiene.vague > 0) {
241
- output += chalk_1.default.dim(` Vague messages: ${commitQuality.codeHygiene.vague}\n`);
242
- }
243
- }
244
- // File & Code Hotspots Section
245
- output += '\n' + chalk_1.default.bold.hex('#58a6ff')('๐Ÿ”ฅ TOP FILE HOTSPOTS') + '\n';
246
- if (!fileHotspots || fileHotspots.topFiles.length === 0) {
247
- output += chalk_1.default.dim(' No file changes detected in this period.\n');
248
- }
249
- else {
250
- const topFilesToShow = fileHotspots.topFiles.slice(0, 7);
251
- const maxChanges = Math.max(...topFilesToShow.map((f) => f.changeCount));
252
- topFilesToShow.forEach((file) => {
253
- const barLength = Math.max(1, Math.round((file.changeCount / maxChanges) * 30));
254
- const bar = chalk_1.default.hex('#f85149')('โ–ˆ'.repeat(barLength));
255
- const fileName = file.path.length > 40 ? '...' + file.path.slice(-37) : file.path;
256
- output += chalk_1.default.dim(` ${fileName}\n`);
257
- output += ` ${bar} ${chalk_1.default.bold(file.changeCount)} changes, ${file.authors.length} author${file.authors.length !== 1 ? 's' : ''}\n`;
258
- });
259
- }
260
- // Technical Debt Warnings
261
- if (fileHotspots && fileHotspots.technicalDebt.length > 0) {
262
- output += '\n' + chalk_1.default.bold.hex('#f85149')('โš ๏ธ TECHNICAL DEBT WARNINGS') + '\n';
263
- const topDebt = fileHotspots.technicalDebt.slice(0, 5);
264
- topDebt.forEach((debt) => {
265
- const color = (0, fileHotspotAnalyzer_1.getRiskLevelColor)(debt.riskLevel);
266
- const colorFn = color === 'red' ? chalk_1.default.red : color === 'yellow' ? chalk_1.default.yellow : chalk_1.default.gray;
267
- const fileName = debt.file.length > 45 ? '...' + debt.file.slice(-42) : debt.file;
268
- output += colorFn(` ${fileName}\n`);
269
- output += chalk_1.default.dim(` Risk: ${debt.riskLevel.toUpperCase()} (complexity: ${debt.score}/10)\n`);
270
- if (debt.recommendations.length > 0) {
271
- output += chalk_1.default.dim(` โ†’ ${debt.recommendations[0]}\n`);
272
- }
273
- });
274
- }
275
- // Ownership Risks
276
- if (fileHotspots && fileHotspots.ownershipRisks) {
277
- const highRiskOwnership = fileHotspots.ownershipRisks.filter((risk) => risk.busFactorRisk >= 7);
278
- if (highRiskOwnership.length > 0) {
279
- output += '\n' + chalk_1.default.bold.hex('#d29922')('๐ŸšŒ BUS FACTOR WARNINGS') + '\n';
280
- const topRisks = highRiskOwnership.slice(0, 5);
281
- topRisks.forEach((risk) => {
282
- const icon = (0, fileHotspotAnalyzer_1.getOwnershipIcon)(risk.ownershipType);
283
- const fileName = risk.file.length > 45 ? '...' + risk.file.slice(-42) : risk.file;
284
- output += chalk_1.default.yellow(` ${icon} ${fileName}\n`);
285
- const topOwner = risk.owners[0];
286
- output += chalk_1.default.dim(` ${topOwner.author} owns ${topOwner.percentage.toFixed(0)}% (${topOwner.commits} commits)\n`);
287
- });
288
- }
289
- }
290
- // Achievements Section
291
- if (achievementProgress) {
292
- output += '\n';
293
- output += chalk_1.default.bold.hex('#ffd700')('๐Ÿ† ACHIEVEMENTS') + '\n';
294
- output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
295
- // Show newly earned badges
296
- if (achievementProgress.recentlyEarned && achievementProgress.recentlyEarned.length > 0) {
297
- output += '\n' + chalk_1.default.bold.green('โœจ NEW BADGES EARNED! ๐ŸŽ‰') + '\n';
298
- achievementProgress.recentlyEarned.forEach((achievement) => {
299
- output += chalk_1.default.green(` ${achievement.emoji} ${achievement.name} - ${achievement.description}\n`);
300
- });
301
- }
302
- // Show progress
303
- output += `\n${chalk_1.default.cyan('Progress:')} ${chalk_1.default.bold.white(achievementProgress.unlockedCount)}/${achievementProgress.totalCount} badges unlocked `;
304
- output += chalk_1.default.dim(`(${achievementProgress.completionPercentage.toFixed(1)}%)\n`);
305
- // Progress bar
306
- const barLength = 30;
307
- const filledLength = Math.round((achievementProgress.completionPercentage / 100) * barLength);
308
- const bar = 'โ–ˆ'.repeat(filledLength) + 'โ–‘'.repeat(barLength - filledLength);
309
- output += ` ${chalk_1.default.hex('#ffd700')(bar)}\n`;
310
- // Show next milestone
311
- if (achievementProgress.nextMilestones && achievementProgress.nextMilestones.length > 0) {
312
- const next = achievementProgress.nextMilestones[0];
313
- output += `\n${chalk_1.default.yellow('๐ŸŽฏ Next Milestone:')}\n`;
314
- output += ` ${next.emoji} ${chalk_1.default.bold(next.name)} ${chalk_1.default.dim(`(${next.progress.toFixed(0)}%)`)}\n`;
315
- output += chalk_1.default.dim(` ${next.description}\n`);
316
- // Progress bar for next milestone
317
- const nextBarLength = 25;
318
- const nextFilledLength = Math.round((next.progress / 100) * nextBarLength);
319
- const nextBar = 'โ–ˆ'.repeat(nextFilledLength) + 'โ–‘'.repeat(nextBarLength - nextFilledLength);
320
- output += ` ${chalk_1.default.hex('#58a6ff')(nextBar)} ${chalk_1.default.dim((100 - next.progress).toFixed(0) + '% to go!')}\n`;
321
- }
322
- // Show recent unlocks (excluding today's new badges)
323
- const recentUnlocks = achievementProgress.achievements
324
- .filter((a) => a.isUnlocked &&
325
- !achievementProgress.recentlyEarned.some((r) => r.id === a.id) &&
326
- a.earnedDate)
327
- .sort((a, b) => b.earnedDate.getTime() - a.earnedDate.getTime())
328
- .slice(0, 3);
329
- if (recentUnlocks.length > 0) {
330
- output += `\n${chalk_1.default.dim('Recent Unlocks:')}\n`;
331
- recentUnlocks.forEach((achievement) => {
332
- const daysAgo = Math.floor((Date.now() - achievement.earnedDate.getTime()) / (1000 * 60 * 60 * 24));
333
- const timeStr = daysAgo === 0 ? 'today' : daysAgo === 1 ? '1 day ago' : `${daysAgo} days ago`;
334
- output += chalk_1.default.dim(` ${achievement.emoji} ${achievement.name} (${timeStr})\n`);
335
- });
336
- }
337
- }
338
- return output;
339
- }
340
- function formatCell(count) {
341
- if (count === 0)
342
- return chalk_1.default.hex('#ebedf0')('โ– ');
343
- if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_1)
344
- return chalk_1.default.hex('#9be9a8')('โ– ');
345
- if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_2)
346
- return chalk_1.default.hex('#40c463')('โ– ');
347
- if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_3)
348
- return chalk_1.default.hex('#30a14e')('โ– ');
349
- return chalk_1.default.hex('#216e39')('โ– ');
350
- }
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateCommitMatrix = generateCommitMatrix;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const date_fns_1 = require("date-fns");
9
+ const constants_1 = require("../constants");
10
+ const achievementEngine_1 = require("./achievementEngine");
11
+ const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
12
+ const fileHotspotAnalyzer_1 = require("./fileHotspotAnalyzer");
13
+ const streakCalculator_1 = require("./streakCalculator");
14
+ const timePatternAnalyzer_1 = require("./timePatternAnalyzer");
15
+ function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, repoUrl) {
16
+ // Get today or end of year if it's a past year
17
+ const now = new Date();
18
+ const isCurrentYear = year === now.getFullYear();
19
+ // Calculate the end date (today if current year, or end of December for past years)
20
+ const endDate = isCurrentYear
21
+ ? now
22
+ : new Date(year, 11, 31);
23
+ // Calculate start date (N months back from end date)
24
+ const startDate = (0, date_fns_1.startOfMonth)((0, date_fns_1.subMonths)(endDate, monthsToShow - 1));
25
+ // Get the full week range (start on Sunday, end on Saturday)
26
+ const weekStartDate = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 0 });
27
+ const weekEndDate = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 0 });
28
+ // Create a map of date string -> commit count
29
+ const commitMap = new Map();
30
+ commits.forEach(commit => {
31
+ const commitDate = new Date(commit.date);
32
+ if (commitDate >= startDate && commitDate <= endDate) {
33
+ const dateKey = (0, date_fns_1.format)(commitDate, 'yyyy-MM-dd');
34
+ commitMap.set(dateKey, (commitMap.get(dateKey) || 0) + 1);
35
+ }
36
+ });
37
+ const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
38
+ // Calculate streaks
39
+ const streakData = (0, streakCalculator_1.calculateStreaks)(commits, startDate, endDate);
40
+ // Analyze time patterns
41
+ const timePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(commits, startDate, endDate);
42
+ // Analyze commit quality
43
+ const commitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits);
44
+ // Analyze file hotspots
45
+ const fileHotspots = (0, fileHotspotAnalyzer_1.analyzeFileHotspots)(repoPath, startDate, endDate);
46
+ // Prepare analysis data for achievements
47
+ const analysisData = {
48
+ commits,
49
+ totalCommits,
50
+ streakData,
51
+ timePattern,
52
+ commitQuality,
53
+ fileHotspots,
54
+ dateRange: { start: startDate, end: endDate }
55
+ };
56
+ // Check achievements
57
+ const achievementProgress = (0, achievementEngine_1.checkAchievements)(analysisData);
58
+ return formatGitHubStyle(commitMap, weekStartDate, weekEndDate, startDate, endDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl);
59
+ }
60
+ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate, dataEndDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl) {
61
+ const days = (0, date_fns_1.eachDayOfInterval)({ start: weekStartDate, end: weekEndDate });
62
+ const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
63
+ // Group days into weeks
64
+ const weeks = [];
65
+ let currentWeek = [];
66
+ days.forEach(day => {
67
+ currentWeek.push(day);
68
+ if (currentWeek.length === 7) {
69
+ weeks.push(currentWeek);
70
+ currentWeek = [];
71
+ }
72
+ });
73
+ // Build the output with header
74
+ let output = '\n';
75
+ // Add repository info if available
76
+ if (repoName) {
77
+ output += chalk_1.default.bold.cyan(` ${repoName}\n`);
78
+ if (repoUrl) {
79
+ output += chalk_1.default.dim(` ${repoUrl}\n`);
80
+ }
81
+ output += '\n';
82
+ }
83
+ // Add stats
84
+ output += chalk_1.default.dim(' ') + chalk_1.default.white(`${totalCommits} contributions in the last ${(0, date_fns_1.format)(dataStartDate, 'MMM yyyy')} - ${(0, date_fns_1.format)(dataEndDate, 'MMM yyyy')}\n\n`);
85
+ // Build month headers
86
+ let monthHeader = ' ';
87
+ let currentMonth = '';
88
+ weeks.forEach((week, weekIndex) => {
89
+ const firstDayOfWeek = week[0];
90
+ const monthName = (0, date_fns_1.format)(firstDayOfWeek, 'MMM');
91
+ if (monthName !== currentMonth) {
92
+ monthHeader += chalk_1.default.bold(monthName.padEnd(4));
93
+ currentMonth = monthName;
94
+ }
95
+ else {
96
+ monthHeader += ' ';
97
+ }
98
+ });
99
+ output += monthHeader + '\n';
100
+ // Day rows with better formatting
101
+ for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
102
+ // Only show Mon, Wed, Fri labels
103
+ const label = [1, 3, 5].includes(dayOfWeek)
104
+ ? chalk_1.default.dim(dayLabels[dayOfWeek].padEnd(5))
105
+ : ' ';
106
+ let row = label + ' ';
107
+ weeks.forEach(week => {
108
+ const day = week[dayOfWeek];
109
+ if (day) {
110
+ const dateKey = (0, date_fns_1.format)(day, 'yyyy-MM-dd');
111
+ const count = commitMap.get(dateKey) || 0;
112
+ const isInRange = day >= dataStartDate && day <= dataEndDate;
113
+ if (isInRange) {
114
+ row += formatCell(count) + ' ';
115
+ }
116
+ else {
117
+ row += chalk_1.default.dim('โ– ') + ' ';
118
+ }
119
+ }
120
+ else {
121
+ row += ' ';
122
+ }
123
+ });
124
+ output += row + '\n';
125
+ }
126
+ // Add legend with better spacing
127
+ output += '\n';
128
+ output += chalk_1.default.dim(' Learn how we count contributions\n');
129
+ output += chalk_1.default.dim(' Less ') +
130
+ formatCell(0) + ' ' +
131
+ formatCell(1) + ' ' +
132
+ formatCell(4) + ' ' +
133
+ formatCell(7) + ' ' +
134
+ formatCell(10) + ' ' +
135
+ chalk_1.default.dim('More\n');
136
+ // Streak & Momentum Section
137
+ output += '\n';
138
+ output += chalk_1.default.bold.cyan('๐Ÿ“Š Streak & Momentum\n');
139
+ output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
140
+ const currentStreakEmoji = streakData.currentStreak.isActive ? '๐Ÿ”ฅ' : '๐Ÿ’ค';
141
+ const currentStreakText = streakData.currentStreak.days > 0
142
+ ? `${streakData.currentStreak.days} day${streakData.currentStreak.days > 1 ? 's' : ''}`
143
+ : 'No active streak';
144
+ output += `${currentStreakEmoji} Current Streak: ${chalk_1.default.bold.green(currentStreakText)}`;
145
+ if (streakData.currentStreak.isActive && streakData.currentStreak.days > 0) {
146
+ output += chalk_1.default.dim(` - ${(0, streakCalculator_1.getStreakMotivation)(streakData.currentStreak.days, true)}`);
147
+ }
148
+ output += '\n';
149
+ const longestStreakText = streakData.longestStreak.days > 0
150
+ ? `${streakData.longestStreak.days} day${streakData.longestStreak.days > 1 ? 's' : ''} (${(0, date_fns_1.format)(streakData.longestStreak.startDate, 'MMM d')} - ${(0, date_fns_1.format)(streakData.longestStreak.endDate, 'MMM d, yyyy')})`
151
+ : 'No streaks yet';
152
+ output += `๐Ÿ† Longest Streak: ${chalk_1.default.bold.yellow(longestStreakText)}\n`;
153
+ const activePercentage = streakData.activeDayPercentage.toFixed(1);
154
+ output += `๐Ÿ“ˆ Active Days: ${chalk_1.default.bold.cyan(streakData.totalActiveDays)}/${streakData.totalDays} (${activePercentage}%)\n`;
155
+ // Time & Productivity Patterns Section
156
+ output += '\n';
157
+ output += chalk_1.default.bold.cyan('โฐ Productivity Patterns\n');
158
+ output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
159
+ // Peak hour
160
+ const peakHourLabel = timePattern.peakHour.hour === 0 ? '12:00 AM' :
161
+ timePattern.peakHour.hour < 12 ? `${timePattern.peakHour.hour}:00 AM` :
162
+ timePattern.peakHour.hour === 12 ? '12:00 PM' :
163
+ `${timePattern.peakHour.hour - 12}:00 PM`;
164
+ output += `โฑ๏ธ Peak Hour: ${chalk_1.default.bold.green(peakHourLabel)} (${timePattern.peakHour.commitCount} commits)\n`;
165
+ // Chronotype
166
+ const chronotypeLabel = (0, timePatternAnalyzer_1.getChronotypeLabel)(timePattern.chronotype);
167
+ const chronotypeDesc = (0, timePatternAnalyzer_1.getChronotypeDescription)(timePattern.chronotype);
168
+ output += `${chronotypeLabel} ${chalk_1.default.dim(`(${timePattern.chronotypeConfidence.toFixed(0)}% confidence)`)}\n`;
169
+ if (chronotypeDesc) {
170
+ output += chalk_1.default.dim(` ${chronotypeDesc}\n`);
171
+ }
172
+ // Work-life balance
173
+ const stars = 'โญ'.repeat(timePattern.workLifeBalance.score);
174
+ output += `โš–๏ธ Work-Life Balance: ${stars} ${chalk_1.default.dim(`(${timePattern.workLifeBalance.score}/5)`)}\n`;
175
+ output += chalk_1.default.dim(` Weekday: ${timePattern.workLifeBalance.weekdayCommits} | Weekend: ${timePattern.workLifeBalance.weekendCommits}\n`);
176
+ // Consistency
177
+ output += `๐Ÿ“Š Consistency: ${chalk_1.default.bold.yellow(timePattern.consistency.score.toFixed(1))}/10 ${chalk_1.default.dim(`(${timePattern.consistency.regularity})`)}\n`;
178
+ // Burnout warnings
179
+ if (timePattern.burnoutRisk.level !== 'low') {
180
+ output += `\n${chalk_1.default.yellow('โš ๏ธ Burnout Risk: ' + timePattern.burnoutRisk.level.toUpperCase())}\n`;
181
+ timePattern.burnoutRisk.indicators.forEach((indicator) => {
182
+ output += chalk_1.default.dim(` โ€ข ${indicator}\n`);
183
+ });
184
+ }
185
+ // Commit Quality Section
186
+ output += '\n';
187
+ output += chalk_1.default.bold.cyan('๐Ÿ“ Commit Quality\n');
188
+ output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
189
+ const rating = (0, commitQualityAnalyzer_1.getQualityRating)(commitQuality.overallScore);
190
+ const level = (0, commitQualityAnalyzer_1.getQualityLevel)(commitQuality.overallScore);
191
+ output += `๐Ÿ“Š Overall Score: ${chalk_1.default.bold.green(commitQuality.overallScore.toFixed(1))}/10 ${rating} ${chalk_1.default.dim(`(${level})`)}\n`;
192
+ output += `โœ… Convention: ${chalk_1.default.bold.cyan(commitQuality.conventionalCommits.adherence.toFixed(1))}%`;
193
+ if (commitQuality.conventionalCommits.adherence < 70) {
194
+ output += chalk_1.default.yellow(' โš ๏ธ');
195
+ }
196
+ else {
197
+ output += chalk_1.default.green(' โœจ');
198
+ }
199
+ output += '\n';
200
+ // Subject quality
201
+ output += `๐Ÿ“ Subject Quality: ${chalk_1.default.bold.yellow(commitQuality.subjectQuality.score.toFixed(1))}/10 `;
202
+ output += chalk_1.default.dim(`(avg ${commitQuality.subjectQuality.avgLength} chars)\n`);
203
+ // Body quality
204
+ output += `๐Ÿ“„ Body Quality: ${chalk_1.default.bold.yellow(commitQuality.bodyQuality.score.toFixed(1))}/10 `;
205
+ output += chalk_1.default.dim(`(${commitQuality.bodyQuality.withBody}/${commitQuality.totalCommits} with body)\n`);
206
+ // Top commit types
207
+ if (Object.keys(commitQuality.conventionalCommits.types).length > 0) {
208
+ output += `\n${chalk_1.default.dim('Top Commit Types:')}\n`;
209
+ const sortedTypes = Object.entries(commitQuality.conventionalCommits.types)
210
+ .sort(([, a], [, b]) => b - a)
211
+ .slice(0, 3);
212
+ sortedTypes.forEach(([type, count]) => {
213
+ output += chalk_1.default.dim(` ${type}: ${count}\n`);
214
+ });
215
+ }
216
+ // Improvements
217
+ if (commitQuality.improvements && commitQuality.improvements.length > 0) {
218
+ output += `\n${chalk_1.default.yellow('๐Ÿ’ก Tips for Improvement:')}\n`;
219
+ commitQuality.improvements.slice(0, 2).forEach((tip) => {
220
+ output += chalk_1.default.dim(` โ€ข ${tip}\n`);
221
+ });
222
+ }
223
+ // Code hygiene metrics (if any)
224
+ const totalHygiene = commitQuality.codeHygiene.quickFixes +
225
+ commitQuality.codeHygiene.workInProgress +
226
+ commitQuality.codeHygiene.oops +
227
+ commitQuality.codeHygiene.vague;
228
+ if (totalHygiene > 0) {
229
+ output += `\n${chalk_1.default.dim('๐Ÿงน Code Hygiene Indicators:\n')}`;
230
+ if (commitQuality.codeHygiene.quickFixes > 0) {
231
+ output += chalk_1.default.dim(` Quick fixes (typos/spelling): ${commitQuality.codeHygiene.quickFixes}\n`);
232
+ }
233
+ if (commitQuality.codeHygiene.workInProgress > 0) {
234
+ output += chalk_1.default.dim(` Work in progress: ${commitQuality.codeHygiene.workInProgress}\n`);
235
+ }
236
+ if (commitQuality.codeHygiene.oops > 0) {
237
+ output += chalk_1.default.dim(` Correction commits: ${commitQuality.codeHygiene.oops}\n`);
238
+ }
239
+ if (commitQuality.codeHygiene.vague > 0) {
240
+ output += chalk_1.default.dim(` Vague messages: ${commitQuality.codeHygiene.vague}\n`);
241
+ }
242
+ }
243
+ // File & Code Hotspots Section
244
+ output += '\n' + chalk_1.default.bold.hex('#58a6ff')('๐Ÿ”ฅ TOP FILE HOTSPOTS') + '\n';
245
+ if (!fileHotspots || fileHotspots.topFiles.length === 0) {
246
+ output += chalk_1.default.dim(' No file changes detected in this period.\n');
247
+ }
248
+ else {
249
+ const topFilesToShow = fileHotspots.topFiles.slice(0, 7);
250
+ const maxChanges = Math.max(...topFilesToShow.map((f) => f.changeCount));
251
+ topFilesToShow.forEach((file) => {
252
+ const barLength = Math.max(1, Math.round((file.changeCount / maxChanges) * 30));
253
+ const bar = chalk_1.default.hex('#f85149')('โ–ˆ'.repeat(barLength));
254
+ const fileName = file.path.length > 40 ? '...' + file.path.slice(-37) : file.path;
255
+ output += chalk_1.default.dim(` ${fileName}\n`);
256
+ output += ` ${bar} ${chalk_1.default.bold(file.changeCount)} changes, ${file.authors.length} author${file.authors.length !== 1 ? 's' : ''}\n`;
257
+ });
258
+ }
259
+ // Technical Debt Warnings
260
+ if (fileHotspots && fileHotspots.technicalDebt.length > 0) {
261
+ output += '\n' + chalk_1.default.bold.hex('#f85149')('โš ๏ธ TECHNICAL DEBT WARNINGS') + '\n';
262
+ const topDebt = fileHotspots.technicalDebt.slice(0, 5);
263
+ topDebt.forEach((debt) => {
264
+ const color = (0, fileHotspotAnalyzer_1.getRiskLevelColor)(debt.riskLevel);
265
+ const colorFn = color === 'red' ? chalk_1.default.red : color === 'yellow' ? chalk_1.default.yellow : chalk_1.default.gray;
266
+ const fileName = debt.file.length > 45 ? '...' + debt.file.slice(-42) : debt.file;
267
+ output += colorFn(` ${fileName}\n`);
268
+ output += chalk_1.default.dim(` Risk: ${debt.riskLevel.toUpperCase()} (complexity: ${debt.score}/10)\n`);
269
+ if (debt.recommendations.length > 0) {
270
+ output += chalk_1.default.dim(` โ†’ ${debt.recommendations[0]}\n`);
271
+ }
272
+ });
273
+ }
274
+ // Ownership Risks
275
+ if (fileHotspots && fileHotspots.ownershipRisks) {
276
+ const highRiskOwnership = fileHotspots.ownershipRisks.filter((risk) => risk.busFactorRisk >= 7);
277
+ if (highRiskOwnership.length > 0) {
278
+ output += '\n' + chalk_1.default.bold.hex('#d29922')('๐ŸšŒ BUS FACTOR WARNINGS') + '\n';
279
+ const topRisks = highRiskOwnership.slice(0, 5);
280
+ topRisks.forEach((risk) => {
281
+ const icon = (0, fileHotspotAnalyzer_1.getOwnershipIcon)(risk.ownershipType);
282
+ const fileName = risk.file.length > 45 ? '...' + risk.file.slice(-42) : risk.file;
283
+ output += chalk_1.default.yellow(` ${icon} ${fileName}\n`);
284
+ const topOwner = risk.owners[0];
285
+ output += chalk_1.default.dim(` ${topOwner.author} owns ${topOwner.percentage.toFixed(0)}% (${topOwner.commits} commits)\n`);
286
+ });
287
+ }
288
+ }
289
+ // Achievements Section
290
+ if (achievementProgress) {
291
+ output += '\n';
292
+ output += chalk_1.default.bold.hex('#ffd700')('๐Ÿ† ACHIEVEMENTS') + '\n';
293
+ output += chalk_1.default.dim('โ”€'.repeat(50)) + '\n';
294
+ // Show newly earned badges
295
+ if (achievementProgress.recentlyEarned && achievementProgress.recentlyEarned.length > 0) {
296
+ output += '\n' + chalk_1.default.bold.green('โœจ NEW BADGES EARNED! ๐ŸŽ‰') + '\n';
297
+ achievementProgress.recentlyEarned.forEach((achievement) => {
298
+ output += chalk_1.default.green(` ${achievement.emoji} ${achievement.name} - ${achievement.description}\n`);
299
+ });
300
+ }
301
+ // Show progress
302
+ output += `\n${chalk_1.default.cyan('Progress:')} ${chalk_1.default.bold.white(achievementProgress.unlockedCount)}/${achievementProgress.totalCount} badges unlocked `;
303
+ output += chalk_1.default.dim(`(${achievementProgress.completionPercentage.toFixed(1)}%)\n`);
304
+ // Progress bar
305
+ const barLength = 30;
306
+ const filledLength = Math.round((achievementProgress.completionPercentage / 100) * barLength);
307
+ const bar = 'โ–ˆ'.repeat(filledLength) + 'โ–‘'.repeat(barLength - filledLength);
308
+ output += ` ${chalk_1.default.hex('#ffd700')(bar)}\n`;
309
+ // Show next milestone
310
+ if (achievementProgress.nextMilestones && achievementProgress.nextMilestones.length > 0) {
311
+ const next = achievementProgress.nextMilestones[0];
312
+ output += `\n${chalk_1.default.yellow('๐ŸŽฏ Next Milestone:')}\n`;
313
+ output += ` ${next.emoji} ${chalk_1.default.bold(next.name)} ${chalk_1.default.dim(`(${next.progress.toFixed(0)}%)`)}\n`;
314
+ output += chalk_1.default.dim(` ${next.description}\n`);
315
+ // Progress bar for next milestone
316
+ const nextBarLength = 25;
317
+ const nextFilledLength = Math.round((next.progress / 100) * nextBarLength);
318
+ const nextBar = 'โ–ˆ'.repeat(nextFilledLength) + 'โ–‘'.repeat(nextBarLength - nextFilledLength);
319
+ output += ` ${chalk_1.default.hex('#58a6ff')(nextBar)} ${chalk_1.default.dim((100 - next.progress).toFixed(0) + '% to go!')}\n`;
320
+ }
321
+ // Show recent unlocks (excluding today's new badges)
322
+ const recentUnlocks = achievementProgress.achievements
323
+ .filter((a) => a.isUnlocked &&
324
+ !achievementProgress.recentlyEarned.some((r) => r.id === a.id) &&
325
+ a.earnedDate)
326
+ .sort((a, b) => b.earnedDate.getTime() - a.earnedDate.getTime())
327
+ .slice(0, 3);
328
+ if (recentUnlocks.length > 0) {
329
+ output += `\n${chalk_1.default.dim('Recent Unlocks:')}\n`;
330
+ recentUnlocks.forEach((achievement) => {
331
+ const daysAgo = Math.floor((Date.now() - achievement.earnedDate.getTime()) / (1000 * 60 * 60 * 24));
332
+ const timeStr = daysAgo === 0 ? 'today' : daysAgo === 1 ? '1 day ago' : `${daysAgo} days ago`;
333
+ output += chalk_1.default.dim(` ${achievement.emoji} ${achievement.name} (${timeStr})\n`);
334
+ });
335
+ }
336
+ }
337
+ return output;
338
+ }
339
+ function formatCell(count) {
340
+ if (count === 0)
341
+ return chalk_1.default.hex('#ebedf0')('โ– ');
342
+ if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_1)
343
+ return chalk_1.default.hex('#9be9a8')('โ– ');
344
+ if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_2)
345
+ return chalk_1.default.hex('#40c463')('โ– ');
346
+ if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_3)
347
+ return chalk_1.default.hex('#30a14e')('โ– ');
348
+ return chalk_1.default.hex('#216e39')('โ– ');
349
+ }