repo-wrapped 0.0.7 → 0.0.9
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/.github/agents/complete.agent.md +257 -0
- package/.github/agents/feature-scaffold.agent.md +248 -0
- package/.github/agents/jsdoc.agent.md +243 -0
- package/.github/agents/plan.agent.md +202 -0
- package/.github/agents/spec-writer.agent.md +169 -0
- package/.github/agents/test-writer.agent.md +169 -0
- package/.stylelintrc.json +27 -0
- package/README.md +94 -94
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +446 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +446 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +7039 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/commands/generate.js +56 -56
- package/dist/config/defaults.js +158 -0
- package/dist/config/index.js +10 -0
- package/dist/features/achievements/data/achievements.json +284 -0
- package/dist/features/achievements/engine.js +140 -0
- package/dist/features/achievements/evaluators.js +246 -0
- package/dist/features/achievements/helpers.js +58 -0
- package/dist/features/achievements/index.js +57 -0
- package/dist/features/achievements/loader.js +88 -0
- package/dist/features/achievements/template.js +155 -0
- package/dist/features/achievements/types.js +7 -0
- package/dist/features/commit-quality/analyzer.js +378 -0
- package/dist/features/commit-quality/analyzer.test.js +484 -0
- package/dist/features/commit-quality/index.js +28 -0
- package/dist/features/commit-quality/template.js +114 -0
- package/dist/features/commit-quality/types.js +2 -0
- package/dist/features/comparison/analyzer.js +222 -0
- package/dist/features/comparison/index.js +28 -0
- package/dist/features/comparison/template.js +119 -0
- package/dist/features/comparison/types.js +2 -0
- package/dist/features/contribution-graph/index.js +9 -0
- package/dist/features/contribution-graph/template.js +89 -0
- package/dist/features/events/index.js +31 -0
- package/dist/features/events/parser.js +253 -0
- package/dist/features/events/template.js +113 -0
- package/dist/features/events/types.js +2 -0
- package/dist/features/executive-summary/generator.js +275 -0
- package/dist/features/executive-summary/index.js +27 -0
- package/dist/features/executive-summary/template.js +80 -0
- package/dist/features/executive-summary/types.js +2 -0
- package/dist/features/gaps/analyzer.js +298 -0
- package/dist/features/gaps/analyzer.test.js +517 -0
- package/dist/features/gaps/index.js +27 -0
- package/dist/features/gaps/template.js +190 -0
- package/dist/features/gaps/types.js +2 -0
- package/dist/features/impact/analyzer.js +248 -0
- package/dist/features/impact/index.js +26 -0
- package/dist/features/impact/template.js +118 -0
- package/dist/features/impact/types.js +2 -0
- package/dist/features/index.js +40 -0
- package/dist/features/knowledge/analyzer.js +385 -0
- package/dist/features/knowledge/index.js +26 -0
- package/dist/features/knowledge/template.js +239 -0
- package/dist/features/knowledge/types.js +2 -0
- package/dist/features/streaks/calculator.js +184 -0
- package/dist/features/streaks/calculator.test.js +366 -0
- package/dist/features/streaks/index.js +36 -0
- package/dist/features/streaks/template.js +41 -0
- package/dist/features/streaks/types.js +9 -0
- package/dist/features/team/analyzer.js +316 -0
- package/dist/features/team/index.js +30 -0
- package/dist/features/team/template.js +146 -0
- package/dist/features/team/types.js +2 -0
- package/dist/features/time-patterns/analyzer.js +319 -0
- package/dist/features/time-patterns/analyzer.test.js +278 -0
- package/dist/features/time-patterns/index.js +37 -0
- package/dist/features/time-patterns/template.js +109 -0
- package/dist/features/time-patterns/types.js +9 -0
- package/dist/features/velocity/analyzer.js +257 -0
- package/dist/features/velocity/analyzer.test.js +383 -0
- package/dist/features/velocity/index.js +27 -0
- package/dist/features/velocity/template.js +189 -0
- package/dist/features/velocity/types.js +2 -0
- package/dist/generators/html/scripts/knowledge.js +17 -0
- package/dist/generators/html/styles/base.css +8 -3
- package/dist/generators/html/styles/components.css +121 -1
- package/dist/generators/html/styles/knowledge.css +21 -0
- package/dist/generators/html/styles/leaddev.css +108 -48
- package/dist/generators/html/styles/strategic-insights.css +1337 -0
- package/dist/generators/html/templates/commitQualitySection.js +28 -2
- package/dist/generators/html/templates/executiveSummarySection.js +0 -4
- package/dist/generators/html/templates/impactSection.js +8 -6
- package/dist/generators/html/templates/knowledgeSection.js +16 -2
- package/dist/generators/html/templates/velocitySection.js +2 -2
- package/dist/generators/html/types.js +7 -0
- package/dist/generators/html/utils/analysisRunner.js +93 -0
- package/dist/generators/html/utils/cardBuilder.js +47 -0
- package/dist/generators/html/utils/contextBuilder.js +54 -0
- package/dist/generators/html/utils/htmlDocumentBuilder.js +396 -0
- package/dist/generators/html/utils/kpiBuilder.js +76 -0
- package/dist/generators/html/utils/sectionWrapper.js +71 -0
- package/dist/generators/html/utils/styleLoader.js +2 -2
- package/dist/html/analysisRunner.js +93 -0
- package/dist/html/htmlDocumentBuilder.js +396 -0
- package/dist/html/index.js +29 -0
- package/dist/html/shared/colorUtils.js +61 -0
- package/dist/html/shared/commitMapBuilder.js +23 -0
- package/dist/html/shared/components/cardBuilder.js +47 -0
- package/dist/html/shared/components/index.js +18 -0
- package/dist/html/shared/components/kpiBuilder.js +76 -0
- package/dist/html/shared/components/sectionWrapper.js +71 -0
- package/dist/html/shared/contextBuilder.js +54 -0
- package/dist/html/shared/dateRangeCalculator.js +56 -0
- package/dist/html/shared/developerStatsCalculator.js +28 -0
- package/dist/html/shared/index.js +39 -0
- package/dist/html/shared/scriptLoader.js +15 -0
- package/dist/html/shared/scripts/export.js +125 -0
- package/dist/html/shared/scripts/knowledge.js +137 -0
- package/dist/html/shared/scripts/modal.js +68 -0
- package/dist/html/shared/scripts/navigation.js +156 -0
- package/dist/html/shared/scripts/tabs.js +18 -0
- package/dist/html/shared/scripts/tooltip.js +21 -0
- package/dist/html/shared/styleLoader.js +18 -0
- package/dist/html/shared/styles/achievements.css +387 -0
- package/dist/html/shared/styles/base.css +822 -0
- package/dist/html/shared/styles/components.css +1511 -0
- package/dist/html/shared/styles/knowledge.css +242 -0
- package/dist/html/shared/styles/strategic-insights.css +1337 -0
- package/dist/html/shared/weekGrouper.js +27 -0
- package/dist/html/types.js +7 -0
- package/dist/index.js +39 -39
- package/dist/test/helpers/commitFactory.js +166 -0
- package/dist/test/helpers/dateUtils.js +101 -0
- package/dist/test/helpers/index.js +29 -0
- package/dist/test/setup.js +17 -0
- package/dist/test/smoke.test.js +94 -0
- package/dist/types/achievements.js +7 -0
- package/dist/types/analysis.js +7 -0
- package/dist/types/core.js +7 -0
- package/dist/types/index.js +38 -0
- package/dist/types/options.js +7 -0
- package/dist/types/shared.js +7 -0
- package/dist/types/strategic.js +7 -0
- package/dist/types/summary.js +7 -0
- package/dist/utils/achievementDefinitions.js +22 -22
- package/dist/utils/analyzerContextBuilder.js +124 -0
- package/dist/utils/commitQualityAnalyzer.js +13 -2
- package/dist/utils/emptyResults.js +95 -0
- package/dist/utils/fileHotspotAnalyzer.js +4 -12
- package/dist/utils/gapAnalyzer.js +26 -28
- package/dist/utils/gitParser.test.js +363 -0
- package/dist/utils/htmlGenerator.js +62 -466
- package/dist/utils/impactAnalyzer.js +20 -19
- package/dist/utils/knowledgeDistributionAnalyzer.js +32 -27
- package/dist/utils/matrixGenerator.js +13 -13
- package/dist/utils/rangeComparisonAnalyzer.js +2 -2
- package/dist/utils/streakCalculator.js +77 -27
- package/dist/utils/teamAnalyzer.js +20 -1
- package/dist/utils/timePatternAnalyzer.js +18 -3
- package/dist/utils/velocityAnalyzer.js +23 -18
- package/dist/utils/wrappedGenerator.js +8 -8
- package/package.json +74 -64
- package/vitest.config.ts +46 -0
- package/SPECS.md +0 -490
- package/dist/cli.js +0 -24
- package/dist/commands/index.js +0 -24
- package/test-team.txt +0 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.analyzeKnowledgeDistribution = analyzeKnowledgeDistribution;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
+
const emptyResults_1 = require("./emptyResults");
|
|
5
6
|
/**
|
|
6
7
|
* Extract directory from file path
|
|
7
8
|
*/
|
|
@@ -13,18 +14,15 @@ function getDirectory(filePath) {
|
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Calculate bus factor risk score
|
|
16
|
-
* 1 owner = 10 (critical), 2
|
|
17
|
+
* 1 owner = 10 (critical), 2+ = 1 (low/safe)
|
|
18
|
+
* Adjusted for realistic team dynamics where 2+ knowledgeable owners is considered safe
|
|
17
19
|
*/
|
|
18
20
|
function calculateRiskScore(significantOwners) {
|
|
19
21
|
if (significantOwners === 0)
|
|
20
22
|
return 10;
|
|
21
23
|
if (significantOwners === 1)
|
|
22
24
|
return 10;
|
|
23
|
-
|
|
24
|
-
return 7;
|
|
25
|
-
if (significantOwners === 3)
|
|
26
|
-
return 4;
|
|
27
|
-
return 1;
|
|
25
|
+
return 1; // 2+ owners = low risk
|
|
28
26
|
}
|
|
29
27
|
/**
|
|
30
28
|
* Determine ownership type based on contribution percentages
|
|
@@ -151,7 +149,7 @@ function buildFileOwnership(fileStats, directoryPath) {
|
|
|
151
149
|
});
|
|
152
150
|
const ownershipPercentage = Math.round((maxCommits / totalCommits) * 100);
|
|
153
151
|
const knowledgeAge = calculateKnowledgeAge(lastModified);
|
|
154
|
-
const significantOwners = Array.from(data.authors.values()).filter(a => (a.commits / totalCommits) >= 0.
|
|
152
|
+
const significantOwners = Array.from(data.authors.values()).filter(a => (a.commits / totalCommits) >= 0.65).length;
|
|
155
153
|
const busFactorRiskScore = calculateRiskScore(significantOwners);
|
|
156
154
|
const riskLevel = getRiskLevel(busFactorRiskScore);
|
|
157
155
|
files.push({
|
|
@@ -175,11 +173,29 @@ function buildFileOwnership(fileStats, directoryPath) {
|
|
|
175
173
|
.slice(0, 20); // Limit to 20 files per directory
|
|
176
174
|
}
|
|
177
175
|
/**
|
|
178
|
-
*
|
|
176
|
+
* Analyzes knowledge distribution across the codebase to identify bus factor risks.
|
|
177
|
+
*
|
|
178
|
+
* Identifies:
|
|
179
|
+
* - Directory ownership patterns (solo, primary, shared, collaborative)
|
|
180
|
+
* - Bus factor risks (areas with single knowledge holders)
|
|
181
|
+
* - Knowledge silos (directories owned by one person)
|
|
182
|
+
* - Shared knowledge areas (directories with 3+ active contributors)
|
|
183
|
+
*
|
|
184
|
+
* @param commits - Array of commit data to analyze
|
|
185
|
+
* @param repoPath - Optional repository path for deep file-level analysis via git log
|
|
186
|
+
* @param deepAnalysis - Whether to perform file-level analysis (slower but more accurate)
|
|
187
|
+
* @returns Knowledge distribution with ownership data, risks, and recommendations
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* const knowledge = analyzeKnowledgeDistribution(commits, '/path/to/repo', true);
|
|
192
|
+
* console.log(`Bus factor risk: ${knowledge.busFactorRisk.level}`);
|
|
193
|
+
* console.log(`Knowledge silos: ${knowledge.knowledgeSilos.length}`);
|
|
194
|
+
* ```
|
|
179
195
|
*/
|
|
180
196
|
function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
|
|
181
197
|
if (commits.length === 0) {
|
|
182
|
-
return getEmptyKnowledgeDistribution();
|
|
198
|
+
return (0, emptyResults_1.getEmptyKnowledgeDistribution)();
|
|
183
199
|
}
|
|
184
200
|
// For deep analysis, parse git log with file names
|
|
185
201
|
let fileStats = null;
|
|
@@ -262,6 +278,11 @@ function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
|
|
|
262
278
|
let dirCount = 0;
|
|
263
279
|
directoryStats.forEach((authorStats, dirPath) => {
|
|
264
280
|
const totalCommits = Array.from(authorStats.values()).reduce((sum, a) => sum + a.commits, 0);
|
|
281
|
+
// Skip areas with too few commits - not enough data to assess bus factor
|
|
282
|
+
const MIN_COMMITS_FOR_ASSESSMENT = 20;
|
|
283
|
+
if (totalCommits < MIN_COMMITS_FOR_ASSESSMENT) {
|
|
284
|
+
return; // Skip this directory
|
|
285
|
+
}
|
|
265
286
|
// Get the latest and earliest activity date for the directory
|
|
266
287
|
let latestActivity = new Date(0);
|
|
267
288
|
let earliestActivity = new Date();
|
|
@@ -285,8 +306,8 @@ function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
|
|
|
285
306
|
knowledgeAge: calculateKnowledgeAge(stats.lastActive)
|
|
286
307
|
}))
|
|
287
308
|
.sort((a, b) => b.commits - a.commits);
|
|
288
|
-
// Calculate significant owners (>=
|
|
289
|
-
const significantOwners = owners.filter(o => o.percentage >=
|
|
309
|
+
// Calculate significant owners (>= 65% contribution)
|
|
310
|
+
const significantOwners = owners.filter(o => o.percentage >= 65).length;
|
|
290
311
|
const busFactorRisk = calculateRiskScore(significantOwners);
|
|
291
312
|
const ownershipType = determineOwnershipType(owners);
|
|
292
313
|
// Build high-risk files list if deep analysis is enabled
|
|
@@ -362,19 +383,3 @@ function generateRecommendations(data) {
|
|
|
362
383
|
}
|
|
363
384
|
return recommendations.slice(0, 5);
|
|
364
385
|
}
|
|
365
|
-
/**
|
|
366
|
-
* Return empty distribution for no commits
|
|
367
|
-
*/
|
|
368
|
-
function getEmptyKnowledgeDistribution() {
|
|
369
|
-
return {
|
|
370
|
-
directories: [],
|
|
371
|
-
busFactorRisk: {
|
|
372
|
-
overall: 0,
|
|
373
|
-
level: 'low',
|
|
374
|
-
criticalPaths: []
|
|
375
|
-
},
|
|
376
|
-
knowledgeSilos: [],
|
|
377
|
-
sharedKnowledge: [],
|
|
378
|
-
recommendations: ['No commit data available for analysis']
|
|
379
|
-
};
|
|
380
|
-
}
|
|
@@ -6,11 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.generateCommitMatrix = generateCommitMatrix;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const date_fns_1 = require("date-fns");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const achievements_1 = require("../features/achievements");
|
|
10
|
+
const commit_quality_1 = require("../features/commit-quality");
|
|
11
11
|
const fileHotspotAnalyzer_1 = require("./fileHotspotAnalyzer");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const streaks_1 = require("../features/streaks");
|
|
13
|
+
const time_patterns_1 = require("../features/time-patterns");
|
|
14
14
|
/**
|
|
15
15
|
* Calculate dynamic thresholds based on commit distribution for terminal output
|
|
16
16
|
*/
|
|
@@ -53,11 +53,11 @@ function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, r
|
|
|
53
53
|
});
|
|
54
54
|
const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
|
|
55
55
|
// Calculate streaks
|
|
56
|
-
const streakData = (0,
|
|
56
|
+
const streakData = (0, streaks_1.calculateStreaks)(commits, startDate, endDate);
|
|
57
57
|
// Analyze time patterns
|
|
58
|
-
const timePattern = (0,
|
|
58
|
+
const timePattern = (0, time_patterns_1.analyzeTimePatterns)(commits, startDate, endDate);
|
|
59
59
|
// Analyze commit quality
|
|
60
|
-
const commitQuality = (0,
|
|
60
|
+
const commitQuality = (0, commit_quality_1.analyzeCommitQuality)(commits);
|
|
61
61
|
// Analyze file hotspots
|
|
62
62
|
const fileHotspots = (0, fileHotspotAnalyzer_1.analyzeFileHotspots)(repoPath, startDate, endDate);
|
|
63
63
|
// Prepare analysis data for achievements
|
|
@@ -71,7 +71,7 @@ function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, r
|
|
|
71
71
|
dateRange: { start: startDate, end: endDate }
|
|
72
72
|
};
|
|
73
73
|
// Check achievements
|
|
74
|
-
const achievementProgress = (0,
|
|
74
|
+
const achievementProgress = (0, achievements_1.checkAchievements)(analysisData);
|
|
75
75
|
return formatGitHubStyle(commitMap, weekStartDate, weekEndDate, startDate, endDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl);
|
|
76
76
|
}
|
|
77
77
|
function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate, dataEndDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl) {
|
|
@@ -174,7 +174,7 @@ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate,
|
|
|
174
174
|
: 'No active streak';
|
|
175
175
|
output += `${currentStreakEmoji} Current Streak: ${chalk_1.default.bold.green(currentStreakText)}`;
|
|
176
176
|
if (streakData.currentStreak.isActive && streakData.currentStreak.days > 0) {
|
|
177
|
-
output += chalk_1.default.dim(` - ${(0,
|
|
177
|
+
output += chalk_1.default.dim(` - ${(0, streaks_1.getStreakMotivation)(streakData.currentStreak.days, true)}`);
|
|
178
178
|
}
|
|
179
179
|
output += '\n';
|
|
180
180
|
const longestStreakText = streakData.longestStreak.days > 0
|
|
@@ -194,8 +194,8 @@ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate,
|
|
|
194
194
|
`${timePattern.peakHour.hour - 12}:00 PM`;
|
|
195
195
|
output += `⏱️ Peak Hour: ${chalk_1.default.bold.green(peakHourLabel)} (${timePattern.peakHour.commitCount} commits)\n`;
|
|
196
196
|
// Chronotype
|
|
197
|
-
const chronotypeLabel = (0,
|
|
198
|
-
const chronotypeDesc = (0,
|
|
197
|
+
const chronotypeLabel = (0, time_patterns_1.getChronotypeLabel)(timePattern.chronotype);
|
|
198
|
+
const chronotypeDesc = (0, time_patterns_1.getChronotypeDescription)(timePattern.chronotype);
|
|
199
199
|
output += `${chronotypeLabel} ${chalk_1.default.dim(`(${timePattern.chronotypeConfidence.toFixed(0)}% confidence)`)}\n`;
|
|
200
200
|
if (chronotypeDesc) {
|
|
201
201
|
output += chalk_1.default.dim(` ${chronotypeDesc}\n`);
|
|
@@ -217,8 +217,8 @@ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate,
|
|
|
217
217
|
output += '\n';
|
|
218
218
|
output += chalk_1.default.bold.cyan('📝 Commit Quality\n');
|
|
219
219
|
output += chalk_1.default.dim('─'.repeat(50)) + '\n';
|
|
220
|
-
const rating = (0,
|
|
221
|
-
const level = (0,
|
|
220
|
+
const rating = (0, commit_quality_1.getQualityRating)(commitQuality.overallScore);
|
|
221
|
+
const level = (0, commit_quality_1.getQualityLevel)(commitQuality.overallScore);
|
|
222
222
|
output += `📊 Overall Score: ${chalk_1.default.bold.green(commitQuality.overallScore.toFixed(1))}/10 ${rating} ${chalk_1.default.dim(`(${level})`)}\n`;
|
|
223
223
|
output += `✅ Convention: ${chalk_1.default.bold.cyan(commitQuality.conventionalCommits.adherence.toFixed(1))}%`;
|
|
224
224
|
if (commitQuality.conventionalCommits.adherence < 70) {
|
|
@@ -5,7 +5,7 @@ exports.parseDateRange = parseDateRange;
|
|
|
5
5
|
exports.formatComparisonInsights = formatComparisonInsights;
|
|
6
6
|
const date_fns_1 = require("date-fns");
|
|
7
7
|
const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
|
|
8
|
-
const
|
|
8
|
+
const streaks_1 = require("../features/streaks");
|
|
9
9
|
/**
|
|
10
10
|
* Compares metrics between two date ranges to show before/after impact
|
|
11
11
|
*/
|
|
@@ -64,7 +64,7 @@ function calculateRangeMetrics(commits, startDate, endDate, skipBodyCheck) {
|
|
|
64
64
|
const qualityResult = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits, { skipBodyCheck });
|
|
65
65
|
const qualityScore = Math.round(qualityResult.overallScore * 10) / 10;
|
|
66
66
|
// Streak data
|
|
67
|
-
const streakData = (0,
|
|
67
|
+
const streakData = (0, streaks_1.calculateStreaks)(commits, startDate, endDate);
|
|
68
68
|
const longestStreak = streakData.longestStreak.days;
|
|
69
69
|
const averageStreak = streakData.streaks.length > 0
|
|
70
70
|
? Math.round(streakData.streaks.reduce((sum, s) => sum + s.days, 0) / streakData.streaks.length * 10) / 10
|
|
@@ -3,25 +3,61 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.calculateStreaks = calculateStreaks;
|
|
4
4
|
exports.getStreakMotivation = getStreakMotivation;
|
|
5
5
|
const date_fns_1 = require("date-fns");
|
|
6
|
-
|
|
6
|
+
const analyzerContextBuilder_1 = require("./analyzerContextBuilder");
|
|
7
|
+
/**
|
|
8
|
+
* Check if a date is a business day (Monday-Friday)
|
|
9
|
+
*/
|
|
10
|
+
function isBusinessDay(date) {
|
|
11
|
+
const day = (0, date_fns_1.getDay)(date);
|
|
12
|
+
return day !== 0 && day !== 6; // 0 = Sunday, 6 = Saturday
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the previous business day from a given date
|
|
16
|
+
*/
|
|
17
|
+
function getPreviousBusinessDay(date) {
|
|
18
|
+
let prevDate = (0, date_fns_1.subDays)(date, 1);
|
|
19
|
+
while (!isBusinessDay(prevDate)) {
|
|
20
|
+
prevDate = (0, date_fns_1.subDays)(prevDate, 1);
|
|
21
|
+
}
|
|
22
|
+
return prevDate;
|
|
23
|
+
}
|
|
24
|
+
function calculateStreaks(ctxOrCommits, startDate, endDate) {
|
|
25
|
+
// Normalize to internal variables
|
|
26
|
+
let commits;
|
|
27
|
+
let start;
|
|
28
|
+
let end;
|
|
29
|
+
if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
|
|
30
|
+
commits = ctxOrCommits.commits;
|
|
31
|
+
start = ctxOrCommits.dateRange.start;
|
|
32
|
+
end = ctxOrCommits.dateRange.end;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
commits = ctxOrCommits;
|
|
36
|
+
start = startDate;
|
|
37
|
+
end = endDate;
|
|
38
|
+
}
|
|
7
39
|
// Create set of unique commit dates
|
|
8
40
|
const commitDates = new Set();
|
|
9
41
|
commits.forEach(commit => {
|
|
10
42
|
const commitDate = new Date(commit.date);
|
|
11
|
-
if (commitDate >=
|
|
43
|
+
if (commitDate >= start && commitDate <= end) {
|
|
12
44
|
commitDates.add((0, date_fns_1.format)(commitDate, 'yyyy-MM-dd'));
|
|
13
45
|
}
|
|
14
46
|
});
|
|
15
47
|
// Calculate total days and active days
|
|
16
|
-
const totalDays = (0, date_fns_1.differenceInDays)(
|
|
48
|
+
const totalDays = (0, date_fns_1.differenceInDays)(end, start) + 1;
|
|
17
49
|
const totalActiveDays = commitDates.size;
|
|
18
50
|
const activeDayPercentage = totalDays > 0 ? (totalActiveDays / totalDays) * 100 : 0;
|
|
19
|
-
//
|
|
51
|
+
// Calculate business day stats
|
|
52
|
+
const allDays = (0, date_fns_1.eachDayOfInterval)({ start: start, end: end });
|
|
53
|
+
const businessDays = allDays.filter(isBusinessDay);
|
|
54
|
+
const totalBusinessDays = businessDays.length;
|
|
55
|
+
const activeBusinessDays = businessDays.filter(d => commitDates.has((0, date_fns_1.format)(d, 'yyyy-MM-dd'))).length;
|
|
56
|
+
// Find all streaks (business days only)
|
|
20
57
|
const streaks = [];
|
|
21
58
|
let currentStreakDays = 0;
|
|
22
59
|
let currentStreakStart = null;
|
|
23
|
-
const
|
|
24
|
-
for (const date of allDays) {
|
|
60
|
+
for (const date of businessDays) {
|
|
25
61
|
const dateKey = (0, date_fns_1.format)(date, 'yyyy-MM-dd');
|
|
26
62
|
if (commitDates.has(dateKey)) {
|
|
27
63
|
if (currentStreakDays === 0) {
|
|
@@ -31,10 +67,12 @@ function calculateStreaks(commits, startDate, endDate) {
|
|
|
31
67
|
}
|
|
32
68
|
else {
|
|
33
69
|
if (currentStreakDays > 0 && currentStreakStart) {
|
|
70
|
+
// Find the last business day in the streak
|
|
71
|
+
const streakEnd = getPreviousBusinessDay(date);
|
|
34
72
|
streaks.push({
|
|
35
73
|
days: currentStreakDays,
|
|
36
74
|
startDate: currentStreakStart,
|
|
37
|
-
endDate:
|
|
75
|
+
endDate: streakEnd > currentStreakStart ? streakEnd : currentStreakStart
|
|
38
76
|
});
|
|
39
77
|
currentStreakDays = 0;
|
|
40
78
|
currentStreakStart = null;
|
|
@@ -43,54 +81,63 @@ function calculateStreaks(commits, startDate, endDate) {
|
|
|
43
81
|
}
|
|
44
82
|
// Handle streak that extends to end date
|
|
45
83
|
if (currentStreakDays > 0 && currentStreakStart) {
|
|
84
|
+
// Find the last business day in range
|
|
85
|
+
const lastBusinessDay = businessDays[businessDays.length - 1] || end;
|
|
46
86
|
streaks.push({
|
|
47
87
|
days: currentStreakDays,
|
|
48
88
|
startDate: currentStreakStart,
|
|
49
|
-
endDate:
|
|
89
|
+
endDate: lastBusinessDay
|
|
50
90
|
});
|
|
51
91
|
}
|
|
52
92
|
// Find longest streak
|
|
53
93
|
const longestStreak = streaks.length > 0
|
|
54
94
|
? streaks.reduce((longest, current) => current.days > longest.days ? current : longest)
|
|
55
|
-
: { days: 0, startDate:
|
|
56
|
-
// Determine current streak (from today or most recent
|
|
95
|
+
: { days: 0, startDate: start, endDate: start };
|
|
96
|
+
// Determine current streak (from today or most recent business day going backward)
|
|
57
97
|
const today = new Date();
|
|
58
98
|
const todayKey = (0, date_fns_1.format)(today, 'yyyy-MM-dd');
|
|
59
99
|
let currentStreak;
|
|
60
|
-
|
|
61
|
-
|
|
100
|
+
// For current streak, we check today or the last business day
|
|
101
|
+
const checkToday = isBusinessDay(today) ? today : getPreviousBusinessDay(today);
|
|
102
|
+
const checkTodayKey = (0, date_fns_1.format)(checkToday, 'yyyy-MM-dd');
|
|
103
|
+
if (commitDates.has(checkTodayKey)) {
|
|
104
|
+
// Recent business day has a commit, find streak backward through business days only
|
|
62
105
|
let streakDays = 0;
|
|
63
|
-
let checkDate =
|
|
106
|
+
let checkDate = checkToday;
|
|
107
|
+
let streakStartDate = checkToday;
|
|
64
108
|
while (commitDates.has((0, date_fns_1.format)(checkDate, 'yyyy-MM-dd'))) {
|
|
65
109
|
streakDays++;
|
|
66
|
-
|
|
67
|
-
|
|
110
|
+
streakStartDate = checkDate;
|
|
111
|
+
checkDate = getPreviousBusinessDay(checkDate);
|
|
112
|
+
if (checkDate < start)
|
|
68
113
|
break;
|
|
69
114
|
}
|
|
70
115
|
currentStreak = {
|
|
71
116
|
days: streakDays,
|
|
72
|
-
startDate:
|
|
73
|
-
endDate:
|
|
117
|
+
startDate: streakStartDate,
|
|
118
|
+
endDate: checkToday,
|
|
74
119
|
isActive: true
|
|
75
120
|
};
|
|
76
121
|
}
|
|
77
122
|
else {
|
|
78
|
-
// No commit
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
if (commitDates.has(
|
|
123
|
+
// No commit on recent business day, check previous business day
|
|
124
|
+
const prevBusinessDay = getPreviousBusinessDay(checkToday);
|
|
125
|
+
const prevKey = (0, date_fns_1.format)(prevBusinessDay, 'yyyy-MM-dd');
|
|
126
|
+
if (commitDates.has(prevKey)) {
|
|
82
127
|
let streakDays = 0;
|
|
83
|
-
let checkDate =
|
|
128
|
+
let checkDate = prevBusinessDay;
|
|
129
|
+
let streakStartDate = prevBusinessDay;
|
|
84
130
|
while (commitDates.has((0, date_fns_1.format)(checkDate, 'yyyy-MM-dd'))) {
|
|
85
131
|
streakDays++;
|
|
86
|
-
|
|
87
|
-
|
|
132
|
+
streakStartDate = checkDate;
|
|
133
|
+
checkDate = getPreviousBusinessDay(checkDate);
|
|
134
|
+
if (checkDate < start)
|
|
88
135
|
break;
|
|
89
136
|
}
|
|
90
137
|
currentStreak = {
|
|
91
138
|
days: streakDays,
|
|
92
|
-
startDate:
|
|
93
|
-
endDate:
|
|
139
|
+
startDate: streakStartDate,
|
|
140
|
+
endDate: prevBusinessDay,
|
|
94
141
|
isActive: false
|
|
95
142
|
};
|
|
96
143
|
}
|
|
@@ -110,7 +157,10 @@ function calculateStreaks(commits, startDate, endDate) {
|
|
|
110
157
|
totalActiveDays,
|
|
111
158
|
totalDays,
|
|
112
159
|
activeDayPercentage,
|
|
113
|
-
streaks
|
|
160
|
+
streaks,
|
|
161
|
+
// Add business day specific data
|
|
162
|
+
totalBusinessDays,
|
|
163
|
+
activeBusinessDays
|
|
114
164
|
};
|
|
115
165
|
}
|
|
116
166
|
function getStreakMotivation(days, isActive) {
|
|
@@ -110,7 +110,26 @@ function loadTeamFile(filePath) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
-
* Analyzes
|
|
113
|
+
* Analyzes commit patterns and distribution for a team of contributors.
|
|
114
|
+
*
|
|
115
|
+
* Calculates team-level metrics including:
|
|
116
|
+
* - Commit distribution across members (Gini coefficient)
|
|
117
|
+
* - Individual member statistics and rankings
|
|
118
|
+
* - Quality scores and active days
|
|
119
|
+
* - Load balancing insights
|
|
120
|
+
*
|
|
121
|
+
* @param commits - Array of all commit data (will be filtered to team members)
|
|
122
|
+
* @param teamMembers - Array of team member names/patterns to include
|
|
123
|
+
* @param teamName - Optional team name for display
|
|
124
|
+
* @param skipBodyCheck - Whether to skip commit body quality checks (default: true)
|
|
125
|
+
* @returns Team analysis with member breakdown, distribution metrics, and insights
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const team = analyzeTeam(commits, ['Alice', 'Bob', 'Charlie'], 'Backend Team');
|
|
130
|
+
* console.log(`Team commits: ${team.stats.totalCommits}`);
|
|
131
|
+
* console.log(`Distribution: ${team.loadDistribution.assessment}`);
|
|
132
|
+
* ```
|
|
114
133
|
*/
|
|
115
134
|
function analyzeTeam(commits, teamMembers, teamName, skipBodyCheck = true) {
|
|
116
135
|
// Filter commits to team members only
|
|
@@ -4,14 +4,29 @@ exports.analyzeTimePatterns = analyzeTimePatterns;
|
|
|
4
4
|
exports.getChronotypeLabel = getChronotypeLabel;
|
|
5
5
|
exports.getChronotypeDescription = getChronotypeDescription;
|
|
6
6
|
const date_fns_1 = require("date-fns");
|
|
7
|
-
|
|
7
|
+
const analyzerContextBuilder_1 = require("./analyzerContextBuilder");
|
|
8
|
+
function analyzeTimePatterns(ctxOrCommits, startDate, endDate) {
|
|
9
|
+
// Normalize to internal variables
|
|
10
|
+
let commits;
|
|
11
|
+
let start;
|
|
12
|
+
let end;
|
|
13
|
+
if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
|
|
14
|
+
commits = ctxOrCommits.commits;
|
|
15
|
+
start = ctxOrCommits.dateRange.start;
|
|
16
|
+
end = ctxOrCommits.dateRange.end;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
commits = ctxOrCommits;
|
|
20
|
+
start = startDate;
|
|
21
|
+
end = endDate;
|
|
22
|
+
}
|
|
8
23
|
if (commits.length === 0) {
|
|
9
24
|
return getEmptyPattern();
|
|
10
25
|
}
|
|
11
26
|
// Filter commits within the date range
|
|
12
27
|
const filteredCommits = commits.filter(c => {
|
|
13
28
|
const commitDate = new Date(c.date);
|
|
14
|
-
return commitDate >=
|
|
29
|
+
return commitDate >= start && commitDate <= end;
|
|
15
30
|
});
|
|
16
31
|
if (filteredCommits.length === 0) {
|
|
17
32
|
return getEmptyPattern();
|
|
@@ -43,7 +58,7 @@ function analyzeTimePatterns(commits, startDate, endDate) {
|
|
|
43
58
|
const dailyAverage = Array.from(dayMap.entries()).map(([dayIndex, commits]) => ({
|
|
44
59
|
dayOfWeek: dayNames[dayIndex],
|
|
45
60
|
commitCount: commits.length,
|
|
46
|
-
avgCommitsPerDay: commits.length / Math.ceil((
|
|
61
|
+
avgCommitsPerDay: commits.length / Math.ceil((end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000))
|
|
47
62
|
}));
|
|
48
63
|
// 3. Peak hour
|
|
49
64
|
const peakHourEntry = hourly.reduce((max, curr) => curr.commitCount > max.commitCount ? curr : max);
|
|
@@ -3,17 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.analyzeVelocity = analyzeVelocity;
|
|
4
4
|
exports.formatVelocityInsights = formatVelocityInsights;
|
|
5
5
|
const date_fns_1 = require("date-fns");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const emptyResults_1 = require("./emptyResults");
|
|
7
|
+
const analyzerContextBuilder_1 = require("./analyzerContextBuilder");
|
|
8
|
+
function analyzeVelocity(ctxOrCommits, startDate, endDate, rollingWindowWeeks = 4) {
|
|
9
|
+
// Normalize to internal variables
|
|
10
|
+
let commits;
|
|
11
|
+
let start;
|
|
12
|
+
let end;
|
|
13
|
+
let windowWeeks;
|
|
14
|
+
if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
|
|
15
|
+
commits = ctxOrCommits.commits;
|
|
16
|
+
start = ctxOrCommits.dateRange.start;
|
|
17
|
+
end = ctxOrCommits.dateRange.end;
|
|
18
|
+
windowWeeks = (0, analyzerContextBuilder_1.getOption)(ctxOrCommits, 'windowWeeks', 4);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
commits = ctxOrCommits;
|
|
22
|
+
start = startDate;
|
|
23
|
+
end = endDate;
|
|
24
|
+
windowWeeks = rollingWindowWeeks;
|
|
25
|
+
}
|
|
10
26
|
if (commits.length === 0) {
|
|
11
|
-
return getEmptyVelocityAnalysis();
|
|
27
|
+
return (0, emptyResults_1.getEmptyVelocityAnalysis)();
|
|
12
28
|
}
|
|
13
29
|
// Build weekly data points
|
|
14
|
-
const timeline = buildWeeklyTimeline(commits,
|
|
30
|
+
const timeline = buildWeeklyTimeline(commits, start, end);
|
|
15
31
|
// Calculate rolling averages
|
|
16
|
-
calculateRollingAverages(timeline,
|
|
32
|
+
calculateRollingAverages(timeline, windowWeeks);
|
|
17
33
|
// Detect anomalies
|
|
18
34
|
const anomalies = detectAnomalies(timeline);
|
|
19
35
|
// Calculate overall trend
|
|
@@ -210,17 +226,6 @@ function findLowestWeek(timeline) {
|
|
|
210
226
|
const lowest = nonZeroWeeks.reduce((min, dp) => dp.commits < min.commits ? dp : min, nonZeroWeeks[0]);
|
|
211
227
|
return { weekStart: lowest.weekStart, commits: lowest.commits };
|
|
212
228
|
}
|
|
213
|
-
function getEmptyVelocityAnalysis() {
|
|
214
|
-
return {
|
|
215
|
-
timeline: [],
|
|
216
|
-
overallTrend: 'stable',
|
|
217
|
-
trendPercentage: 0,
|
|
218
|
-
averageCommitsPerWeek: 0,
|
|
219
|
-
peakWeek: { weekStart: new Date(), commits: 0 },
|
|
220
|
-
lowestWeek: { weekStart: new Date(), commits: 0 },
|
|
221
|
-
anomalies: [],
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
229
|
/**
|
|
225
230
|
* Format velocity analysis for display
|
|
226
231
|
*/
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateYearWrapped = generateYearWrapped;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const achievements_1 = require("../features/achievements");
|
|
6
|
+
const commit_quality_1 = require("../features/commit-quality");
|
|
7
7
|
const fileHotspotAnalyzer_js_1 = require("./fileHotspotAnalyzer.js");
|
|
8
8
|
const gitParser_js_1 = require("./gitParser.js");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const streaks_1 = require("../features/streaks");
|
|
10
|
+
const time_patterns_1 = require("../features/time-patterns");
|
|
11
11
|
function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
|
|
12
12
|
// Define year boundaries
|
|
13
13
|
const startDate = new Date(`${year}-01-01`);
|
|
@@ -21,9 +21,9 @@ function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
|
|
|
21
21
|
throw new Error(`No commits found for year ${year}`);
|
|
22
22
|
}
|
|
23
23
|
// Calculate all metrics using existing utilities
|
|
24
|
-
const streakData = (0,
|
|
25
|
-
const timePattern = (0,
|
|
26
|
-
const commitQuality = (0,
|
|
24
|
+
const streakData = (0, streaks_1.calculateStreaks)(commits, startDate, endDate);
|
|
25
|
+
const timePattern = (0, time_patterns_1.analyzeTimePatterns)(commits, startDate, endDate);
|
|
26
|
+
const commitQuality = (0, commit_quality_1.analyzeCommitQuality)(commits);
|
|
27
27
|
const fileHotspots = (0, fileHotspotAnalyzer_js_1.analyzeFileHotspots)(repoPath, startDate, endDate);
|
|
28
28
|
// Create AnalysisData for achievement checking
|
|
29
29
|
const analysisData = {
|
|
@@ -38,7 +38,7 @@ function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
|
|
|
38
38
|
end: endDate
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
const achievementProgress = (0,
|
|
41
|
+
const achievementProgress = (0, achievements_1.checkAchievements)(analysisData);
|
|
42
42
|
// Calculate overview
|
|
43
43
|
const overview = {
|
|
44
44
|
totalCommits: commits.length,
|