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.
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 +64 -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 +61 -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 +245 -233
  29. package/dist/utils/impactAnalyzer.js +247 -248
  30. package/dist/utils/knowledgeDistributionAnalyzer.js +380 -374
  31. package/dist/utils/matrixGenerator.js +369 -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,270 +1,269 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getOwnershipIcon = exports.getRiskLevelColor = exports.analyzeFileHotspots = void 0;
4
- const child_process_1 = require("child_process");
5
- function analyzeFileHotspots(repoPath, startDate, endDate) {
6
- try {
7
- // Get file changes with author info from git log
8
- const since = startDate.toISOString().split('T')[0];
9
- const until = endDate.toISOString().split('T')[0];
10
- let gitLog;
11
- try {
12
- gitLog = (0, child_process_1.execSync)(`git log --name-only --format="%H|%an|%ad" --date=short --since="${since}" --until="${until}"`, { cwd: repoPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }).trim();
13
- }
14
- catch (gitError) {
15
- // Git command failed, likely not in a git repo or no commits in range
16
- return getEmptyAnalysis();
17
- }
18
- if (!gitLog) {
19
- return getEmptyAnalysis();
20
- }
21
- // Parse git log output
22
- const fileMap = new Map();
23
- const lines = gitLog.split('\n');
24
- let currentCommit = null;
25
- for (const line of lines) {
26
- const trimmed = line.trim();
27
- if (trimmed.includes('|')) {
28
- // Commit header line
29
- const [hash, author, date] = trimmed.split('|');
30
- currentCommit = { hash, author, date };
31
- }
32
- else if (trimmed && currentCommit) {
33
- // File path line
34
- const filePath = trimmed;
35
- // Skip binary files, lock files, and common generated files
36
- if (shouldIgnoreFile(filePath)) {
37
- continue;
38
- }
39
- if (!fileMap.has(filePath)) {
40
- fileMap.set(filePath, {
41
- path: filePath,
42
- changeCount: 0,
43
- authors: [],
44
- authorDistribution: {},
45
- firstChange: new Date(currentCommit.date),
46
- lastChange: new Date(currentCommit.date),
47
- complexityScore: 0
48
- });
49
- }
50
- const fileStats = fileMap.get(filePath);
51
- fileStats.changeCount++;
52
- // Track authors
53
- if (!fileStats.authors.includes(currentCommit.author)) {
54
- fileStats.authors.push(currentCommit.author);
55
- }
56
- fileStats.authorDistribution[currentCommit.author] =
57
- (fileStats.authorDistribution[currentCommit.author] || 0) + 1;
58
- // Update dates
59
- const commitDate = new Date(currentCommit.date);
60
- if (commitDate < fileStats.firstChange) {
61
- fileStats.firstChange = commitDate;
62
- }
63
- if (commitDate > fileStats.lastChange) {
64
- fileStats.lastChange = commitDate;
65
- }
66
- }
67
- }
68
- // Calculate complexity scores
69
- for (const fileStats of fileMap.values()) {
70
- fileStats.complexityScore = calculateComplexityScore(fileStats);
71
- }
72
- // Get top files
73
- const topFiles = Array.from(fileMap.values())
74
- .sort((a, b) => b.changeCount - a.changeCount)
75
- .slice(0, 20);
76
- // Analyze directories
77
- const topDirectories = analyzeDirectories(fileMap);
78
- // Identify technical debt
79
- const technicalDebt = identifyTechnicalDebt(Array.from(fileMap.values()));
80
- // Analyze ownership risks
81
- const ownershipRisks = analyzeOwnership(Array.from(fileMap.values()));
82
- return {
83
- topFiles,
84
- topDirectories,
85
- technicalDebt,
86
- ownershipRisks,
87
- totalFilesAnalyzed: fileMap.size
88
- };
89
- }
90
- catch (error) {
91
- console.error('Error analyzing file hotspots:', error);
92
- return getEmptyAnalysis();
93
- }
94
- }
95
- exports.analyzeFileHotspots = analyzeFileHotspots;
96
- function shouldIgnoreFile(path) {
97
- const ignoredExtensions = ['.lock', '.log', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'];
98
- const ignoredPatterns = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'node_modules/', 'dist/', 'build/', '.next/', 'coverage/'];
99
- // Check extensions
100
- if (ignoredExtensions.some(ext => path.toLowerCase().endsWith(ext))) {
101
- return true;
102
- }
103
- // Check patterns
104
- if (ignoredPatterns.some(pattern => path.includes(pattern))) {
105
- return true;
106
- }
107
- return false;
108
- }
109
- function calculateComplexityScore(fileStats) {
110
- let score = 0;
111
- // High churn
112
- if (fileStats.changeCount > 100)
113
- score += 3;
114
- else if (fileStats.changeCount > 50)
115
- score += 2;
116
- else if (fileStats.changeCount > 25)
117
- score += 1;
118
- // Many authors
119
- if (fileStats.authors.length > 10)
120
- score += 2;
121
- else if (fileStats.authors.length > 5)
122
- score += 1;
123
- // Time since last change (older = potentially more stable, but also potentially abandoned)
124
- const daysSinceLastChange = Math.floor((Date.now() - fileStats.lastChange.getTime()) / (1000 * 60 * 60 * 24));
125
- if (daysSinceLastChange > 180)
126
- score += 1; // No changes in 6 months
127
- return Math.min(10, score);
128
- }
129
- function analyzeDirectories(fileMap) {
130
- const dirMap = new Map();
131
- // Group files by directory
132
- for (const file of fileMap.values()) {
133
- const dirPath = file.path.includes('/')
134
- ? file.path.substring(0, file.path.lastIndexOf('/'))
135
- : '.';
136
- if (!dirMap.has(dirPath)) {
137
- dirMap.set(dirPath, { files: [], totalChanges: 0 });
138
- }
139
- const dirStats = dirMap.get(dirPath);
140
- dirStats.files.push(file);
141
- dirStats.totalChanges += file.changeCount;
142
- }
143
- // Create directory stats
144
- const directories = [];
145
- for (const [path, data] of dirMap) {
146
- const mostActive = data.files.sort((a, b) => b.changeCount - a.changeCount)[0];
147
- directories.push({
148
- path,
149
- totalChanges: data.totalChanges,
150
- fileCount: data.files.length,
151
- avgChangesPerFile: data.totalChanges / data.files.length,
152
- mostActiveFile: mostActive.path
153
- });
154
- }
155
- return directories
156
- .sort((a, b) => b.totalChanges - a.totalChanges)
157
- .slice(0, 10);
158
- }
159
- function identifyTechnicalDebt(files) {
160
- const debtFiles = [];
161
- for (const file of files) {
162
- const score = file.complexityScore;
163
- // Only flag files with complexity score >= 4
164
- if (score < 4)
165
- continue;
166
- let riskLevel = 'low';
167
- if (score >= 8)
168
- riskLevel = 'critical';
169
- else if (score >= 6)
170
- riskLevel = 'high';
171
- else if (score >= 4)
172
- riskLevel = 'medium';
173
- const recommendations = [];
174
- if (file.changeCount > 50) {
175
- recommendations.push('High change frequency - consider refactoring for stability');
176
- }
177
- if (file.authors.length > 8) {
178
- recommendations.push('Many different authors - ensure clear ownership');
179
- }
180
- const daysSinceLastChange = Math.floor((Date.now() - file.lastChange.getTime()) / (1000 * 60 * 60 * 24));
181
- if (daysSinceLastChange > 180) {
182
- recommendations.push('No recent changes - may be stable or abandoned');
183
- }
184
- debtFiles.push({
185
- file: file.path,
186
- riskLevel,
187
- changeCount: file.changeCount,
188
- authorCount: file.authors.length,
189
- lastModified: file.lastChange,
190
- score,
191
- recommendations
192
- });
193
- }
194
- return debtFiles.sort((a, b) => b.score - a.score).slice(0, 10);
195
- }
196
- function analyzeOwnership(files) {
197
- const ownershipData = [];
198
- for (const file of files) {
199
- // Only analyze files with multiple commits
200
- if (file.changeCount < 3)
201
- continue;
202
- // Calculate owner percentages
203
- const owners = Object.entries(file.authorDistribution)
204
- .map(([author, commits]) => ({
205
- author,
206
- commits,
207
- percentage: (commits / file.changeCount) * 100
208
- }))
209
- .sort((a, b) => b.commits - a.commits)
210
- .slice(0, 5); // Top 5 contributors
211
- // Determine ownership type
212
- let ownershipType = 'collaborative';
213
- let busFactorRisk = 0;
214
- if (owners.length === 1 || owners[0].percentage >= 90) {
215
- ownershipType = 'single-owner';
216
- busFactorRisk = 9;
217
- }
218
- else if (owners[0].percentage >= 70) {
219
- ownershipType = 'single-owner';
220
- busFactorRisk = 7;
221
- }
222
- else if (owners[0].percentage >= 50) {
223
- ownershipType = 'shared';
224
- busFactorRisk = 4;
225
- }
226
- else {
227
- ownershipType = 'collaborative';
228
- busFactorRisk = 2;
229
- }
230
- ownershipData.push({
231
- file: file.path,
232
- owners,
233
- ownershipType,
234
- busFactorRisk
235
- });
236
- }
237
- // Return files with ownership risks
238
- return ownershipData
239
- .filter(o => o.busFactorRisk >= 7)
240
- .sort((a, b) => b.busFactorRisk - a.busFactorRisk)
241
- .slice(0, 10);
242
- }
243
- function getEmptyAnalysis() {
244
- return {
245
- topFiles: [],
246
- topDirectories: [],
247
- technicalDebt: [],
248
- ownershipRisks: [],
249
- totalFilesAnalyzed: 0
250
- };
251
- }
252
- function getRiskLevelColor(level) {
253
- switch (level) {
254
- case 'critical': return '🔴';
255
- case 'high': return '🟠';
256
- case 'medium': return '🟡';
257
- case 'low': return '🟢';
258
- default: return '';
259
- }
260
- }
261
- exports.getRiskLevelColor = getRiskLevelColor;
262
- function getOwnershipIcon(type) {
263
- switch (type) {
264
- case 'single-owner': return '🔴';
265
- case 'shared': return '🟡';
266
- case 'collaborative': return '🟢';
267
- default: return '⚪';
268
- }
269
- }
270
- exports.getOwnershipIcon = getOwnershipIcon;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeFileHotspots = analyzeFileHotspots;
4
+ exports.getRiskLevelColor = getRiskLevelColor;
5
+ exports.getOwnershipIcon = getOwnershipIcon;
6
+ const child_process_1 = require("child_process");
7
+ function analyzeFileHotspots(repoPath, startDate, endDate) {
8
+ try {
9
+ // Get file changes with author info from git log
10
+ const since = startDate.toISOString().split('T')[0];
11
+ const until = endDate.toISOString().split('T')[0];
12
+ let gitLog;
13
+ try {
14
+ gitLog = (0, child_process_1.execSync)(`git log --name-only --format="%H|%an|%ad" --date=short --since="${since}" --until="${until}"`, { cwd: repoPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }).trim();
15
+ }
16
+ catch (gitError) {
17
+ // Git command failed, likely not in a git repo or no commits in range
18
+ return getEmptyAnalysis();
19
+ }
20
+ if (!gitLog) {
21
+ return getEmptyAnalysis();
22
+ }
23
+ // Parse git log output
24
+ const fileMap = new Map();
25
+ const lines = gitLog.split('\n');
26
+ let currentCommit = null;
27
+ for (const line of lines) {
28
+ const trimmed = line.trim();
29
+ if (trimmed.includes('|')) {
30
+ // Commit header line
31
+ const [hash, author, date] = trimmed.split('|');
32
+ currentCommit = { hash, author, date };
33
+ }
34
+ else if (trimmed && currentCommit) {
35
+ // File path line
36
+ const filePath = trimmed;
37
+ // Skip binary files, lock files, and common generated files
38
+ if (shouldIgnoreFile(filePath)) {
39
+ continue;
40
+ }
41
+ if (!fileMap.has(filePath)) {
42
+ fileMap.set(filePath, {
43
+ path: filePath,
44
+ changeCount: 0,
45
+ authors: [],
46
+ authorDistribution: {},
47
+ firstChange: new Date(currentCommit.date),
48
+ lastChange: new Date(currentCommit.date),
49
+ complexityScore: 0
50
+ });
51
+ }
52
+ const fileStats = fileMap.get(filePath);
53
+ fileStats.changeCount++;
54
+ // Track authors
55
+ if (!fileStats.authors.includes(currentCommit.author)) {
56
+ fileStats.authors.push(currentCommit.author);
57
+ }
58
+ fileStats.authorDistribution[currentCommit.author] =
59
+ (fileStats.authorDistribution[currentCommit.author] || 0) + 1;
60
+ // Update dates
61
+ const commitDate = new Date(currentCommit.date);
62
+ if (commitDate < fileStats.firstChange) {
63
+ fileStats.firstChange = commitDate;
64
+ }
65
+ if (commitDate > fileStats.lastChange) {
66
+ fileStats.lastChange = commitDate;
67
+ }
68
+ }
69
+ }
70
+ // Calculate complexity scores
71
+ for (const fileStats of fileMap.values()) {
72
+ fileStats.complexityScore = calculateComplexityScore(fileStats);
73
+ }
74
+ // Get top files
75
+ const topFiles = Array.from(fileMap.values())
76
+ .sort((a, b) => b.changeCount - a.changeCount)
77
+ .slice(0, 20);
78
+ // Analyze directories
79
+ const topDirectories = analyzeDirectories(fileMap);
80
+ // Identify technical debt
81
+ const technicalDebt = identifyTechnicalDebt(Array.from(fileMap.values()));
82
+ // Analyze ownership risks
83
+ const ownershipRisks = analyzeOwnership(Array.from(fileMap.values()));
84
+ return {
85
+ topFiles,
86
+ topDirectories,
87
+ technicalDebt,
88
+ ownershipRisks,
89
+ totalFilesAnalyzed: fileMap.size
90
+ };
91
+ }
92
+ catch (error) {
93
+ console.error('Error analyzing file hotspots:', error);
94
+ return getEmptyAnalysis();
95
+ }
96
+ }
97
+ function shouldIgnoreFile(path) {
98
+ const ignoredExtensions = ['.lock', '.log', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'];
99
+ const ignoredPatterns = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'node_modules/', 'dist/', 'build/', '.next/', 'coverage/'];
100
+ // Check extensions
101
+ if (ignoredExtensions.some(ext => path.toLowerCase().endsWith(ext))) {
102
+ return true;
103
+ }
104
+ // Check patterns
105
+ if (ignoredPatterns.some(pattern => path.includes(pattern))) {
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+ function calculateComplexityScore(fileStats) {
111
+ let score = 0;
112
+ // High churn
113
+ if (fileStats.changeCount > 100)
114
+ score += 3;
115
+ else if (fileStats.changeCount > 50)
116
+ score += 2;
117
+ else if (fileStats.changeCount > 25)
118
+ score += 1;
119
+ // Many authors
120
+ if (fileStats.authors.length > 10)
121
+ score += 2;
122
+ else if (fileStats.authors.length > 5)
123
+ score += 1;
124
+ // Time since last change (older = potentially more stable, but also potentially abandoned)
125
+ const daysSinceLastChange = Math.floor((Date.now() - fileStats.lastChange.getTime()) / (1000 * 60 * 60 * 24));
126
+ if (daysSinceLastChange > 180)
127
+ score += 1; // No changes in 6 months
128
+ return Math.min(10, score);
129
+ }
130
+ function analyzeDirectories(fileMap) {
131
+ const dirMap = new Map();
132
+ // Group files by directory
133
+ for (const file of fileMap.values()) {
134
+ const dirPath = file.path.includes('/')
135
+ ? file.path.substring(0, file.path.lastIndexOf('/'))
136
+ : '.';
137
+ if (!dirMap.has(dirPath)) {
138
+ dirMap.set(dirPath, { files: [], totalChanges: 0 });
139
+ }
140
+ const dirStats = dirMap.get(dirPath);
141
+ dirStats.files.push(file);
142
+ dirStats.totalChanges += file.changeCount;
143
+ }
144
+ // Create directory stats
145
+ const directories = [];
146
+ for (const [path, data] of dirMap) {
147
+ const mostActive = data.files.sort((a, b) => b.changeCount - a.changeCount)[0];
148
+ directories.push({
149
+ path,
150
+ totalChanges: data.totalChanges,
151
+ fileCount: data.files.length,
152
+ avgChangesPerFile: data.totalChanges / data.files.length,
153
+ mostActiveFile: mostActive.path
154
+ });
155
+ }
156
+ return directories
157
+ .sort((a, b) => b.totalChanges - a.totalChanges)
158
+ .slice(0, 10);
159
+ }
160
+ function identifyTechnicalDebt(files) {
161
+ const debtFiles = [];
162
+ for (const file of files) {
163
+ const score = file.complexityScore;
164
+ // Only flag files with complexity score >= 4
165
+ if (score < 4)
166
+ continue;
167
+ let riskLevel = 'low';
168
+ if (score >= 8)
169
+ riskLevel = 'critical';
170
+ else if (score >= 6)
171
+ riskLevel = 'high';
172
+ else if (score >= 4)
173
+ riskLevel = 'medium';
174
+ const recommendations = [];
175
+ if (file.changeCount > 50) {
176
+ recommendations.push('High change frequency - consider refactoring for stability');
177
+ }
178
+ if (file.authors.length > 8) {
179
+ recommendations.push('Many different authors - ensure clear ownership');
180
+ }
181
+ const daysSinceLastChange = Math.floor((Date.now() - file.lastChange.getTime()) / (1000 * 60 * 60 * 24));
182
+ if (daysSinceLastChange > 180) {
183
+ recommendations.push('No recent changes - may be stable or abandoned');
184
+ }
185
+ debtFiles.push({
186
+ file: file.path,
187
+ riskLevel,
188
+ changeCount: file.changeCount,
189
+ authorCount: file.authors.length,
190
+ lastModified: file.lastChange,
191
+ score,
192
+ recommendations
193
+ });
194
+ }
195
+ return debtFiles.sort((a, b) => b.score - a.score).slice(0, 10);
196
+ }
197
+ function analyzeOwnership(files) {
198
+ const ownershipData = [];
199
+ for (const file of files) {
200
+ // Only analyze files with multiple commits
201
+ if (file.changeCount < 3)
202
+ continue;
203
+ // Calculate owner percentages
204
+ const owners = Object.entries(file.authorDistribution)
205
+ .map(([author, commits]) => ({
206
+ author,
207
+ commits,
208
+ percentage: (commits / file.changeCount) * 100
209
+ }))
210
+ .sort((a, b) => b.commits - a.commits)
211
+ .slice(0, 5); // Top 5 contributors
212
+ // Determine ownership type
213
+ let ownershipType = 'collaborative';
214
+ let busFactorRisk = 0;
215
+ if (owners.length === 1 || owners[0].percentage >= 90) {
216
+ ownershipType = 'single-owner';
217
+ busFactorRisk = 9;
218
+ }
219
+ else if (owners[0].percentage >= 70) {
220
+ ownershipType = 'single-owner';
221
+ busFactorRisk = 7;
222
+ }
223
+ else if (owners[0].percentage >= 50) {
224
+ ownershipType = 'shared';
225
+ busFactorRisk = 4;
226
+ }
227
+ else {
228
+ ownershipType = 'collaborative';
229
+ busFactorRisk = 2;
230
+ }
231
+ ownershipData.push({
232
+ file: file.path,
233
+ owners,
234
+ ownershipType,
235
+ busFactorRisk
236
+ });
237
+ }
238
+ // Return files with ownership risks
239
+ return ownershipData
240
+ .filter(o => o.busFactorRisk >= 7)
241
+ .sort((a, b) => b.busFactorRisk - a.busFactorRisk)
242
+ .slice(0, 10);
243
+ }
244
+ function getEmptyAnalysis() {
245
+ return {
246
+ topFiles: [],
247
+ topDirectories: [],
248
+ technicalDebt: [],
249
+ ownershipRisks: [],
250
+ totalFilesAnalyzed: 0
251
+ };
252
+ }
253
+ function getRiskLevelColor(level) {
254
+ switch (level) {
255
+ case 'critical': return '🔴';
256
+ case 'high': return '🟠';
257
+ case 'medium': return '🟡';
258
+ case 'low': return '🟢';
259
+ default: return '⚪';
260
+ }
261
+ }
262
+ function getOwnershipIcon(type) {
263
+ switch (type) {
264
+ case 'single-owner': return '🔴';
265
+ case 'shared': return '🟡';
266
+ case 'collaborative': return '🟢';
267
+ default: return '⚪';
268
+ }
269
+ }