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,71 @@
1
+ "use strict";
2
+ /**
3
+ * Section wrapper utilities for dashboard sections.
4
+ *
5
+ * @module generators/html/utils/sectionWrapper
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.wrapInSection = wrapInSection;
9
+ exports.wrapInStaticSection = wrapInStaticSection;
10
+ exports.createTabButton = createTabButton;
11
+ exports.createTabContent = createTabContent;
12
+ /**
13
+ * Wrap content in a collapsible dashboard section.
14
+ *
15
+ * @param id - Unique section ID for targeting
16
+ * @param title - Section title displayed in header
17
+ * @param content - HTML content to wrap
18
+ * @param icon - Optional icon emoji to display before title
19
+ * @returns HTML string for the wrapped section
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const html = wrapInSection('velocity', 'Velocity Timeline', velocityContent, '📈');
24
+ * ```
25
+ */
26
+ function wrapInSection(id, title, content, icon = '') {
27
+ return `
28
+ <section class="dashboard-section" id="${id}">
29
+ <header class="section-header" role="button" aria-expanded="true">
30
+ <h2>${icon ? icon + ' ' : ''}${title}</h2>
31
+ <span class="collapse-icon">▼</span>
32
+ </header>
33
+ <div class="section-content">
34
+ ${content}
35
+ </div>
36
+ </section>
37
+ `;
38
+ }
39
+ /**
40
+ * Wrap content in a non-collapsible section (for embedded content).
41
+ */
42
+ function wrapInStaticSection(id, title, content, icon = '') {
43
+ return `
44
+ <section class="dashboard-section" id="${id}">
45
+ <header class="section-header">
46
+ <h2>${icon ? icon + ' ' : ''}${title}</h2>
47
+ </header>
48
+ <div class="section-content">
49
+ ${content}
50
+ </div>
51
+ </section>
52
+ `;
53
+ }
54
+ /**
55
+ * Create a tab button for the tab navigation.
56
+ */
57
+ function createTabButton(id, label, icon = '', isActive = false) {
58
+ const activeClass = isActive ? ' active' : '';
59
+ return `<button class="tab-button${activeClass}" data-tab="${id}">${icon ? icon + ' ' : ''}${label}</button>`;
60
+ }
61
+ /**
62
+ * Create a tab content container.
63
+ */
64
+ function createTabContent(id, content, isActive = false) {
65
+ const activeClass = isActive ? ' active' : '';
66
+ return `
67
+ <div class="tab-content${activeClass}" id="${id}-content">
68
+ ${content}
69
+ </div>
70
+ `;
71
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /**
3
+ * Context builder for HTML generation.
4
+ *
5
+ * @module html/shared/contextBuilder
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.buildHtmlContext = buildHtmlContext;
9
+ const dateRangeCalculator_1 = require("./dateRangeCalculator");
10
+ const developerStatsCalculator_1 = require("./developerStatsCalculator");
11
+ /**
12
+ * Build the HTML generation context from options.
13
+ *
14
+ * @param options - Generation options
15
+ * @returns Fully populated context for HTML generation
16
+ */
17
+ function buildHtmlContext(options) {
18
+ const { commits, repoPath, repoName, repoUrl, year = new Date().getFullYear(), monthsToShow = 12, allTime = false, } = options;
19
+ // Get current git user
20
+ const { getCurrentGitUser } = require('../../utils/gitParser');
21
+ const currentUser = getCurrentGitUser(repoPath);
22
+ // Calculate date ranges
23
+ const dateRanges = allTime
24
+ ? (0, dateRangeCalculator_1.calculateDateRangesFromCommits)(commits)
25
+ : (0, dateRangeCalculator_1.calculateDateRanges)(year, monthsToShow);
26
+ const { startDate, endDate, weekStartDate, weekEndDate } = dateRanges;
27
+ // Filter commits to date range
28
+ const filteredCommits = commits.filter(commit => {
29
+ const commitDate = new Date(commit.date);
30
+ return commitDate >= startDate && commitDate <= endDate;
31
+ });
32
+ // Filter personal commits
33
+ const personalCommits = filteredCommits.filter(c => c.author === currentUser);
34
+ // Calculate developer stats
35
+ const developerStats = (0, developerStatsCalculator_1.calculateDeveloperStats)(filteredCommits, startDate, endDate);
36
+ return {
37
+ options,
38
+ repoInfo: {
39
+ name: repoName,
40
+ url: repoUrl,
41
+ path: repoPath,
42
+ },
43
+ currentUser,
44
+ dateRange: {
45
+ start: startDate,
46
+ end: endDate,
47
+ weekStart: weekStartDate,
48
+ weekEnd: weekEndDate,
49
+ },
50
+ filteredCommits,
51
+ personalCommits,
52
+ developerStats,
53
+ };
54
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateDateRanges = calculateDateRanges;
4
+ exports.calculateDateRangesFromCommits = calculateDateRangesFromCommits;
5
+ const date_fns_1 = require("date-fns");
6
+ /**
7
+ * Calculates date ranges for the visualization
8
+ */
9
+ function calculateDateRanges(year, monthsToShow) {
10
+ const now = new Date();
11
+ const isCurrentYear = year === now.getFullYear();
12
+ // Calculate the end date (today if current year, or end of December for past years)
13
+ const endDate = isCurrentYear ? now : new Date(year, 11, 31);
14
+ // Calculate start date (N months back from end date)
15
+ const startDate = (0, date_fns_1.startOfMonth)((0, date_fns_1.subMonths)(endDate, monthsToShow - 1));
16
+ // Get the full week range (start on Sunday, end on Saturday)
17
+ const weekStartDate = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 0 });
18
+ const weekEndDate = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 0 });
19
+ return {
20
+ startDate,
21
+ endDate,
22
+ weekStartDate,
23
+ weekEndDate
24
+ };
25
+ }
26
+ /**
27
+ * Calculates date ranges from the entire commit history (first to last commit)
28
+ */
29
+ function calculateDateRangesFromCommits(commits) {
30
+ if (commits.length === 0) {
31
+ const now = new Date();
32
+ return {
33
+ startDate: now,
34
+ endDate: now,
35
+ weekStartDate: (0, date_fns_1.startOfWeek)(now, { weekStartsOn: 0 }),
36
+ weekEndDate: (0, date_fns_1.endOfWeek)(now, { weekStartsOn: 0 })
37
+ };
38
+ }
39
+ // Sort commits by date to find first and last
40
+ const sortedCommits = [...commits].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
41
+ const firstCommitDate = new Date(sortedCommits[0].date);
42
+ const lastCommitDate = new Date(sortedCommits[sortedCommits.length - 1].date);
43
+ // Use start of month for first commit and today/last commit date for end
44
+ const now = new Date();
45
+ const startDate = (0, date_fns_1.startOfMonth)(firstCommitDate);
46
+ const endDate = lastCommitDate > now ? lastCommitDate : now;
47
+ // Get the full week range (start on Sunday, end on Saturday)
48
+ const weekStartDate = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 0 });
49
+ const weekEndDate = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 0 });
50
+ return {
51
+ startDate,
52
+ endDate,
53
+ weekStartDate,
54
+ weekEndDate
55
+ };
56
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateDeveloperStats = calculateDeveloperStats;
4
+ /**
5
+ * Calculates statistics for each developer from commits within a date range
6
+ */
7
+ function calculateDeveloperStats(commits, startDate, endDate) {
8
+ const developerStats = new Map();
9
+ commits.forEach(commit => {
10
+ const commitDate = new Date(commit.date);
11
+ if (commitDate >= startDate && commitDate <= endDate) {
12
+ if (!developerStats.has(commit.author)) {
13
+ developerStats.set(commit.author, {
14
+ commits: 0,
15
+ firstCommit: commitDate,
16
+ lastCommit: commitDate
17
+ });
18
+ }
19
+ const stats = developerStats.get(commit.author);
20
+ stats.commits++;
21
+ if (commitDate < stats.firstCommit)
22
+ stats.firstCommit = commitDate;
23
+ if (commitDate > stats.lastCommit)
24
+ stats.lastCommit = commitDate;
25
+ }
26
+ });
27
+ return developerStats;
28
+ }
@@ -0,0 +1,39 @@
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.buildHtmlContext = exports.calculateDeveloperStats = exports.calculateDateRangesFromCommits = exports.calculateDateRanges = exports.getDayLabels = exports.groupDaysIntoWeeks = exports.createCommitMaps = exports.loadScripts = exports.loadStyles = void 0;
18
+ /**
19
+ * HTML Shared Utilities
20
+ * @module html/shared
21
+ */
22
+ __exportStar(require("./components"), exports);
23
+ var styleLoader_1 = require("./styleLoader");
24
+ Object.defineProperty(exports, "loadStyles", { enumerable: true, get: function () { return styleLoader_1.loadStyles; } });
25
+ var scriptLoader_1 = require("./scriptLoader");
26
+ Object.defineProperty(exports, "loadScripts", { enumerable: true, get: function () { return scriptLoader_1.loadScripts; } });
27
+ var commitMapBuilder_1 = require("./commitMapBuilder");
28
+ Object.defineProperty(exports, "createCommitMaps", { enumerable: true, get: function () { return commitMapBuilder_1.createCommitMaps; } });
29
+ var weekGrouper_1 = require("./weekGrouper");
30
+ Object.defineProperty(exports, "groupDaysIntoWeeks", { enumerable: true, get: function () { return weekGrouper_1.groupDaysIntoWeeks; } });
31
+ Object.defineProperty(exports, "getDayLabels", { enumerable: true, get: function () { return weekGrouper_1.getDayLabels; } });
32
+ var dateRangeCalculator_1 = require("./dateRangeCalculator");
33
+ Object.defineProperty(exports, "calculateDateRanges", { enumerable: true, get: function () { return dateRangeCalculator_1.calculateDateRanges; } });
34
+ Object.defineProperty(exports, "calculateDateRangesFromCommits", { enumerable: true, get: function () { return dateRangeCalculator_1.calculateDateRangesFromCommits; } });
35
+ var developerStatsCalculator_1 = require("./developerStatsCalculator");
36
+ Object.defineProperty(exports, "calculateDeveloperStats", { enumerable: true, get: function () { return developerStatsCalculator_1.calculateDeveloperStats; } });
37
+ var contextBuilder_1 = require("./contextBuilder");
38
+ Object.defineProperty(exports, "buildHtmlContext", { enumerable: true, get: function () { return contextBuilder_1.buildHtmlContext; } });
39
+ __exportStar(require("./colorUtils"), exports);
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadScripts = loadScripts;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ function loadScripts() {
7
+ const scriptsDir = (0, path_1.join)(__dirname, 'scripts');
8
+ const tabs = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'tabs.js'), 'utf-8');
9
+ const tooltip = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'tooltip.js'), 'utf-8');
10
+ const modal = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'modal.js'), 'utf-8');
11
+ const navigation = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'navigation.js'), 'utf-8');
12
+ const exportScript = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'export.js'), 'utf-8');
13
+ const knowledge = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'knowledge.js'), 'utf-8');
14
+ return `${tabs}\n\n${tooltip}\n\n${modal}\n\n${navigation}\n\n${exportScript}\n\n${knowledge}`;
15
+ }
@@ -0,0 +1,125 @@
1
+ // Export functionality for Git Wrapped reports
2
+
3
+ function initExport() {
4
+ document.querySelectorAll('[data-export]').forEach(btn => {
5
+ btn.addEventListener('click', (e) => {
6
+ const format = e.currentTarget.dataset.export;
7
+ exportData(format);
8
+ });
9
+ });
10
+ }
11
+
12
+ function exportData(format) {
13
+ const data = window.__GITWRAPPED_DATA__;
14
+
15
+ if (!data) {
16
+ console.warn('No export data available');
17
+ return;
18
+ }
19
+
20
+ const repoName = (data.repository || 'report').replace(/[^a-z0-9]/gi, '-').toLowerCase();
21
+ const timestamp = new Date().toISOString().slice(0, 10);
22
+
23
+ if (format === 'json') {
24
+ downloadFile(
25
+ JSON.stringify(data, null, 2),
26
+ `git-wrapped-${repoName}-${timestamp}.json`,
27
+ 'application/json'
28
+ );
29
+ } else if (format === 'csv') {
30
+ const csv = convertToCSV(data);
31
+ downloadFile(csv, `git-wrapped-${repoName}-${timestamp}.csv`, 'text/csv');
32
+ }
33
+ }
34
+
35
+ function convertToCSV(data) {
36
+ const sections = [];
37
+
38
+ // Section 1: Summary Metrics
39
+ sections.push('# Summary Metrics');
40
+ sections.push('Metric,Value');
41
+ sections.push(`Repository,${escapeCSV(data.repository || '')}`);
42
+ sections.push(`Period Start,${data.period?.start || ''}`);
43
+ sections.push(`Period End,${data.period?.end || ''}`);
44
+ sections.push(`Author Filter,${escapeCSV(data.author || 'All')}`);
45
+ sections.push(`Total Commits,${data.summary?.totalCommits || 0}`);
46
+ sections.push(`Total Contributors,${data.summary?.totalContributors || 0}`);
47
+ sections.push(`Active Days,${data.summary?.activeDays || 0}`);
48
+
49
+ // Section 2: Streak Data
50
+ if (data.streaks) {
51
+ sections.push('');
52
+ sections.push('# Streak Analysis');
53
+ sections.push('Metric,Value');
54
+ sections.push(`Current Streak (days),${data.streaks.currentStreak || 0}`);
55
+ sections.push(`Longest Streak (days),${data.streaks.longestStreak || 0}`);
56
+ sections.push(`Total Active Days,${data.streaks.totalActiveDays || 0}`);
57
+ sections.push(`Active Day Percentage,${(data.streaks.activeDayPercentage || 0).toFixed(1)}%`);
58
+ }
59
+
60
+ // Section 3: Quality Metrics
61
+ if (data.quality) {
62
+ sections.push('');
63
+ sections.push('# Commit Quality');
64
+ sections.push('Metric,Value');
65
+ sections.push(`Overall Score,${(data.quality.overallScore || 0).toFixed(1)}`);
66
+ sections.push(`Conventional Commits %,${(data.quality.conventionalAdherence || 0).toFixed(1)}%`);
67
+ sections.push(`Avg Subject Length,${(data.quality.avgSubjectLength || 0).toFixed(0)} chars`);
68
+ }
69
+
70
+ // Section 4: Time Patterns
71
+ if (data.timePattern) {
72
+ sections.push('');
73
+ sections.push('# Time Patterns');
74
+ sections.push('Metric,Value');
75
+ sections.push(`Chronotype,${data.timePattern.chronotype || 'Unknown'}`);
76
+ sections.push(`Peak Hour,${data.timePattern.peakHour || 0}:00`);
77
+ sections.push(`Consistency Score,${(data.timePattern.consistency || 0).toFixed(1)}`);
78
+ }
79
+
80
+ // Section 5: Developer Stats
81
+ if (data.developerStats && data.developerStats.length > 0) {
82
+ sections.push('');
83
+ sections.push('# Developer Statistics');
84
+ sections.push('Developer,Commits,First Commit,Last Commit');
85
+ data.developerStats.forEach(dev => {
86
+ sections.push(`${escapeCSV(dev.name)},${dev.commits},${dev.firstCommit || ''},${dev.lastCommit || ''}`);
87
+ });
88
+ }
89
+
90
+ // Section 6: Recent Commits (last 100)
91
+ if (data.commits && data.commits.length > 0) {
92
+ sections.push('');
93
+ sections.push('# Commits (last 100)');
94
+ sections.push('Date,Author,Message,Hash');
95
+ data.commits.slice(0, 100).forEach(c => {
96
+ sections.push(`${c.date},${escapeCSV(c.author)},${escapeCSV(c.message)},${c.hash || ''}`);
97
+ });
98
+ }
99
+
100
+ return sections.join('\n');
101
+ }
102
+
103
+ function escapeCSV(str) {
104
+ if (str == null) return '';
105
+ const s = String(str);
106
+ if (s.includes(',') || s.includes('"') || s.includes('\n') || s.includes('\r')) {
107
+ return '"' + s.replace(/"/g, '""') + '"';
108
+ }
109
+ return s;
110
+ }
111
+
112
+ function downloadFile(content, filename, mimeType) {
113
+ const blob = new Blob([content], { type: mimeType });
114
+ const url = URL.createObjectURL(blob);
115
+ const a = document.createElement('a');
116
+ a.href = url;
117
+ a.download = filename;
118
+ document.body.appendChild(a);
119
+ a.click();
120
+ document.body.removeChild(a);
121
+ URL.revokeObjectURL(url);
122
+ }
123
+
124
+ // Initialize when DOM is ready
125
+ document.addEventListener('DOMContentLoaded', initExport);
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Knowledge Distribution - Expandable Row Interactivity
3
+ * Handles accordion-style expand/collapse for directory deep-dive analysis
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Initialize expandable rows in the knowledge table
11
+ */
12
+ function initExpandableRows() {
13
+ const expandableRows = document.querySelectorAll('.knowledge-table .expandable-row');
14
+
15
+ expandableRows.forEach(row => {
16
+ row.addEventListener('click', handleRowExpand);
17
+ row.addEventListener('keydown', handleRowKeydown);
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Handle click on expandable row
23
+ */
24
+ function handleRowExpand(event) {
25
+ const row = event.currentTarget;
26
+ const rowId = row.getAttribute('data-row-id');
27
+ if (!rowId) return;
28
+
29
+ const contentRow = document.getElementById(`${rowId}-content`);
30
+ if (!contentRow) return;
31
+
32
+ const isExpanded = row.getAttribute('aria-expanded') === 'true';
33
+
34
+ if (isExpanded) {
35
+ collapseRow(row, contentRow);
36
+ } else {
37
+ expandRow(row, contentRow);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Handle keyboard navigation on expandable rows
43
+ */
44
+ function handleRowKeydown(event) {
45
+ if (event.key === 'Enter' || event.key === ' ') {
46
+ event.preventDefault();
47
+ handleRowExpand(event);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Expand a directory row to show details
53
+ */
54
+ function expandRow(row, contentRow) {
55
+ row.setAttribute('aria-expanded', 'true');
56
+ row.classList.add('expanded');
57
+ contentRow.classList.remove('hidden');
58
+
59
+ // Animate expand icon
60
+ const icon = row.querySelector('.expand-icon');
61
+ if (icon) {
62
+ icon.textContent = '▼';
63
+ }
64
+
65
+ // Animate the content panel
66
+ const panel = contentRow.querySelector('.expansion-panel');
67
+ if (panel) {
68
+ panel.style.maxHeight = panel.scrollHeight + 'px';
69
+ panel.style.opacity = '1';
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Collapse a directory row to hide details
75
+ */
76
+ function collapseRow(row, contentRow) {
77
+ row.setAttribute('aria-expanded', 'false');
78
+ row.classList.remove('expanded');
79
+ contentRow.classList.add('hidden');
80
+
81
+ // Animate expand icon
82
+ const icon = row.querySelector('.expand-icon');
83
+ if (icon) {
84
+ icon.textContent = '▶';
85
+ }
86
+
87
+ // Animate the content panel
88
+ const panel = contentRow.querySelector('.expansion-panel');
89
+ if (panel) {
90
+ panel.style.maxHeight = '0';
91
+ panel.style.opacity = '0';
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Collapse all expanded rows
97
+ */
98
+ function collapseAllRows() {
99
+ const expandedRows = document.querySelectorAll('.knowledge-table .expandable-row.expanded');
100
+ expandedRows.forEach(row => {
101
+ const rowId = row.getAttribute('data-row-id');
102
+ const contentRow = document.getElementById(`${rowId}-content`);
103
+ if (contentRow) {
104
+ collapseRow(row, contentRow);
105
+ }
106
+ });
107
+ }
108
+
109
+ // Initialize when DOM is ready
110
+ if (document.readyState === 'loading') {
111
+ document.addEventListener('DOMContentLoaded', initExpandableRows);
112
+ } else {
113
+ initExpandableRows();
114
+ }
115
+
116
+ // Expose function for external use (e.g., collapse all when switching tabs)
117
+ window.knowledgeModule = {
118
+ collapseAllRows: collapseAllRows
119
+ };
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
+ }
@@ -0,0 +1,68 @@
1
+ // Modal functionality for commit details
2
+ const modal = document.getElementById('modal');
3
+ const modalTitle = document.getElementById('modal-title');
4
+ const modalClose = document.getElementById('modal-close');
5
+ const commitList = document.getElementById('commit-list');
6
+
7
+ // Helper function to escape HTML
8
+ function escapeHtml(text) {
9
+ const div = document.createElement('div');
10
+ div.textContent = text;
11
+ return div.innerHTML;
12
+ }
13
+
14
+ // Modal click handlers
15
+ const clickableDays = document.querySelectorAll('.day.clickable');
16
+ clickableDays.forEach(day => {
17
+ day.addEventListener('click', () => {
18
+ const details = day.getAttribute('data-details');
19
+ const dateStr = day.getAttribute('data-date');
20
+
21
+ if (details && dateStr) {
22
+ try {
23
+ const commits = JSON.parse(details);
24
+ const date = new Date(dateStr);
25
+ const formattedDate = date.toLocaleDateString('en-US', {
26
+ weekday: 'long',
27
+ year: 'numeric',
28
+ month: 'long',
29
+ day: 'numeric'
30
+ });
31
+
32
+ modalTitle.textContent = `${commits.length} contribution${commits.length !== 1 ? 's' : ''} on ${formattedDate}`;
33
+
34
+ // Build commit list
35
+ commitList.innerHTML = commits.map(commit => `
36
+ <div class="commit-item">
37
+ <div class="commit-message">${escapeHtml(commit.message)}</div>
38
+ <div class="commit-meta">
39
+ <span>by <strong>${escapeHtml(commit.author)}</strong></span>
40
+ <span class="commit-sha">${commit.hash.substring(0, 7)}</span>
41
+ </div>
42
+ </div>
43
+ `).join('');
44
+
45
+ modal.classList.add('active');
46
+ } catch (e) {
47
+ console.error('Failed to parse commit details:', e);
48
+ }
49
+ }
50
+ });
51
+ });
52
+
53
+ // Close modal handlers
54
+ modalClose.addEventListener('click', () => {
55
+ modal.classList.remove('active');
56
+ });
57
+
58
+ modal.addEventListener('click', (e) => {
59
+ if (e.target === modal) {
60
+ modal.classList.remove('active');
61
+ }
62
+ });
63
+
64
+ document.addEventListener('keydown', (e) => {
65
+ if (e.key === 'Escape' && modal.classList.contains('active')) {
66
+ modal.classList.remove('active');
67
+ }
68
+ });