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
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.analyzeKnowledgeDistribution = analyzeKnowledgeDistribution;
4
4
  const child_process_1 = require("child_process");
5
+ const emptyResults_1 = require("./emptyResults");
5
6
  /**
6
7
  * Extract directory from file path
7
8
  */
@@ -13,18 +14,15 @@ function getDirectory(filePath) {
13
14
  }
14
15
  /**
15
16
  * Calculate bus factor risk score
16
- * 1 owner = 10 (critical), 2 = 7 (high), 3 = 4 (medium), 4+ = 1 (low)
17
+ * 1 owner = 10 (critical), 2+ = 1 (low/safe)
18
+ * Adjusted for realistic team dynamics where 2+ knowledgeable owners is considered safe
17
19
  */
18
20
  function calculateRiskScore(significantOwners) {
19
21
  if (significantOwners === 0)
20
22
  return 10;
21
23
  if (significantOwners === 1)
22
24
  return 10;
23
- if (significantOwners === 2)
24
- return 7;
25
- if (significantOwners === 3)
26
- return 4;
27
- return 1;
25
+ return 1; // 2+ owners = low risk
28
26
  }
29
27
  /**
30
28
  * Determine ownership type based on contribution percentages
@@ -151,7 +149,7 @@ function buildFileOwnership(fileStats, directoryPath) {
151
149
  });
152
150
  const ownershipPercentage = Math.round((maxCommits / totalCommits) * 100);
153
151
  const knowledgeAge = calculateKnowledgeAge(lastModified);
154
- const significantOwners = Array.from(data.authors.values()).filter(a => (a.commits / totalCommits) >= 0.2).length;
152
+ const significantOwners = Array.from(data.authors.values()).filter(a => (a.commits / totalCommits) >= 0.65).length;
155
153
  const busFactorRiskScore = calculateRiskScore(significantOwners);
156
154
  const riskLevel = getRiskLevel(busFactorRiskScore);
157
155
  files.push({
@@ -175,11 +173,29 @@ function buildFileOwnership(fileStats, directoryPath) {
175
173
  .slice(0, 20); // Limit to 20 files per directory
176
174
  }
177
175
  /**
178
- * Analyze knowledge distribution across the codebase
176
+ * Analyzes knowledge distribution across the codebase to identify bus factor risks.
177
+ *
178
+ * Identifies:
179
+ * - Directory ownership patterns (solo, primary, shared, collaborative)
180
+ * - Bus factor risks (areas with single knowledge holders)
181
+ * - Knowledge silos (directories owned by one person)
182
+ * - Shared knowledge areas (directories with 3+ active contributors)
183
+ *
184
+ * @param commits - Array of commit data to analyze
185
+ * @param repoPath - Optional repository path for deep file-level analysis via git log
186
+ * @param deepAnalysis - Whether to perform file-level analysis (slower but more accurate)
187
+ * @returns Knowledge distribution with ownership data, risks, and recommendations
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const knowledge = analyzeKnowledgeDistribution(commits, '/path/to/repo', true);
192
+ * console.log(`Bus factor risk: ${knowledge.busFactorRisk.level}`);
193
+ * console.log(`Knowledge silos: ${knowledge.knowledgeSilos.length}`);
194
+ * ```
179
195
  */
180
196
  function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
181
197
  if (commits.length === 0) {
182
- return getEmptyKnowledgeDistribution();
198
+ return (0, emptyResults_1.getEmptyKnowledgeDistribution)();
183
199
  }
184
200
  // For deep analysis, parse git log with file names
185
201
  let fileStats = null;
@@ -262,6 +278,11 @@ function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
262
278
  let dirCount = 0;
263
279
  directoryStats.forEach((authorStats, dirPath) => {
264
280
  const totalCommits = Array.from(authorStats.values()).reduce((sum, a) => sum + a.commits, 0);
281
+ // Skip areas with too few commits - not enough data to assess bus factor
282
+ const MIN_COMMITS_FOR_ASSESSMENT = 20;
283
+ if (totalCommits < MIN_COMMITS_FOR_ASSESSMENT) {
284
+ return; // Skip this directory
285
+ }
265
286
  // Get the latest and earliest activity date for the directory
266
287
  let latestActivity = new Date(0);
267
288
  let earliestActivity = new Date();
@@ -285,8 +306,8 @@ function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
285
306
  knowledgeAge: calculateKnowledgeAge(stats.lastActive)
286
307
  }))
287
308
  .sort((a, b) => b.commits - a.commits);
