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,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildGapSection = buildGapSection;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
function buildGapSection(gapAnalysis) {
|
|
6
|
+
const statsHtml = buildGapStats(gapAnalysis);
|
|
7
|
+
const timelineHtml = buildGapTimeline(gapAnalysis);
|
|
8
|
+
const gapsListHtml = buildGapsList(gapAnalysis.gaps);
|
|
9
|
+
return `
|
|
10
|
+
<div class="gap-section">
|
|
11
|
+
<h2>🕳️ Gap & Blocker Analysis</h2>
|
|
12
|
+
|
|
13
|
+
${statsHtml}
|
|
14
|
+
|
|
15
|
+
${timelineHtml}
|
|
16
|
+
|
|
17
|
+
${gapsListHtml}
|
|
18
|
+
</div>
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
function buildGapStats(gapAnalysis) {
|
|
22
|
+
const riskColors = {
|
|
23
|
+
low: '#69db7c',
|
|
24
|
+
medium: '#ffd43b',
|
|
25
|
+
high: '#ff922b',
|
|
26
|
+
critical: '#ff6b6b',
|
|
27
|
+
};
|
|
28
|
+
const riskEmoji = {
|
|
29
|
+
low: '🟢',
|
|
30
|
+
medium: '🟡',
|
|
31
|
+
high: '🟠',
|
|
32
|
+
critical: '🔴',
|
|
33
|
+
};
|
|
34
|
+
return `
|
|
35
|
+
<div class="gap-stats">
|
|
36
|
+
<div class="gap-stat-card">
|
|
37
|
+
<div class="stat-icon">🕳️</div>
|
|
38
|
+
<div class="stat-info">
|
|
39
|
+
<div class="stat-label">Total Gaps</div>
|
|
40
|
+
<div class="stat-value">${gapAnalysis.gaps.length}</div>
|
|
41
|
+
<div class="stat-detail">${gapAnalysis.gapFrequency}/month avg</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="gap-stat-card">
|
|
45
|
+
<div class="stat-icon">📅</div>
|
|
46
|
+
<div class="stat-info">
|
|
47
|
+
<div class="stat-label">Gap Days</div>
|
|
48
|
+
<div class="stat-value">${gapAnalysis.totalGapDays}</div>
|
|
49
|
+
<div class="stat-detail">${gapAnalysis.percentageOfPeriodInGaps}% of period</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="gap-stat-card">
|
|
53
|
+
<div class="stat-icon">⏱️</div>
|
|
54
|
+
<div class="stat-info">
|
|
55
|
+
<div class="stat-label">Longest Gap</div>
|
|
56
|
+
<div class="stat-value">${gapAnalysis.longestGap?.durationDays || 0} days</div>
|
|
57
|
+
<div class="stat-detail">avg: ${gapAnalysis.averageGapLength} days</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="gap-stat-card risk-card" style="--risk-color: ${riskColors[gapAnalysis.riskLevel]}">
|
|
61
|
+
<div class="stat-icon">${riskEmoji[gapAnalysis.riskLevel]}</div>
|
|
62
|
+
<div class="stat-info">
|
|
63
|
+
<div class="stat-label">Risk Level</div>
|
|
64
|
+
<div class="stat-value risk-${gapAnalysis.riskLevel}">${gapAnalysis.riskLevel.toUpperCase()}</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
${gapAnalysis.riskFactors.length > 0 ? `
|
|
70
|
+
<div class="risk-factors">
|
|
71
|
+
<h4>Risk Factors</h4>
|
|
72
|
+
<ul>
|
|
73
|
+
${gapAnalysis.riskFactors.map(f => `<li>${f}</li>`).join('')}
|
|
74
|
+
</ul>
|
|
75
|
+
</div>
|
|
76
|
+
` : ''}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
function buildGapTimeline(gapAnalysis) {
|
|
80
|
+
if (gapAnalysis.gaps.length === 0) {
|
|
81
|
+
return `
|
|
82
|
+
<div class="gap-timeline-container">
|
|
83
|
+
<h3>Activity Timeline</h3>
|
|
84
|
+
<div class="no-gaps-message">
|
|
85
|
+
<span class="success-icon">✅</span>
|
|
86
|
+
<span>No significant activity gaps detected</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
// Find the full date range
|
|
92
|
+
const allDates = gapAnalysis.gaps.flatMap(g => [g.start, g.end]);
|
|
93
|
+
const minDate = new Date(Math.min(...allDates.map(d => d.getTime())));
|
|
94
|
+
const maxDate = new Date(Math.max(...allDates.map(d => d.getTime())));
|
|
95
|
+
const totalDays = Math.ceil((maxDate.getTime() - minDate.getTime()) / (1000 * 60 * 60 * 24)) + 1;
|
|
96
|
+
if (totalDays <= 0) {
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
// Build gap segments
|
|
100
|
+
const segments = gapAnalysis.gaps.map(gap => {
|
|
101
|
+
const startOffset = Math.ceil((gap.start.getTime() - minDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
102
|
+
const width = gap.durationDays;
|
|
103
|
+
const leftPercent = (startOffset / totalDays) * 100;
|
|
104
|
+
const widthPercent = (width / totalDays) * 100;
|
|
105
|
+
const typeClass = gap.possibleType === 'blocker' ? 'blocker' :
|
|
106
|
+
gap.possibleType === 'vacation' ? 'vacation' :
|
|
107
|
+
gap.possibleType === 'holiday' ? 'holiday' : 'unknown';
|
|
108
|
+
return `
|
|
109
|
+
<div class="gap-segment ${typeClass}"
|
|
110
|
+
style="left: ${leftPercent}%; width: ${widthPercent}%"
|
|
111
|
+
title="${gap.durationDays} days - ${gap.possibleType}">
|
|
112
|
+
</div>
|
|
113
|
+
`;
|
|
114
|
+
}).join('');
|
|
115
|
+
return `
|
|
116
|
+
<div class="gap-timeline-container">
|
|
117
|
+
<h3>Activity Timeline</h3>
|
|
118
|
+
<div class="gap-timeline">
|
|
119
|
+
<div class="timeline-track">
|
|
120
|
+
${segments}
|
|
121
|
+
</div>
|
|
122
|
+
<div class="timeline-labels">
|
|
123
|
+
<span>${(0, date_fns_1.format)(minDate, 'MMM d, yyyy')}</span>
|
|
124
|
+
<span>${(0, date_fns_1.format)(maxDate, 'MMM d, yyyy')}</span>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="timeline-legend">
|
|
128
|
+
<span class="legend-item"><span class="legend-color blocker"></span> Potential Blocker</span>
|
|
129
|
+
<span class="legend-item"><span class="legend-color vacation"></span> Vacation</span>
|
|
130
|
+
<span class="legend-item"><span class="legend-color holiday"></span> Holiday</span>
|
|
131
|
+
<span class="legend-item"><span class="legend-color unknown"></span> Unknown</span>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
function buildGapsList(gaps) {
|
|
137
|
+
if (gaps.length === 0) {
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
// Sort by duration descending, take top 5
|
|
141
|
+
const sortedGaps = [...gaps].sort((a, b) => b.durationDays - a.durationDays).slice(0, 5);
|
|
142
|
+
const gapItems = sortedGaps.map(gap => {
|
|
143
|
+
const typeEmoji = {
|
|
144
|
+
blocker: '🚧',
|
|
145
|
+
vacation: '🏖️',
|
|
146
|
+
holiday: '🎄',
|
|
147
|
+
weekend: '📅',
|
|
148
|
+
unknown: '❓',
|
|
149
|
+
};
|
|
150
|
+
return `
|
|
151
|
+
<div class="gap-item">
|
|
152
|
+
<div class="gap-item-header">
|
|
153
|
+
<span class="gap-type-icon">${typeEmoji[gap.possibleType]}</span>
|
|
154
|
+
<span class="gap-duration">${gap.durationDays} days</span>
|
|
155
|
+
<span class="gap-type-badge ${gap.possibleType}">${gap.possibleType}</span>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="gap-dates">
|
|
158
|
+
${(0, date_fns_1.format)(gap.start, 'MMM d')} - ${(0, date_fns_1.format)(gap.end, 'MMM d, yyyy')}
|
|
159
|
+
</div>
|
|
160
|
+
<div class="gap-context">
|
|
161
|
+
${gap.commitBefore ? `
|
|
162
|
+
<div class="context-item before">
|
|
163
|
+
<span class="context-label">Before:</span>
|
|
164
|
+
<span class="context-message">"${truncate(gap.commitBefore.message, 50)}"</span>
|
|
165
|
+
</div>
|
|
166
|
+
` : ''}
|
|
167
|
+
${gap.commitAfter ? `
|
|
168
|
+
<div class="context-item after">
|
|
169
|
+
<span class="context-label">After:</span>
|
|
170
|
+
<span class="context-message">"${truncate(gap.commitAfter.message, 50)}"</span>
|
|
171
|
+
</div>
|
|
172
|
+
` : ''}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
`;
|
|
176
|
+
}).join('');
|
|
177
|
+
return `
|
|
178
|
+
<div class="gaps-list-section">
|
|
179
|
+
<h3>Top Gaps by Duration</h3>
|
|
180
|
+
<div class="gaps-list">
|
|
181
|
+
${gapItems}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
function truncate(str, maxLength) {
|
|
187
|
+
if (str.length <= maxLength)
|
|
188
|
+
return str;
|
|
189
|
+
return str.substring(0, maxLength - 3) + '...';
|
|
190
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeImpact = analyzeImpact;
|
|
4
|
+
const emptyResults_1 = require("../../utils/emptyResults");
|
|
5
|
+
// File weight patterns based on criticality
|
|
6
|
+
const FILE_WEIGHTS = [
|
|
7
|
+
{ pattern: /^src\/(core|lib)\//, weight: 1.5, category: 'core' },
|
|
8
|
+
{ pattern: /^(src|lib)\/.*\.(ts|js|tsx|jsx)$/, weight: 1.0, category: 'feature' },
|
|
9
|
+
{ pattern: /(test|spec|__tests__)\/|\.test\.|\.spec\./, weight: 0.8, category: 'test' },
|
|
10
|
+
{ pattern: /\.(config|rc)\.|\.env|tsconfig|webpack|vite|eslint|prettier/, weight: 0.6, category: 'config' },
|
|
11
|
+
{ pattern: /\.md$|^docs\/|^README/, weight: 0.4, category: 'docs' },
|
|
12
|
+
{ pattern: /package\.json|\.lock$|yarn\.lock|pnpm-lock/, weight: 0.3, category: 'config' },
|
|
13
|
+
];
|
|
14
|
+
// Commit type weights based on conventional commits
|
|
15
|
+
const TYPE_WEIGHTS = {
|
|
16
|
+
feat: 1.5,
|
|
17
|
+
fix: 1.2,
|
|
18
|
+
refactor: 1.0,
|
|
19
|
+
perf: 1.3,
|
|
20
|
+
test: 0.9,
|
|
21
|
+
docs: 0.5,
|
|
22
|
+
chore: 0.4,
|
|
23
|
+
style: 0.3,
|
|
24
|
+
ci: 0.4,
|
|
25
|
+
build: 0.5,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Get file weight and category based on path
|
|
29
|
+
*/
|
|
30
|
+
function getFileInfo(filePath) {
|
|
31
|
+
const normalizedPath = filePath.toLowerCase().replace(/\\/g, '/');
|
|
32
|
+
for (const { pattern, weight, category } of FILE_WEIGHTS) {
|
|
33
|
+
if (pattern.test(normalizedPath)) {
|
|
34
|
+
return { weight, category };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Default for unknown files
|
|
38
|
+
return { weight: 0.7, category: 'feature' };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extract commit type from conventional commit message
|
|
42
|
+
*/
|
|
43
|
+
function getCommitType(message) {
|
|
44
|
+
const match = message.match(/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?:/i);
|
|
45
|
+
return match ? match[1].toLowerCase() : null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Calculate recency factor (recent changes weighted higher)
|
|
49
|
+
*/
|
|
50
|
+
function getRecencyFactor(commitDate, now = new Date()) {
|
|
51
|
+
const daysSinceCommit = Math.floor((now.getTime() - commitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
52
|
+
// Exponential decay over 90 days
|
|
53
|
+
if (daysSinceCommit <= 7)
|
|
54
|
+
return 1.0;
|
|
55
|
+
if (daysSinceCommit <= 30)
|
|
56
|
+
return 0.9;
|
|
57
|
+
if (daysSinceCommit <= 60)
|
|
58
|
+
return 0.7;
|
|
59
|
+
if (daysSinceCommit <= 90)
|
|
60
|
+
return 0.5;
|
|
61
|
+
return 0.3;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extract files changed from commit message (simplified - assumes file paths in message)
|
|
65
|
+
* In a real implementation, this would come from git diff
|
|
66
|
+
*/
|
|
67
|
+
function extractFilesFromCommit(commit) {
|
|
68
|
+
// For now, return empty - in a real implementation we'd parse git log --name-only
|
|
69
|
+
// This is a placeholder that allows the analyzer to work with existing data
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Analyzes the impact of commits based on file criticality and change patterns.
|
|
74
|
+
*
|
|
75
|
+
* Calculates an overall impact score (0-100) by weighting commits based on:
|
|
76
|
+
* - File criticality (core files > features > tests > config > docs)
|
|
77
|
+
* - Commit type (feat/perf > fix/refactor > test > docs/chore)
|
|
78
|
+
* - Recency (recent changes weighted higher)
|
|
79
|
+
*
|
|
80
|
+
* @param commits - Array of commit data to analyze
|
|
81
|
+
* @param changedFiles - Optional pre-computed file change data from fileHotspotAnalyzer
|
|
82
|
+
* @returns Impact analysis with score breakdown, top files, trend, and insights
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const impact = analyzeImpact(commits);
|
|
87
|
+
* console.log(`Impact score: ${impact.overallScore}/100`);
|
|
88
|
+
* console.log(`Trend: ${impact.impactTrend}`);
|
|
89
|
+
* console.log(`Core contributions: ${impact.scoreBreakdown.coreContributions} points`);
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function analyzeImpact(commits, changedFiles) {
|
|
93
|
+
if (commits.length === 0) {
|
|
94
|
+
return (0, emptyResults_1.getEmptyImpactAnalysis)();
|
|
95
|
+
}
|
|
96
|
+
const now = new Date();
|
|
97
|
+
const fileImpacts = new Map();
|
|
98
|
+
let coreContributions = 0;
|
|
99
|
+
let featureWork = 0;
|
|
100
|
+
let maintenanceWork = 0;
|
|
101
|
+
let documentationWork = 0;
|
|
102
|
+
let totalRawScore = 0;
|
|
103
|
+
let recentScore = 0; // Last 30 days
|
|
104
|
+
let olderScore = 0; // 30-90 days ago
|
|
105
|
+
// If we have file change data from fileHotspotAnalyzer, use it
|
|
106
|
+
if (changedFiles && changedFiles.size > 0) {
|
|
107
|
+
changedFiles.forEach((data, filePath) => {
|
|
108
|
+
const { weight, category } = getFileInfo(filePath);
|
|
109
|
+
const impactScore = data.count * weight;
|
|
110
|
+
fileImpacts.set(filePath, {
|
|
111
|
+
score: impactScore,
|
|
112
|
+
count: data.count,
|
|
113
|
+
lastChanged: data.lastChanged,
|
|
114
|
+
category
|
|
115
|
+
});
|
|
116
|
+
totalRawScore += impactScore;
|
|
117
|
+
// Categorize contributions
|
|
118
|
+
switch (category) {
|
|
119
|
+
case 'core':
|
|
120
|
+
coreContributions += impactScore;
|
|
121
|
+
break;
|
|
122
|
+
case 'test':
|
|
123
|
+
case 'config':
|
|
124
|
+
maintenanceWork += impactScore;
|
|
125
|
+
break;
|
|
126
|
+
case 'docs':
|
|
127
|
+
documentationWork += impactScore;
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
featureWork += impactScore;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Process commits for type-based weighting and recency
|
|
135
|
+
commits.forEach(commit => {
|
|
136
|
+
const commitDate = new Date(commit.date);
|
|
137
|
+
const commitType = getCommitType(commit.message);
|
|
138
|
+
const typeWeight = commitType ? (TYPE_WEIGHTS[commitType] || 1.0) : 1.0;
|
|
139
|
+
const recencyFactor = getRecencyFactor(commitDate, now);
|
|
140
|
+
const daysSinceCommit = Math.floor((now.getTime() - commitDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
141
|
+
const baseScore = typeWeight * recencyFactor;
|
|
142
|
+
if (daysSinceCommit <= 30) {
|
|
143
|
+
recentScore += baseScore;
|
|
144
|
+
}
|
|
145
|
+
else if (daysSinceCommit <= 90) {
|
|
146
|
+
olderScore += baseScore;
|
|
147
|
+
}
|
|
148
|
+
// Additional category scoring based on commit type
|
|
149
|
+
if (commitType === 'feat') {
|
|
150
|
+
featureWork += typeWeight;
|
|
151
|
+
}
|
|
152
|
+
else if (commitType === 'fix' || commitType === 'refactor') {
|
|
153
|
+
maintenanceWork += typeWeight;
|
|
154
|
+
}
|
|
155
|
+
else if (commitType === 'docs') {
|
|
156
|
+
documentationWork += typeWeight;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// Normalize overall score to 0-100
|
|
160
|
+
const maxPossibleScore = commits.length * 1.5 * 1.0; // Max type weight * max recency
|
|
161
|
+
const normalizedScore = Math.min(100, Math.round((totalRawScore + recentScore) / Math.max(maxPossibleScore, 1) * 100));
|
|
162
|
+
// Get top 10 impact files
|
|
163
|
+
const topImpactFiles = Array.from(fileImpacts.entries())
|
|
164
|
+
.sort((a, b) => b[1].score - a[1].score)
|
|
165
|
+
.slice(0, 10)
|
|
166
|
+
.map(([path, data]) => ({
|
|
167
|
+
path,
|
|
168
|
+
impactScore: Math.round(data.score * 10) / 10,
|
|
169
|
+
changeCount: data.count,
|
|
170
|
+
lastChanged: data.lastChanged,
|
|
171
|
+
category: data.category
|
|
172
|
+
}));
|
|
173
|
+
// Determine trend
|
|
174
|
+
let impactTrend = 'stable';
|
|
175
|
+
if (recentScore > olderScore * 1.2) {
|
|
176
|
+
impactTrend = 'increasing';
|
|
177
|
+
}
|
|
178
|
+
else if (recentScore < olderScore * 0.8) {
|
|
179
|
+
impactTrend = 'decreasing';
|
|
180
|
+
}
|
|
181
|
+
// Generate insights
|
|
182
|
+
const insights = generateInsights({
|
|
183
|
+
coreContributions,
|
|
184
|
+
featureWork,
|
|
185
|
+
maintenanceWork,
|
|
186
|
+
documentationWork,
|
|
187
|
+
topImpactFiles,
|
|
188
|
+
impactTrend,
|
|
189
|
+
totalCommits: commits.length
|
|
190
|
+
});
|
|
191
|
+
// Normalize breakdown scores
|
|
192
|
+
const breakdownTotal = coreContributions + featureWork + maintenanceWork + documentationWork || 1;
|
|
193
|
+
return {
|
|
194
|
+
overallScore: normalizedScore,
|
|
195
|
+
scoreBreakdown: {
|
|
196
|
+
coreContributions: Math.round((coreContributions / breakdownTotal) * 100),
|
|
197
|
+
featureWork: Math.round((featureWork / breakdownTotal) * 100),
|
|
198
|
+
maintenanceWork: Math.round((maintenanceWork / breakdownTotal) * 100),
|
|
199
|
+
documentationWork: Math.round((documentationWork / breakdownTotal) * 100),
|
|
200
|
+
},
|
|
201
|
+
topImpactFiles,
|
|
202
|
+
impactTrend,
|
|
203
|
+
insights
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Generate meaningful insights from impact data
|
|
208
|
+
*/
|
|
209
|
+
function generateInsights(data) {
|
|
210
|
+
const insights = [];
|
|
211
|
+
const total = data.coreContributions + data.featureWork + data.maintenanceWork + data.documentationWork || 1;
|
|
212
|
+
// Core contributions insight
|
|
213
|
+
const corePercentage = (data.coreContributions / total) * 100;
|
|
214
|
+
if (corePercentage > 30) {
|
|
215
|
+
insights.push(`Strong focus on core modules — ${corePercentage.toFixed(0)}% of impact from critical paths`);
|
|
216
|
+
}
|
|
217
|
+
// Feature work insight
|
|
218
|
+
const featurePercentage = (data.featureWork / total) * 100;
|
|
219
|
+
if (featurePercentage > 50) {
|
|
220
|
+
insights.push(`Feature-focused development — ${featurePercentage.toFixed(0)}% of contributions are new functionality`);
|
|
221
|
+
}
|
|
222
|
+
// Maintenance insight
|
|
223
|
+
const maintenancePercentage = (data.maintenanceWork / total) * 100;
|
|
224
|
+
if (maintenancePercentage > 40) {
|
|
225
|
+
insights.push(`Strong maintenance focus — keeping the codebase healthy`);
|
|
226
|
+
}
|
|
227
|
+
// Documentation insight
|
|
228
|
+
const docPercentage = (data.documentationWork / total) * 100;
|
|
229
|
+
if (docPercentage < 5 && data.totalCommits > 20) {
|
|
230
|
+
insights.push(`Documentation contributions below average — consider adding READMEs or inline docs`);
|
|
231
|
+
}
|
|
232
|
+
else if (docPercentage > 15) {
|
|
233
|
+
insights.push(`Good documentation practices — ${docPercentage.toFixed(0)}% of work includes docs`);
|
|
234
|
+
}
|
|
235
|
+
// Trend insight
|
|
236
|
+
if (data.impactTrend === 'increasing') {
|
|
237
|
+
insights.push(`Impact trending upward — recent contributions are higher value`);
|
|
238
|
+
}
|
|
239
|
+
else if (data.impactTrend === 'decreasing') {
|
|
240
|
+
insights.push(`Recent activity lower than historical average`);
|
|
241
|
+
}
|
|
242
|
+
// Top files insight
|
|
243
|
+
if (data.topImpactFiles.length > 0) {
|
|
244
|
+
const topFile = data.topImpactFiles[0];
|
|
245
|
+
insights.push(`Highest impact: ${topFile.path.split('/').pop()} (${topFile.changeCount} changes)`);
|
|
246
|
+
}
|
|
247
|
+
return insights.slice(0, 5); // Limit to 5 insights
|
|
248
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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.buildImpactSection = exports.analyzeImpact = void 0;
|
|
18
|
+
/**
|
|
19
|
+
* Impact Feature
|
|
20
|
+
* @module features/impact
|
|
21
|
+
*/
|
|
22
|
+
__exportStar(require("./types"), exports);
|
|
23
|
+
var analyzer_1 = require("./analyzer");
|
|
24
|
+
Object.defineProperty(exports, "analyzeImpact", { enumerable: true, get: function () { return analyzer_1.analyzeImpact; } });
|
|
25
|
+
var template_1 = require("./template");
|
|
26
|
+
Object.defineProperty(exports, "buildImpactSection", { enumerable: true, get: function () { return template_1.buildImpactSection; } });
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildImpactSection = buildImpactSection;
|
|
4
|
+
/**
|
|
5
|
+
* Build the Impact Analysis section for the dashboard
|
|
6
|
+
*/
|
|
7
|
+
function buildImpactSection(impact) {
|
|
8
|
+
const trendIcon = impact.impactTrend === 'increasing' ? '↑' : impact.impactTrend === 'decreasing' ? '↓' : '→';
|
|
9
|
+
const trendClass = impact.impactTrend === 'increasing' ? 'trend-up' : impact.impactTrend === 'decreasing' ? 'trend-down' : 'trend-stable';
|
|
10
|
+
return `
|
|
11
|
+
<div class="impact-section">
|
|
12
|
+
<h2>Impact Analysis</h2>
|
|
13
|
+
|
|
14
|
+
<div class="impact-content">
|
|
15
|
+
<div class="impact-overview">
|
|
16
|
+
<div class="impact-score-display">
|
|
17
|
+
<div class="score-circle">
|
|
18
|
+
<span class="score-value">${impact.overallScore}</span>
|
|
19
|
+
<span class="score-max">/100</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="score-meta">
|
|
22
|
+
<span class="score-label">Impact Score</span>
|
|
23
|
+
<span class="score-trend ${trendClass}">${trendIcon} ${impact.impactTrend}</span>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="impact-breakdown">
|
|
28
|
+
<h3>Contribution Breakdown</h3>
|
|
29
|
+
<div class="breakdown-items">
|
|
30
|
+
<div class="breakdown-item">
|
|
31
|
+
<div class="breakdown-header">
|
|
32
|
+
<span class="breakdown-label">Core Contributions <span class="info-tooltip" title="Changes to src/core/ and src/lib/ folders - foundational code">ⓘ</span></span>
|
|
33
|
+
<span class="breakdown-value">${impact.scoreBreakdown.coreContributions}%</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="breakdown-bar">
|
|
36
|
+
<div class="breakdown-fill core" style="width: ${impact.scoreBreakdown.coreContributions}%"></div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="breakdown-item">
|
|
41
|
+
<div class="breakdown-header">
|
|
42
|
+
<span class="breakdown-label">Feature Work <span class="info-tooltip" title="Changes to source files (.ts, .js, .tsx, .jsx) outside core/lib">ⓘ</span></span>
|
|
43
|
+
<span class="breakdown-value">${impact.scoreBreakdown.featureWork}%</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="breakdown-bar">
|
|
46
|
+
<div class="breakdown-fill feature" style="width: ${impact.scoreBreakdown.featureWork}%"></div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="breakdown-item">
|
|
51
|
+
<div class="breakdown-header">
|
|
52
|
+
<span class="breakdown-label">Maintenance <span class="info-tooltip" title="Changes to tests, configs, CI/CD, and build files">ⓘ</span></span>
|
|
53
|
+
<span class="breakdown-value">${impact.scoreBreakdown.maintenanceWork}%</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="breakdown-bar">
|
|
56
|
+
<div class="breakdown-fill maintenance" style="width: ${impact.scoreBreakdown.maintenanceWork}%"></div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="breakdown-item">
|
|
61
|
+
<div class="breakdown-header">
|
|
62
|
+
<span class="breakdown-label">Documentation <span class="info-tooltip" title="Changes to .md files, README, and docs/ folder">ⓘ</span></span>
|
|
63
|
+
<span class="breakdown-value">${impact.scoreBreakdown.documentationWork}%</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="breakdown-bar">
|
|
66
|
+
<div class="breakdown-fill docs" style="width: ${impact.scoreBreakdown.documentationWork}%"></div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
${impact.topImpactFiles.length > 0 ? `
|
|
74
|
+
<div class="impact-files">
|
|
75
|
+
<h3>Highest Impact Changes</h3>
|
|
76
|
+
<table class="data-table">
|
|
77
|
+
<thead>
|
|
78
|
+
<tr>
|
|
79
|
+
<th>File</th>
|
|
80
|
+
<th>Category</th>
|
|
81
|
+
<th>Changes</th>
|
|
82
|
+
<th>Impact</th>
|
|
83
|
+
</tr>
|
|
84
|
+
</thead>
|
|
85
|
+
<tbody>
|
|
86
|
+
${impact.topImpactFiles.map(file => `
|
|
87
|
+
<tr>
|
|
88
|
+
<td class="file-path">${truncatePath(file.path)}</td>
|
|
89
|
+
<td><span class="category-badge ${file.category}">${file.category}</span></td>
|
|
90
|
+
<td>${file.changeCount}</td>
|
|
91
|
+
<td><span class="impact-badge">${file.impactScore.toFixed(1)}</span></td>
|
|
92
|
+
</tr>
|
|
93
|
+
`).join('')}
|
|
94
|
+
</tbody>
|
|
95
|
+
</table>
|
|
96
|
+
</div>
|
|
97
|
+
` : ''}
|
|
98
|
+
|
|
99
|
+
${impact.insights.length > 0 ? `
|
|
100
|
+
<div class="insights-list">
|
|
101
|
+
<h3>Insights</h3>
|
|
102
|
+
<ul>
|
|
103
|
+
${impact.insights.map(insight => `<li>💡 ${insight}</li>`).join('')}
|
|
104
|
+
</ul>
|
|
105
|
+
</div>
|
|
106
|
+
` : ''}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Truncate file path for display
|
|
113
|
+
*/
|
|
114
|
+
function truncatePath(path, maxLength = 40) {
|
|
115
|
+
if (path.length <= maxLength)
|
|
116
|
+
return path;
|
|
117
|
+
return '...' + path.slice(-maxLength + 3);
|
|
118
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Features Module
|
|
4
|
+
*
|
|
5
|
+
* Central export for all feature modules.
|
|
6
|
+
* Each feature is self-contained with its own types, logic, and templates.
|
|
7
|
+
*
|
|
8
|
+
* @module features
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
22
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
// Core features
|
|
26
|
+
__exportStar(require("./achievements"), exports);
|
|
27
|
+
__exportStar(require("./streaks"), exports);
|
|
28
|
+
__exportStar(require("./time-patterns"), exports);
|
|
29
|
+
// Analysis features
|
|
30
|
+
__exportStar(require("./velocity"), exports);
|
|
31
|
+
__exportStar(require("./gaps"), exports);
|
|
32
|
+
__exportStar(require("./knowledge"), exports);
|
|
33
|
+
__exportStar(require("./impact"), exports);
|
|
34
|
+
__exportStar(require("./commit-quality"), exports);
|
|
35
|
+
__exportStar(require("./team"), exports);
|
|
36
|
+
__exportStar(require("./comparison"), exports);
|
|
37
|
+
// Content features
|
|
38
|
+
__exportStar(require("./executive-summary"), exports);
|
|
39
|
+
__exportStar(require("./events"), exports);
|
|
40
|
+
__exportStar(require("./contribution-graph"), exports);
|