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,383 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for Velocity Analyzer
4
+ *
5
+ * Tests the analyzeVelocity function which calculates:
6
+ * - Weekly commit timeline
7
+ * - Rolling averages
8
+ * - Trend detection (increasing, decreasing, stable, volatile)
9
+ * - Peak and lowest weeks
10
+ * - Anomaly detection (spikes, drops, zero-activity)
11
+ *
12
+ * @spec TEST-02 - Core Analyzers Unit Tests
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const vitest_1 = require("vitest");
16
+ const analyzer_1 = require("./analyzer");
17
+ const date_fns_1 = require("date-fns");
18
+ // ============================================================================
19
+ // Test Helpers
20
+ // ============================================================================
21
+ function createCommit(date, overrides = {}) {
22
+ return {
23
+ hash: `hash-${date.getTime()}`,
24
+ author: 'Test Author',
25
+ date: date.toISOString(),
26
+ message: 'test: commit message',
27
+ ...overrides,
28
+ };
29
+ }
30
+ function createCommitsForWeek(weekStart, count) {
31
+ const commits = [];
32
+ for (let i = 0; i < count; i++) {
33
+ // Spread commits across the week
34
+ const day = i % 5; // Mon-Fri
35
+ commits.push(createCommit((0, date_fns_1.addDays)(weekStart, day)));
36
+ }
37
+ return commits;
38
+ }
39
+ function createCommitsOverWeeks(startDate, weeklyCommits) {
40
+ const commits = [];
41
+ let currentWeek = startDate;
42
+ for (const count of weeklyCommits) {
43
+ commits.push(...createCommitsForWeek(currentWeek, count));
44
+ currentWeek = (0, date_fns_1.addDays)(currentWeek, 7);
45
+ }
46
+ return commits;
47
+ }
48
+ // ============================================================================
49
+ // Tests
50
+ // ============================================================================
51
+ (0, vitest_1.describe)('analyzeVelocity', () => {
52
+ // ==========================================================================
53
+ // Empty/Basic Input
54
+ // ==========================================================================
55
+ (0, vitest_1.describe)('empty input', () => {
56
+ (0, vitest_1.it)('returns empty analysis for empty commits', () => {
57
+ const result = (0, analyzer_1.analyzeVelocity)([]);
58
+ (0, vitest_1.expect)(result.timeline).toEqual([]);
59
+ (0, vitest_1.expect)(result.averageCommitsPerWeek).toBe(0);
60
+ (0, vitest_1.expect)(result.overallTrend).toBe('stable');
61
+ (0, vitest_1.expect)(result.anomalies).toEqual([]);
62
+ });
63
+ });
64
+ (0, vitest_1.describe)('basic calculations', () => {
65
+ (0, vitest_1.it)('creates weekly timeline for commits', () => {
66
+ const start = new Date('2025-06-02'); // Monday
67
+ const end = new Date('2025-06-15'); // Sunday of week 2
68
+ const commits = [
69
+ createCommit(new Date('2025-06-02')), // Week 1
70
+ createCommit(new Date('2025-06-03')),
71
+ createCommit(new Date('2025-06-09')), // Week 2
72
+ ];
73
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
74
+ (0, vitest_1.expect)(result.timeline.length).toBeGreaterThanOrEqual(2);
75
+ });
76
+ (0, vitest_1.it)('calculates average commits per week', () => {
77
+ const start = new Date('2025-06-02');
78
+ const end = new Date('2025-06-29');
79
+ // 4 weeks with 5, 3, 7, 5 commits = 20 total / 4 weeks = 5 avg
80
+ const commits = createCommitsOverWeeks(start, [5, 3, 7, 5]);
81
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
82
+ (0, vitest_1.expect)(result.averageCommitsPerWeek).toBeCloseTo(5, 0);
83
+ });
84
+ (0, vitest_1.it)('tracks multiple authors in a week', () => {
85
+ const start = new Date('2025-06-02');
86
+ const end = new Date('2025-06-08');
87
+ const commits = [
88
+ createCommit(new Date('2025-06-02'), { author: 'Alice' }),
89
+ createCommit(new Date('2025-06-03'), { author: 'Bob' }),
90
+ createCommit(new Date('2025-06-04'), { author: 'Alice' }),
91
+ ];
92
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
93
+ (0, vitest_1.expect)(result.timeline[0].authors).toContain('Alice');
94
+ (0, vitest_1.expect)(result.timeline[0].authors).toContain('Bob');
95
+ (0, vitest_1.expect)(result.timeline[0].authors.length).toBe(2);
96
+ });
97
+ });
98
+ // ==========================================================================
99
+ // Rolling Averages
100
+ // ==========================================================================
101
+ (0, vitest_1.describe)('rolling averages', () => {
102
+ (0, vitest_1.it)('calculates rolling average with default 4-week window', () => {
103
+ const start = new Date('2025-06-02');
104
+ const end = new Date('2025-07-27');
105
+ // 8 weeks of commits
106
+ const commits = createCommitsOverWeeks(start, [4, 4, 4, 4, 8, 8, 8, 8]);
107
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
108
+ // Week 8 should have higher rolling average than week 4
109
+ // due to increased commits in weeks 5-8
110
+ (0, vitest_1.expect)(result.timeline.length).toBeGreaterThanOrEqual(8);
111
+ });
112
+ (0, vitest_1.it)('uses custom window size from options', () => {
113
+ const start = new Date('2025-06-02');
114
+ const end = new Date('2025-06-29');
115
+ const commits = createCommitsOverWeeks(start, [2, 4, 6, 8]);
116
+ // Use 2-week window
117
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end, 2);
118
+ // Each point should use 2-week rolling window
119
+ (0, vitest_1.expect)(result.timeline.length).toBeGreaterThanOrEqual(4);
120
+ // Week 4 rolling avg should be avg of weeks 3,4 = (6+8)/2 = 7
121
+ const week4 = result.timeline[result.timeline.length - 1];
122
+ (0, vitest_1.expect)(week4.rollingAverage).toBeGreaterThanOrEqual(5);
123
+ });
124
+ });
125
+ // ==========================================================================
126
+ // Trend Detection
127
+ // ==========================================================================
128
+ (0, vitest_1.describe)('trend detection', () => {
129
+ (0, vitest_1.it)('detects increasing trend', () => {
130
+ const start = new Date('2025-01-06');
131
+ const end = new Date('2025-03-30');
132
+ // 12 weeks: starts low, ends high
133
+ const commits = createCommitsOverWeeks(start, [2, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10]);
134
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
135
+ (0, vitest_1.expect)(result.overallTrend).toBe('increasing');
136
+ (0, vitest_1.expect)(result.trendPercentage).toBeGreaterThan(0);
137
+ });
138
+ (0, vitest_1.it)('detects decreasing trend', () => {
139
+ const start = new Date('2025-01-06');
140
+ const end = new Date('2025-03-30');
141
+ // 12 weeks: starts high, ends low
142
+ const commits = createCommitsOverWeeks(start, [10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 2, 2]);
143
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
144
+ (0, vitest_1.expect)(result.overallTrend).toBe('decreasing');
145
+ (0, vitest_1.expect)(result.trendPercentage).toBeLessThan(0);
146
+ });
147
+ (0, vitest_1.it)('detects stable trend', () => {
148
+ const start = new Date('2025-01-06');
149
+ const end = new Date('2025-03-30');
150
+ // 12 weeks: consistent activity
151
+ const commits = createCommitsOverWeeks(start, [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]);
152
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
153
+ (0, vitest_1.expect)(result.overallTrend).toBe('stable');
154
+ });
155
+ (0, vitest_1.it)('detects volatile trend with high variance', () => {
156
+ const start = new Date('2025-01-06');
157
+ const end = new Date('2025-03-30');
158
+ // 12 weeks: wild swings
159
+ const commits = createCommitsOverWeeks(start, [1, 15, 2, 20, 1, 18, 2, 16, 1, 17, 2, 15]);
160
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
161
+ (0, vitest_1.expect)(result.overallTrend).toBe('volatile');
162
+ });
163
+ (0, vitest_1.it)('returns stable for short timelines', () => {
164
+ const start = new Date('2025-06-02');
165
+ const end = new Date('2025-06-15');
166
+ const commits = createCommitsOverWeeks(start, [5, 10]);
167
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
168
+ // Too few weeks to determine trend
169
+ (0, vitest_1.expect)(result.overallTrend).toBe('stable');
170
+ });
171
+ });
172
+ // ==========================================================================
173
+ // Peak and Lowest Weeks
174
+ // ==========================================================================
175
+ (0, vitest_1.describe)('peak and lowest weeks', () => {
176
+ (0, vitest_1.it)('identifies peak week correctly', () => {
177
+ const start = new Date('2025-06-02');
178
+ const end = new Date('2025-06-29');
179
+ // Week 3 has the most commits (10)
180
+ const commits = createCommitsOverWeeks(start, [3, 5, 10, 4]);
181
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
182
+ (0, vitest_1.expect)(result.peakWeek.commits).toBe(10);
183
+ });
184
+ (0, vitest_1.it)('identifies lowest non-zero week correctly', () => {
185
+ const start = new Date('2025-06-02');
186
+ const end = new Date('2025-06-29');
187
+ // Week 1 has the fewest commits (2)
188
+ const commits = createCommitsOverWeeks(start, [2, 5, 8, 6]);
189
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
190
+ (0, vitest_1.expect)(result.lowestWeek.commits).toBe(2);
191
+ });
192
+ (0, vitest_1.it)('handles all-zero weeks for lowest', () => {
193
+ const start = new Date('2025-06-02');
194
+ const end = new Date('2025-06-15');
195
+ // No commits at all
196
+ const commits = [];
197
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
198
+ (0, vitest_1.expect)(result.lowestWeek.commits).toBe(0);
199
+ });
200
+ });
201
+ // ==========================================================================
202
+ // Anomaly Detection
203
+ // ==========================================================================
204
+ (0, vitest_1.describe)('anomaly detection', () => {
205
+ (0, vitest_1.it)('detects critical drop (>60% decrease)', () => {
206
+ const start = new Date('2025-06-02');
207
+ const end = new Date('2025-07-20');
208
+ // Consistent 10 commits, then sudden drop to 2
209
+ const commits = createCommitsOverWeeks(start, [10, 10, 10, 10, 10, 2, 5]);
210
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
211
+ const criticalDrops = result.anomalies.filter((a) => a.type === 'drop' && a.severity === 'critical');
212
+ (0, vitest_1.expect)(criticalDrops.length).toBeGreaterThan(0);
213
+ });
214
+ (0, vitest_1.it)('detects significant drop (>40% decrease)', () => {
215
+ const start = new Date('2025-06-02');
216
+ const end = new Date('2025-07-20');
217
+ // 10 commits/week, then 5 (50% drop)
218
+ const commits = createCommitsOverWeeks(start, [10, 10, 10, 10, 10, 5, 8]);
219
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
220
+ const significantDrops = result.anomalies.filter((a) => a.type === 'drop' && (a.severity === 'significant' || a.severity === 'critical'));
221
+ (0, vitest_1.expect)(significantDrops.length).toBeGreaterThan(0);
222
+ });
223
+ (0, vitest_1.it)('detects significant spike (>100% increase)', () => {
224
+ const start = new Date('2025-06-02');
225
+ const end = new Date('2025-07-20');
226
+ // Consistent 5 commits, then jump to 15
227
+ const commits = createCommitsOverWeeks(start, [5, 5, 5, 5, 5, 15, 6]);
228
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
229
+ const spikes = result.anomalies.filter((a) => a.type === 'spike' && a.severity === 'significant');
230
+ (0, vitest_1.expect)(spikes.length).toBeGreaterThan(0);
231
+ });
232
+ (0, vitest_1.it)('detects zero-activity weeks', () => {
233
+ const start = new Date('2025-06-02');
234
+ const end = new Date('2025-06-29');
235
+ // Week 3 has no commits
236
+ const commits = [
237
+ ...createCommitsForWeek(new Date('2025-06-02'), 5),
238
+ ...createCommitsForWeek(new Date('2025-06-09'), 5),
239
+ // Week 3: no commits
240
+ ...createCommitsForWeek(new Date('2025-06-23'), 5),
241
+ ];
242
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
243
+ const zeroActivity = result.anomalies.filter((a) => a.type === 'zero-activity');
244
+ (0, vitest_1.expect)(zeroActivity.length).toBeGreaterThan(0);
245
+ });
246
+ (0, vitest_1.it)('includes possible causes for anomalies', () => {
247
+ const start = new Date('2025-06-02');
248
+ const end = new Date('2025-07-06');
249
+ const commits = createCommitsOverWeeks(start, [10, 10, 10, 10, 2]); // Big drop
250
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
251
+ const drops = result.anomalies.filter((a) => a.type === 'drop');
252
+ if (drops.length > 0) {
253
+ (0, vitest_1.expect)(drops[0].possibleCauses).toBeDefined();
254
+ (0, vitest_1.expect)(drops[0].possibleCauses.length).toBeGreaterThan(0);
255
+ }
256
+ });
257
+ (0, vitest_1.it)('returns no anomalies for consistent activity', () => {
258
+ const start = new Date('2025-06-02');
259
+ const end = new Date('2025-06-29');
260
+ // Consistent 5 commits/week
261
+ const commits = createCommitsOverWeeks(start, [5, 5, 5, 5]);
262
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
263
+ // May have minor anomalies but no critical ones
264
+ const criticalAnomalies = result.anomalies.filter((a) => a.severity === 'critical');
265
+ (0, vitest_1.expect)(criticalAnomalies.length).toBe(0);
266
+ });
267
+ });
268
+ // ==========================================================================
269
+ // Weekly Breakdown
270
+ // ==========================================================================
271
+ (0, vitest_1.describe)('weekly breakdown', () => {
272
+ (0, vitest_1.it)('groups commits by week correctly', () => {
273
+ const start = new Date('2025-06-02'); // Monday
274
+ const end = new Date('2025-06-15'); // End of week 2
275
+ const commits = [
276
+ createCommit(new Date('2025-06-02')), // Week 1 Mon
277
+ createCommit(new Date('2025-06-05')), // Week 1 Thu
278
+ createCommit(new Date('2025-06-06')), // Week 1 Fri
279
+ createCommit(new Date('2025-06-09')), // Week 2 Mon
280
+ ];
281
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
282
+ // Week 1 should have 3 commits
283
+ const week1 = result.timeline[0];
284
+ (0, vitest_1.expect)(week1.commits).toBe(3);
285
+ // Week 2 should have 1 commit
286
+ const week2 = result.timeline[1];
287
+ (0, vitest_1.expect)(week2.commits).toBe(1);
288
+ });
289
+ (0, vitest_1.it)('includes week start and end dates', () => {
290
+ const start = new Date('2025-06-02');
291
+ const end = new Date('2025-06-08');
292
+ const commits = [createCommit(new Date('2025-06-04'))];
293
+ const result = (0, analyzer_1.analyzeVelocity)(commits, start, end);
294
+ (0, vitest_1.expect)(result.timeline[0].weekStart).toBeInstanceOf(Date);
295
+ (0, vitest_1.expect)(result.timeline[0].weekEnd).toBeInstanceOf(Date);
296
+ });
297
+ });
298
+ // ==========================================================================
299
+ // Context-based API
300
+ // ==========================================================================
301
+ (0, vitest_1.describe)('context-based API', () => {
302
+ (0, vitest_1.it)('works with AnalyzerContext', () => {
303
+ const commits = createCommitsOverWeeks(new Date('2025-06-02'), [5, 8, 3]);
304
+ const ctx = {
305
+ commits,
306
+ dateRange: {
307
+ start: new Date('2025-06-02'),
308
+ end: new Date('2025-06-22'),
309
+ },
310
+ author: 'Test Author',
311
+ };
312
+ const result = (0, analyzer_1.analyzeVelocity)(ctx);
313
+ (0, vitest_1.expect)(result.timeline.length).toBeGreaterThanOrEqual(3);
314
+ });
315
+ (0, vitest_1.it)('respects windowWeeks option from context', () => {
316
+ const commits = createCommitsOverWeeks(new Date('2025-06-02'), [2, 4, 6, 8]);
317
+ const ctx = {
318
+ commits,
319
+ dateRange: {
320
+ start: new Date('2025-06-02'),
321
+ end: new Date('2025-06-29'),
322
+ },
323
+ author: 'Test Author',
324
+ options: {
325
+ windowWeeks: 2,
326
+ },
327
+ };
328
+ const result = (0, analyzer_1.analyzeVelocity)(ctx);
329
+ // Should use 2-week rolling window
330
+ (0, vitest_1.expect)(result.timeline.length).toBeGreaterThanOrEqual(4);
331
+ });
332
+ });
333
+ });
334
+ // ============================================================================
335
+ // formatVelocityInsights Tests
336
+ // ============================================================================
337
+ (0, vitest_1.describe)('formatVelocityInsights', () => {
338
+ (0, vitest_1.it)('returns empty message for no data', () => {
339
+ const analysis = {
340
+ timeline: [],
341
+ overallTrend: 'stable',
342
+ trendPercentage: 0,
343
+ averageCommitsPerWeek: 0,
344
+ peakWeek: { weekStart: new Date(), commits: 0 },
345
+ lowestWeek: { weekStart: new Date(), commits: 0 },
346
+ anomalies: [],
347
+ };
348
+ const insights = (0, analyzer_1.formatVelocityInsights)(analysis);
349
+ (0, vitest_1.expect)(insights).toContain('No velocity data available');
350
+ });
351
+ (0, vitest_1.it)('includes trend information', () => {
352
+ const start = new Date('2025-06-02');
353
+ const commits = createCommitsOverWeeks(start, [5, 5, 5, 5]);
354
+ const analysis = (0, analyzer_1.analyzeVelocity)(commits, start, new Date('2025-06-29'));
355
+ const insights = (0, analyzer_1.formatVelocityInsights)(analysis);
356
+ (0, vitest_1.expect)(insights.some((i) => i.toLowerCase().includes('trend'))).toBe(true);
357
+ });
358
+ (0, vitest_1.it)('includes average commits per week', () => {
359
+ const start = new Date('2025-06-02');
360
+ const commits = createCommitsOverWeeks(start, [5, 5, 5, 5]);
361
+ const analysis = (0, analyzer_1.analyzeVelocity)(commits, start, new Date('2025-06-29'));
362
+ const insights = (0, analyzer_1.formatVelocityInsights)(analysis);
363
+ (0, vitest_1.expect)(insights.some((i) => i.includes('commits/week'))).toBe(true);
364
+ });
365
+ (0, vitest_1.it)('includes peak week information', () => {
366
+ const start = new Date('2025-06-02');
367
+ const commits = createCommitsOverWeeks(start, [5, 10, 5, 5]);
368
+ const analysis = (0, analyzer_1.analyzeVelocity)(commits, start, new Date('2025-06-29'));
369
+ const insights = (0, analyzer_1.formatVelocityInsights)(analysis);
370
+ (0, vitest_1.expect)(insights.some((i) => i.toLowerCase().includes('peak'))).toBe(true);
371
+ });
372
+ (0, vitest_1.it)('reports critical drops', () => {
373
+ const start = new Date('2025-06-02');
374
+ const commits = createCommitsOverWeeks(start, [10, 10, 10, 10, 1]);
375
+ const analysis = (0, analyzer_1.analyzeVelocity)(commits, start, new Date('2025-07-06'));
376
+ const insights = (0, analyzer_1.formatVelocityInsights)(analysis);
377
+ // Should mention critical drops if detected
378
+ const hasCriticalDrops = analysis.anomalies.some((a) => a.type === 'drop' && a.severity === 'critical');
379
+ if (hasCriticalDrops) {
380
+ (0, vitest_1.expect)(insights.some((i) => i.toLowerCase().includes('drop'))).toBe(true);
381
+ }
382
+ });
383
+ });
@@ -0,0 +1,27 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.buildVelocitySection = exports.formatVelocityInsights = exports.analyzeVelocity = void 0;
18
+ /**
19
+ * Velocity Feature
20
+ * @module features/velocity
21
+ */
22
+ __exportStar(require("./types"), exports);
23
+ var analyzer_1 = require("./analyzer");
24
+ Object.defineProperty(exports, "analyzeVelocity", { enumerable: true, get: function () { return analyzer_1.analyzeVelocity; } });
25
+ Object.defineProperty(exports, "formatVelocityInsights", { enumerable: true, get: function () { return analyzer_1.formatVelocityInsights; } });
26
+ var template_1 = require("./template");
27
+ Object.defineProperty(exports, "buildVelocitySection", { enumerable: true, get: function () { return template_1.buildVelocitySection; } });
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildVelocitySection = buildVelocitySection;
4
+ const date_fns_1 = require("date-fns");
5
+ function buildVelocitySection(velocity) {
6
+ if (velocity.timeline.length === 0) {
7
+ return `
8
+ <div class="velocity-section">
9
+ <h2>📈 Velocity Analysis</h2>
10
+ <div class="empty-state">No velocity data available</div>
11
+ </div>
12
+ `;
13
+ }
14
+ const chartHtml = buildVelocityChart(velocity);
15
+ const statsHtml = buildVelocityStats(velocity);
16
+ const anomaliesHtml = buildAnomaliesList(velocity.anomalies);
17
+ return `
18
+ <div class="velocity-section">
19
+ <h2>📈 Velocity Analysis</h2>
20
+
21
+ ${statsHtml}
22
+
23
+ <div class="velocity-chart-container">
24
+ <h3>Commits Per Week</h3>
25
+ ${chartHtml}
26
+ </div>
27
+
28
+ ${anomaliesHtml}
29
+ </div>
30
+ `;
31
+ }
32
+ function buildVelocityStats(velocity) {
33
+ const trendEmoji = velocity.overallTrend === 'increasing' ? '📈' :
34
+ velocity.overallTrend === 'decreasing' ? '📉' :
35
+ velocity.overallTrend === 'volatile' ? '📊' : '➡️';
36
+ const trendClass = velocity.overallTrend === 'increasing' ? 'positive' :
37
+ velocity.overallTrend === 'decreasing' ? 'negative' : 'neutral';
38
+ const trendSign = velocity.trendPercentage >= 0 ? '+' : '';
39
+ return `
40
+ <div class="velocity-stats">
41
+ <div class="velocity-stat-card">
42
+ <div class="stat-icon">${trendEmoji}</div>
43
+ <div class="stat-info">
44
+ <div class="stat-label">Overall Trend</div>
45
+ <div class="stat-value ${trendClass}">${velocity.overallTrend}</div>
46
+ <div class="stat-detail">${trendSign}${velocity.trendPercentage}% over period</div>
47
+ </div>
48
+ </div>
49
+ <div class="velocity-stat-card">
50
+ <div class="stat-icon">📊</div>
51
+ <div class="stat-info">
52
+ <div class="stat-label">Average</div>
53
+ <div class="stat-value">${velocity.averageCommitsPerWeek}</div>
54
+ <div class="stat-detail">commits/week</div>
55
+ </div>
56
+ </div>
57
+ <div class="velocity-stat-card">
58
+ <div class="stat-icon">🚀</div>
59
+ <div class="stat-info">
60
+ <div class="stat-label">Peak Week</div>
61
+ <div class="stat-value">${velocity.peakWeek.commits}</div>
62
+ <div class="stat-detail">${(0, date_fns_1.format)(velocity.peakWeek.weekStart, 'MMM d, yyyy')}</div>
63
+ </div>
64
+ </div>
65
+ <div class="velocity-stat-card">
66
+ <div class="stat-icon">🐢</div>
67
+ <div class="stat-info">
68
+ <div class="stat-label">Lowest Week</div>
69
+ <div class="stat-value">${velocity.lowestWeek.commits}</div>
70
+ <div class="stat-detail">${(0, date_fns_1.format)(velocity.lowestWeek.weekStart, 'MMM d, yyyy')}</div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ `;
75
+ }
76
+ function buildVelocityChart(velocity) {
77
+ const timeline = velocity.timeline;
78
+ const maxCommits = Math.max(...timeline.map(d => d.commits), 1);
79
+ const chartWidth = 800;
80
+ const chartHeight = 200;
81
+ const padding = { top: 20, right: 20, bottom: 40, left: 50 };
82
+ const innerWidth = chartWidth - padding.left - padding.right;
83
+ const innerHeight = chartHeight - padding.top - padding.bottom;
84
+ // Build SVG path for commits
85
+ const xScale = (i) => padding.left + (i / (timeline.length - 1 || 1)) * innerWidth;
86
+ const yScale = (v) => padding.top + innerHeight - (v / maxCommits) * innerHeight;
87
+ // Commits line
88
+ const commitsPath = timeline.map((d, i) => `${i === 0 ? 'M' : 'L'} ${xScale(i)} ${yScale(d.commits)}`).join(' ');
89
+ // Rolling average line
90
+ const avgPath = timeline.map((d, i) => `${i === 0 ? 'M' : 'L'} ${xScale(i)} ${yScale(d.rollingAverage)}`).join(' ');
91
+ // Area fill under commits line
92
+ const areaPath = `${commitsPath} L ${xScale(timeline.length - 1)} ${yScale(0)} L ${xScale(0)} ${yScale(0)} Z`;
93
+ // Find anomaly positions
94
+ const anomalyMarkers = velocity.anomalies.map(anomaly => {
95
+ const idx = timeline.findIndex(d => (0, date_fns_1.format)(d.weekStart, 'yyyy-MM-dd') === (0, date_fns_1.format)(anomaly.weekStart, 'yyyy-MM-dd'));
96
+ if (idx === -1)
97
+ return '';
98
+ const x = xScale(idx);
99
+ const y = yScale(timeline[idx].commits);
100
+ const color = anomaly.type === 'drop' ? '#ff6b6b' : anomaly.type === 'spike' ? '#ffa94d' : '#868e96';
101
+ return `<circle cx="${x}" cy="${y}" r="6" fill="${color}" class="anomaly-marker" data-type="${anomaly.type}" data-severity="${anomaly.severity}"/>`;
102
+ }).join('');
103
+ // X-axis labels (show every nth label to avoid crowding)
104
+ const labelInterval = Math.max(1, Math.floor(timeline.length / 8));
105
+ const xLabels = timeline.map((d, i) => {
106
+ if (i % labelInterval !== 0 && i !== timeline.length - 1)
107
+ return '';
108
+ return `<text x="${xScale(i)}" y="${chartHeight - 10}" text-anchor="middle" class="axis-label">${(0, date_fns_1.format)(d.weekStart, 'MMM d')}</text>`;
109
+ }).join('');
110
+ // Y-axis labels
111
+ const yTicks = [0, Math.round(maxCommits / 2), maxCommits];
112
+ const yLabels = yTicks.map(v => `<text x="${padding.left - 10}" y="${yScale(v) + 4}" text-anchor="end" class="axis-label">${v}</text>`).join('');
113
+ // Grid lines
114
+ const gridLines = yTicks.map(v => `<line x1="${padding.left}" y1="${yScale(v)}" x2="${chartWidth - padding.right}" y2="${yScale(v)}" class="grid-line"/>`).join('');
115
+ return `
116
+ <svg class="velocity-chart" viewBox="0 0 ${chartWidth} ${chartHeight}" preserveAspectRatio="xMidYMid meet">
117
+ <defs>
118
+ <linearGradient id="areaGradient" x1="0%" y1="0%" x2="0%" y2="100%">
119
+ <stop offset="0%" style="stop-color:var(--accent-color);stop-opacity:0.3"/>
120
+ <stop offset="100%" style="stop-color:var(--accent-color);stop-opacity:0.05"/>
121
+ </linearGradient>
122
+ </defs>
123
+
124
+ <!-- Grid -->
125
+ ${gridLines}
126
+
127
+ <!-- Area fill -->
128
+ <path d="${areaPath}" fill="url(#areaGradient)" class="area-fill"/>
129
+
130
+ <!-- Commits line -->
131
+ <path d="${commitsPath}" fill="none" stroke="var(--accent-color)" stroke-width="2" class="commits-line"/>
132
+
133
+ <!-- Rolling average line -->
134
+ <path d="${avgPath}" fill="none" stroke="#ff922b" stroke-width="3" stroke-dasharray="6,4" class="avg-line"/>
135
+
136
+ <!-- Anomaly markers -->
137
+ ${anomalyMarkers}
138
+
139
+ <!-- Axes labels -->
140
+ ${xLabels}
141
+ ${yLabels}
142
+
143
+ <!-- Legend -->
144
+ <g transform="translate(${chartWidth - 150}, 10)">
145
+ <line x1="0" y1="5" x2="20" y2="5" stroke="var(--accent-color)" stroke-width="2"/>
146
+ <text x="25" y="9" class="legend-label">Commits</text>
147
+ <line x1="0" y1="20" x2="20" y2="20" stroke="#ff922b" stroke-width="3" stroke-dasharray="6,4"/>
148
+ <text x="25" y="24" class="legend-label">Rolling Avg</text>
149
+ </g>
150
+ </svg>
151
+ `;
152
+ }
153
+ function buildAnomaliesList(anomalies) {
154
+ if (anomalies.length === 0) {
155
+ return '';
156
+ }
157
+ const criticalAnomalies = anomalies.filter(a => a.severity === 'critical' || a.severity === 'significant');
158
+ if (criticalAnomalies.length === 0) {
159
+ return '';
160
+ }
161
+ const anomalyItems = criticalAnomalies.slice(0, 5).map(anomaly => {
162
+ const typeEmoji = anomaly.type === 'drop' ? '📉' : anomaly.type === 'spike' ? '📈' : '🚫';
163
+ const severityClass = anomaly.severity === 'critical' ? 'critical' : 'warning';
164
+ const dateStr = (0, date_fns_1.format)(anomaly.weekStart, 'MMM d, yyyy');
165
+ return `
166
+ <div class="anomaly-item ${severityClass}">
167
+ <span class="anomaly-icon">${typeEmoji}</span>
168
+ <div class="anomaly-info">
169
+ <div class="anomaly-header">
170
+ <span class="anomaly-type">${anomaly.type}</span>
171
+ <span class="anomaly-date">${dateStr}</span>
172
+ <span class="anomaly-change">${anomaly.percentageChange >= 0 ? '+' : ''}${anomaly.percentageChange}%</span>
173
+ </div>
174
+ <div class="anomaly-causes">
175
+ Possible: ${anomaly.possibleCauses.slice(0, 2).join(', ')}
176
+ </div>
177
+ </div>
178
+ </div>
179
+ `;
180
+ }).join('');
181
+ return `
182
+ <div class="anomalies-section">
183
+ <h3>⚠️ Detected Anomalies</h3>
184
+ <div class="anomalies-list">
185
+ ${anomalyItems}
186
+ </div>
187
+ </div>
188
+ `;
189
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -118,3 +118,20 @@
118
118
  collapseAllRows: collapseAllRows
119
119
  };
120
120
  })();
121
+
122
+ /**
123
+ * Toggle visibility of additional files in the high-risk files list
124
+ */
125
+ function toggleMoreFiles(button) {
126
+ const targetGroup = button.getAttribute('data-target');
127
+ const count = button.getAttribute('data-count');
128
+ const hiddenRows = document.querySelectorAll(`tr.hidden-file[data-group="${targetGroup}"]`);
129
+ const isExpanded = button.getAttribute('data-expanded') === 'true';
130
+
131
+ hiddenRows.forEach(row => {
132
+ row.style.display = isExpanded ? 'none' : 'table-row';
133
+ });
134
+
135
+ button.setAttribute('data-expanded', !isExpanded);
136
+ button.textContent = isExpanded ? `+ ${count} more files` : 'Show less';
137
+ }
@@ -23,15 +23,19 @@
23
23
  --accent-blue: #58a6ff;
24
24
  --accent-blue-muted: #388bfd;
25
25
  --accent-green: #3fb950;
26
+ --accent-green-light: #69db7c;
26
27
  --accent-green-muted: #238636;
27
28
  --accent-gold: #d4a72c;
28
29
  --accent-gold-muted: #9e7a1f;
29
30
  --accent-purple: #8b5cf6;
31
+ --accent-orange: #ff922b;
32
+ --accent-orange-light: #ffa94d;
30
33
 
31
34
  /* Status Colors */
32
35
  --status-success: #238636;
33
- --status-warning: #9e6a03;
34
- --status-danger: #da3633;
36
+ --status-warning: #ffd43b;
37
+ --status-warning-muted: #9e6a03;
38
+ --status-danger: #ff6b6b;
35
39
  --status-info: #58a6ff;
36
40
 
37
41
  /* Tier Colors (Muted) */
@@ -53,6 +57,7 @@
53
57
  --radius-sm: 2px;
54
58
  --radius-md: 4px;
55
59
  --radius-lg: 6px;
60
+ --radius-xl: 12px;
56
61
 
57
62
  /* Shadows */
58
63
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);
@@ -356,7 +361,7 @@ h3 {
356
361
 
357
362
  .filter-group {
358
363
  display: flex;
359
- align-items: center;
364
+ align-items: baseline;
360
365
  gap: var(--spacing-sm);
361
366
  }
362
367