repo-wrapped 0.0.6 → 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 +262 -5
- 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 +10 -6
- 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 +1335 -0
- package/dist/generators/html/styles/strategic-insights.css +1337 -0
- package/dist/generators/html/templates/commitQualitySection.js +28 -2
- package/dist/generators/html/templates/comparisonSection.js +119 -0
- package/dist/generators/html/templates/eventsSection.js +113 -0
- package/dist/generators/html/templates/executiveSummarySection.js +80 -0
- package/dist/generators/html/templates/gapSection.js +190 -0
- package/dist/generators/html/templates/impactSection.js +8 -6
- package/dist/generators/html/templates/knowledgeSection.js +16 -2
- package/dist/generators/html/templates/teamSection.js +146 -0
- package/dist/generators/html/templates/velocitySection.js +189 -0
- 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 -1
- 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 +54 -21
- 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/eventAnnotationParser.js +253 -0
- package/dist/utils/executiveSummaryGenerator.js +275 -0
- package/dist/utils/fileHotspotAnalyzer.js +4 -12
- package/dist/utils/gapAnalyzer.js +298 -0
- package/dist/utils/gitParser.test.js +363 -0
- package/dist/utils/htmlGenerator.js +126 -450
- 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 +222 -0
- package/dist/utils/streakCalculator.js +77 -27
- package/dist/utils/teamAnalyzer.js +316 -0
- package/dist/utils/timePatternAnalyzer.js +18 -3
- package/dist/utils/velocityAnalyzer.js +257 -0
- package/dist/utils/wrappedGenerator.js +8 -8
- package/package.json +74 -55
- package/vitest.config.ts +46 -0
- package/dist/cli.js +0 -24
- package/dist/commands/index.js +0 -24
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTimePatternsSection = buildTimePatternsSection;
|
|
4
|
+
const constants_1 = require("../../constants");
|
|
5
|
+
const formatters_1 = require("../../formatters");
|
|
6
|
+
const analyzer_1 = require("./analyzer");
|
|
7
|
+
function buildTimePatternsSection(timePattern) {
|
|
8
|
+
// Get chronotype details for dual display
|
|
9
|
+
const chronotypeDetails = getChronotypeTimeRange(timePattern.chronotype);
|
|
10
|
+
return `
|
|
11
|
+
<div class="time-patterns-section">
|
|
12
|
+
<h2>Time Patterns</h2>
|
|
13
|
+
|
|
14
|
+
<div class="patterns-grid">
|
|
15
|
+
<div class="pattern-card">
|
|
16
|
+
<div class="pattern-header">
|
|
17
|
+
<span class="pattern-icon">⏱️</span>
|
|
18
|
+
<span class="pattern-title">Peak Hour</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="pattern-value">${(0, formatters_1.formatHour)(timePattern.peakHour.hour)}</div>
|
|
21
|
+
<div class="pattern-detail">${timePattern.peakHour.commitCount} commits</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="pattern-card">
|
|
25
|
+
<div class="pattern-header">
|
|
26
|
+
<span class="pattern-icon">${constants_1.ANALYSIS_CHRONOTYPE_EMOJIS[timePattern.chronotype] || '⚖️'}</span>
|
|
27
|
+
<span class="pattern-title">Chronotype</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="chronotype-display">
|
|
30
|
+
<div class="chronotype-label">${(0, analyzer_1.getChronotypeLabel)(timePattern.chronotype)}</div>
|
|
31
|
+
<div class="chronotype-detail">${chronotypeDetails}</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="pattern-detail">${timePattern.chronotypeConfidence.toFixed(0)}% confidence</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="pattern-card">
|
|
37
|
+
<div class="pattern-header">
|
|
38
|
+
<span class="pattern-icon">⚖️</span>
|
|
39
|
+
<span class="pattern-title">Work-Life Balance</span>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="pattern-value">${getBalanceScore(timePattern.workLifeBalance.score)}</div>
|
|
42
|
+
<div class="pattern-detail">Weekday: ${timePattern.workLifeBalance.weekdayCommits} | Weekend: ${timePattern.workLifeBalance.weekendCommits}</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="pattern-card">
|
|
46
|
+
<div class="pattern-header">
|
|
47
|
+
<span class="pattern-icon">📊</span>
|
|
48
|
+
<span class="pattern-title">Consistency</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="pattern-value">${timePattern.consistency.score.toFixed(1)}/10</div>
|
|
51
|
+
<div class="pattern-detail">${formatRegularity(timePattern.consistency.regularity)}</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
${timePattern.burnoutRisk.level !== 'low' ? `
|
|
56
|
+
<div class="wellbeing-note">
|
|
57
|
+
<div class="wellbeing-header">
|
|
58
|
+
<span class="wellbeing-icon">💚</span>
|
|
59
|
+
<span class="wellbeing-title">Pace Advisory: ${formatRiskLevel(timePattern.burnoutRisk.level)}</span>
|
|
60
|
+
</div>
|
|
61
|
+
<ul class="wellbeing-indicators">
|
|
62
|
+
${timePattern.burnoutRisk.indicators.map((indicator) => `<li>${indicator}</li>`).join('')}
|
|
63
|
+
</ul>
|
|
64
|
+
<div class="wellbeing-message">
|
|
65
|
+
Sustainable pace matters. These patterns are informational, not prescriptive.
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
` : ''}
|
|
69
|
+
|
|
70
|
+
<div class="chronotype-insight">
|
|
71
|
+
${(0, analyzer_1.getChronotypeDescription)(timePattern.chronotype)}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
function getChronotypeTimeRange(chronotype) {
|
|
77
|
+
switch (chronotype) {
|
|
78
|
+
case 'early-bird': return 'Peak hours: 6 AM – 9 AM';
|
|
79
|
+
case 'night-owl': return 'Peak hours: 9 PM – 12 AM';
|
|
80
|
+
case 'vampire': return 'Peak hours: 12 AM – 6 AM';
|
|
81
|
+
case 'balanced': return 'Consistent throughout day';
|
|
82
|
+
default: return '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function getBalanceScore(score) {
|
|
86
|
+
if (score >= 4)
|
|
87
|
+
return 'Excellent';
|
|
88
|
+
if (score >= 3)
|
|
89
|
+
return 'Good';
|
|
90
|
+
if (score >= 2)
|
|
91
|
+
return 'Fair';
|
|
92
|
+
return 'Consider adjusting';
|
|
93
|
+
}
|
|
94
|
+
function formatRegularity(regularity) {
|
|
95
|
+
switch (regularity) {
|
|
96
|
+
case 'very-consistent': return 'Very Consistent';
|
|
97
|
+
case 'consistent': return 'Consistent';
|
|
98
|
+
case 'irregular': return 'Irregular';
|
|
99
|
+
case 'chaotic': return 'Variable';
|
|
100
|
+
default: return regularity;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function formatRiskLevel(level) {
|
|
104
|
+
switch (level) {
|
|
105
|
+
case 'high': return 'High';
|
|
106
|
+
case 'medium': return 'Moderate';
|
|
107
|
+
default: return level;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeVelocity = analyzeVelocity;
|
|
4
|
+
exports.formatVelocityInsights = formatVelocityInsights;
|
|
5
|
+
const date_fns_1 = require("date-fns");
|
|
6
|
+
const emptyResults_1 = require("../../utils/emptyResults");
|
|
7
|
+
const analyzerContextBuilder_1 = require("../../utils/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
|
+
}
|
|
26
|
+
if (commits.length === 0) {
|
|
27
|
+
return (0, emptyResults_1.getEmptyVelocityAnalysis)();
|
|
28
|
+
}
|
|
29
|
+
// Build weekly data points
|
|
30
|
+
const timeline = buildWeeklyTimeline(commits, start, end);
|
|
31
|
+
// Calculate rolling averages
|
|
32
|
+
calculateRollingAverages(timeline, windowWeeks);
|
|
33
|
+
// Detect anomalies
|
|
34
|
+
const anomalies = detectAnomalies(timeline);
|
|
35
|
+
// Calculate overall trend
|
|
36
|
+
const { trend, trendPercentage } = calculateTrend(timeline);
|
|
37
|
+
// Find peak and lowest weeks
|
|
38
|
+
const peakWeek = findPeakWeek(timeline);
|
|
39
|
+
const lowestWeek = findLowestWeek(timeline);
|
|
40
|
+
// Calculate average
|
|
41
|
+
const totalCommits = timeline.reduce((sum, dp) => sum + dp.commits, 0);
|
|
42
|
+
const averageCommitsPerWeek = timeline.length > 0 ? totalCommits / timeline.length : 0;
|
|
43
|
+
return {
|
|
44
|
+
timeline,
|
|
45
|
+
overallTrend: trend,
|
|
46
|
+
trendPercentage,
|
|
47
|
+
averageCommitsPerWeek: Math.round(averageCommitsPerWeek * 10) / 10,
|
|
48
|
+
peakWeek,
|
|
49
|
+
lowestWeek,
|
|
50
|
+
anomalies,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function buildWeeklyTimeline(commits, startDate, endDate) {
|
|
54
|
+
const timeline = [];
|
|
55
|
+
const weekCount = (0, date_fns_1.differenceInWeeks)(endDate, startDate) + 1;
|
|
56
|
+
// Create a map for quick commit lookup by week
|
|
57
|
+
const commitsByWeek = new Map();
|
|
58
|
+
for (const commit of commits) {
|
|
59
|
+
const commitDate = new Date(commit.date);
|
|
60
|
+
const weekStart = (0, date_fns_1.startOfWeek)(commitDate, { weekStartsOn: 1 });
|
|
61
|
+
const weekKey = (0, date_fns_1.format)(weekStart, 'yyyy-MM-dd');
|
|
62
|
+
if (!commitsByWeek.has(weekKey)) {
|
|
63
|
+
commitsByWeek.set(weekKey, []);
|
|
64
|
+
}
|
|
65
|
+
commitsByWeek.get(weekKey).push(commit);
|
|
66
|
+
}
|
|
67
|
+
// Build timeline for each week
|
|
68
|
+
let currentWeekStart = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 1 });
|
|
69
|
+
for (let i = 0; i < weekCount && currentWeekStart <= endDate; i++) {
|
|
70
|
+
const weekEnd = (0, date_fns_1.endOfWeek)(currentWeekStart, { weekStartsOn: 1 });
|
|
71
|
+
const weekKey = (0, date_fns_1.format)(currentWeekStart, 'yyyy-MM-dd');
|
|
72
|
+
const weekCommits = commitsByWeek.get(weekKey) || [];
|
|
73
|
+
const authors = [...new Set(weekCommits.map(c => c.author))];
|
|
74
|
+
// For now, we don't have line stats in CommitData, so we'll use 0
|
|
75
|
+
// This could be enhanced later by parsing git diff stats
|
|
76
|
+
timeline.push({
|
|
77
|
+
weekStart: new Date(currentWeekStart),
|
|
78
|
+
weekEnd: new Date(weekEnd),
|
|
79
|
+
commits: weekCommits.length,
|
|
80
|
+
linesAdded: 0,
|
|
81
|
+
linesDeleted: 0,
|
|
82
|
+
filesChanged: 0,
|
|
83
|
+
authors,
|
|
84
|
+
rollingAverage: 0, // Will be calculated later
|
|
85
|
+
});
|
|
86
|
+
currentWeekStart = (0, date_fns_1.addWeeks)(currentWeekStart, 1);
|
|
87
|
+
}
|
|
88
|
+
return timeline;
|
|
89
|
+
}
|
|
90
|
+
function calculateRollingAverages(timeline, windowWeeks) {
|
|
91
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
92
|
+
const windowStart = Math.max(0, i - windowWeeks + 1);
|
|
93
|
+
const windowEnd = i + 1;
|
|
94
|
+
const window = timeline.slice(windowStart, windowEnd);
|
|
95
|
+
const sum = window.reduce((acc, dp) => acc + dp.commits, 0);
|
|
96
|
+
timeline[i].rollingAverage = Math.round((sum / window.length) * 10) / 10;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function detectAnomalies(timeline) {
|
|
100
|
+
const anomalies = [];
|
|
101
|
+
if (timeline.length < 3) {
|
|
102
|
+
return anomalies;
|
|
103
|
+
}
|
|
104
|
+
for (let i = 1; i < timeline.length; i++) {
|
|
105
|
+
const current = timeline[i];
|
|
106
|
+
const rollingAvg = timeline[i - 1].rollingAverage;
|
|
107
|
+
if (rollingAvg === 0) {
|
|
108
|
+
if (current.commits === 0) {
|
|
109
|
+
anomalies.push({
|
|
110
|
+
weekStart: current.weekStart,
|
|
111
|
+
type: 'zero-activity',
|
|
112
|
+
severity: 'significant',
|
|
113
|
+
percentageChange: -100,
|
|
114
|
+
possibleCauses: ['Holiday period', 'Team blocked', 'Sprint planning', 'Vacation'],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const percentageChange = ((current.commits - rollingAvg) / rollingAvg) * 100;
|
|
120
|
+
// Detect drops
|
|
121
|
+
if (percentageChange <= -60) {
|
|
122
|
+
anomalies.push({
|
|
123
|
+
weekStart: current.weekStart,
|
|
124
|
+
type: 'drop',
|
|
125
|
+
severity: 'critical',
|
|
126
|
+
percentageChange: Math.round(percentageChange),
|
|
127
|
+
possibleCauses: ['Major blocker', 'Team disruption', 'Holiday/vacation', 'Context switching'],
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else if (percentageChange <= -40) {
|
|
131
|
+
anomalies.push({
|
|
132
|
+
weekStart: current.weekStart,
|
|
133
|
+
type: 'drop',
|
|
134
|
+
severity: 'significant',
|
|
135
|
+
percentageChange: Math.round(percentageChange),
|
|
136
|
+
possibleCauses: ['Potential blocker', 'Reduced capacity', 'Complex work'],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else if (percentageChange <= -20) {
|
|
140
|
+
anomalies.push({
|
|
141
|
+
weekStart: current.weekStart,
|
|
142
|
+
type: 'drop',
|
|
143
|
+
severity: 'minor',
|
|
144
|
+
percentageChange: Math.round(percentageChange),
|
|
145
|
+
possibleCauses: ['Normal variance', 'Focus on larger tasks'],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Detect spikes
|
|
149
|
+
if (percentageChange >= 100) {
|
|
150
|
+
anomalies.push({
|
|
151
|
+
weekStart: current.weekStart,
|
|
152
|
+
type: 'spike',
|
|
153
|
+
severity: 'significant',
|
|
154
|
+
percentageChange: Math.round(percentageChange),
|
|
155
|
+
possibleCauses: ['Release push', 'Catch-up after blocker', 'New team members'],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else if (percentageChange >= 50) {
|
|
159
|
+
anomalies.push({
|
|
160
|
+
weekStart: current.weekStart,
|
|
161
|
+
type: 'spike',
|
|
162
|
+
severity: 'minor',
|
|
163
|
+
percentageChange: Math.round(percentageChange),
|
|
164
|
+
possibleCauses: ['Productive week', 'Smaller tasks'],
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Zero activity detection
|
|
168
|
+
if (current.commits === 0) {
|
|
169
|
+
anomalies.push({
|
|
170
|
+
weekStart: current.weekStart,
|
|
171
|
+
type: 'zero-activity',
|
|
172
|
+
severity: 'critical',
|
|
173
|
+
percentageChange: -100,
|
|
174
|
+
possibleCauses: ['Holiday period', 'Team blocked', 'Vacation', 'Sprint transition'],
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return anomalies;
|
|
179
|
+
}
|
|
180
|
+
function calculateTrend(timeline) {
|
|
181
|
+
if (timeline.length < 4) {
|
|
182
|
+
return { trend: 'stable', trendPercentage: 0 };
|
|
183
|
+
}
|
|
184
|
+
// Compare first quarter to last quarter of the timeline
|
|
185
|
+
const quarterSize = Math.max(1, Math.floor(timeline.length / 4));
|
|
186
|
+
const firstQuarter = timeline.slice(0, quarterSize);
|
|
187
|
+
const lastQuarter = timeline.slice(-quarterSize);
|
|
188
|
+
const firstAvg = firstQuarter.reduce((sum, dp) => sum + dp.commits, 0) / firstQuarter.length;
|
|
189
|
+
const lastAvg = lastQuarter.reduce((sum, dp) => sum + dp.commits, 0) / lastQuarter.length;
|
|
190
|
+
if (firstAvg === 0) {
|
|
191
|
+
return { trend: lastAvg > 0 ? 'increasing' : 'stable', trendPercentage: 100 };
|
|
192
|
+
}
|
|
193
|
+
const percentageChange = ((lastAvg - firstAvg) / firstAvg) * 100;
|
|
194
|
+
// Check for volatility (high variance)
|
|
195
|
+
const allCommits = timeline.map(dp => dp.commits);
|
|
196
|
+
const mean = allCommits.reduce((a, b) => a + b, 0) / allCommits.length;
|
|
197
|
+
const variance = allCommits.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / allCommits.length;
|
|
198
|
+
const coefficientOfVariation = mean > 0 ? (Math.sqrt(variance) / mean) * 100 : 0;
|
|
199
|
+
if (coefficientOfVariation > 80) {
|
|
200
|
+
return { trend: 'volatile', trendPercentage: Math.round(percentageChange) };
|
|
201
|
+
}
|
|
202
|
+
if (percentageChange > 15) {
|
|
203
|
+
return { trend: 'increasing', trendPercentage: Math.round(percentageChange) };
|
|
204
|
+
}
|
|
205
|
+
else if (percentageChange < -15) {
|
|
206
|
+
return { trend: 'decreasing', trendPercentage: Math.round(percentageChange) };
|
|
207
|
+
}
|
|
208
|
+
return { trend: 'stable', trendPercentage: Math.round(percentageChange) };
|
|
209
|
+
}
|
|
210
|
+
function findPeakWeek(timeline) {
|
|
211
|
+
if (timeline.length === 0) {
|
|
212
|
+
return { weekStart: new Date(), commits: 0 };
|
|
213
|
+
}
|
|
214
|
+
const peak = timeline.reduce((max, dp) => dp.commits > max.commits ? dp : max, timeline[0]);
|
|
215
|
+
return { weekStart: peak.weekStart, commits: peak.commits };
|
|
216
|
+
}
|
|
217
|
+
function findLowestWeek(timeline) {
|
|
218
|
+
if (timeline.length === 0) {
|
|
219
|
+
return { weekStart: new Date(), commits: 0 };
|
|
220
|
+
}
|
|
221
|
+
// Find lowest non-zero week, or zero if all are zero
|
|
222
|
+
const nonZeroWeeks = timeline.filter(dp => dp.commits > 0);
|
|
223
|
+
if (nonZeroWeeks.length === 0) {
|
|
224
|
+
return { weekStart: timeline[0].weekStart, commits: 0 };
|
|
225
|
+
}
|
|
226
|
+
const lowest = nonZeroWeeks.reduce((min, dp) => dp.commits < min.commits ? dp : min, nonZeroWeeks[0]);
|
|
227
|
+
return { weekStart: lowest.weekStart, commits: lowest.commits };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Format velocity analysis for display
|
|
231
|
+
*/
|
|
232
|
+
function formatVelocityInsights(analysis) {
|
|
233
|
+
const insights = [];
|
|
234
|
+
if (analysis.timeline.length === 0) {
|
|
235
|
+
return ['No velocity data available'];
|
|
236
|
+
}
|
|
237
|
+
// Trend insight
|
|
238
|
+
const trendDirection = analysis.trendPercentage >= 0 ? 'up' : 'down';
|
|
239
|
+
const trendEmoji = analysis.overallTrend === 'increasing' ? '📈' :
|
|
240
|
+
analysis.overallTrend === 'decreasing' ? '📉' :
|
|
241
|
+
analysis.overallTrend === 'volatile' ? '📊' : '➡️';
|
|
242
|
+
insights.push(`${trendEmoji} Velocity trend: ${analysis.overallTrend} (${trendDirection === 'up' ? '+' : ''}${analysis.trendPercentage}%)`);
|
|
243
|
+
insights.push(`📊 Average: ${analysis.averageCommitsPerWeek} commits/week`);
|
|
244
|
+
// Peak week
|
|
245
|
+
const peakDate = (0, date_fns_1.format)(analysis.peakWeek.weekStart, 'MMM d, yyyy');
|
|
246
|
+
insights.push(`🚀 Peak week: ${peakDate} (${analysis.peakWeek.commits} commits)`);
|
|
247
|
+
// Anomalies summary
|
|
248
|
+
const criticalDrops = analysis.anomalies.filter(a => a.type === 'drop' && a.severity === 'critical');
|
|
249
|
+
const zeroWeeks = analysis.anomalies.filter(a => a.type === 'zero-activity');
|
|
250
|
+
if (criticalDrops.length > 0) {
|
|
251
|
+
insights.push(`⚠️ ${criticalDrops.length} critical productivity drop(s) detected`);
|
|
252
|
+
}
|
|
253
|
+
if (zeroWeeks.length > 0) {
|
|
254
|
+
insights.push(`🚫 ${zeroWeeks.length} week(s) with zero activity`);
|
|
255
|
+
}
|
|
256
|
+
return insights;
|
|
257
|
+
}
|