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,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateStreaks = calculateStreaks;
|
|
4
|
+
exports.getStreakMotivation = getStreakMotivation;
|
|
5
|
+
const date_fns_1 = require("date-fns");
|
|
6
|
+
const analyzerContextBuilder_1 = require("../../utils/analyzerContextBuilder");
|
|
7
|
+
/**
|
|
8
|
+
* Check if a date is a business day (Monday-Friday)
|
|
9
|
+
*/
|
|
10
|
+
function isBusinessDay(date) {
|
|
11
|
+
const day = (0, date_fns_1.getDay)(date);
|
|
12
|
+
return day !== 0 && day !== 6; // 0 = Sunday, 6 = Saturday
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the previous business day from a given date
|
|
16
|
+
*/
|
|
17
|
+
function getPreviousBusinessDay(date) {
|
|
18
|
+
let prevDate = (0, date_fns_1.subDays)(date, 1);
|
|
19
|
+
while (!isBusinessDay(prevDate)) {
|
|
20
|
+
prevDate = (0, date_fns_1.subDays)(prevDate, 1);
|
|
21
|
+
}
|
|
22
|
+
return prevDate;
|
|
23
|
+
}
|
|
24
|
+
function calculateStreaks(ctxOrCommits, startDate, endDate) {
|
|
25
|
+
// Normalize to internal variables
|
|
26
|
+
let commits;
|
|
27
|
+
let start;
|
|
28
|
+
let end;
|
|
29
|
+
if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
|
|
30
|
+
commits = ctxOrCommits.commits;
|
|
31
|
+
start = ctxOrCommits.dateRange.start;
|
|
32
|
+
end = ctxOrCommits.dateRange.end;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
commits = ctxOrCommits;
|
|
36
|
+
start = startDate;
|
|
37
|
+
end = endDate;
|
|
38
|
+
}
|
|
39
|
+
// Create set of unique commit dates
|
|
40
|
+
const commitDates = new Set();
|
|
41
|
+
commits.forEach(commit => {
|
|
42
|
+
const commitDate = new Date(commit.date);
|
|
43
|
+
if (commitDate >= start && commitDate <= end) {
|
|
44
|
+
commitDates.add((0, date_fns_1.format)(commitDate, 'yyyy-MM-dd'));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// Calculate total days and active days
|
|
48
|
+
const totalDays = (0, date_fns_1.differenceInDays)(end, start) + 1;
|
|
49
|
+
const totalActiveDays = commitDates.size;
|
|
50
|
+
const activeDayPercentage = totalDays > 0 ? (totalActiveDays / totalDays) * 100 : 0;
|
|
51
|
+
// Calculate business day stats
|
|
52
|
+
const allDays = (0, date_fns_1.eachDayOfInterval)({ start: start, end: end });
|
|
53
|
+
const businessDays = allDays.filter(isBusinessDay);
|
|
54
|
+
const totalBusinessDays = businessDays.length;
|
|
55
|
+
const activeBusinessDays = businessDays.filter(d => commitDates.has((0, date_fns_1.format)(d, 'yyyy-MM-dd'))).length;
|
|
56
|
+
// Find all streaks (business days only)
|
|
57
|
+
const streaks = [];
|
|
58
|
+
let currentStreakDays = 0;
|
|
59
|
+
let currentStreakStart = null;
|
|
60
|
+
for (const date of businessDays) {
|
|
61
|
+
const dateKey = (0, date_fns_1.format)(date, 'yyyy-MM-dd');
|
|
62
|
+
if (commitDates.has(dateKey)) {
|
|
63
|
+
if (currentStreakDays === 0) {
|
|
64
|
+
currentStreakStart = date;
|
|
65
|
+
}
|
|
66
|
+
currentStreakDays++;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (currentStreakDays > 0 && currentStreakStart) {
|
|
70
|
+
// Find the last business day in the streak
|
|
71
|
+
const streakEnd = getPreviousBusinessDay(date);
|
|
72
|
+
streaks.push({
|
|
73
|
+
days: currentStreakDays,
|
|
74
|
+
startDate: currentStreakStart,
|
|
75
|
+
endDate: streakEnd > currentStreakStart ? streakEnd : currentStreakStart
|
|
76
|
+
});
|
|
77
|
+
currentStreakDays = 0;
|
|
78
|
+
currentStreakStart = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Handle streak that extends to end date
|
|
83
|
+
if (currentStreakDays > 0 && currentStreakStart) {
|
|
84
|
+
// Find the last business day in range
|
|
85
|
+
const lastBusinessDay = businessDays[businessDays.length - 1] || end;
|
|
86
|
+
streaks.push({
|
|
87
|
+
days: currentStreakDays,
|
|
88
|
+
startDate: currentStreakStart,
|
|
89
|
+
endDate: lastBusinessDay
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Find longest streak
|
|
93
|
+
const longestStreak = streaks.length > 0
|
|
94
|
+
? streaks.reduce((longest, current) => current.days > longest.days ? current : longest)
|
|
95
|
+
: { days: 0, startDate: start, endDate: start };
|
|
96
|
+
// Determine current streak (from today or most recent business day going backward)
|
|
97
|
+
const today = new Date();
|
|
98
|
+
const todayKey = (0, date_fns_1.format)(today, 'yyyy-MM-dd');
|
|
99
|
+
let currentStreak;
|
|
100
|
+
// For current streak, we check today or the last business day
|
|
101
|
+
const checkToday = isBusinessDay(today) ? today : getPreviousBusinessDay(today);
|
|
102
|
+
const checkTodayKey = (0, date_fns_1.format)(checkToday, 'yyyy-MM-dd');
|
|
103
|
+
if (commitDates.has(checkTodayKey)) {
|
|
104
|
+
// Recent business day has a commit, find streak backward through business days only
|
|
105
|
+
let streakDays = 0;
|
|
106
|
+
let checkDate = checkToday;
|
|
107
|
+
let streakStartDate = checkToday;
|
|
108
|
+
while (commitDates.has((0, date_fns_1.format)(checkDate, 'yyyy-MM-dd'))) {
|
|
109
|
+
streakDays++;
|
|
110
|
+
streakStartDate = checkDate;
|
|
111
|
+
checkDate = getPreviousBusinessDay(checkDate);
|
|
112
|
+
if (checkDate < start)
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
currentStreak = {
|
|
116
|
+
days: streakDays,
|
|
117
|
+
startDate: streakStartDate,
|
|
118
|
+
endDate: checkToday,
|
|
119
|
+
isActive: true
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// No commit on recent business day, check previous business day
|
|
124
|
+
const prevBusinessDay = getPreviousBusinessDay(checkToday);
|
|
125
|
+
const prevKey = (0, date_fns_1.format)(prevBusinessDay, 'yyyy-MM-dd');
|
|
126
|
+
if (commitDates.has(prevKey)) {
|
|
127
|
+
let streakDays = 0;
|
|
128
|
+
let checkDate = prevBusinessDay;
|
|
129
|
+
let streakStartDate = prevBusinessDay;
|
|
130
|
+
while (commitDates.has((0, date_fns_1.format)(checkDate, 'yyyy-MM-dd'))) {
|
|
131
|
+
streakDays++;
|
|
132
|
+
streakStartDate = checkDate;
|
|
133
|
+
checkDate = getPreviousBusinessDay(checkDate);
|
|
134
|
+
if (checkDate < start)
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
currentStreak = {
|
|
138
|
+
days: streakDays,
|
|
139
|
+
startDate: streakStartDate,
|
|
140
|
+
endDate: prevBusinessDay,
|
|
141
|
+
isActive: false
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// No recent streak
|
|
146
|
+
currentStreak = {
|
|
147
|
+
days: 0,
|
|
148
|
+
startDate: today,
|
|
149
|
+
endDate: today,
|
|
150
|
+
isActive: false
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
currentStreak,
|
|
156
|
+
longestStreak,
|
|
157
|
+
totalActiveDays,
|
|
158
|
+
totalDays,
|
|
159
|
+
activeDayPercentage,
|
|
160
|
+
streaks,
|
|
161
|
+
// Add business day specific data
|
|
162
|
+
totalBusinessDays,
|
|
163
|
+
activeBusinessDays
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function getStreakMotivation(days, isActive) {
|
|
167
|
+
if (days === 0) {
|
|
168
|
+
return "Start building momentum today";
|
|
169
|
+
}
|
|
170
|
+
if (!isActive) {
|
|
171
|
+
return `Your ${days}-day streak ended. Time for a fresh start.`;
|
|
172
|
+
}
|
|
173
|
+
if (days >= 61)
|
|
174
|
+
return "Outstanding consistency";
|
|
175
|
+
if (days >= 31)
|
|
176
|
+
return "Exceptional dedication";
|
|
177
|
+
if (days >= 15)
|
|
178
|
+
return "Strong momentum 🔥";
|
|
179
|
+
if (days >= 8)
|
|
180
|
+
return "Solid consistency";
|
|
181
|
+
if (days >= 4)
|
|
182
|
+
return "Building momentum";
|
|
183
|
+
return "Just getting started";
|
|
184
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for the streak calculator
|
|
4
|
+
*
|
|
5
|
+
* Tests calculateStreaks function for correct streak calculation across
|
|
6
|
+
* consecutive days, business days, weekends, and various edge cases.
|
|
7
|
+
*
|
|
8
|
+
* @module features/streaks/calculator.test
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const vitest_1 = require("vitest");
|
|
12
|
+
const calculator_1 = require("./calculator");
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Test Helpers
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Creates a commit with a specific date
|
|
18
|
+
*/
|
|
19
|
+
function createCommit(date, overrides = {}) {
|
|
20
|
+
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
|
21
|
+
return {
|
|
22
|
+
hash: `hash-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
23
|
+
author: 'Test Author',
|
|
24
|
+
date: dateObj.toISOString(),
|
|
25
|
+
message: 'test commit',
|
|
26
|
+
...overrides,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates commits for an array of date strings
|
|
31
|
+
*/
|
|
32
|
+
function createCommits(dates) {
|
|
33
|
+
return dates.map(d => createCommit(d));
|
|
34
|
+
}
|
|
35
|
+
// Fixed dates for testing
|
|
36
|
+
const TEST_DATES = {
|
|
37
|
+
// June 2025 calendar reference:
|
|
38
|
+
// Mon Tue Wed Thu Fri Sat Sun
|
|
39
|
+
// 2 3 4 5 6 7 8
|
|
40
|
+
// 9 10 11 12 13 14 15
|
|
41
|
+
// 16 17 18 19 20 21 22
|
|
42
|
+
monday: '2025-06-09T10:00:00Z',
|
|
43
|
+
tuesday: '2025-06-10T10:00:00Z',
|
|
44
|
+
wednesday: '2025-06-11T10:00:00Z',
|
|
45
|
+
thursday: '2025-06-12T10:00:00Z',
|
|
46
|
+
friday: '2025-06-13T10:00:00Z',
|
|
47
|
+
saturday: '2025-06-14T10:00:00Z',
|
|
48
|
+
sunday: '2025-06-15T10:00:00Z',
|
|
49
|
+
nextMonday: '2025-06-16T10:00:00Z',
|
|
50
|
+
nextTuesday: '2025-06-17T10:00:00Z',
|
|
51
|
+
};
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Basic Streak Calculation
|
|
54
|
+
// ============================================================================
|
|
55
|
+
(0, vitest_1.describe)('calculateStreaks', () => {
|
|
56
|
+
(0, vitest_1.describe)('basic streak calculation', () => {
|
|
57
|
+
(0, vitest_1.it)('returns zero streaks for empty commits', () => {
|
|
58
|
+
const start = new Date('2025-06-01');
|
|
59
|
+
const end = new Date('2025-06-30');
|
|
60
|
+
const result = (0, calculator_1.calculateStreaks)([], start, end);
|
|
61
|
+
(0, vitest_1.expect)(result.currentStreak.days).toBe(0);
|
|
62
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(0);
|
|
63
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBe(0);
|
|
64
|
+
(0, vitest_1.expect)(result.streaks).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.it)('calculates streak of 1 for single commit', () => {
|
|
67
|
+
const commits = [createCommit(TEST_DATES.monday)];
|
|
68
|
+
const start = new Date('2025-06-01');
|
|
69
|
+
const end = new Date('2025-06-30');
|
|
70
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
71
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(1);
|
|
72
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBe(1);
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)('calculates streak for consecutive business days', () => {
|
|
75
|
+
const commits = createCommits([
|
|
76
|
+
TEST_DATES.monday,
|
|
77
|
+
TEST_DATES.tuesday,
|
|
78
|
+
TEST_DATES.wednesday,
|
|
79
|
+
]);
|
|
80
|
+
const start = new Date('2025-06-01');
|
|
81
|
+
const end = new Date('2025-06-30');
|
|
82
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
83
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(3);
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.it)('multiple commits on same day count as one streak day', () => {
|
|
86
|
+
const commits = [
|
|
87
|
+
createCommit('2025-06-09T09:00:00Z'),
|
|
88
|
+
createCommit('2025-06-09T14:00:00Z'),
|
|
89
|
+
createCommit('2025-06-09T18:00:00Z'),
|
|
90
|
+
];
|
|
91
|
+
const start = new Date('2025-06-01');
|
|
92
|
+
const end = new Date('2025-06-30');
|
|
93
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
94
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(1);
|
|
95
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBe(1);
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.it)('calculates full work week streak (Mon-Fri)', () => {
|
|
98
|
+
const commits = createCommits([
|
|
99
|
+
TEST_DATES.monday,
|
|
100
|
+
TEST_DATES.tuesday,
|
|
101
|
+
TEST_DATES.wednesday,
|
|
102
|
+
TEST_DATES.thursday,
|
|
103
|
+
TEST_DATES.friday,
|
|
104
|
+
]);
|
|
105
|
+
const start = new Date('2025-06-01');
|
|
106
|
+
const end = new Date('2025-06-30');
|
|
107
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
108
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(5);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// Weekend Handling (Business Days Mode)
|
|
113
|
+
// ============================================================================
|
|
114
|
+
(0, vitest_1.describe)('weekend handling', () => {
|
|
115
|
+
(0, vitest_1.it)('continues streak across weekend (Fri → Mon) because weekends are skipped', () => {
|
|
116
|
+
// Calculator uses business days only by default
|
|
117
|
+
const commits = createCommits([
|
|
118
|
+
TEST_DATES.friday, // Friday June 13
|
|
119
|
+
TEST_DATES.nextMonday, // Monday June 16
|
|
120
|
+
]);
|
|
121
|
+
const start = new Date('2025-06-01');
|
|
122
|
+
const end = new Date('2025-06-30');
|
|
123
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
124
|
+
// Should be 2 because Sat/Sun are not business days
|
|
125
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(2);
|
|
126
|
+
});
|
|
127
|
+
(0, vitest_1.it)('breaks streak when missing Monday after Friday', () => {
|
|
128
|
+
const commits = createCommits([
|
|
129
|
+
TEST_DATES.friday, // Friday June 13
|
|
130
|
+
TEST_DATES.nextTuesday, // Tuesday June 17 (missing Monday)
|
|
131
|
+
]);
|
|
132
|
+
const start = new Date('2025-06-01');
|
|
133
|
+
const end = new Date('2025-06-30');
|
|
134
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
135
|
+
// Should be two separate streaks of 1 each
|
|
136
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(1);
|
|
137
|
+
});
|
|
138
|
+
(0, vitest_1.it)('weekend commits are not counted in business day streaks', () => {
|
|
139
|
+
const commits = createCommits([
|
|
140
|
+
TEST_DATES.friday,
|
|
141
|
+
TEST_DATES.saturday,
|
|
142
|
+
TEST_DATES.sunday,
|
|
143
|
+
TEST_DATES.nextMonday,
|
|
144
|
+
]);
|
|
145
|
+
const start = new Date('2025-06-01');
|
|
146
|
+
const end = new Date('2025-06-30');
|
|
147
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
148
|
+
// Only Fri and Mon count as streak (weekend days ignored)
|
|
149
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(2);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Gap Detection
|
|
154
|
+
// ============================================================================
|
|
155
|
+
(0, vitest_1.describe)('gap detection', () => {
|
|
156
|
+
(0, vitest_1.it)('detects gap and creates separate streaks', () => {
|
|
157
|
+
const commits = createCommits([
|
|
158
|
+
'2025-06-09T10:00:00Z', // Monday week 1
|
|
159
|
+
'2025-06-10T10:00:00Z', // Tuesday week 1
|
|
160
|
+
// Gap: Wednesday, Thursday, Friday
|
|
161
|
+
'2025-06-16T10:00:00Z', // Monday week 2
|
|
162
|
+
'2025-06-17T10:00:00Z', // Tuesday week 2
|
|
163
|
+
]);
|
|
164
|
+
const start = new Date('2025-06-01');
|
|
165
|
+
const end = new Date('2025-06-30');
|
|
166
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
167
|
+
(0, vitest_1.expect)(result.streaks.length).toBe(2);
|
|
168
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(2);
|
|
169
|
+
});
|
|
170
|
+
(0, vitest_1.it)('finds longest streak among multiple streaks', () => {
|
|
171
|
+
const commits = createCommits([
|
|
172
|
+
// Streak 1: Mon-Tue (2 days)
|
|
173
|
+
'2025-06-02T10:00:00Z',
|
|
174
|
+
'2025-06-03T10:00:00Z',
|
|
175
|
+
// Gap: Wed, Thu, Fri
|
|
176
|
+
// Streak 2: Mon-Fri (5 days) - longest
|
|
177
|
+
'2025-06-09T10:00:00Z',
|
|
178
|
+
'2025-06-10T10:00:00Z',
|
|
179
|
+
'2025-06-11T10:00:00Z',
|
|
180
|
+
'2025-06-12T10:00:00Z',
|
|
181
|
+
'2025-06-13T10:00:00Z',
|
|
182
|
+
// Gap: Sat, Sun, Mon
|
|
183
|
+
// Streak 3: Tue-Wed (2 days)
|
|
184
|
+
'2025-06-17T10:00:00Z',
|
|
185
|
+
'2025-06-18T10:00:00Z',
|
|
186
|
+
]);
|
|
187
|
+
const start = new Date('2025-06-01');
|
|
188
|
+
const end = new Date('2025-06-30');
|
|
189
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
190
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(5);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Statistics Calculation
|
|
195
|
+
// ============================================================================
|
|
196
|
+
(0, vitest_1.describe)('statistics', () => {
|
|
197
|
+
(0, vitest_1.it)('calculates total active days correctly', () => {
|
|
198
|
+
const commits = createCommits([
|
|
199
|
+
'2025-06-09T10:00:00Z',
|
|
200
|
+
'2025-06-10T10:00:00Z',
|
|
201
|
+
'2025-06-11T10:00:00Z',
|
|
202
|
+
'2025-06-16T10:00:00Z',
|
|
203
|
+
'2025-06-17T10:00:00Z',
|
|
204
|
+
]);
|
|
205
|
+
const start = new Date('2025-06-01');
|
|
206
|
+
const end = new Date('2025-06-30');
|
|
207
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
208
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBe(5);
|
|
209
|
+
});
|
|
210
|
+
(0, vitest_1.it)('calculates total days in range', () => {
|
|
211
|
+
const commits = [createCommit(TEST_DATES.monday)];
|
|
212
|
+
const start = new Date('2025-06-01');
|
|
213
|
+
const end = new Date('2025-06-30');
|
|
214
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
215
|
+
(0, vitest_1.expect)(result.totalDays).toBe(30);
|
|
216
|
+
});
|
|
217
|
+
(0, vitest_1.it)('calculates active day percentage', () => {
|
|
218
|
+
const commits = createCommits([
|
|
219
|
+
'2025-06-02T10:00:00Z', // Monday
|
|
220
|
+
'2025-06-03T10:00:00Z', // Tuesday
|
|
221
|
+
'2025-06-04T10:00:00Z', // Wednesday
|
|
222
|
+
]);
|
|
223
|
+
const start = new Date('2025-06-01');
|
|
224
|
+
const end = new Date('2025-06-10'); // 10 days
|
|
225
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
226
|
+
// 3 active days out of 10 = 30%
|
|
227
|
+
(0, vitest_1.expect)(result.activeDayPercentage).toBe(30);
|
|
228
|
+
});
|
|
229
|
+
(0, vitest_1.it)('calculates business day statistics', () => {
|
|
230
|
+
const commits = createCommits([
|
|
231
|
+
'2025-06-09T10:00:00Z', // Monday
|
|
232
|
+
'2025-06-10T10:00:00Z', // Tuesday
|
|
233
|
+
'2025-06-11T10:00:00Z', // Wednesday
|
|
234
|
+
]);
|
|
235
|
+
const start = new Date('2025-06-09'); // Monday
|
|
236
|
+
const end = new Date('2025-06-15'); // Sunday
|
|
237
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
238
|
+
// 5 business days (Mon-Fri), 3 active
|
|
239
|
+
(0, vitest_1.expect)(result.totalBusinessDays).toBe(5);
|
|
240
|
+
(0, vitest_1.expect)(result.activeBusinessDays).toBe(3);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// Edge Cases
|
|
245
|
+
// ============================================================================
|
|
246
|
+
(0, vitest_1.describe)('edge cases', () => {
|
|
247
|
+
(0, vitest_1.it)('handles commits outside date range', () => {
|
|
248
|
+
const commits = createCommits([
|
|
249
|
+
'2025-05-15T10:00:00Z', // Before range
|
|
250
|
+
'2025-06-10T10:00:00Z', // In range
|
|
251
|
+
'2025-07-15T10:00:00Z', // After range
|
|
252
|
+
]);
|
|
253
|
+
const start = new Date('2025-06-01');
|
|
254
|
+
const end = new Date('2025-06-30');
|
|
255
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
256
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBe(1);
|
|
257
|
+
});
|
|
258
|
+
(0, vitest_1.it)('handles single day range', () => {
|
|
259
|
+
// June 9, 2025 is a Monday (business day)
|
|
260
|
+
const commits = [createCommit('2025-06-09T10:00:00Z')];
|
|
261
|
+
const start = new Date('2025-06-09');
|
|
262
|
+
const end = new Date('2025-06-09');
|
|
263
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
264
|
+
(0, vitest_1.expect)(result.totalDays).toBe(1);
|
|
265
|
+
// The commit date is within range, should be counted
|
|
266
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBeGreaterThanOrEqual(0);
|
|
267
|
+
});
|
|
268
|
+
(0, vitest_1.it)('handles commits at different times on same day', () => {
|
|
269
|
+
// All commits on June 9, 2025 (Monday)
|
|
270
|
+
const commits = [
|
|
271
|
+
createCommit('2025-06-09T08:00:00Z'),
|
|
272
|
+
createCommit('2025-06-09T12:00:00Z'),
|
|
273
|
+
createCommit('2025-06-09T16:00:00Z'),
|
|
274
|
+
];
|
|
275
|
+
const start = new Date('2025-06-01');
|
|
276
|
+
const end = new Date('2025-06-30');
|
|
277
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
278
|
+
// Should be 1 active day (all commits on same date)
|
|
279
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBeGreaterThanOrEqual(1);
|
|
280
|
+
});
|
|
281
|
+
(0, vitest_1.it)('handles unsorted commits', () => {
|
|
282
|
+
// Commits not in chronological order
|
|
283
|
+
const commits = createCommits([
|
|
284
|
+
'2025-06-11T10:00:00Z', // Wednesday
|
|
285
|
+
'2025-06-09T10:00:00Z', // Monday
|
|
286
|
+
'2025-06-10T10:00:00Z', // Tuesday
|
|
287
|
+
]);
|
|
288
|
+
const start = new Date('2025-06-01');
|
|
289
|
+
const end = new Date('2025-06-30');
|
|
290
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
291
|
+
// Should still calculate correctly
|
|
292
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(3);
|
|
293
|
+
});
|
|
294
|
+
(0, vitest_1.it)('handles large number of commits', () => {
|
|
295
|
+
// Create commits for every business day in June
|
|
296
|
+
const commits = [];
|
|
297
|
+
for (let d = 2; d <= 30; d++) {
|
|
298
|
+
const date = new Date(2025, 5, d); // June 2025
|
|
299
|
+
const dayOfWeek = date.getDay();
|
|
300
|
+
if (dayOfWeek !== 0 && dayOfWeek !== 6) { // Skip weekends
|
|
301
|
+
commits.push(createCommit(date.toISOString()));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const start = new Date('2025-06-01');
|
|
305
|
+
const end = new Date('2025-06-30');
|
|
306
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
307
|
+
// Should be 21 business days in June 2025
|
|
308
|
+
(0, vitest_1.expect)(result.totalActiveDays).toBeGreaterThan(15);
|
|
309
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBeGreaterThan(15);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
// ============================================================================
|
|
313
|
+
// Streak Start/End Dates
|
|
314
|
+
// ============================================================================
|
|
315
|
+
(0, vitest_1.describe)('streak dates', () => {
|
|
316
|
+
(0, vitest_1.it)('records correct start and end dates for streaks', () => {
|
|
317
|
+
const commits = createCommits([
|
|
318
|
+
'2025-06-09T10:00:00Z', // Monday
|
|
319
|
+
'2025-06-10T10:00:00Z', // Tuesday
|
|
320
|
+
'2025-06-11T10:00:00Z', // Wednesday
|
|
321
|
+
]);
|
|
322
|
+
const start = new Date('2025-06-01');
|
|
323
|
+
const end = new Date('2025-06-30');
|
|
324
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
325
|
+
// Streak should span 3 days
|
|
326
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(3);
|
|
327
|
+
// Start and end dates should be set
|
|
328
|
+
(0, vitest_1.expect)(result.longestStreak.startDate).toBeInstanceOf(Date);
|
|
329
|
+
(0, vitest_1.expect)(result.longestStreak.endDate).toBeInstanceOf(Date);
|
|
330
|
+
});
|
|
331
|
+
(0, vitest_1.it)('handles streak extending to end of range', () => {
|
|
332
|
+
const commits = createCommits([
|
|
333
|
+
'2025-06-26T10:00:00Z', // Thursday
|
|
334
|
+
'2025-06-27T10:00:00Z', // Friday
|
|
335
|
+
'2025-06-30T10:00:00Z', // Monday (next week)
|
|
336
|
+
]);
|
|
337
|
+
const start = new Date('2025-06-01');
|
|
338
|
+
const end = new Date('2025-06-30');
|
|
339
|
+
const result = (0, calculator_1.calculateStreaks)(commits, start, end);
|
|
340
|
+
// Should capture the streak continuing to end of range
|
|
341
|
+
(0, vitest_1.expect)(result.streaks.length).toBeGreaterThan(0);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
// ============================================================================
|
|
345
|
+
// Context-based API
|
|
346
|
+
// ============================================================================
|
|
347
|
+
(0, vitest_1.describe)('context-based API', () => {
|
|
348
|
+
(0, vitest_1.it)('works with AnalyzerContext', () => {
|
|
349
|
+
const commits = createCommits([
|
|
350
|
+
TEST_DATES.monday,
|
|
351
|
+
TEST_DATES.tuesday,
|
|
352
|
+
TEST_DATES.wednesday,
|
|
353
|
+
]);
|
|
354
|
+
const ctx = {
|
|
355
|
+
commits,
|
|
356
|
+
dateRange: {
|
|
357
|
+
start: new Date('2025-06-01'),
|
|
358
|
+
end: new Date('2025-06-30'),
|
|
359
|
+
},
|
|
360
|
+
author: 'Test Author',
|
|
361
|
+
};
|
|
362
|
+
const result = (0, calculator_1.calculateStreaks)(ctx);
|
|
363
|
+
(0, vitest_1.expect)(result.longestStreak.days).toBe(3);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Streaks Feature
|
|
4
|
+
*
|
|
5
|
+
* Self-contained streak calculation and display including:
|
|
6
|
+
* - Types: Streak interfaces
|
|
7
|
+
* - Calculator: Streak calculation logic
|
|
8
|
+
* - Template: HTML rendering
|
|
9
|
+
*
|
|
10
|
+
* @module features/streaks
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
24
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.buildStreakSection = exports.getStreakMotivation = exports.calculateStreaks = void 0;
|
|
28
|
+
// Types
|
|
29
|
+
__exportStar(require("./types"), exports);
|
|
30
|
+
// Calculator
|
|
31
|
+
var calculator_1 = require("./calculator");
|
|
32
|
+
Object.defineProperty(exports, "calculateStreaks", { enumerable: true, get: function () { return calculator_1.calculateStreaks; } });
|
|
33
|
+
Object.defineProperty(exports, "getStreakMotivation", { enumerable: true, get: function () { return calculator_1.getStreakMotivation; } });
|
|
34
|
+
// Template
|
|
35
|
+
var template_1 = require("./template");
|
|
36
|
+
Object.defineProperty(exports, "buildStreakSection", { enumerable: true, get: function () { return template_1.buildStreakSection; } });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildStreakSection = buildStreakSection;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
const calculator_1 = require("./calculator");
|
|
6
|
+
function buildStreakSection(streakData) {
|
|
7
|
+
return `
|
|
8
|
+
<div class="streak-section">
|
|
9
|
+
<h2>Streak Analysis</h2>
|
|
10
|
+
<div class="streak-cards">
|
|
11
|
+
<div class="streak-card">
|
|
12
|
+
<div class="streak-icon">${streakData.currentStreak.isActive ? '🔥' : '💤'}</div>
|
|
13
|
+
<div class="streak-info">
|
|
14
|
+
<div class="streak-label">Current Streak</div>
|
|
15
|
+
<div class="streak-value">${streakData.currentStreak.days} day${streakData.currentStreak.days !== 1 ? 's' : ''}</div>
|
|
16
|
+
${streakData.currentStreak.isActive && streakData.currentStreak.days > 0 ? `<div class="streak-motivation">${(0, calculator_1.getStreakMotivation)(streakData.currentStreak.days, true)}</div>` : ''}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="streak-card">
|
|
20
|
+
<div class="streak-icon">🏆</div>
|
|
21
|
+
<div class="streak-info">
|
|
22
|
+
<div class="streak-label">Longest Streak</div>
|
|
23
|
+
<div class="streak-value">${streakData.longestStreak.days} day${streakData.longestStreak.days !== 1 ? 's' : ''}</div>
|
|
24
|
+
${streakData.longestStreak.days > 0 ? `<div class="streak-date-range">${(0, date_fns_1.format)(streakData.longestStreak.startDate, 'MMM d')} - ${(0, date_fns_1.format)(streakData.longestStreak.endDate, 'MMM d, yyyy')}</div>` : ''}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="streak-card">
|
|
28
|
+
<div class="streak-icon">📈</div>
|
|
29
|
+
<div class="streak-info">
|
|
30
|
+
<div class="streak-label">Active Days</div>
|
|
31
|
+
<div class="streak-value">${streakData.totalActiveDays}/${streakData.totalDays}</div>
|
|
32
|
+
<div class="active-days-bar">
|
|
33
|
+
<div class="active-days-progress" style="width: ${streakData.activeDayPercentage}%"></div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="active-days-percentage">${streakData.activeDayPercentage.toFixed(1)}% active</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
`;
|
|
41
|
+
}
|