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.
Files changed (172) hide show
  1. package/.github/agents/complete.agent.md +257 -0
  2. package/.github/agents/feature-scaffold.agent.md +248 -0
  3. package/.github/agents/jsdoc.agent.md +243 -0
  4. package/.github/agents/plan.agent.md +202 -0
  5. package/.github/agents/spec-writer.agent.md +169 -0
  6. package/.github/agents/test-writer.agent.md +169 -0
  7. package/.stylelintrc.json +27 -0
  8. package/README.md +94 -94
  9. package/coverage/base.css +224 -0
  10. package/coverage/block-navigation.js +87 -0
  11. package/coverage/favicon.png +0 -0
  12. package/coverage/index.html +446 -0
  13. package/coverage/lcov-report/base.css +224 -0
  14. package/coverage/lcov-report/block-navigation.js +87 -0
  15. package/coverage/lcov-report/favicon.png +0 -0
  16. package/coverage/lcov-report/index.html +446 -0
  17. package/coverage/lcov-report/prettify.css +1 -0
  18. package/coverage/lcov-report/prettify.js +2 -0
  19. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  20. package/coverage/lcov-report/sorter.js +210 -0
  21. package/coverage/lcov.info +7039 -0
  22. package/coverage/prettify.css +1 -0
  23. package/coverage/prettify.js +2 -0
  24. package/coverage/sort-arrow-sprite.png +0 -0
  25. package/coverage/sorter.js +210 -0
  26. package/dist/commands/generate.js +56 -56
  27. package/dist/config/defaults.js +158 -0
  28. package/dist/config/index.js +10 -0
  29. package/dist/features/achievements/data/achievements.json +284 -0
  30. package/dist/features/achievements/engine.js +140 -0
  31. package/dist/features/achievements/evaluators.js +246 -0
  32. package/dist/features/achievements/helpers.js +58 -0
  33. package/dist/features/achievements/index.js +57 -0
  34. package/dist/features/achievements/loader.js +88 -0
  35. package/dist/features/achievements/template.js +155 -0
  36. package/dist/features/achievements/types.js +7 -0
  37. package/dist/features/commit-quality/analyzer.js +378 -0
  38. package/dist/features/commit-quality/analyzer.test.js +484 -0
  39. package/dist/features/commit-quality/index.js +28 -0
  40. package/dist/features/commit-quality/template.js +114 -0
  41. package/dist/features/commit-quality/types.js +2 -0
  42. package/dist/features/comparison/analyzer.js +222 -0
  43. package/dist/features/comparison/index.js +28 -0
  44. package/dist/features/comparison/template.js +119 -0
  45. package/dist/features/comparison/types.js +2 -0
  46. package/dist/features/contribution-graph/index.js +9 -0
  47. package/dist/features/contribution-graph/template.js +89 -0
  48. package/dist/features/events/index.js +31 -0
  49. package/dist/features/events/parser.js +253 -0
  50. package/dist/features/events/template.js +113 -0
  51. package/dist/features/events/types.js +2 -0
  52. package/dist/features/executive-summary/generator.js +275 -0
  53. package/dist/features/executive-summary/index.js +27 -0
  54. package/dist/features/executive-summary/template.js +80 -0
  55. package/dist/features/executive-summary/types.js +2 -0
  56. package/dist/features/gaps/analyzer.js +298 -0
  57. package/dist/features/gaps/analyzer.test.js +517 -0
  58. package/dist/features/gaps/index.js +27 -0
  59. package/dist/features/gaps/template.js +190 -0
  60. package/dist/features/gaps/types.js +2 -0
  61. package/dist/features/impact/analyzer.js +248 -0
  62. package/dist/features/impact/index.js +26 -0
  63. package/dist/features/impact/template.js +118 -0
  64. package/dist/features/impact/types.js +2 -0
  65. package/dist/features/index.js +40 -0
  66. package/dist/features/knowledge/analyzer.js +385 -0
  67. package/dist/features/knowledge/index.js +26 -0
  68. package/dist/features/knowledge/template.js +239 -0
  69. package/dist/features/knowledge/types.js +2 -0
  70. package/dist/features/streaks/calculator.js +184 -0
  71. package/dist/features/streaks/calculator.test.js +366 -0
  72. package/dist/features/streaks/index.js +36 -0
  73. package/dist/features/streaks/template.js +41 -0
  74. package/dist/features/streaks/types.js +9 -0
  75. package/dist/features/team/analyzer.js +316 -0
  76. package/dist/features/team/index.js +30 -0
  77. package/dist/features/team/template.js +146 -0
  78. package/dist/features/team/types.js +2 -0
  79. package/dist/features/time-patterns/analyzer.js +319 -0
  80. package/dist/features/time-patterns/analyzer.test.js +278 -0
  81. package/dist/features/time-patterns/index.js +37 -0
  82. package/dist/features/time-patterns/template.js +109 -0
  83. package/dist/features/time-patterns/types.js +9 -0
  84. package/dist/features/velocity/analyzer.js +257 -0
  85. package/dist/features/velocity/analyzer.test.js +383 -0
  86. package/dist/features/velocity/index.js +27 -0
  87. package/dist/features/velocity/template.js +189 -0
  88. package/dist/features/velocity/types.js +2 -0
  89. package/dist/generators/html/scripts/knowledge.js +17 -0
  90. package/dist/generators/html/styles/base.css +8 -3
  91. package/dist/generators/html/styles/components.css +121 -1
  92. package/dist/generators/html/styles/knowledge.css +21 -0
  93. package/dist/generators/html/styles/leaddev.css +108 -48
  94. package/dist/generators/html/styles/strategic-insights.css +1337 -0
  95. package/dist/generators/html/templates/commitQualitySection.js +28 -2
  96. package/dist/generators/html/templates/executiveSummarySection.js +0 -4
  97. package/dist/generators/html/templates/impactSection.js +8 -6
  98. package/dist/generators/html/templates/knowledgeSection.js +16 -2
  99. package/dist/generators/html/templates/velocitySection.js +2 -2
  100. package/dist/generators/html/types.js +7 -0
  101. package/dist/generators/html/utils/analysisRunner.js +93 -0
  102. package/dist/generators/html/utils/cardBuilder.js +47 -0
  103. package/dist/generators/html/utils/contextBuilder.js +54 -0
  104. package/dist/generators/html/utils/htmlDocumentBuilder.js +396 -0
  105. package/dist/generators/html/utils/kpiBuilder.js +76 -0
  106. package/dist/generators/html/utils/sectionWrapper.js +71 -0
  107. package/dist/generators/html/utils/styleLoader.js +2 -2
  108. package/dist/html/analysisRunner.js +93 -0
  109. package/dist/html/htmlDocumentBuilder.js +396 -0
  110. package/dist/html/index.js +29 -0
  111. package/dist/html/shared/colorUtils.js +61 -0
  112. package/dist/html/shared/commitMapBuilder.js +23 -0
  113. package/dist/html/shared/components/cardBuilder.js +47 -0
  114. package/dist/html/shared/components/index.js +18 -0
  115. package/dist/html/shared/components/kpiBuilder.js +76 -0
  116. package/dist/html/shared/components/sectionWrapper.js +71 -0
  117. package/dist/html/shared/contextBuilder.js +54 -0
  118. package/dist/html/shared/dateRangeCalculator.js +56 -0
  119. package/dist/html/shared/developerStatsCalculator.js +28 -0
  120. package/dist/html/shared/index.js +39 -0
  121. package/dist/html/shared/scriptLoader.js +15 -0
  122. package/dist/html/shared/scripts/export.js +125 -0
  123. package/dist/html/shared/scripts/knowledge.js +137 -0
  124. package/dist/html/shared/scripts/modal.js +68 -0
  125. package/dist/html/shared/scripts/navigation.js +156 -0
  126. package/dist/html/shared/scripts/tabs.js +18 -0
  127. package/dist/html/shared/scripts/tooltip.js +21 -0
  128. package/dist/html/shared/styleLoader.js +18 -0
  129. package/dist/html/shared/styles/achievements.css +387 -0
  130. package/dist/html/shared/styles/base.css +822 -0
  131. package/dist/html/shared/styles/components.css +1511 -0
  132. package/dist/html/shared/styles/knowledge.css +242 -0
  133. package/dist/html/shared/styles/strategic-insights.css +1337 -0
  134. package/dist/html/shared/weekGrouper.js +27 -0
  135. package/dist/html/types.js +7 -0
  136. package/dist/index.js +39 -39
  137. package/dist/test/helpers/commitFactory.js +166 -0
  138. package/dist/test/helpers/dateUtils.js +101 -0
  139. package/dist/test/helpers/index.js +29 -0
  140. package/dist/test/setup.js +17 -0
  141. package/dist/test/smoke.test.js +94 -0
  142. package/dist/types/achievements.js +7 -0
  143. package/dist/types/analysis.js +7 -0
  144. package/dist/types/core.js +7 -0
  145. package/dist/types/index.js +38 -0
  146. package/dist/types/options.js +7 -0
  147. package/dist/types/shared.js +7 -0
  148. package/dist/types/strategic.js +7 -0
  149. package/dist/types/summary.js +7 -0
  150. package/dist/utils/achievementDefinitions.js +22 -22
  151. package/dist/utils/analyzerContextBuilder.js +124 -0
  152. package/dist/utils/commitQualityAnalyzer.js +13 -2
  153. package/dist/utils/emptyResults.js +95 -0
  154. package/dist/utils/fileHotspotAnalyzer.js +4 -12
  155. package/dist/utils/gapAnalyzer.js +26 -28
  156. package/dist/utils/gitParser.test.js +363 -0
  157. package/dist/utils/htmlGenerator.js +62 -466
  158. package/dist/utils/impactAnalyzer.js +20 -19
  159. package/dist/utils/knowledgeDistributionAnalyzer.js +32 -27
  160. package/dist/utils/matrixGenerator.js +13 -13
  161. package/dist/utils/rangeComparisonAnalyzer.js +2 -2
  162. package/dist/utils/streakCalculator.js +77 -27
  163. package/dist/utils/teamAnalyzer.js +20 -1
  164. package/dist/utils/timePatternAnalyzer.js +18 -3
  165. package/dist/utils/velocityAnalyzer.js +23 -18
  166. package/dist/utils/wrappedGenerator.js +8 -8
  167. package/package.json +74 -64
  168. package/vitest.config.ts +46 -0
  169. package/SPECS.md +0 -490
  170. package/dist/cli.js +0 -24
  171. package/dist/commands/index.js +0 -24
  172. package/test-team.txt +0 -2
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * Streak Types
4
+ *
5
+ * Re-exports streak-related types from core for feature co-location.
6
+ *
7
+ * @module features/streaks/types
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });