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,316 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.filterCommitsByAuthor = filterCommitsByAuthor;
37
+ exports.parseAuthorFilter = parseAuthorFilter;
38
+ exports.loadTeamFile = loadTeamFile;
39
+ exports.analyzeTeam = analyzeTeam;
40
+ exports.formatTeamInsights = formatTeamInsights;
41
+ const fs = __importStar(require("fs"));
42
+ const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
43
+ /**
44
+ * Filters commits by author(s) based on the provided filter
45
+ */
46
+ function filterCommitsByAuthor(commits, filter) {
47
+ return commits.filter(commit => {
48
+ const authorLower = commit.author.toLowerCase();
49
+ const matches = filter.authors.some(filterAuthor => {
50
+ const filterLower = filterAuthor.toLowerCase();
51
+ if (filter.matchType === 'exact') {
52
+ return authorLower === filterLower;
53
+ }
54
+ else {
55
+ return authorLower.includes(filterLower);
56
+ }
57
+ });
58
+ return filter.mode === 'include' ? matches : !matches;
59
+ });
60
+ }
61
+ /**
62
+ * Parse author filter from CLI options
63
+ */
64
+ function parseAuthorFilter(author, authors, excludeAuthor, excludeAuthors) {
65
+ // Include filters take precedence
66
+ if (author) {
67
+ return {
68
+ mode: 'include',
69
+ authors: [author],
70
+ matchType: 'partial',
71
+ };
72
+ }
73
+ if (authors) {
74
+ return {
75
+ mode: 'include',
76
+ authors: authors.split(',').map(a => a.trim()).filter(a => a.length > 0),
77
+ matchType: 'partial',
78
+ };
79
+ }
80
+ // Exclude filters
81
+ if (excludeAuthor) {
82
+ return {
83
+ mode: 'exclude',
84
+ authors: [excludeAuthor],
85
+ matchType: 'partial',
86
+ };
87
+ }
88
+ if (excludeAuthors) {
89
+ return {
90
+ mode: 'exclude',
91
+ authors: excludeAuthors.split(',').map(a => a.trim()).filter(a => a.length > 0),
92
+ matchType: 'partial',
93
+ };
94
+ }
95
+ return null;
96
+ }
97
+ /**
98
+ * Load team members from a file (one name per line)
99
+ */
100
+ function loadTeamFile(filePath) {
101
+ try {
102
+ const content = fs.readFileSync(filePath, 'utf-8');
103
+ return content
104
+ .split('\n')
105
+ .map(line => line.trim())
106
+ .filter(line => line.length > 0 && !line.startsWith('#'));
107
+ }
108
+ catch (error) {
109
+ throw new Error(`Failed to load team file: ${error.message}`);
110
+ }
111
+ }
112
+ /**
113
+ * Analyzes commit patterns and distribution for a team of contributors.
114
+ *
115
+ * Calculates team-level metrics including:
116
+ * - Commit distribution across members (Gini coefficient)
117
+ * - Individual member statistics and rankings
118
+ * - Quality scores and active days
119
+ * - Load balancing insights
120
+ *
121
+ * @param commits - Array of all commit data (will be filtered to team members)
122
+ * @param teamMembers - Array of team member names/patterns to include
123
+ * @param teamName - Optional team name for display
124
+ * @param skipBodyCheck - Whether to skip commit body quality checks (default: true)
125
+ * @returns Team analysis with member breakdown, distribution metrics, and insights
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const team = analyzeTeam(commits, ['Alice', 'Bob', 'Charlie'], 'Backend Team');
130
+ * console.log(`Team commits: ${team.stats.totalCommits}`);
131
+ * console.log(`Distribution: ${team.loadDistribution.assessment}`);
132
+ * ```
133
+ */
134
+ function analyzeTeam(commits, teamMembers, teamName, skipBodyCheck = true) {
135
+ // Filter commits to team members only
136
+ const teamFilter = {
137
+ mode: 'include',
138
+ authors: teamMembers,
139
+ matchType: 'partial',
140
+ };
141
+ const teamCommits = filterCommitsByAuthor(commits, teamFilter);
142
+ // Calculate team metrics
143
+ const totalCommits = teamCommits.length;
144
+ const commitsPerMember = teamMembers.length > 0
145
+ ? Math.round((totalCommits / teamMembers.length) * 10) / 10
146
+ : 0;
147
+ // Active days
148
+ const activeDatesSet = new Set(teamCommits.map(c => {
149
+ const d = new Date(c.date);
150
+ return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
151
+ }));
152
+ const activeDays = activeDatesSet.size;
153
+ // Quality score
154
+ const qualityResult = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(teamCommits, { skipBodyCheck });
155
+ const qualityScore = Math.round(qualityResult.overallScore * 10) / 10;
156
+ // Member breakdown
157
+ const memberBreakdown = calculateMemberBreakdown(teamCommits, teamMembers, skipBodyCheck);
158
+ // Load distribution
159
+ const loadDistribution = calculateLoadDistribution(memberBreakdown, totalCommits);
160
+ return {
161
+ teamName,
162
+ members: teamMembers,
163
+ teamMetrics: {
164
+ totalCommits,
165
+ commitsPerMember,
166
+ activeDays,
167
+ qualityScore,
168
+ },
169
+ memberBreakdown,
170
+ loadDistribution,
171
+ };
172
+ }
173
+ function calculateMemberBreakdown(commits, teamMembers, skipBodyCheck) {
174
+ const breakdown = [];
175
+ const totalCommits = commits.length;
176
+ // Build a map of author -> commits
177
+ const commitsByAuthor = new Map();
178
+ for (const commit of commits) {
179
+ const authorLower = commit.author.toLowerCase();
180
+ // Find the matching team member
181
+ const matchedMember = teamMembers.find(member => authorLower.includes(member.toLowerCase()));
182
+ if (matchedMember) {
183
+ if (!commitsByAuthor.has(matchedMember)) {
184
+ commitsByAuthor.set(matchedMember, []);
185
+ }
186
+ commitsByAuthor.get(matchedMember).push(commit);
187
+ }
188
+ }
189
+ // Calculate stats for each team member
190
+ for (const member of teamMembers) {
191
+ const memberCommits = commitsByAuthor.get(member) || [];
192
+ const commitCount = memberCommits.length;
193
+ const percentage = totalCommits > 0
194
+ ? Math.round((commitCount / totalCommits) * 1000) / 10
195
+ : 0;
196
+ // Active days for this member
197
+ const memberActiveDates = new Set(memberCommits.map(c => {
198
+ const d = new Date(c.date);
199
+ return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
200
+ }));
201
+ // Quality score for this member
202
+ const qualityResult = memberCommits.length > 0
203
+ ? (0, commitQualityAnalyzer_1.analyzeCommitQuality)(memberCommits, { skipBodyCheck })
204
+ : { overallScore: 0 };
205
+ // Top areas (directories)
206
+ const areaCounts = new Map();
207
+ for (const commit of memberCommits) {
208
+ // Extract directory from commit message if available (heuristic)
209
+ // In a real implementation, this would use file change data
210
+ const match = commit.message.match(/^(?:feat|fix|docs|refactor|test|chore)\(([^)]+)\)/i);
211
+ if (match) {
212
+ const area = match[1];
213
+ areaCounts.set(area, (areaCounts.get(area) || 0) + 1);
214
+ }
215
+ }
216
+ const topAreas = [...areaCounts.entries()]
217
+ .sort((a, b) => b[1] - a[1])
218
+ .slice(0, 3)
219
+ .map(([area]) => area);
220
+ breakdown.push({
221
+ author: member,
222
+ commits: commitCount,
223
+ percentage,
224
+ activeDays: memberActiveDates.size,
225
+ qualityScore: Math.round(qualityResult.overallScore * 10) / 10,
226
+ topAreas,
227
+ });
228
+ }
229
+ // Sort by commits descending
230
+ breakdown.sort((a, b) => b.commits - a.commits);
231
+ return breakdown;
232
+ }
233
+ function calculateLoadDistribution(memberBreakdown, totalCommits) {
234
+ const insights = [];
235
+ if (memberBreakdown.length === 0 || totalCommits === 0) {
236
+ return {
237
+ giniCoefficient: 0,
238
+ assessment: 'balanced',
239
+ insights: ['No team data available'],
240
+ };
241
+ }
242
+ // Calculate Gini coefficient for commit distribution
243
+ const commitCounts = memberBreakdown.map(m => m.commits).sort((a, b) => a - b);
244
+ const n = commitCounts.length;
245
+ let giniNumerator = 0;
246
+ for (let i = 0; i < n; i++) {
247
+ giniNumerator += (2 * (i + 1) - n - 1) * commitCounts[i];
248
+ }
249
+ const giniCoefficient = n > 1
250
+ ? Math.round((giniNumerator / (n * totalCommits)) * 100) / 100
251
+ : 0;
252
+ // Determine assessment
253
+ let assessment;
254
+ if (giniCoefficient <= 0.3) {
255
+ assessment = 'balanced';
256
+ insights.push('Work is distributed fairly evenly across team members');
257
+ }
258
+ else if (giniCoefficient <= 0.5) {
259
+ assessment = 'moderate-imbalance';
260
+ insights.push('Some team members are contributing significantly more than others');
261
+ }
262
+ else {
263
+ assessment = 'high-imbalance';
264
+ insights.push('Work is heavily concentrated among a few team members');
265
+ }
266
+ // Additional insights
267
+ const topContributor = memberBreakdown[0];
268
+ if (topContributor && topContributor.percentage > 50) {
269
+ insights.push(`${topContributor.author} accounts for ${topContributor.percentage}% of all commits`);
270
+ }
271
+ const inactiveMembers = memberBreakdown.filter(m => m.commits === 0);
272
+ if (inactiveMembers.length > 0) {
273
+ insights.push(`${inactiveMembers.length} team member(s) have no commits in this period`);
274
+ }
275
+ return {
276
+ giniCoefficient,
277
+ assessment,
278
+ insights,
279
+ };
280
+ }
281
+ /**
282
+ * Format team analysis for display
283
+ */
284
+ function formatTeamInsights(analysis) {
285
+ const insights = [];
286
+ const teamLabel = analysis.teamName || 'Team';
287
+ insights.push(`👥 ${teamLabel} Analysis (${analysis.members.length} members)`);
288
+ insights.push('');
289
+ // Team metrics
290
+ insights.push(`📊 Total commits: ${analysis.teamMetrics.totalCommits}`);
291
+ insights.push(`📅 Active days: ${analysis.teamMetrics.activeDays}`);
292
+ insights.push(`⚡ Avg commits/member: ${analysis.teamMetrics.commitsPerMember}`);
293
+ insights.push(`✨ Quality score: ${analysis.teamMetrics.qualityScore}/10`);
294
+ insights.push('');
295
+ // Load distribution
296
+ const loadEmoji = {
297
+ 'balanced': '🟢',
298
+ 'moderate-imbalance': '🟡',
299
+ 'high-imbalance': '🔴',
300
+ }[analysis.loadDistribution.assessment];
301
+ insights.push(`${loadEmoji} Load distribution: ${analysis.loadDistribution.assessment} (Gini: ${analysis.loadDistribution.giniCoefficient})`);
302
+ for (const insight of analysis.loadDistribution.insights) {
303
+ insights.push(` • ${insight}`);
304
+ }
305
+ insights.push('');
306
+ // Member breakdown (top 5)
307
+ insights.push('👤 Member breakdown:');
308
+ for (const member of analysis.memberBreakdown.slice(0, 5)) {
309
+ const areas = member.topAreas.length > 0 ? ` [${member.topAreas.join(', ')}]` : '';
310
+ insights.push(` • ${member.author}: ${member.commits} commits (${member.percentage}%)${areas}`);
311
+ }
312
+ if (analysis.memberBreakdown.length > 5) {
313
+ insights.push(` ... and ${analysis.memberBreakdown.length - 5} more`);
314
+ }
315
+ return insights;
316
+ }
@@ -4,14 +4,29 @@ exports.analyzeTimePatterns = analyzeTimePatterns;
4
4
  exports.getChronotypeLabel = getChronotypeLabel;