288
- // Calculate significant owners (>= 20% contribution)
289
- const significantOwners = owners.filter(o => o.percentage >= 20).length;
309
+ // Calculate significant owners (>= 65% contribution)
310
+ const significantOwners = owners.filter(o => o.percentage >= 65).length;
290
311
  const busFactorRisk = calculateRiskScore(significantOwners);
291
312
  const ownershipType = determineOwnershipType(owners);
292
313
  // Build high-risk files list if deep analysis is enabled
@@ -362,19 +383,3 @@ function generateRecommendations(data) {
362
383
  }
363
384
  return recommendations.slice(0, 5);
364
385
  }
365
- /**
366
- * Return empty distribution for no commits
367
- */
368
- function getEmptyKnowledgeDistribution() {
369
- return {
370
- directories: [],
371
- busFactorRisk: {
372
- overall: 0,
373
- level: 'low',
374
- criticalPaths: []
375
- },
376
- knowledgeSilos: [],
377
- sharedKnowledge: [],
378
- recommendations: ['No commit data available for analysis']
379
- };
380
- }
@@ -6,11 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateCommitMatrix = generateCommitMatrix;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const date_fns_1 = require("date-fns");
9
- const achievementEngine_1 = require("./achievementEngine");
10
- const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
9
+ const achievements_1 = require("../features/achievements");
10
+ const commit_quality_1 = require("../features/commit-quality");
11
11
  const fileHotspotAnalyzer_1 = require("./fileHotspotAnalyzer");
12
- const streakCalculator_1 = require("./streakCalculator");
13
- const timePatternAnalyzer_1 = require("./timePatternAnalyzer");
12
+ const streaks_1 = require("../features/streaks");
13
+ const time_patterns_1 = require("../features/time-patterns");
14
14
  /**
15
15
  * Calculate dynamic thresholds based on commit distribution for terminal output
16
16
  */
@@ -53,11 +53,11 @@ function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, r
53
53
  });
54
54
  const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
55
55
  // Calculate streaks
56
- const streakData = (0, streakCalculator_1.calculateStreaks)(commits, startDate, endDate);
56
+ const streakData = (0, streaks_1.calculateStreaks)(commits, startDate, endDate);
57
57
  // Analyze time patterns
58
- const timePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(commits, startDate, endDate);
58
+ const timePattern = (0, time_patterns_1.analyzeTimePatterns)(commits, startDate, endDate);
59
59
  // Analyze commit quality
60
- const commitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits);
60
+ const commitQuality = (0, commit_quality_1.analyzeCommitQuality)(commits);
61
61
  // Analyze file hotspots
62
62
  const fileHotspots = (0, fileHotspotAnalyzer_1.analyzeFileHotspots)(repoPath, startDate, endDate);
63
63
  // Prepare analysis data for achievements
@@ -71,7 +71,7 @@ function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, r
71
71
  dateRange: { start: startDate, end: endDate }
72
72
  };
73
73
  // Check achievements
74
- const achievementProgress = (0, achievementEngine_1.checkAchievements)(analysisData);
74
+ const achievementProgress = (0, achievements_1.checkAchievements)(analysisData);
75
75
  return formatGitHubStyle(commitMap, weekStartDate, weekEndDate, startDate, endDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl);
76
76
  }
