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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTML document builder - assembles the final HTML document.
|
|
4
|
+
*
|
|
5
|
+
* @module html/htmlDocumentBuilder
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.buildHtmlDocument = buildHtmlDocument;
|
|
9
|
+
const date_fns_1 = require("date-fns");
|
|
10
|
+
const package_json_1 = require("../../package.json");
|
|
11
|
+
const styleLoader_1 = require("./shared/styleLoader");
|
|
12
|
+
const scriptLoader_1 = require("./shared/scriptLoader");
|
|
13
|
+
const kpiBuilder_1 = require("./shared/components/kpiBuilder");
|
|
14
|
+
const sectionWrapper_1 = require("./shared/components/sectionWrapper");
|
|
15
|
+
const weekGrouper_1 = require("./shared/weekGrouper");
|
|
16
|
+
const colorUtils_1 = require("./shared/colorUtils");
|
|
17
|
+
const contribution_graph_1 = require("../features/contribution-graph");
|
|
18
|
+
const streaks_1 = require("../features/streaks");
|
|
19
|
+
const time_patterns_1 = require("../features/time-patterns");
|
|
20
|
+
const commit_quality_1 = require("../features/commit-quality");
|
|
21
|
+
const achievements_1 = require("../features/achievements");
|
|
22
|
+
const impact_1 = require("../features/impact");
|
|
23
|
+
const knowledge_1 = require("../features/knowledge");
|
|
24
|
+
const velocity_1 = require("../features/velocity");
|
|
25
|
+
const gaps_1 = require("../features/gaps");
|
|
26
|
+
const comparison_1 = require("../features/comparison");
|
|
27
|
+
const executive_summary_1 = require("../features/executive-summary");
|
|
28
|
+
const team_1 = require("../features/team");
|
|
29
|
+
const events_1 = require("../features/events");
|
|
30
|
+
/**
|
|
31
|
+
* Format date range for display.
|
|
32
|
+
*/
|
|
33
|
+
function formatDateRange(start, end) {
|
|
34
|
+
return `${(0, date_fns_1.format)(start, 'MMM d, yyyy')} – ${(0, date_fns_1.format)(end, 'MMM d, yyyy')}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the overall contribution graph with month labels.
|
|
38
|
+
*/
|
|
39
|
+
function buildOverallContributionGraph(context, results) {
|
|
40
|
+
const { dateRange } = context;
|
|
41
|
+
const { commitMap, commitDetailsMap } = results.overall;
|
|
42
|
+
const weeks = (0, weekGrouper_1.groupDaysIntoWeeks)(dateRange.weekStart, dateRange.weekEnd);
|
|
43
|
+
const dayLabels = (0, weekGrouper_1.getDayLabels)();
|
|
44
|
+
const getColor = (0, colorUtils_1.createDynamicColorFn)(commitMap);
|
|
45
|
+
// Build month labels
|
|
46
|
+
let monthLabelsHtml = '';
|
|
47
|
+
let currentMonth = '';
|
|
48
|
+
weeks.forEach((week) => {
|
|
49
|
+
const firstDayOfWeek = week[0];
|
|
50
|
+
const monthName = (0, date_fns_1.format)(firstDayOfWeek, 'MMM');
|
|
51
|
+
if (monthName !== currentMonth) {
|
|
52
|
+
monthLabelsHtml += `<div class="month-label">${monthName}</div>`;
|
|
53
|
+
currentMonth = monthName;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
monthLabelsHtml += `<div class="month-label"></div>`;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Build week columns
|
|
60
|
+
let weekColumns = '';
|
|
61
|
+
weeks.forEach((week) => {
|
|
62
|
+
let weekColumn = `<div class="graph-column">`;
|
|
63
|
+
week.forEach((day) => {
|
|
64
|
+
const dateKey = (0, date_fns_1.format)(day, 'yyyy-MM-dd');
|
|
65
|
+
const count = commitMap.get(dateKey) || 0;
|
|
66
|
+
const isInRange = day >= dateRange.start && day <= dateRange.end;
|
|
67
|
+
const color = isInRange ? getColor(count) : 'transparent';
|
|
68
|
+
const dateStr = (0, date_fns_1.format)(day, 'MMM d, yyyy');
|
|
69
|
+
const emptyClass = count === 0 ? ' empty' : '';
|
|
70
|
+
const clickable = count > 0 ? ' clickable' : '';
|
|
71
|
+
const rawCommits = commitDetailsMap.get(dateKey) || [];
|
|
72
|
+
const sanitizedCommits = rawCommits.map(c => ({
|
|
73
|
+
...c,
|
|
74
|
+
message: c.message
|
|
75
|
+
.replace(/[\r\n]+/g, ' ')
|
|
76
|
+
.replace(/'/g, ''')
|
|
77
|
+
.replace(/"/g, '"')
|
|
78
|
+
.replace(/</g, '<')
|
|
79
|
+
.replace(/>/g, '>')
|
|
80
|
+
.substring(0, 200)
|
|
81
|
+
}));
|
|
82
|
+
const detailsData = count > 0 ? `data-details='${JSON.stringify(sanitizedCommits)}'` : '';
|
|
83
|
+
weekColumn += `<div class="day${emptyClass}${clickable}" style="background-color: ${color};" data-count="${count}" data-date="${dateStr}" ${detailsData}></div>`;
|
|
84
|
+
});
|
|
85
|
+
weekColumn += '</div>';
|
|
86
|
+
weekColumns += weekColumn;
|
|
87
|
+
});
|
|
88
|
+
// Build day labels
|
|
89
|
+
let dayLabelsHtml = '<div class="day-labels">';
|
|
90
|
+
dayLabels.forEach((label, index) => {
|
|
91
|
+
const displayLabel = [1, 3, 5].includes(index) ? label : '';
|
|
92
|
+
dayLabelsHtml += `<div class="day-label">${displayLabel}</div>`;
|
|
93
|
+
});
|
|
94
|
+
dayLabelsHtml += '</div>';
|
|
95
|
+
return `
|
|
96
|
+
<div class="graph-container">
|
|
97
|
+
<div class="months">${monthLabelsHtml}</div>
|
|
98
|
+
<div class="graph">
|
|
99
|
+
${dayLabelsHtml}
|
|
100
|
+
${weekColumns}
|
|
101
|
+
</div>
|
|
102
|
+
<div class="legend">
|
|
103
|
+
<span>Less</span>
|
|
104
|
+
<span class="legend-box" style="background-color: rgba(235, 237, 240, 0.1); border: 1px solid #21262d;"></span>
|
|
105
|
+
<span class="legend-box" style="background-color: #9be9a8;"></span>
|
|
106
|
+
<span class="legend-box" style="background-color: #40c463;"></span>
|
|
107
|
+
<span class="legend-box" style="background-color: #30a14e;"></span>
|
|
108
|
+
<span class="legend-box" style="background-color: #216e39;"></span>
|
|
109
|
+
<span>More</span>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build developer breakdown section.
|
|
116
|
+
*/
|
|
117
|
+
function buildDeveloperSection(developerStats, totalCommits) {
|
|
118
|
+
const sortedDevelopers = Array.from(developerStats.entries())
|
|
119
|
+
.sort((a, b) => b[1].commits - a[1].commits);
|
|
120
|
+
const developerAnalysisHtml = sortedDevelopers.map(([author, stats]) => {
|
|
121
|
+
const percentage = ((stats.commits / totalCommits) * 100).toFixed(1);
|
|
122
|
+
const dateRange = stats.firstCommit.toDateString() === stats.lastCommit.toDateString()
|
|
123
|
+
? (0, date_fns_1.format)(stats.firstCommit, 'MMM d, yyyy')
|
|
124
|
+
: `${(0, date_fns_1.format)(stats.firstCommit, 'MMM d, yyyy')} - ${(0, date_fns_1.format)(stats.lastCommit, 'MMM d, yyyy')}`;
|
|
125
|
+
return `
|
|
126
|
+
<div class="developer-row">
|
|
127
|
+
<div class="developer-info">
|
|
128
|
+
<div class="developer-name">${author}</div>
|
|
129
|
+
<div class="developer-date-range">${dateRange}</div>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="developer-stats">
|
|
132
|
+
<div class="developer-bar-container">
|
|
133
|
+
<div class="developer-bar" style="width: ${percentage}%"></div>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="developer-count">${stats.commits} commits (${percentage}%)</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
`;
|
|
139
|
+
}).join('');
|
|
140
|
+
return `<div class="developer-analysis">${developerAnalysisHtml}</div>`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build personal tab content.
|
|
144
|
+
*/
|
|
145
|
+
function buildPersonalContent(context, results, skipBodyCheck) {
|
|
146
|
+
const { dateRange, currentUser } = context;
|
|
147
|
+
const { personal } = results;
|
|
148
|
+
const weeks = (0, weekGrouper_1.groupDaysIntoWeeks)(dateRange.weekStart, dateRange.weekEnd);
|
|
149
|
+
const dayLabels = (0, weekGrouper_1.getDayLabels)();
|
|
150
|
+
const kpiHeader = (0, kpiBuilder_1.buildKPIHeader)(personal.totalCommits, personal.commitQuality.overallScore, personal.streakData.currentStreak.days, personal.streakData.activeDayPercentage);
|
|
151
|
+
const contributionGraphHtml = (0, contribution_graph_1.buildContributionGraph)({
|
|
152
|
+
commitMap: personal.commitMap,
|
|
153
|
+
commitDetailsMap: personal.commitDetailsMap,
|
|
154
|
+
weeks,
|
|
155
|
+
dayLabels,
|
|
156
|
+
dataStartDate: dateRange.start,
|
|
157
|
+
dataEndDate: dateRange.end,
|
|
158
|
+
totalCommits: personal.totalCommits,
|
|
159
|
+
title: 'Your contributions',
|
|
160
|
+
getColorFn: (0, colorUtils_1.createDynamicColorFn)(personal.commitMap)
|
|
161
|
+
});
|
|
162
|
+
const streakSectionHtml = (0, streaks_1.buildStreakSection)(personal.streakData);
|
|
163
|
+
const timePatternsHtml = (0, time_patterns_1.buildTimePatternsSection)(personal.timePattern);
|
|
164
|
+
const commitQualityHtml = (0, commit_quality_1.buildCommitQualitySection)(personal.commitQuality, personal.totalCommits, skipBodyCheck);
|
|
165
|
+
const achievementsHtml = (0, achievements_1.buildAchievementsSection)(personal.achievementProgress);
|
|
166
|
+
return `
|
|
167
|
+
${kpiHeader}
|
|
168
|
+
${(0, sectionWrapper_1.wrapInSection)('personal-overview', 'Contribution Overview', contributionGraphHtml)}
|
|
169
|
+
${(0, sectionWrapper_1.wrapInSection)('personal-streaks', 'Streak Analysis', streakSectionHtml)}
|
|
170
|
+
${(0, sectionWrapper_1.wrapInSection)('personal-patterns', 'Time Patterns', timePatternsHtml)}
|
|
171
|
+
${(0, sectionWrapper_1.wrapInSection)('personal-quality', 'Commit Quality', commitQualityHtml)}
|
|
172
|
+
${(0, sectionWrapper_1.wrapInSection)('personal-achievements', 'Achievements', achievementsHtml)}
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build the overall tab content.
|
|
177
|
+
*/
|
|
178
|
+
function buildOverallContent(context, results) {
|
|
179
|
+
const { developerStats } = context;
|
|
180
|
+
const { overall, impactAnalysis, knowledgeDistribution } = results;
|
|
181
|
+
const kpiHeader = (0, kpiBuilder_1.buildKPIHeader)(overall.totalCommits, overall.commitQuality.overallScore, overall.streakData.currentStreak.days, overall.streakData.activeDayPercentage);
|
|
182
|
+
const contributionGraphHtml = buildOverallContributionGraph(context, results);
|
|
183
|
+
const developerSectionHtml = buildDeveloperSection(developerStats, overall.totalCommits);
|
|
184
|
+
return `
|
|
185
|
+
${kpiHeader}
|
|
186
|
+
${(0, sectionWrapper_1.wrapInSection)('contributions', 'Contribution Activity', contributionGraphHtml)}
|
|
187
|
+
${(0, sectionWrapper_1.wrapInSection)('team-developers', 'Developer Contributions', developerSectionHtml)}
|
|
188
|
+
${impactAnalysis ? (0, sectionWrapper_1.wrapInSection)('impact', 'Developer Impact', (0, impact_1.buildImpactSection)(impactAnalysis)) : ''}
|
|
189
|
+
${knowledgeDistribution ? (0, sectionWrapper_1.wrapInSection)('knowledge', 'Knowledge Distribution', (0, knowledge_1.buildKnowledgeSection)(knowledgeDistribution)) : ''}
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Build the strategic insights (lead dev) tab content.
|
|
194
|
+
*/
|
|
195
|
+
function buildLeadDevContent(results) {
|
|
196
|
+
const { leadDev } = results;
|
|
197
|
+
if (!leadDev)
|
|
198
|
+
return '';
|
|
199
|
+
const hasContent = leadDev.velocityAnalysis || leadDev.gapAnalysis ||
|
|
200
|
+
leadDev.rangeComparison || leadDev.executiveSummary ||
|
|
201
|
+
leadDev.teamAnalysis || (leadDev.events && leadDev.events.length > 0);
|
|
202
|
+
if (!hasContent)
|
|
203
|
+
return '';
|
|
204
|
+
return `
|
|
205
|
+
${leadDev.executiveSummary ? (0, sectionWrapper_1.wrapInSection)('executive-summary', 'Executive Summary', (0, executive_summary_1.buildExecutiveSummarySection)(leadDev.executiveSummary), '📋') : ''}
|
|
206
|
+
${leadDev.velocityAnalysis ? (0, sectionWrapper_1.wrapInSection)('velocity', 'Velocity Timeline', (0, velocity_1.buildVelocitySection)(leadDev.velocityAnalysis), '📈') : ''}
|
|
207
|
+
${leadDev.gapAnalysis ? (0, sectionWrapper_1.wrapInSection)('gaps', 'Gap Analysis', (0, gaps_1.buildGapSection)(leadDev.gapAnalysis), '🚧') : ''}
|
|
208
|
+
${leadDev.rangeComparison ? (0, sectionWrapper_1.wrapInSection)('comparison', 'Period Comparison', (0, comparison_1.buildComparisonSection)(leadDev.rangeComparison), '⚖️') : ''}
|
|
209
|
+
${leadDev.teamAnalysis ? (0, sectionWrapper_1.wrapInSection)('team-analysis', 'Team Analysis', (0, team_1.buildTeamSection)(leadDev.teamAnalysis), '👥') : ''}
|
|
210
|
+
${leadDev.events?.length ? (0, sectionWrapper_1.wrapInSection)('events', 'Event Timeline', (0, events_1.buildEventsSection)(leadDev.events), '📅') : ''}
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Build export data for JSON/CSV export.
|
|
215
|
+
*/
|
|
216
|
+
function buildExportData(context, results) {
|
|
217
|
+
const { repoInfo, currentUser, dateRange, developerStats } = context;
|
|
218
|
+
const { overall } = results;
|
|
219
|
+
return {
|
|
220
|
+
repository: repoInfo.name,
|
|
221
|
+
period: {
|
|
222
|
+
start: dateRange.start.toISOString(),
|
|
223
|
+
end: dateRange.end.toISOString()
|
|
224
|
+
},
|
|
225
|
+
author: currentUser,
|
|
226
|
+
summary: {
|
|
227
|
+
totalCommits: overall.totalCommits,
|
|
228
|
+
totalContributors: developerStats.size,
|
|
229
|
+
activeDays: overall.commitMap.size
|
|
230
|
+
},
|
|
231
|
+
streaks: {
|
|
232
|
+
currentStreak: overall.streakData.currentStreak.days,
|
|
233
|
+
longestStreak: overall.streakData.longestStreak.days,
|
|
234
|
+
totalActiveDays: overall.streakData.totalActiveDays,
|
|
235
|
+
activeDayPercentage: overall.streakData.activeDayPercentage
|
|
236
|
+
},
|
|
237
|
+
quality: {
|
|
238
|
+
overallScore: overall.commitQuality.overallScore,
|
|
239
|
+
conventionalAdherence: overall.commitQuality.conventionalCommits.adherence,
|
|
240
|
+
avgSubjectLength: overall.commitQuality.subjectQuality.avgLength
|
|
241
|
+
},
|
|
242
|
+
timePattern: {
|
|
243
|
+
chronotype: overall.timePattern.chronotype,
|
|
244
|
+
peakHour: overall.timePattern.peakHour.hour,
|
|
245
|
+
consistency: overall.timePattern.consistency?.score || 0
|
|
246
|
+
},
|
|
247
|
+
commits: Array.from(overall.commitDetailsMap.values()).flat().map((c) => ({
|
|
248
|
+
date: c.date,
|
|
249
|
+
author: c.author,
|
|
250
|
+
message: c.message.split('\n')[0],
|
|
251
|
+
hash: c.hash
|
|
252
|
+
})),
|
|
253
|
+
developerStats: Array.from(developerStats.entries()).map(([name, stats]) => ({
|
|
254
|
+
name,
|
|
255
|
+
commits: stats.commits,
|
|
256
|
+
firstCommit: stats.firstCommit.toISOString(),
|
|
257
|
+
lastCommit: stats.lastCommit.toISOString()
|
|
258
|
+
}))
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Build the complete HTML document.
|
|
263
|
+
*/
|
|
264
|
+
function buildHtmlDocument(context, results, skipBodyCheck = false) {
|
|
265
|
+
const { repoInfo, currentUser, dateRange } = context;
|
|
266
|
+
const { leadDev } = results;
|
|
267
|
+
const hasLeadDevContent = leadDev && (leadDev.velocityAnalysis || leadDev.gapAnalysis ||
|
|
268
|
+
leadDev.rangeComparison || leadDev.executiveSummary ||
|
|
269
|
+
leadDev.teamAnalysis || (leadDev.events && leadDev.events.length > 0));
|
|
270
|
+
const overallContent = buildOverallContent(context, results);
|
|
271
|
+
const personalContent = buildPersonalContent(context, results, skipBodyCheck);
|
|
272
|
+
const leadDevContent = hasLeadDevContent ? buildLeadDevContent(results) : '';
|
|
273
|
+
const exportData = buildExportData(context, results);
|
|
274
|
+
return `<!DOCTYPE html>
|
|
275
|
+
<html lang="en">
|
|
276
|
+
<head>
|
|
277
|
+
<meta charset="UTF-8">
|
|
278
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
279
|
+
<title>Git Wrapped - ${repoInfo.name}</title>
|
|
280
|
+
<style>
|
|
281
|
+
${(0, styleLoader_1.loadStyles)()}
|
|
282
|
+
</style>
|
|
283
|
+
</head>
|
|
284
|
+
<body>
|
|
285
|
+
<!-- Mobile Navigation Toggle -->
|
|
286
|
+
<button class="mobile-nav-toggle" aria-label="Toggle navigation">
|
|
287
|
+
<span class="hamburger-icon"></span>
|
|
288
|
+
</button>
|
|
289
|
+
|
|
290
|
+
<div class="dashboard-layout">
|
|
291
|
+
<!-- Sidebar Navigation -->
|
|
292
|
+
<nav class="sidebar" role="navigation" aria-label="Main navigation">
|
|
293
|
+
<div class="sidebar-header">
|
|
294
|
+
<h2>Repo-Wrapped</h2>
|
|
295
|
+
<span class="version">v${package_json_1.version}</span>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<div class="nav-section">
|
|
299
|
+
<div class="nav-section-title">Views</div>
|
|
300
|
+
<a href="#" class="nav-item active" data-tab="overall">
|
|
301
|
+
<span class="nav-label">Team Overview</span>
|
|
302
|
+
</a>
|
|
303
|
+
<a href="#" class="nav-item" data-tab="personal">
|
|
304
|
+
<span class="nav-label">Personal Stats</span>
|
|
305
|
+
</a>
|
|
306
|
+
${hasLeadDevContent ? `
|
|
307
|
+
<a href="#" class="nav-item" data-tab="leaddev">
|
|
308
|
+
<span class="nav-label">Strategic Insights</span>
|
|
309
|
+
</a>
|
|
310
|
+
` : ''}
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<div class="sidebar-footer">
|
|
314
|
+
<div class="repo-info">
|
|
315
|
+
<span class="repo-name">${repoInfo.name}</span>
|
|
316
|
+
${repoInfo.url ? `<a href="${repoInfo.url}" target="_blank" class="repo-link">View Repository →</a>` : ''}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</nav>
|
|
320
|
+
|
|
321
|
+
<!-- Main Content Area -->
|
|
322
|
+
<main class="main-content">
|
|
323
|
+
<div class="container">
|
|
324
|
+
<header class="dashboard-header">
|
|
325
|
+
<div class="header-left">
|
|
326
|
+
<h1 class="repo-name">${repoInfo.name}</h1>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="header-right">
|
|
329
|
+
<div class="export-controls">
|
|
330
|
+
<button class="btn btn-secondary btn-sm" data-export="json">Export JSON</button>
|
|
331
|
+
<button class="btn btn-secondary btn-sm" data-export="csv">Export CSV</button>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</header>
|
|
335
|
+
|
|
336
|
+
<div class="filter-bar">
|
|
337
|
+
<div class="filter-group">
|
|
338
|
+
<label class="filter-label">Period</label>
|
|
339
|
+
<span class="filter-value">${formatDateRange(dateRange.start, dateRange.end)}</span>
|
|
340
|
+
</div>
|
|
341
|
+
<div class="filter-group">
|
|
342
|
+
<label class="filter-label">Author</label>
|
|
343
|
+
<span class="filter-value">${currentUser !== 'Unknown' ? currentUser : 'All Contributors'}</span>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<!-- Tab Navigation -->
|
|
348
|
+
<div class="tab-nav">
|
|
349
|
+
<button class="tab-button active" data-tab="overall">📊 Team Overview</button>
|
|
350
|
+
<button class="tab-button" data-tab="personal">👤 Personal</button>
|
|
351
|
+
${hasLeadDevContent ? '<button class="tab-button" data-tab="leaddev">📊 Strategic Insights</button>' : ''}
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<!-- Overall Tab Content -->
|
|
355
|
+
<div class="tab-content active" id="overall-content">
|
|
356
|
+
${overallContent}
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
${hasLeadDevContent ? `
|
|
360
|
+
<!-- Lead Developer Tab Content -->
|
|
361
|
+
<div class="tab-content" id="leaddev-content">
|
|
362
|
+
${leadDevContent}
|
|
363
|
+
</div>
|
|
364
|
+
` : ''}
|
|
365
|
+
|
|
366
|
+
<!-- Personal Tab Content -->
|
|
367
|
+
<div class="tab-content" id="personal-content">
|
|
368
|
+
${personalContent}
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
</div>
|
|
372
|
+
</main>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<div class="tooltip" id="tooltip"></div>
|
|
376
|
+
|
|
377
|
+
<div class="modal" id="modal">
|
|
378
|
+
<div class="modal-content">
|
|
379
|
+
<div class="modal-header">
|
|
380
|
+
<div class="modal-title" id="modal-title"></div>
|
|
381
|
+
<button class="modal-close" id="modal-close">×</button>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="commit-list" id="commit-list"></div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<script>
|
|
388
|
+
window.__GITWRAPPED_DATA__ = ${JSON.stringify(exportData)};
|
|
389
|
+
</script>
|
|
390
|
+
|
|
391
|
+
<script>
|
|
392
|
+
${(0, scriptLoader_1.loadScripts)()}
|
|
393
|
+
</script>
|
|
394
|
+
</body>
|
|
395
|
+
</html>`;
|
|
396
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.buildHtmlContext = exports.runAllAnalyzers = exports.buildHtmlDocument = void 0;
|
|
18
|
+
/**
|
|
19
|
+
* HTML Generation Module
|
|
20
|
+
* @module html
|
|
21
|
+
*/
|
|
22
|
+
__exportStar(require("./types"), exports);
|
|
23
|
+
__exportStar(require("./shared"), exports);
|
|
24
|
+
var htmlDocumentBuilder_1 = require("./htmlDocumentBuilder");
|
|
25
|
+
Object.defineProperty(exports, "buildHtmlDocument", { enumerable: true, get: function () { return htmlDocumentBuilder_1.buildHtmlDocument; } });
|
|
26
|
+
var analysisRunner_1 = require("./analysisRunner");
|
|
27
|
+
Object.defineProperty(exports, "runAllAnalyzers", { enumerable: true, get: function () { return analysisRunner_1.runAllAnalyzers; } });
|
|
28
|
+
var contextBuilder_1 = require("./shared/contextBuilder");
|
|
29
|
+
Object.defineProperty(exports, "buildHtmlContext", { enumerable: true, get: function () { return contextBuilder_1.buildHtmlContext; } });
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GITHUB_COLORS = void 0;
|
|
4
|
+
exports.calculateDynamicThresholds = calculateDynamicThresholds;
|
|
5
|
+
exports.createDynamicColorFn = createDynamicColorFn;
|
|
6
|
+
exports.getColor = getColor;
|
|
7
|
+
const constants_1 = require("../../constants");
|
|
8
|
+
Object.defineProperty(exports, "GITHUB_COLORS", { enumerable: true, get: function () { return constants_1.GITHUB_COLORS; } });
|
|
9
|
+
/**
|
|
10
|
+
* Calculate dynamic thresholds based on the repository's commit distribution.
|
|
11
|
+
* Uses percentiles to ensure colors adapt to the repo's activity level.
|
|
12
|
+
*/
|
|
13
|
+
function calculateDynamicThresholds(commitMap) {
|
|
14
|
+
// Get all non-zero counts
|
|
15
|
+
const counts = Array.from(commitMap.values()).filter(c => c > 0);
|
|
16
|
+
if (counts.length === 0) {
|
|
17
|
+
return { l1: 1, l2: 2, l3: 3 };
|
|
18
|
+
}
|
|
19
|
+
counts.sort((a, b) => a - b);
|
|
20
|
+
const percentile = (p) => {
|
|
21
|
+
const index = Math.ceil((p / 100) * counts.length) - 1;
|
|
22
|
+
return counts[Math.max(0, index)];
|
|
23
|
+
};
|
|
24
|
+
// Use 25th, 50th, 75th percentiles for thresholds
|
|
25
|
+
const l1 = Math.max(1, percentile(25));
|
|
26
|
+
const l2 = Math.max(l1 + 1, percentile(50));
|
|
27
|
+
const l3 = Math.max(l2 + 1, percentile(75));
|
|
28
|
+
return { l1, l2, l3 };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a color function with dynamic thresholds based on commit data
|
|
32
|
+
*/
|
|
33
|
+
function createDynamicColorFn(commitMap) {
|
|
34
|
+
const { l1, l2, l3 } = calculateDynamicThresholds(commitMap);
|
|
35
|
+
return (count) => {
|
|
36
|
+
if (count === 0)
|
|
37
|
+
return constants_1.GITHUB_COLORS.EMPTY;
|
|
38
|
+
if (count <= l1)
|
|
39
|
+
return constants_1.GITHUB_COLORS.LEVEL_1;
|
|
40
|
+
if (count <= l2)
|
|
41
|
+
return constants_1.GITHUB_COLORS.LEVEL_2;
|
|
42
|
+
if (count <= l3)
|
|
43
|
+
return constants_1.GITHUB_COLORS.LEVEL_3;
|
|
44
|
+
return constants_1.GITHUB_COLORS.LEVEL_4;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gets the color for a given contribution count (uses fixed thresholds - legacy)
|
|
49
|
+
* @deprecated Use createDynamicColorFn for better scaling
|
|
50
|
+
*/
|
|
51
|
+
function getColor(count) {
|
|
52
|
+
if (count === 0)
|
|
53
|
+
return constants_1.GITHUB_COLORS.EMPTY;
|
|
54
|
+
if (count <= 3)
|
|
55
|
+
return constants_1.GITHUB_COLORS.LEVEL_1;
|
|
56
|
+
if (count <= 6)
|
|
57
|
+
return constants_1.GITHUB_COLORS.LEVEL_2;
|
|
58
|
+
if (count <= 9)
|
|
59
|
+
return constants_1.GITHUB_COLORS.LEVEL_3;
|
|
60
|
+
return constants_1.GITHUB_COLORS.LEVEL_4;
|
|
61
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCommitMaps = createCommitMaps;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
/**
|
|
6
|
+
* Creates commit maps from an array of commits within a date range
|
|
7
|
+
*/
|
|
8
|
+
function createCommitMaps(commits, startDate, endDate) {
|
|
9
|
+
const commitMap = new Map();
|
|
10
|
+
const commitDetailsMap = new Map();
|
|
11
|
+
commits.forEach(commit => {
|
|
12
|
+
const commitDate = new Date(commit.date);
|
|
13
|
+
if (commitDate >= startDate && commitDate <= endDate) {
|
|
14
|
+
const dateKey = (0, date_fns_1.format)(commitDate, 'yyyy-MM-dd');
|
|
15
|
+
commitMap.set(dateKey, (commitMap.get(dateKey) || 0) + 1);
|
|
16
|
+
if (!commitDetailsMap.has(dateKey)) {
|
|
17
|
+
commitDetailsMap.set(dateKey, []);
|
|
18
|
+
}
|
|
19
|
+
commitDetailsMap.get(dateKey).push(commit);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return { commitMap, commitDetailsMap };
|
|
23
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared utility for building consistent stat cards across templates
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildStatCard = buildStatCard;
|
|
7
|
+
exports.buildStatGrid = buildStatGrid;
|
|
8
|
+
exports.buildCardSection = buildCardSection;
|
|
9
|
+
/**
|
|
10
|
+
* Builds a single stat card with consistent styling
|
|
11
|
+
*/
|
|
12
|
+
function buildStatCard(options) {
|
|
13
|
+
const { icon, value, label, detail, className = '', valueClass = '' } = options;
|
|
14
|
+
return `
|
|
15
|
+
<div class="stat-card-base ${className}">
|
|
16
|
+
${icon ? `<div class="stat-icon">${icon}</div>` : ''}
|
|
17
|
+
<div class="stat-info">
|
|
18
|
+
<div class="stat-label">${label}</div>
|
|
19
|
+
<div class="stat-value ${valueClass}">${value}</div>
|
|
20
|
+
${detail ? `<div class="stat-detail">${detail}</div>` : ''}
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds a grid of stat cards
|
|
27
|
+
*/
|
|
28
|
+
function buildStatGrid(options) {
|
|
29
|
+
return `
|
|
30
|
+
<div class="stat-grid ${options.className || ''}">
|
|
31
|
+
${options.cards.map(card => buildStatCard(card)).join('')}
|
|
32
|
+
</div>
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Builds a card section with title and content
|
|
37
|
+
*/
|
|
38
|
+
function buildCardSection(options) {
|
|
39
|
+
const { title, content, className = '', icon } = options;
|
|
40
|
+
const titleContent = icon ? `${icon} ${title}` : title;
|
|
41
|
+
return `
|
|
42
|
+
<div class="card-section ${className}">
|
|
43
|
+
<h3>${titleContent}</h3>
|
|
44
|
+
${content}
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wrapInSection = exports.buildOverallKPIHeader = exports.buildKPIHeaderFromData = exports.buildKPIHeader = exports.calculateHealthScore = exports.buildCardSection = exports.buildStatGrid = exports.buildStatCard = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Shared HTML Components
|
|
6
|
+
* @module html/shared/components
|
|
7
|
+
*/
|
|
8
|
+
var cardBuilder_1 = require("./cardBuilder");
|
|
9
|
+
Object.defineProperty(exports, "buildStatCard", { enumerable: true, get: function () { return cardBuilder_1.buildStatCard; } });
|
|
10
|
+
Object.defineProperty(exports, "buildStatGrid", { enumerable: true, get: function () { return cardBuilder_1.buildStatGrid; } });
|
|
11
|
+
Object.defineProperty(exports, "buildCardSection", { enumerable: true, get: function () { return cardBuilder_1.buildCardSection; } });
|
|
12
|
+
var kpiBuilder_1 = require("./kpiBuilder");
|
|
13
|
+
Object.defineProperty(exports, "calculateHealthScore", { enumerable: true, get: function () { return kpiBuilder_1.calculateHealthScore; } });
|
|
14
|
+
Object.defineProperty(exports, "buildKPIHeader", { enumerable: true, get: function () { return kpiBuilder_1.buildKPIHeader; } });
|
|
15
|
+
Object.defineProperty(exports, "buildKPIHeaderFromData", { enumerable: true, get: function () { return kpiBuilder_1.buildKPIHeaderFromData; } });
|
|
16
|
+
Object.defineProperty(exports, "buildOverallKPIHeader", { enumerable: true, get: function () { return kpiBuilder_1.buildOverallKPIHeader; } });
|
|
17
|
+
var sectionWrapper_1 = require("./sectionWrapper");
|
|
18
|
+
Object.defineProperty(exports, "wrapInSection", { enumerable: true, get: function () { return sectionWrapper_1.wrapInSection; } });
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* KPI header building utilities.
|
|
4
|
+
*
|
|
5
|
+
* @module html/shared/components/kpiBuilder
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.calculateHealthScore = calculateHealthScore;
|
|
9
|
+
exports.buildKPIHeader = buildKPIHeader;
|
|
10
|
+
exports.buildKPIHeaderFromData = buildKPIHeaderFromData;
|
|
11
|
+
exports.buildOverallKPIHeader = buildOverallKPIHeader;
|
|
12
|
+
/**
|
|
13
|
+
* Build a KPI header card.
|
|
14
|
+
*/
|
|
15
|
+
function buildKpiCard(label, value) {
|
|
16
|
+
return `
|
|
17
|
+
<div class="kpi-card">
|
|
18
|
+
<span class="kpi-label">${label}</span>
|
|
19
|
+
<span class="kpi-value">${value}</span>
|
|
20
|
+
</div>
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Calculate a health score from various metrics.
|
|
25
|
+
*
|
|
26
|
+
* @param qualityScore - Commit quality score (0-10)
|
|
27
|
+
* @param activeDayPercentage - Percentage of active days (0-100)
|
|
28
|
+
* @param currentStreak - Current streak in days
|
|
29
|
+
* @returns Health score (0-10)
|
|
30
|
+
*/
|
|
31
|
+
function calculateHealthScore(qualityScore, activeDayPercentage, currentStreak) {
|
|
32
|
+
return ((qualityScore / 10) * 0.4 +
|
|
33
|
+
(activeDayPercentage / 100) * 0.3 +
|
|
34
|
+
Math.min(currentStreak / 30, 1) * 0.3) * 10;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build a KPI header with standard metrics.
|
|
38
|
+
*
|
|
39
|
+
* @param totalCommits - Total number of commits
|
|
40
|
+
* @param qualityScore - Commit quality score (0-10)
|
|
41
|
+
* @param currentStreak - Current streak in days
|
|
42
|
+
* @param activeDayPercentage - Percentage of active days
|
|
43
|
+
* @returns HTML string for the KPI header
|
|
44
|
+
*/
|
|
45
|
+
function buildKPIHeader(totalCommits, qualityScore, currentStreak, activeDayPercentage) {
|
|
46
|
+
const healthScore = calculateHealthScore(qualityScore, activeDayPercentage, currentStreak);
|
|
47
|
+
return `
|
|
48
|
+
<header class="kpi-header">
|
|
49
|
+
${buildKpiCard('Total Commits', totalCommits.toLocaleString())}
|
|
50
|
+
${buildKpiCard('Health Score', healthScore.toFixed(1))}
|
|
51
|
+
${buildKpiCard('Current Streak', `${currentStreak}d`)}
|
|
52
|
+
${buildKpiCard('Quality', qualityScore.toFixed(1))}
|
|
53
|
+
${buildKpiCard('Active Days', `${activeDayPercentage.toFixed(0)}%`)}
|
|
54
|
+
</header>
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build a KPI header from streak and quality data.
|
|
59
|
+
* Convenience wrapper for common use case.
|
|
60
|
+
*/
|
|
61
|
+
function buildKPIHeaderFromData(totalCommits, streakData, commitQuality) {
|
|
62
|
+
return buildKPIHeader(totalCommits, commitQuality.overallScore, streakData.currentStreak.days, streakData.activeDayPercentage);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build an overview KPI header for team/overall view.
|
|
66
|
+
*/
|
|
67
|
+
function buildOverallKPIHeader(totalCommits, developerCount, activeDays, qualityScore) {
|
|
68
|
+
return `
|
|
69
|
+
<header class="kpi-header">
|
|
70
|
+
${buildKpiCard('Total Commits', totalCommits.toLocaleString())}
|
|
71
|
+
${buildKpiCard('Contributors', developerCount.toString())}
|
|
72
|
+
${buildKpiCard('Active Days', activeDays.toString())}
|
|
73
|
+
${buildKpiCard('Quality Score', qualityScore.toFixed(1))}
|
|
74
|
+
</header>
|
|
75
|
+
`;
|
|
76
|
+
}
|