5
5
  exports.getChronotypeDescription = getChronotypeDescription;
6
6
  const date_fns_1 = require("date-fns");
7
- function analyzeTimePatterns(commits, startDate, endDate) {
7
+ const analyzerContextBuilder_1 = require("./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
+ }
8
23
  if (commits.length === 0) {
9
24
  return getEmptyPattern();
10
25
  }
11
26
  // Filter commits within the date range
12
27
  const filteredCommits = commits.filter(c => {
13
28
  const commitDate = new Date(c.date);
14
- return commitDate >= startDate && commitDate <= endDate;
29
+ return commitDate >= start && commitDate <= end;
15
30
  });
16
31
  if (filteredCommits.length === 0) {
17
32
  return getEmptyPattern();
@@ -43,7 +58,7 @@ function analyzeTimePatterns(commits, startDate, endDate) {
43
58
  const dailyAverage = Array.from(dayMap.entries()).map(([dayIndex, commits]) => ({
44
59
  dayOfWeek: dayNames[dayIndex],
45
60
  commitCount: commits.length,
46
- avgCommitsPerDay: commits.length / Math.ceil((endDate.getTime() - startDate.getTime()) / (7 * 24 * 60 * 60 * 1000))
61
+ avgCommitsPerDay: commits.length / Math.ceil((end.getTime() - start.getTime()) / (7 * 24 * 60 * 60 * 1000))
47
62
  }));
48
63
  // 3. Peak hour
49
64
  const peakHourEntry = hourly.reduce((max, curr) => curr.commitCount > max.commitCount ? curr : max);
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeVelocity = analyzeVelocity;
4
+ exports.formatVelocityInsights = formatVelocityInsights;
5
+ const date_fns_1 = require("date-fns");
6
+ const emptyResults_1 = require("./emptyResults");
7
+ const analyzerContextBuilder_1 = require("./analyzerContextBuilder");
8
+ function analyzeVelocity(ctxOrCommits, startDate, endDate, rollingWindowWeeks = 4) {
9
+ // Normalize to internal variables
10
+ let commits;
11
+ let start;
12
+ let end;
13
+ let windowWeeks;
14
+ if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
15
+ commits = ctxOrCommits.commits;
16
+ start = ctxOrCommits.dateRange.start;
17
+ end = ctxOrCommits.dateRange.end;
18
+ windowWeeks = (0, analyzerContextBuilder_1.getOption)(ctxOrCommits, 'windowWeeks', 4);
19
+ }
20
+ else {
21
+ commits = ctxOrCommits;
22
+ start = startDate;
23
+ end = endDate;
24
+ windowWeeks = rollingWindowWeeks;
25
+ }
26
+ if (commits.length === 0) {
27
+ return (0, emptyResults_1.getEmptyVelocityAnalysis)();
28
+ }
29
+ // Build weekly data points
30
+ const timeline = buildWeeklyTimeline(commits, start, end);
31
+ // Calculate rolling averages
32
+ calculateRollingAverages(timeline, windowWeeks);
33
+ // Detect anomalies
34
+ const anomalies = detectAnomalies(timeline);
35
+ // Calculate overall trend
36
+ const { trend, trendPercentage } = calculateTrend(timeline);
37
+ // Find peak and lowest weeks
38
+ const peakWeek = findPeakWeek(timeline);
39
+ const lowestWeek = findLowestWeek(timeline);
40
+ // Calculate average
41
+ const totalCommits = timeline.reduce((sum, dp) => sum + dp.commits, 0);
42
+ const averageCommitsPerWeek = timeline.length > 0 ? totalCommits / timeline.length : 0;
43
+ return {
44
+ timeline,
45
+ overallTrend: trend,
46
+ trendPercentage,
47
+ averageCommitsPerWeek: Math.round(averageCommitsPerWeek * 10) / 10,
48
+ peakWeek,
49
+ lowestWeek,
50
+ anomalies,
51
+ };
52
+ }
53
+ function buildWeeklyTimeline(commits, startDate, endDate) {
54
+ const timeline = [];
55
+ const weekCount = (0, date_fns_1.differenceInWeeks)(endDate, startDate) + 1;
56
+ // Create a map for quick commit lookup by week
57
+ const commitsByWeek = new Map();
58
+ for (const commit of commits) {
59
+ const commitDate = new Date(commit.date);
60
+ const weekStart = (0, date_fns_1.startOfWeek)(commitDate, { weekStartsOn: 1 });
61
+ const weekKey = (0, date_fns_1.format)(weekStart, 'yyyy-MM-dd');
62
+ if (!commitsByWeek.has(weekKey)) {
63
+ commitsByWeek.set(weekKey, []);
64
+ }
65
+ commitsByWeek.get(weekKey).push(commit);
66
+ }
67
+ // Build timeline for each week
68
+ let currentWeekStart = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 1 });
69
+ for (let i = 0; i < weekCount && currentWeekStart <= endDate; i++) {
70
+ const weekEnd = (0, date_fns_1.endOfWeek)(currentWeekStart, { weekStartsOn: 1 });
71
+ const weekKey = (0, date_fns_1.format)(currentWeekStart, 'yyyy-MM-dd');
72
+ const weekCommits = commitsByWeek.get(weekKey) || [];
73
+ const authors = [...new Set(weekCommits.map(c => c.author))];
74
+ // For now, we don't have line stats in CommitData, so we'll use 0
75
+ // This could be enhanced later by parsing git diff stats
76
+ timeline.push({
77
+ weekStart: new Date(currentWeekStart),
78
+ weekEnd: new Date(weekEnd),
79
+ commits: weekCommits.length,
80
+ linesAdded: 0,
81
+ linesDeleted: 0,
82
+ filesChanged: 0,
83
+ authors,
84
+ rollingAverage: 0, // Will be calculated later
85
+ });
86
+ currentWeekStart = (0, date_fns_1.addWeeks)(currentWeekStart, 1);
87
+ }
88
+ return timeline;
89
+ }
90
+ function calculateRollingAverages(timeline, windowWeeks) {
91
+ for (let i = 0; i < timeline.length; i++) {
92
+ const windowStart = Math.max(0, i - windowWeeks + 1);
93
+ const windowEnd = i + 1;
94
+ const window = timeline.slice(windowStart, windowEnd);
95
+ const sum = window.reduce((acc, dp) => acc + dp.commits, 0);
96
+ timeline[i].rollingAverage = Math.round((sum / window.length) * 10) / 10;
97
+ }
98
+ }
99
+ function detectAnomalies(timeline) {
100
+ const anomalies = [];
101
+ if (timeline.length < 3) {
102
+ return anomalies;
103
+ }
104
+ for (let i = 1; i < timeline.length; i++) {
105
+ const current = timeline[i];
106
+ const rollingAvg = timeline[i - 1].rollingAverage;
107
+ if (rollingAvg === 0) {
108
+ if (current.commits === 0) {
109
+ anomalies.push({
110
+ weekStart: current.weekStart,
111
+ type: 'zero-activity',
112
+ severity: 'significant',
113
+ percentageChange: -100,
114
+ possibleCauses: ['Holiday period', 'Team blocked', 'Sprint planning', 'Vacation'],
115
+ });
116
+ }
117
+ continue;
118
+ }
119
+ const percentageChange = ((current.commits - rollingAvg) / rollingAvg) * 100;
120
+ // Detect drops
121
+ if (percentageChange <= -60) {
122
+ anomalies.push({
123
+ weekStart: current.weekStart,
124
+ type: 'drop',
125
+ severity: 'critical',
126
+ percentageChange: Math.round(percentageChange),
127
+ possibleCauses: ['Major blocker', 'Team disruption', 'Holiday/vacation', 'Context switching'],
128
+ });
129
+ }
130
+ else if (percentageChange <= -40) {
131
+ anomalies.push({
132
+ weekStart: current.weekStart,
133
+ type: 'drop',
134
+ severity: 'significant',
135
+ percentageChange: Math.round(percentageChange),
136
+ possibleCauses: ['Potential blocker', 'Reduced capacity', 'Complex work'],
137
+ });
138
+ }
139
+ else if (percentageChange <= -20) {
140
+ anomalies.push({
141
+ weekStart: current.weekStart,
142
+ type: 'drop',
143
+ severity: 'minor',
144
+ percentageChange: Math.round(percentageChange),
145
+ possibleCauses: ['Normal variance', 'Focus on larger tasks'],
146
+ });
147
+ }
148
+ // Detect spikes
149
+ if (percentageChange >= 100) {
150
+ anomalies.push({
151
+ weekStart: current.weekStart,
152
+ type: 'spike',
153
+ severity: 'significant',
154
+ percentageChange: Math.round(percentageChange),
155
+ possibleCauses: ['Release push', 'Catch-up after blocker', 'New team members'],
156
+ });
157
+ }
158
+ else if (percentageChange >= 50) {
159
+ anomalies.push({
160
+ weekStart: current.weekStart,
161
+ type: 'spike',
162
+ severity: 'minor',
163
+ percentageChange: Math.round(percentageChange),
164
+ possibleCauses: ['Productive week', 'Smaller tasks'],
165
+ });
166
+ }
167
+ // Zero activity detection
168
+ if (current.commits === 0) {
169
+ anomalies.push({
170
+ weekStart: current.weekStart,
171
+ type: 'zero-activity',
172
+ severity: 'critical',
173
+ percentageChange: -100,
174
+ possibleCauses: ['Holiday period', 'Team blocked', 'Vacation', 'Sprint transition'],
175
+ });
176
+ }
177
+ }
178
+ return anomalies;
179
+ }
180
+ function calculateTrend(timeline) {
181
+ if (timeline.length < 4) {
182
+ return { trend: 'stable', trendPercentage: 0 };
183
+ }
184
+ // Compare first quarter to last quarter of the timeline
185
+ const quarterSize = Math.max(1, Math.floor(timeline.length / 4));
186
+ const firstQuarter = timeline.slice(0, quarterSize);
187
+ const lastQuarter = timeline.slice(-quarterSize);
188
+ const firstAvg = firstQuarter.reduce((sum, dp) => sum + dp.commits, 0) / firstQuarter.length;
189
+ const lastAvg = lastQuarter.reduce((sum, dp) => sum + dp.commits, 0) / lastQuarter.length;
190
+ if (firstAvg === 0) {
191
+ return { trend: lastAvg > 0 ? 'increasing' : 'stable', trendPercentage: 100 };
192
+ }
193
+ const percentageChange = ((lastAvg - firstAvg) / firstAvg) * 100;
194
+ // Check for volatility (high variance)
195
+ const allCommits = timeline.map(dp => dp.commits);
196
+ const mean = allCommits.reduce((a, b) => a + b, 0) / allCommits.length;
197
+ const variance = allCommits.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / allCommits.length;
198
+ const coefficientOfVariation = mean > 0 ? (Math.sqrt(variance) / mean) * 100 : 0;
199
+ if (coefficientOfVariation > 80) {
200
+ return { trend: 'volatile', trendPercentage: Math.round(percentageChange) };
201
+ }
202
+ if (percentageChange > 15) {
203
+ return { trend: 'increasing', trendPercentage: Math.round(percentageChange) };
204
+ }
205
+ else if (percentageChange < -15) {
206
+ return { trend: 'decreasing', trendPercentage: Math.round(percentageChange) };
207
+ }
208
+ return { trend: 'stable', trendPercentage: Math.round(percentageChange) };
209
+ }
210
+ function findPeakWeek(timeline) {
211
+ if (timeline.length === 0) {
212
+ return { weekStart: new Date(), commits: 0 };
213
+ }
214
+ const peak = timeline.reduce((max, dp) => dp.commits > max.commits ? dp : max, timeline[0]);
215
+ return { weekStart: peak.weekStart, commits: peak.commits };
216
+ }
217
+ function findLowestWeek(timeline) {
218
+ if (timeline.length === 0) {
219
+ return { weekStart: new Date(), commits: 0 };
220
+ }
221
+ // Find lowest non-zero week, or zero if all are zero
222
+ const nonZeroWeeks = timeline.filter(dp => dp.commits > 0);
223
+ if (nonZeroWeeks.length === 0) {
224
+ return { weekStart: timeline[0].weekStart, commits: 0 };
225
+ }
226
+ const lowest = nonZeroWeeks.reduce((min, dp) => dp.commits < min.commits ? dp : min, nonZeroWeeks[0]);
227
+ return { weekStart: lowest.weekStart, commits: lowest.commits };
228
+ }
229
+ /**
230
+ * Format velocity analysis for display
231
+ */
232
+ function formatVelocityInsights(analysis) {
233
+ const insights = [];
234
+ if (analysis.timeline.length === 0) {
235
+ return ['No velocity data available'];
236
+ }
237
+ // Trend insight
238
+ const trendDirection = analysis.trendPercentage >= 0 ? 'up' : 'down';
239
+ const trendEmoji = analysis.overallTrend === 'increasing' ? '📈' :
240
+ analysis.overallTrend === 'decreasing' ? '📉' :
241
+ analysis.overallTrend === 'volatile' ? '📊' : '➡️';
242
+ insights.push(`${trendEmoji} Velocity trend: ${analysis.overallTrend} (${trendDirection === 'up' ? '+' : ''}${analysis.trendPercentage}%)`);
243
+ insights.push(`📊 Average: ${analysis.averageCommitsPerWeek} commits/week`);
244
+ // Peak week
245
+ const peakDate = (0, date_fns_1.format)(analysis.peakWeek.weekStart, 'MMM d, yyyy');
246
+ insights.push(`🚀 Peak week: ${peakDate} (${analysis.peakWeek.commits} commits)`);
247
+ // Anomalies summary
248
+ const criticalDrops = analysis.anomalies.filter(a => a.type === 'drop' && a.severity === 'critical');
249
+ const zeroWeeks = analysis.anomalies.filter(a => a.type === 'zero-activity');
250
+ if (criticalDrops.length > 0) {
251
+ insights.push(`⚠️ ${criticalDrops.length} critical productivity drop(s) detected`);
252
+ }
253
+ if (zeroWeeks.length > 0) {
254
+ insights.push(`🚫 ${zeroWeeks.length} week(s) with zero activity`);
255
+ }
256
+ return insights;
257
+ }
@@ -2,12 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateYearWrapped = generateYearWrapped;
4
4
  const child_process_1 = require("child_process");
