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.
Files changed (176) 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 +262 -5
  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 +10 -6
  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 +1335 -0
  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/comparisonSection.js +119 -0
  97. package/dist/generators/html/templates/eventsSection.js +113 -0
  98. package/dist/generators/html/templates/executiveSummarySection.js +80 -0
  99. package/dist/generators/html/templates/gapSection.js +190 -0
  100. package/dist/generators/html/templates/impactSection.js +8 -6
  101. package/dist/generators/html/templates/knowledgeSection.js +16 -2
  102. package/dist/generators/html/templates/teamSection.js +146 -0
  103. package/dist/generators/html/templates/velocitySection.js +189 -0
  104. package/dist/generators/html/types.js +7 -0
  105. package/dist/generators/html/utils/analysisRunner.js +93 -0
  106. package/dist/generators/html/utils/cardBuilder.js +47 -0
  107. package/dist/generators/html/utils/contextBuilder.js +54 -0
  108. package/dist/generators/html/utils/htmlDocumentBuilder.js +396 -0
  109. package/dist/generators/html/utils/kpiBuilder.js +76 -0
  110. package/dist/generators/html/utils/sectionWrapper.js +71 -0
  111. package/dist/generators/html/utils/styleLoader.js +2 -1
  112. package/dist/html/analysisRunner.js +93 -0
  113. package/dist/html/htmlDocumentBuilder.js +396 -0
  114. package/dist/html/index.js +29 -0
  115. package/dist/html/shared/colorUtils.js +61 -0
  116. package/dist/html/shared/commitMapBuilder.js +23 -0
  117. package/dist/html/shared/components/cardBuilder.js +47 -0
  118. package/dist/html/shared/components/index.js +18 -0
  119. package/dist/html/shared/components/kpiBuilder.js +76 -0
  120. package/dist/html/shared/components/sectionWrapper.js +71 -0
  121. package/dist/html/shared/contextBuilder.js +54 -0
  122. package/dist/html/shared/dateRangeCalculator.js +56 -0
  123. package/dist/html/shared/developerStatsCalculator.js +28 -0
  124. package/dist/html/shared/index.js +39 -0
  125. package/dist/html/shared/scriptLoader.js +15 -0
  126. package/dist/html/shared/scripts/export.js +125 -0
  127. package/dist/html/shared/scripts/knowledge.js +137 -0
  128. package/dist/html/shared/scripts/modal.js +68 -0
  129. package/dist/html/shared/scripts/navigation.js +156 -0
  130. package/dist/html/shared/scripts/tabs.js +18 -0
  131. package/dist/html/shared/scripts/tooltip.js +21 -0
  132. package/dist/html/shared/styleLoader.js +18 -0
  133. package/dist/html/shared/styles/achievements.css +387 -0
  134. package/dist/html/shared/styles/base.css +822 -0
  135. package/dist/html/shared/styles/components.css +1511 -0
  136. package/dist/html/shared/styles/knowledge.css +242 -0
  137. package/dist/html/shared/styles/strategic-insights.css +1337 -0
  138. package/dist/html/shared/weekGrouper.js +27 -0
  139. package/dist/html/types.js +7 -0
  140. package/dist/index.js +54 -21
  141. package/dist/test/helpers/commitFactory.js +166 -0
  142. package/dist/test/helpers/dateUtils.js +101 -0
  143. package/dist/test/helpers/index.js +29 -0
  144. package/dist/test/setup.js +17 -0
  145. package/dist/test/smoke.test.js +94 -0
  146. package/dist/types/achievements.js +7 -0
  147. package/dist/types/analysis.js +7 -0
  148. package/dist/types/core.js +7 -0
  149. package/dist/types/index.js +38 -0
  150. package/dist/types/options.js +7 -0
  151. package/dist/types/shared.js +7 -0
  152. package/dist/types/strategic.js +7 -0
  153. package/dist/types/summary.js +7 -0
  154. package/dist/utils/achievementDefinitions.js +22 -22
  155. package/dist/utils/analyzerContextBuilder.js +124 -0
  156. package/dist/utils/commitQualityAnalyzer.js +13 -2
  157. package/dist/utils/emptyResults.js +95 -0
  158. package/dist/utils/eventAnnotationParser.js +253 -0
  159. package/dist/utils/executiveSummaryGenerator.js +275 -0
  160. package/dist/utils/fileHotspotAnalyzer.js +4 -12
  161. package/dist/utils/gapAnalyzer.js +298 -0
  162. package/dist/utils/gitParser.test.js +363 -0
  163. package/dist/utils/htmlGenerator.js +126 -450
  164. package/dist/utils/impactAnalyzer.js +20 -19
  165. package/dist/utils/knowledgeDistributionAnalyzer.js +32 -27
  166. package/dist/utils/matrixGenerator.js +13 -13
  167. package/dist/utils/rangeComparisonAnalyzer.js +222 -0
  168. package/dist/utils/streakCalculator.js +77 -27
  169. package/dist/utils/teamAnalyzer.js +316 -0
  170. package/dist/utils/timePatternAnalyzer.js +18 -3
  171. package/dist/utils/velocityAnalyzer.js +257 -0
  172. package/dist/utils/wrappedGenerator.js +8 -8
  173. package/package.json +74 -55
  174. package/vitest.config.ts +46 -0
  175. package/dist/cli.js +0 -24
  176. package/dist/commands/index.js +0 -24
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeTimePatterns = analyzeTimePatterns;
4
+ exports.getChronotypeLabel = getChronotypeLabel;
5
+ exports.getChronotypeDescription = getChronotypeDescription;
6
+ const date_fns_1 = require("date-fns");
7
+ const analyzerContextBuilder_1 = require("../../utils/analyzerContextBuilder");
8
+ function analyzeTimePatterns(ctxOrCommits, startDate, endDate) {
9
+ // Normalize to internal variables
10
+ let commits;
11
+ let start;
12
+ let end;
13
+ if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
14
+ commits = ctxOrCommits.commits;
15
+ start = ctxOrCommits.dateRange.start;
16
+ end = ctxOrCommits.dateRange.end;
17
+ }
18
+ else {
19
+ commits = ctxOrCommits;
20
+ start = startDate;
21
+ end = endDate;
22
+ }
23
+ if (commits.length === 0) {
24
+ return getEmptyPattern();
25
+ }
26
+ // Filter commits within the date range
27
+ const filteredCommits = commits.filter(c => {
28
+ const commitDate = new Date(c.date);
29
+ return commitDate >= start && commitDate <= end;
30
+ });
31
+ if (filteredCommits.length === 0) {
32
+ return getEmptyPattern();
33
+ }
34
+ // 1. Hourly distribution
35
+ const hourlyMap = new Map();
36
+ for (let i = 0; i < 24; i++) {
37
+ hourlyMap.set(i, 0);
38
+ }
39
+ filteredCommits.forEach(commit => {
40
+ const hour = (0, date_fns_1.getHours)(new Date(commit.date));
41
+ hourlyMap.set(hour, (hourlyMap.get(hour) || 0) + 1);
42
+ });
43
+ const hourly = Array.from(hourlyMap.entries()).map(([hour, count]) => ({
44
+ hour,
45
+ commitCount: count,
46
+ percentage: (count / filteredCommits.length) * 100
47
+ }));
48
+ // 2. Daily distribution (day of week)
49
+ const dayMap = new Map();
50
+ for (let i = 0; i < 7; i++) {
51
+ dayMap.set(i, []);
52
+ }
53
+ filteredCommits.forEach(commit => {
54
+ const dayIndex = (0, date_fns_1.getDay)(new Date(commit.date));
55
+ dayMap.get(dayIndex).push(1);
56
+ });
57
+ const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
58
+ const dailyAverage = Array.from(dayMap.entries()).map(([dayIndex, commits]) => ({
59
+ dayOfWeek: dayNames[dayIndex],
60
+ commitCount: commits.length,
61
+ avgCommitsPerDay: commits.length / Math.ceil((end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000))
62
+ }));
63
+ // 3. Peak hour
64
+ const peakHourEntry = hourly.reduce((max, curr) => curr.commitCount > max.commitCount ? curr : max);
65
+ const peakHour = {
66
+ hour: peakHourEntry.hour,
67
+ commitCount: peakHourEntry.commitCount
68
+ };
69
+ // 4. Peak day
70
+ const peakDayEntry = dailyAverage.reduce((max, curr) => curr.commitCount > max.commitCount ? curr : max);
71
+ const peakDay = {
72
+ dayOfWeek: peakDayEntry.dayOfWeek,
73
+ avgCommits: peakDayEntry.avgCommitsPerDay
74
+ };
75
+ // 5. Chronotype detection
76
+ const chronotypeData = getChronotype(hourly);
77
+ // 6. Work-life balance
78
+ const workLifeBalance = calculateWorkLifeBalance(dailyAverage);
79
+ // 7. Timing stats
80
+ const timingStats = calculateTimingStats(filteredCommits);
81
+ // 8. Burnout risk
82
+ const burnoutRisk = detectBurnoutRisk(hourly, dailyAverage, filteredCommits);
83
+ // 9. Consistency score
84
+ const consistency = calculateConsistency(filteredCommits);
85
+ return {
86
+ hourly,
87
+ dailyAverage,
88
+ chronotype: chronotypeData.type,
89
+ chronotypeConfidence: chronotypeData.confidence,
90
+ peakHour,
91
+ peakDay,
92
+ workLifeBalance,
93
+ timingStats,
94
+ burnoutRisk,
95
+ consistency
96
+ };
97
+ }
98
+ function getChronotype(hourly) {
99
+ const totalCommits = hourly.reduce((sum, h) => sum + h.commitCount, 0);
100
+ if (totalCommits === 0) {
101
+ return { type: 'balanced', confidence: 0 };
102
+ }
103
+ // Calculate time period percentages
104
+ const morningCommits = hourly.filter(h => h.hour >= 6 && h.hour < 12).reduce((sum, h) => sum + h.commitCount, 0);
105
+ const eveningCommits = hourly.filter(h => h.hour >= 18 && h.hour < 24).reduce((sum, h) => sum + h.commitCount, 0);
106
+ const nightCommits = hourly.filter(h => h.hour >= 0 && h.hour < 6).reduce((sum, h) => sum + h.commitCount, 0);
107
+ const morningPct = (morningCommits / totalCommits) * 100;
108
+ const eveningPct = (eveningCommits / totalCommits) * 100;
109
+ const nightPct = (nightCommits / totalCommits) * 100;
110
+ // Vampire: 30%+ commits between midnight-6am
111
+ if (nightPct >= 30) {
112
+ return { type: 'vampire', confidence: nightPct };
113
+ }
114
+ // Early Bird: 60%+ commits before 12pm
115
+ if (morningPct >= 60) {
116
+ return { type: 'early-bird', confidence: morningPct };
117
+ }
118
+ // Night Owl: 60%+ commits after 6pm
119
+ if (eveningPct >= 60) {
120
+ return { type: 'night-owl', confidence: eveningPct };
121
+ }
122
+ // Balanced: Even distribution
123
+ return { type: 'balanced', confidence: 100 - Math.max(morningPct, eveningPct, nightPct) };
124
+ }
125
+ function calculateWorkLifeBalance(dailyAverage) {
126
+ const weekdayCommits = dailyAverage
127
+ .filter(d => !['Saturday', 'Sunday'].includes(d.dayOfWeek))
128
+ .reduce((sum, d) => sum + d.commitCount, 0);
129
+ const weekendCommits = dailyAverage
130
+ .filter(d => ['Saturday', 'Sunday'].includes(d.dayOfWeek))
131
+ .reduce((sum, d) => sum + d.commitCount, 0);
132
+ const totalCommits = weekdayCommits + weekendCommits;
133
+ const ratio = totalCommits > 0 ? weekdayCommits / weekendCommits : 0;
134
+ // Score: 5 stars = ratio > 10 (very little weekend work)
135
+ // 1 star = ratio < 2 (too much weekend work)
136
+ let score = 5;
137
+ if (ratio < 2)
138
+ score = 1;
139
+ else if (ratio < 4)
140
+ score = 2;
141
+ else if (ratio < 6)
142
+ score = 3;
143
+ else if (ratio < 10)
144
+ score = 4;
145
+ return {
146
+ weekdayCommits,
147
+ weekendCommits,
148
+ ratio,
149
+ score
150
+ };
151
+ }
152
+ function calculateTimingStats(commits) {
153
+ if (commits.length === 0) {
154
+ return {
155
+ avgFirstCommit: '00:00',
156
+ avgLastCommit: '00:00',
157
+ avgSessionLength: 0,
158
+ longestSession: { date: new Date(), hours: 0 }
159
+ };
160
+ }
161
+ // Group commits by date
162
+ const commitsByDate = new Map();
163
+ commits.forEach(commit => {
164
+ const dateKey = (0, date_fns_1.format)(new Date(commit.date), 'yyyy-MM-dd');
165
+ if (!commitsByDate.has(dateKey)) {
166
+ commitsByDate.set(dateKey, []);
167
+ }
168
+ commitsByDate.get(dateKey).push(new Date(commit.date));
169
+ });
170
+ // Calculate first/last commit times and session lengths
171
+ let totalFirstHour = 0;
172
+ let totalLastHour = 0;
173
+ let totalSessionHours = 0;
174
+ let longestSession = { date: new Date(), hours: 0 };
175
+ commitsByDate.forEach((times, dateKey) => {
176
+ times.sort((a, b) => a.getTime() - b.getTime());
177
+ const firstCommit = times[0];
178
+ const lastCommit = times[times.length - 1];
179
+ totalFirstHour += firstCommit.getHours() + (firstCommit.getMinutes() / 60);
180
+ totalLastHour += lastCommit.getHours() + (lastCommit.getMinutes() / 60);
181
+ const sessionLength = (0, date_fns_1.differenceInHours)(lastCommit, firstCommit);
182
+ totalSessionHours += sessionLength;
183
+ if (sessionLength > longestSession.hours) {
184
+ longestSession = { date: (0, date_fns_1.parse)(dateKey, 'yyyy-MM-dd', new Date()), hours: sessionLength };
185
+ }
186
+ });
187
+ const daysWithCommits = commitsByDate.size;
188
+ const avgFirstHour = totalFirstHour / daysWithCommits;
189
+ const avgLastHour = totalLastHour / daysWithCommits;
190
+ const formatTime = (decimalHour) => {
191
+ const hour = Math.floor(decimalHour);
192
+ const minute = Math.floor((decimalHour - hour) * 60);
193
+ return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
194
+ };
195
+ return {
196
+ avgFirstCommit: formatTime(avgFirstHour),
197
+ avgLastCommit: formatTime(avgLastHour),
198
+ avgSessionLength: totalSessionHours / daysWithCommits,
199
+ longestSession
200
+ };
201
+ }
202
+ function detectBurnoutRisk(hourly, dailyAverage, commits) {
203
+ const indicators = [];
204
+ let riskScore = 0;
205
+ // Check for late-night commits (after 11 PM)
206
+ const lateNightCommits = hourly.filter(h => h.hour >= 23).reduce((sum, h) => sum + h.commitCount, 0);
207
+ const veryLateCommits = hourly.filter(h => h.hour >= 0 && h.hour < 6).reduce((sum, h) => sum + h.commitCount, 0);
208
+ if (lateNightCommits > 15) {
209
+ indicators.push(`${lateNightCommits} late-night commits (after 11 PM)`);
210
+ riskScore += 20;
211
+ }
212
+ if (veryLateCommits > 10) {
213
+ indicators.push(`${veryLateCommits} commits between midnight-6am`);
214
+ riskScore += 30;
215
+ }
216
+ // Weekend work is not counted as a burnout indicator since it's ambiguous
217
+ // (could be hobby projects vs. overwork) - focusing on late night patterns instead
218
+ // Check for uneven distribution (working all hours)
219
+ const activeHours = hourly.filter(h => h.commitCount > 0).length;
220
+ if (activeHours > 16) {
221
+ indicators.push('Commits spread across too many hours');
222
+ riskScore += 15;
223
+ }
224
+ // Determine risk level
225
+ let level = 'low';
226
+ if (riskScore >= 50)
227
+ level = 'high';
228
+ else if (riskScore >= 25)
229
+ level = 'medium';
230
+ // Add positive indicators if low risk
231
+ if (riskScore < 25) {
232
+ indicators.push('✅ Regular commit times (±2 hour variance)');
233
+ indicators.push('✅ Healthy work-life balance');
234
+ }
235
+ return {
236
+ level,
237
+ indicators,
238
+ score: Math.min(riskScore, 100)
239
+ };
240
+ }
241
+ function calculateConsistency(commits) {
242
+ if (commits.length === 0) {
243
+ return { score: 0, variance: 0, regularity: 'chaotic' };
244
+ }
245
+ // Calculate variance in commit times (hour of day)
246
+ const hours = commits.map(c => (0, date_fns_1.getHours)(new Date(c.date)));
247
+ const avgHour = hours.reduce((sum, h) => sum + h, 0) / hours.length;
248
+ const variance = hours.reduce((sum, h) => sum + Math.pow(h - avgHour, 2), 0) / hours.length;
249
+ const stdDev = Math.sqrt(variance);
250
+ // Score based on standard deviation (lower = more consistent)
251
+ // stdDev < 2: very consistent (same 2-hour window)
252
+ // stdDev < 4: consistent
253
+ // stdDev < 6: irregular
254
+ // stdDev >= 6: chaotic
255
+ let score = 10;
256
+ let regularity = 'very-consistent';
257
+ if (stdDev >= 6) {
258
+ score = Math.max(0, 10 - stdDev);
259
+ regularity = 'chaotic';
260
+ }
261
+ else if (stdDev >= 4) {
262
+ score = 10 - stdDev;
263
+ regularity = 'irregular';
264
+ }
265
+ else if (stdDev >= 2) {
266
+ score = 10 - (stdDev / 2);
267
+ regularity = 'consistent';
268
+ }
269
+ else {
270
+ score = 10 - (stdDev / 4);
271
+ regularity = 'very-consistent';
272
+ }
273
+ return {
274
+ score: Math.max(0, Math.min(10, score)),
275
+ variance: stdDev,
276
+ regularity
277
+ };
278
+ }
279
+ function getEmptyPattern() {
280
+ return {
281
+ hourly: Array.from({ length: 24 }, (_, i) => ({ hour: i, commitCount: 0, percentage: 0 })),
282
+ dailyAverage: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].map(day => ({
283
+ dayOfWeek: day,
284
+ commitCount: 0,
285
+ avgCommitsPerDay: 0
286
+ })),
287
+ chronotype: 'balanced',
288
+ chronotypeConfidence: 0,
289
+ peakHour: { hour: 0, commitCount: 0 },
290
+ peakDay: { dayOfWeek: 'Monday', avgCommits: 0 },
291
+ workLifeBalance: { weekdayCommits: 0, weekendCommits: 0, ratio: 0, score: 5 },
292
+ timingStats: {
293
+ avgFirstCommit: '00:00',
294
+ avgLastCommit: '00:00',
295
+ avgSessionLength: 0,
296
+ longestSession: { date: new Date(), hours: 0 }
297
+ },
298
+ burnoutRisk: { level: 'low', indicators: [], score: 0 },
299
+ consistency: { score: 0, variance: 0, regularity: 'chaotic' }
300
+ };
301
+ }
302
+ function getChronotypeLabel(chronotype) {
303
+ const labels = {
304
+ 'early-bird': '🐦 Early Bird',
305
+ 'night-owl': '🦉 Night Owl',
306
+ 'balanced': '⚖️ Balanced',
307
+ 'vampire': '🧛 Vampire'
308
+ };
309
+ return labels[chronotype] || chronotype;
310
+ }
311
+ function getChronotypeDescription(chronotype) {
312
+ const descriptions = {
313
+ 'early-bird': "You're most productive in the morning. Consider scheduling important work before lunch.",
314
+ 'night-owl': "You're most productive in the evening. Night time is when you shine!",
315
+ 'balanced': "You maintain a well-balanced coding schedule throughout the day.",
316
+ 'vampire': "⚠️ Many late-night commits detected. Consider adjusting your schedule for better work-life balance."
317
+ };
318
+ return descriptions[chronotype] || '';
319
+ }
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for the time pattern analyzer
4
+ *
5
+ * Tests analyzeTimePatterns function for correct hourly/daily distribution,
6
+ * chronotype detection, and work-life balance calculations.
7
+ *
8
+ * NOTE: Hour extraction uses local time via date-fns getHours, which means
9
+ * tests need to be timezone-agnostic or use local-time-aware assertions.
10
+ *
11
+ * @module features/time-patterns/analyzer.test
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ const vitest_1 = require("vitest");
15
+ const analyzer_1 = require("./analyzer");
16
+ // ============================================================================
17
+ // Test Helpers
18
+ // ============================================================================
19
+ /**
20
+ * Creates a commit with the specified date
21
+ */
22
+ function createCommit(date) {
23
+ return {
24
+ hash: `hash-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
25
+ author: 'Test Author',
26
+ date,
27
+ message: 'test commit',
28
+ };
29
+ }
30
+ /**
31
+ * Creates multiple commits on consecutive dates
32
+ */
33
+ function createCommits(dates) {
34
+ return dates.map(d => createCommit(d));
35
+ }
36
+ // ============================================================================
37
+ // Basic Functionality
38
+ // ============================================================================
39
+ (0, vitest_1.describe)('analyzeTimePatterns', () => {
40
+ (0, vitest_1.describe)('basic functionality', () => {
41
+ (0, vitest_1.it)('returns empty pattern for empty commits', () => {
42
+ const start = new Date('2025-06-01');
43
+ const end = new Date('2025-06-30');
44
+ const result = (0, analyzer_1.analyzeTimePatterns)([], start, end);
45
+ (0, vitest_1.expect)(result.hourly).toHaveLength(24);
46
+ (0, vitest_1.expect)(result.hourly.every(h => h.commitCount === 0)).toBe(true);
47
+ });
48
+ (0, vitest_1.it)('returns all 24 hours in hourly distribution', () => {
49
+ const commits = [createCommit('2025-06-09T10:00:00Z')];
50
+ const start = new Date('2025-06-01');
51
+ const end = new Date('2025-06-30');
52
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
53
+ (0, vitest_1.expect)(result.hourly).toHaveLength(24);
54
+ // Should have exactly 1 commit total
55
+ const totalCommits = result.hourly.reduce((sum, h) => sum + h.commitCount, 0);
56
+ (0, vitest_1.expect)(totalCommits).toBe(1);
57
+ });
58
+ (0, vitest_1.it)('calculates percentages that sum to 100', () => {
59
+ const commits = createCommits([
60
+ '2025-06-09T08:00:00Z',
61
+ '2025-06-09T12:00:00Z',
62
+ '2025-06-09T16:00:00Z',
63
+ '2025-06-09T20:00:00Z',
64
+ ]);
65
+ const start = new Date('2025-06-01');
66
+ const end = new Date('2025-06-30');
67
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
68
+ // Percentages should sum to 100 (or close due to floating point)
69
+ const totalPercentage = result.hourly.reduce((sum, h) => sum + h.percentage, 0);
70
+ (0, vitest_1.expect)(totalPercentage).toBeCloseTo(100, 0);
71
+ });
72
+ (0, vitest_1.it)('identifies peak hour with highest commit count', () => {
73
+ const commits = createCommits([
74
+ '2025-06-09T10:00:00Z',
75
+ '2025-06-09T10:30:00Z',
76
+ '2025-06-09T10:45:00Z', // 3 commits around 10
77
+ '2025-06-09T16:00:00Z', // 1 commit at 16
78
+ ]);
79
+ const start = new Date('2025-06-01');
80
+ const end = new Date('2025-06-30');
81
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
82
+ // Peak hour should have at least 3 commits
83
+ (0, vitest_1.expect)(result.peakHour.commitCount).toBeGreaterThanOrEqual(3);
84
+ });
85
+ });
86
+ // ============================================================================
87
+ // Chronotype Detection
88
+ // ============================================================================
89
+ (0, vitest_1.describe)('chronotype detection', () => {
90
+ (0, vitest_1.it)('returns one of the valid chronotypes', () => {
91
+ const commits = createCommits([
92
+ '2025-06-09T08:00:00Z',
93
+ '2025-06-10T09:00:00Z',
94
+ '2025-06-11T10:00:00Z',
95
+ ]);
96
+ const start = new Date('2025-06-01');
97
+ const end = new Date('2025-06-30');
98
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
99
+ (0, vitest_1.expect)(['early-bird', 'night-owl', 'balanced', 'vampire']).toContain(result.chronotype);
100
+ });
101
+ (0, vitest_1.it)('returns chronotype confidence score between 0 and 100', () => {
102
+ const commits = createCommits([
103
+ '2025-06-09T09:00:00Z',
104
+ '2025-06-10T09:00:00Z',
105
+ '2025-06-11T09:00:00Z',
106
+ ]);
107
+ const start = new Date('2025-06-01');
108
+ const end = new Date('2025-06-30');
109
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
110
+ (0, vitest_1.expect)(result.chronotypeConfidence).toBeGreaterThanOrEqual(0);
111
+ (0, vitest_1.expect)(result.chronotypeConfidence).toBeLessThanOrEqual(100);
112
+ });
113
+ });
114
+ // ============================================================================
115
+ // Daily Distribution
116
+ // ============================================================================
117
+ (0, vitest_1.describe)('daily distribution', () => {
118
+ (0, vitest_1.it)('calculates daily averages', () => {
119
+ const commits = [
120
+ // Multiple Mondays (2025-06-02, 2025-06-09, 2025-06-16 are Mondays)
121
+ { hash: 'h1', author: 'A', date: '2025-06-02T10:00:00Z', message: 'm' },
122
+ { hash: 'h2', author: 'A', date: '2025-06-09T10:00:00Z', message: 'm' },
123
+ { hash: 'h3', author: 'A', date: '2025-06-16T10:00:00Z', message: 'm' },
124
+ // One Tuesday (2025-06-03)
125
+ { hash: 'h4', author: 'A', date: '2025-06-03T10:00:00Z', message: 'm' },
126
+ ];
127
+ const start = new Date('2025-06-01');
128
+ const end = new Date('2025-06-30');
129
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
130
+ (0, vitest_1.expect)(result.dailyAverage).toHaveLength(7);
131
+ // Total commits should be 4
132
+ const totalCommits = result.dailyAverage.reduce((sum, d) => sum + d.commitCount, 0);
133
+ (0, vitest_1.expect)(totalCommits).toBe(4);
134
+ });
135
+ (0, vitest_1.it)('identifies peak day', () => {
136
+ const commits = [
137
+ // 5 commits on Monday
138
+ { hash: 'h1', author: 'A', date: '2025-06-02T10:00:00Z', message: 'm' },
139
+ { hash: 'h2', author: 'A', date: '2025-06-09T10:00:00Z', message: 'm' },
140
+ { hash: 'h3', author: 'A', date: '2025-06-16T10:00:00Z', message: 'm' },
141
+ { hash: 'h4', author: 'A', date: '2025-06-23T10:00:00Z', message: 'm' },
142
+ { hash: 'h5', author: 'A', date: '2025-06-30T10:00:00Z', message: 'm' },
143
+ // 2 commits on Friday
144
+ { hash: 'h6', author: 'A', date: '2025-06-06T10:00:00Z', message: 'm' },
145
+ { hash: 'h7', author: 'A', date: '2025-06-13T10:00:00Z', message: 'm' },
146
+ ];
147
+ const start = new Date('2025-06-01');
148
+ const end = new Date('2025-06-30');
149
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
150
+ (0, vitest_1.expect)(result.peakDay.dayOfWeek).toBe('Monday');
151
+ });
152
+ });
153
+ // ============================================================================
154
+ // Work-Life Balance
155
+ // ============================================================================
156
+ (0, vitest_1.describe)('work-life balance', () => {
157
+ (0, vitest_1.it)('counts weekday vs weekend commits', () => {
158
+ const commits = [
159
+ // Weekday commits (Mon-Fri)
160
+ { hash: 'h1', author: 'A', date: '2025-06-09T10:00:00Z', message: 'm' }, // Mon
161
+ { hash: 'h2', author: 'A', date: '2025-06-10T10:00:00Z', message: 'm' }, // Tue
162
+ { hash: 'h3', author: 'A', date: '2025-06-11T10:00:00Z', message: 'm' }, // Wed
163
+ // Weekend commits (Sat-Sun)
164
+ { hash: 'h4', author: 'A', date: '2025-06-07T10:00:00Z', message: 'm' }, // Sat
165
+ { hash: 'h5', author: 'A', date: '2025-06-08T10:00:00Z', message: 'm' }, // Sun
166
+ ];
167
+ const start = new Date('2025-06-01');
168
+ const end = new Date('2025-06-30');
169
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
170
+ (0, vitest_1.expect)(result.workLifeBalance.weekdayCommits).toBe(3);
171
+ (0, vitest_1.expect)(result.workLifeBalance.weekendCommits).toBe(2);
172
+ });
173
+ (0, vitest_1.it)('calculates weekday/weekend ratio', () => {
174
+ const commits = [
175
+ { hash: 'h1', author: 'A', date: '2025-06-09T10:00:00Z', message: 'm' }, // Mon
176
+ { hash: 'h2', author: 'A', date: '2025-06-10T10:00:00Z', message: 'm' }, // Tue
177
+ { hash: 'h3', author: 'A', date: '2025-06-07T10:00:00Z', message: 'm' }, // Sat
178
+ ];
179
+ const start = new Date('2025-06-01');
180
+ const end = new Date('2025-06-30');
181
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
182
+ // 2 weekday : 1 weekend = ratio of 2
183
+ (0, vitest_1.expect)(result.workLifeBalance.ratio).toBeGreaterThan(0);
184
+ });
185
+ (0, vitest_1.it)('returns work-life balance score', () => {
186
+ const commits = [
187
+ { hash: 'h1', author: 'A', date: '2025-06-09T10:00:00Z', message: 'm' },
188
+ { hash: 'h2', author: 'A', date: '2025-06-10T10:00:00Z', message: 'm' },
189
+ ];
190
+ const start = new Date('2025-06-01');
191
+ const end = new Date('2025-06-30');
192
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
193
+ (0, vitest_1.expect)(result.workLifeBalance.score).toBeGreaterThanOrEqual(0);
194
+ (0, vitest_1.expect)(result.workLifeBalance.score).toBeLessThanOrEqual(100);
195
+ });
196
+ (0, vitest_1.it)('handles all-weekend commits', () => {
197
+ const commits = [
198
+ { hash: 'h1', author: 'A', date: '2025-06-07T10:00:00Z', message: 'm' }, // Sat
199
+ { hash: 'h2', author: 'A', date: '2025-06-08T10:00:00Z', message: 'm' }, // Sun
200
+ { hash: 'h3', author: 'A', date: '2025-06-14T10:00:00Z', message: 'm' }, // Sat
201
+ ];
202
+ const start = new Date('2025-06-01');
203
+ const end = new Date('2025-06-30');
204
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
205
+ (0, vitest_1.expect)(result.workLifeBalance.weekdayCommits).toBe(0);
206
+ (0, vitest_1.expect)(result.workLifeBalance.weekendCommits).toBe(3);
207
+ });
208
+ });
209
+ // ============================================================================
210
+ // Edge Cases
211
+ // ============================================================================
212
+ (0, vitest_1.describe)('edge cases', () => {
213
+ (0, vitest_1.it)('handles commits outside date range', () => {
214
+ const commits = [
215
+ { hash: 'h1', author: 'A', date: '2025-05-15T10:00:00Z', message: 'm' }, // Before
216
+ { hash: 'h2', author: 'A', date: '2025-06-15T10:00:00Z', message: 'm' }, // In range
217
+ { hash: 'h3', author: 'A', date: '2025-07-15T10:00:00Z', message: 'm' }, // After
218
+ ];
219
+ const start = new Date('2025-06-01');
220
+ const end = new Date('2025-06-30');
221
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
222
+ // Only 1 commit should be counted
223
+ const totalCommits = result.hourly.reduce((sum, h) => sum + h.commitCount, 0);
224
+ (0, vitest_1.expect)(totalCommits).toBe(1);
225
+ });
226
+ (0, vitest_1.it)('handles single commit', () => {
227
+ const commits = [createCommit('2025-06-09T10:00:00Z')];
228
+ const start = new Date('2025-06-01');
229
+ const end = new Date('2025-06-30');
230
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
231
+ // Should have one peak hour with 1 commit
232
+ (0, vitest_1.expect)(result.peakHour.commitCount).toBe(1);
233
+ });
234
+ (0, vitest_1.it)('returns valid structure for all commits', () => {
235
+ const commits = createCommits([
236
+ '2025-06-09T00:00:00Z',
237
+ '2025-06-10T06:00:00Z',
238
+ '2025-06-11T12:00:00Z',
239
+ '2025-06-12T18:00:00Z',
240
+ '2025-06-13T23:59:59Z',
241
+ ]);
242
+ const start = new Date('2025-06-01');
243
+ const end = new Date('2025-06-30');
244
+ const result = (0, analyzer_1.analyzeTimePatterns)(commits, start, end);
245
+ // Structure should be complete
246
+ (0, vitest_1.expect)(result.hourly).toHaveLength(24);
247
+ (0, vitest_1.expect)(result.dailyAverage).toHaveLength(7);
248
+ (0, vitest_1.expect)(result.peakHour).toBeDefined();
249
+ (0, vitest_1.expect)(result.peakDay).toBeDefined();
250
+ (0, vitest_1.expect)(result.workLifeBalance).toBeDefined();
251
+ (0, vitest_1.expect)(result.chronotype).toBeDefined();
252
+ });
253
+ });
254
+ // ============================================================================
255
+ // Context-based API
256
+ // ============================================================================
257
+ (0, vitest_1.describe)('context-based API', () => {
258
+ (0, vitest_1.it)('works with AnalyzerContext', () => {
259
+ const commits = createCommits([
260
+ '2025-06-09T09:00:00Z',
261
+ '2025-06-10T10:00:00Z',
262
+ '2025-06-11T11:00:00Z',
263
+ ]);
264
+ const ctx = {
265
+ commits,
266
+ dateRange: {
267
+ start: new Date('2025-06-01'),
268
+ end: new Date('2025-06-30'),
269
+ },
270
+ author: 'Test Author',
271
+ };
272
+ const result = (0, analyzer_1.analyzeTimePatterns)(ctx);
273
+ // Should count all 3 commits
274
+ const totalCommits = result.hourly.reduce((sum, h) => sum + h.commitCount, 0);
275
+ (0, vitest_1.expect)(totalCommits).toBe(3);
276
+ });
277
+ });
278
+ });
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * Time Patterns Feature
4
+ *
5
+ * Self-contained time pattern analysis including:
6
+ * - Types: TimePattern interfaces
7
+ * - Analyzer: Time pattern analysis logic
8
+ * - Template: HTML rendering
9
+ *
10
+ * @module features/time-patterns
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.buildTimePatternsSection = exports.getChronotypeLabel = exports.getChronotypeDescription = exports.analyzeTimePatterns = void 0;
28
+ // Types
29
+ __exportStar(require("./types"), exports);
30
+ // Analyzer
31
+ var analyzer_1 = require("./analyzer");
32
+ Object.defineProperty(exports, "analyzeTimePatterns", { enumerable: true, get: function () { return analyzer_1.analyzeTimePatterns; } });
33
+ Object.defineProperty(exports, "getChronotypeDescription", { enumerable: true, get: function () { return analyzer_1.getChronotypeDescription; } });
34
+ Object.defineProperty(exports, "getChronotypeLabel", { enumerable: true, get: function () { return analyzer_1.getChronotypeLabel; } });
35
+ // Template
36
+ var template_1 = require("./template");
37
+ Object.defineProperty(exports, "buildTimePatternsSection", { enumerable: true, get: function () { return template_1.buildTimePatternsSection; } });