77
77
  function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate, dataEndDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl) {
@@ -174,7 +174,7 @@ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate,
174
174
  : 'No active streak';
175
175
  output += `${currentStreakEmoji} Current Streak: ${chalk_1.default.bold.green(currentStreakText)}`;
176
176
  if (streakData.currentStreak.isActive && streakData.currentStreak.days > 0) {
177
- output += chalk_1.default.dim(` - ${(0, streakCalculator_1.getStreakMotivation)(streakData.currentStreak.days, true)}`);
177
+ output += chalk_1.default.dim(` - ${(0, streaks_1.getStreakMotivation)(streakData.currentStreak.days, true)}`);
178
178
  }
179
179
  output += '\n';
180
180
  const longestStreakText = streakData.longestStreak.days > 0
@@ -194,8 +194,8 @@ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate,
194
194
  `${timePattern.peakHour.hour - 12}:00 PM`;
195
195
  output += `⏱️ Peak Hour: ${chalk_1.default.bold.green(peakHourLabel)} (${timePattern.peakHour.commitCount} commits)\n`;
196
196
  // Chronotype
197
- const chronotypeLabel = (0, timePatternAnalyzer_1.getChronotypeLabel)(timePattern.chronotype);
198
- const chronotypeDesc = (0, timePatternAnalyzer_1.getChronotypeDescription)(timePattern.chronotype);
197
+ const chronotypeLabel = (0, time_patterns_1.getChronotypeLabel)(timePattern.chronotype);
198
+ const chronotypeDesc = (0, time_patterns_1.getChronotypeDescription)(timePattern.chronotype);
199
199
  output += `${chronotypeLabel} ${chalk_1.default.dim(`(${timePattern.chronotypeConfidence.toFixed(0)}% confidence)`)}\n`;
200
200
  if (chronotypeDesc) {
201
201
  output += chalk_1.default.dim(` ${chronotypeDesc}\n`);
@@ -217,8 +217,8 @@ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate,
217
217
  output += '\n';
218
218
  output += chalk_1.default.bold.cyan('📝 Commit Quality\n');
219
219
  output += chalk_1.default.dim('─'.repeat(50)) + '\n';
220
- const rating = (0, commitQualityAnalyzer_1.getQualityRating)(commitQuality.overallScore);
221
- const level = (0, commitQualityAnalyzer_1.getQualityLevel)(commitQuality.overallScore);
220
+ const rating = (0, commit_quality_1.getQualityRating)(commitQuality.overallScore);
221
+ const level = (0, commit_quality_1.getQualityLevel)(commitQuality.overallScore);
222
222
  output += `📊 Overall Score: ${chalk_1.default.bold.green(commitQuality.overallScore.toFixed(1))}/10 ${rating} ${chalk_1.default.dim(`(${level})`)}\n`;
223
223
  output += `✅ Convention: ${chalk_1.default.bold.cyan(commitQuality.conventionalCommits.adherence.toFixed(1))}%`;
224
224
  if (commitQuality.conventionalCommits.adherence < 70) {
@@ -5,7 +5,7 @@ exports.parseDateRange = parseDateRange;
5
5
  exports.formatComparisonInsights = formatComparisonInsights;
6
6
  const date_fns_1 = require("date-fns");
7
7
  const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
8
- const streakCalculator_1 = require("./streakCalculator");
8
+ const streaks_1 = require("../features/streaks");
9
9
  /**
10
10
  * Compares metrics between two date ranges to show before/after impact
11
11
  */
@@ -64,7 +64,7 @@ function calculateRangeMetrics(commits, startDate, endDate, skipBodyCheck) {
64
64
  const qualityResult = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits, { skipBodyCheck });
65
65
  const qualityScore = Math.round(qualityResult.overallScore * 10) / 10;
66
66
  // Streak data
67
- const streakData = (0, streakCalculator_1.calculateStreaks)(commits, startDate, endDate);
67
+ const streakData = (0, streaks_1.calculateStreaks)(commits, startDate, endDate);
68
68
  const longestStreak = streakData.longestStreak.days;
69
69
  const averageStreak = streakData.streaks.length > 0
70
70
  ? Math.round(streakData.streaks.reduce((sum, s) => sum + s.days, 0) / streakData.streaks.length * 10) / 10
@@ -3,25 +3,61 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.calculateStreaks = calculateStreaks;
4
4
  exports.getStreakMotivation = getStreakMotivation;
5
5
  const date_fns_1 = require("date-fns");
6
- function calculateStreaks(commits, startDate, endDate) {
6
+ const analyzerContextBuilder_1 = require("./analyzerContextBuilder");
7
+ /**
8
+ * Check if a date is a business day (Monday-Friday)
9
+ */
10
+ function isBusinessDay(date) {
11
+ const day = (0, date_fns_1.getDay)(date);
12
+ return day !== 0 && day !== 6; // 0 = Sunday, 6 = Saturday
13
+ }
14
+ /**
15
+ * Get the previous business day from a given date
16
+ */
17
+ function getPreviousBusinessDay(date) {
18
+ let prevDate = (0, date_fns_1.subDays)(date, 1);
19
+ while (!isBusinessDay(prevDate)) {
20
+ prevDate = (0, date_fns_1.subDays)(prevDate, 1);
21
+ }
22
+ return prevDate;
23
+ }
24
+ function calculateStreaks(ctxOrCommits, startDate, endDate) {
25
+ // Normalize to internal variables
26
+ let commits;
27
+ let start;
28
+ let end;
29
+ if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
30
+ commits = ctxOrCommits.commits;
31
+ start = ctxOrCommits.dateRange.start;
32
+ end = ctxOrCommits.dateRange.end;
33
+ }
34
+ else {
35
+ commits = ctxOrCommits;
36
+ start = startDate;
37
+ end = endDate;
38
+ }
7
39
  // Create set of unique commit dates
8
40
  const commitDates = new Set();
9
41
  commits.forEach(commit => {
10
42
  const commitDate = new Date(commit.date);
11
- if (commitDate >= startDate && commitDate <= endDate) {
43
+ if (commitDate >= start && commitDate <= end) {
12
44
  commitDates.add((0, date_fns_1.format)(commitDate, 'yyyy-MM-dd'));
13
45
  }
14
46
  });
15
47
  // Calculate total days and active days
16
- const totalDays = (0, date_fns_1.differenceInDays)(endDate, startDate) + 1;
48
+ const totalDays = (0, date_fns_1.differenceInDays)(end, start) + 1;
17
49
  const totalActiveDays = commitDates.size;
18
50
  const activeDayPercentage = totalDays > 0 ? (totalActiveDays / totalDays) * 100 : 0;
19
- // Find all streaks
51
+ // Calculate business day stats
52
+ const allDays = (0, date_fns_1.eachDayOfInterval)({ start: start, end: end });
53
+ const businessDays = allDays.filter(isBusinessDay);
54
+ const totalBusinessDays = businessDays.length;
55
+ const activeBusinessDays = businessDays.filter(d => commitDates.has((0, date_fns_1.format)(d, 'yyyy-MM-dd'))).length;
56
+ // Find all streaks (business days only)
20
57
  const streaks = [];
21
58
  let currentStreakDays = 0;
22
59
  let currentStreakStart = null;
23
- const allDays = (0, date_fns_1.eachDayOfInterval)({ start: startDate, end: endDate });
24
- for (const date of allDays) {
60
+ for (const date of businessDays) {
25
61
  const dateKey = (0, date_fns_1.format)(date, 'yyyy-MM-dd');
26
62
  if (commitDates.has(dateKey)) {
27
63
  if (currentStreakDays === 0) {
@@ -31,10 +67,12 @@ function calculateStreaks(commits, startDate, endDate) {
31
67
  }
32
68
  else {
33
69
  if (currentStreakDays > 0 && currentStreakStart) {
70
+ // Find the last business day in the streak
71
+ const streakEnd = getPreviousBusinessDay(date);
34
72
  streaks.push({
35
73
  days: currentStreakDays,
36
74
  startDate: currentStreakStart,
37
- endDate: (0, date_fns_1.subDays)(date, 1)
75
+ endDate: streakEnd > currentStreakStart ? streakEnd : currentStreakStart
38
76
  });
39
77
  currentStreakDays = 0;
40
78
  currentStreakStart = null;
@@ -43,54 +81,63 @@ function calculateStreaks(commits, startDate, endDate) {
43
81
  }
44
82
  // Handle streak that extends to end date
45
83
  if (currentStreakDays > 0 && currentStreakStart) {
84
+ // Find the last business day in range
85
+ const lastBusinessDay = businessDays[businessDays.length - 1] || end;
46
86
  streaks.push({
47
87
  days: currentStreakDays,
48
88
  startDate: currentStreakStart,
49
- endDate: endDate
89
+ endDate: lastBusinessDay
50
90
  });
51
91
  }
52
92
  // Find longest streak
53
93
  const longestStreak = streaks.length > 0
54
94
  ? streaks.reduce((longest, current) => current.days > longest.days ? current : longest)
55
- : { days: 0, startDate: startDate, endDate: startDate };
56
- // Determine current streak (from today or most recent commit going backward)
95
+ : { days: 0, startDate: start, endDate: start };
96
+ // Determine current streak (from today or most recent business day going backward)
57
97
  const today = new Date();
58
98
  const todayKey = (0, date_fns_1.format)(today, 'yyyy-MM-dd');
59
99
  let currentStreak;
60
- if (commitDates.has(todayKey)) {
61
- // Today has a commit, find streak from today backward
100
+ // For current streak, we check today or the last business day
101
+ const checkToday = isBusinessDay(today) ? today : getPreviousBusinessDay(today);
102
+ const checkTodayKey = (0, date_fns_1.format)(checkToday, 'yyyy-MM-dd');
103
+ if (commitDates.has(checkTodayKey)) {
104
+ // Recent business day has a commit, find streak backward through business days only
62
105
  let streakDays = 0;
63
- let checkDate = today;
106
+ let checkDate = checkToday;
107
+ let streakStartDate = checkToday;
64
108
  while (commitDates.has((0, date_fns_1.format)(checkDate, 'yyyy-MM-dd'))) {
65
109
  streakDays++;
66
- checkDate = (0, date_fns_1.subDays)(checkDate, 1);
67
- if (checkDate < startDate)
110
+ streakStartDate = checkDate;
111
+ checkDate = getPreviousBusinessDay(checkDate);
112
+ if (checkDate < start)
68
113
  break;
69
114
  }
70
115
  currentStreak = {
71
116
  days: streakDays,
72
- startDate: (0, date_fns_1.subDays)(today, streakDays - 1),
73
- endDate: today,
117
+ startDate: streakStartDate,
118
+ endDate: checkToday,
74
119
  isActive: true
75
120
  };
76
121
  }
77
122
  else {
78
- // No commit today, check if yesterday had commit (streak just broke)
79
- const yesterday = (0, date_fns_1.subDays)(today, 1);
80
- const yesterdayKey = (0, date_fns_1.format)(yesterday, 'yyyy-MM-dd');
81
- if (commitDates.has(yesterdayKey)) {
123
+ // No commit on recent business day, check previous business day
124
+ const prevBusinessDay = getPreviousBusinessDay(checkToday);
125
+ const prevKey = (0, date_fns_1.format)(prevBusinessDay, 'yyyy-MM-dd');
126
+ if (commitDates.has(prevKey)) {
82
127
  let streakDays = 0;
83
- let checkDate = yesterday;
128
+ let checkDate = prevBusinessDay;
129
+ let streakStartDate = prevBusinessDay;
84
130
  while (commitDates.has((0, date_fns_1.format)(checkDate, 'yyyy-MM-dd'))) {
85
131
  streakDays++;
86
- checkDate = (0, date_fns_1.subDays)(checkDate, 1);
87
- if (checkDate < startDate)
132
+ streakStartDate = checkDate;
133
+ checkDate = getPreviousBusinessDay(checkDate);
134
+ if (checkDate < start)
88
135
  break;
89
136
  }
90
137
  currentStreak = {
91
138
  days: streakDays,
92
- startDate: (0, date_fns_1.subDays)(yesterday, streakDays - 1),
93
- endDate: yesterday,
139
+ startDate: streakStartDate,
140
+ endDate: prevBusinessDay,
94
141
  isActive: false
95
142
  };
96
143
  }
@@ -110,7 +157,10 @@ function calculateStreaks(commits, startDate, endDate) {
110
157
  totalActiveDays,
111
158
  totalDays,
112
159
  activeDayPercentage,
113
- streaks
160
+ streaks,
161
+ // Add business day specific data
162
+ totalBusinessDays,
163
+ activeBusinessDays
114
164
  };
115
165
  }
116
166
  function getStreakMotivation(days, isActive) {
@@ -110,7 +110,26 @@ function loadTeamFile(filePath) {
110
110
  }
111
111
  }
112
112
  /**
113
- * Analyzes commits for a team of contributors
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
+ * ```
114
133
  */
115
134
  function analyzeTeam(commits, teamMembers, teamName, skipBodyCheck = true) {
116
135
  // Filter commits to team members only
@@ -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);
@@ -3,17 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.analyzeVelocity = analyzeVelocity;
4
4
  exports.formatVelocityInsights = formatVelocityInsights;
5
5
  const date_fns_1 = require("date-fns");
6
- /**
7
- * Analyzes commit velocity over time to show productivity trends
8
- */
9
- function analyzeVelocity(commits, startDate, endDate, rollingWindowWeeks = 4) {
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
+ }
10
26
  if (commits.length === 0) {
11
- return getEmptyVelocityAnalysis();
27
+ return (0, emptyResults_1.getEmptyVelocityAnalysis)();
12
28
  }
13
29
  // Build weekly data points
14
- const timeline = buildWeeklyTimeline(commits, startDate, endDate);
30
+ const timeline = buildWeeklyTimeline(commits, start, end);
15
31
  // Calculate rolling averages
16
- calculateRollingAverages(timeline, rollingWindowWeeks);
32
+ calculateRollingAverages(timeline, windowWeeks);
17
33
  // Detect anomalies
18
34
  const anomalies = detectAnomalies(timeline);
19
35
  // Calculate overall trend
@@ -210,17 +226,6 @@ function findLowestWeek(timeline) {
210
226
  const lowest = nonZeroWeeks.reduce((min, dp) => dp.commits < min.commits ? dp : min, nonZeroWeeks[0]);
211
227
  return { weekStart: lowest.weekStart, commits: lowest.commits };
212
228
  }
213
- function getEmptyVelocityAnalysis() {
214
- return {
215
- timeline: [],
216
- overallTrend: 'stable',
217
- trendPercentage: 0,
218
- averageCommitsPerWeek: 0,
219
- peakWeek: { weekStart: new Date(), commits: 0 },
220
- lowestWeek: { weekStart: new Date(), commits: 0 },
221
- anomalies: [],
222
- };
223
- }
224
229
  /**
225
230
  * Format velocity analysis for display
226
231
  */
@@ -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,