5
- const achievementEngine_js_1 = require("./achievementEngine.js");
6
- const commitQualityAnalyzer_js_1 = require("./commitQualityAnalyzer.js");
5
+ const achievements_1 = require("../features/achievements");
6
+ const commit_quality_1 = require("../features/commit-quality");
7
7
  const fileHotspotAnalyzer_js_1 = require("./fileHotspotAnalyzer.js");
8
8
  const gitParser_js_1 = require("./gitParser.js");
9
- const streakCalculator_js_1 = require("./streakCalculator.js");
10
- const timePatternAnalyzer_js_1 = require("./timePatternAnalyzer.js");
9
+ const streaks_1 = require("../features/streaks");
10
+ const time_patterns_1 = require("../features/time-patterns");
11
11
  function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
12
12
  // Define year boundaries
13
13
  const startDate = new Date(`${year}-01-01`);
@@ -21,9 +21,9 @@ function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
21
21
  throw new Error(`No commits found for year ${year}`);
22
22
  }
23
23
  // Calculate all metrics using existing utilities
24
- const streakData = (0, streakCalculator_js_1.calculateStreaks)(commits, startDate, endDate);
25
- const timePattern = (0, timePatternAnalyzer_js_1.analyzeTimePatterns)(commits, startDate, endDate);
26
- const commitQuality = (0, commitQualityAnalyzer_js_1.analyzeCommitQuality)(commits);
24
+ const streakData = (0, streaks_1.calculateStreaks)(commits, startDate, endDate);
25
+ const timePattern = (0, time_patterns_1.analyzeTimePatterns)(commits, startDate, endDate);
26
+ const commitQuality = (0, commit_quality_1.analyzeCommitQuality)(commits);
27
27
  const fileHotspots = (0, fileHotspotAnalyzer_js_1.analyzeFileHotspots)(repoPath, startDate, endDate);
28
28
  // Create AnalysisData for achievement checking
29
29
  const analysisData = {
@@ -38,7 +38,7 @@ function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
38
38
  end: endDate
39
39
  }
40
40
  };
41
- const achievementProgress = (0, achievementEngine_js_1.checkAchievements)(analysisData);
41
+ const achievementProgress = (0, achievements_1.checkAchievements)(analysisData);
42
42
  // Calculate overview
43
43
  const overview = {
44
44
  totalCommits: commits.length,