repo-wrapped 0.0.2

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 (51) hide show
  1. package/README.md +94 -0
  2. package/dist/cli.js +24 -0
  3. package/dist/commands/generate.js +95 -0
  4. package/dist/commands/index.js +24 -0
  5. package/dist/constants/chronotypes.js +23 -0
  6. package/dist/constants/colors.js +18 -0
  7. package/dist/constants/index.js +18 -0
  8. package/dist/formatters/index.js +17 -0
  9. package/dist/formatters/timeFormatter.js +29 -0
  10. package/dist/generators/html/scripts/export.js +125 -0
  11. package/dist/generators/html/scripts/knowledge.js +120 -0
  12. package/dist/generators/html/scripts/modal.js +68 -0
  13. package/dist/generators/html/scripts/navigation.js +156 -0
  14. package/dist/generators/html/scripts/tabs.js +18 -0
  15. package/dist/generators/html/scripts/tooltip.js +21 -0
  16. package/dist/generators/html/styles/achievements.css +387 -0
  17. package/dist/generators/html/styles/base.css +818 -0
  18. package/dist/generators/html/styles/components.css +1391 -0
  19. package/dist/generators/html/styles/knowledge.css +221 -0
  20. package/dist/generators/html/templates/achievementsSection.js +156 -0
  21. package/dist/generators/html/templates/commitQualitySection.js +89 -0
  22. package/dist/generators/html/templates/contributionGraph.js +73 -0
  23. package/dist/generators/html/templates/impactSection.js +117 -0
  24. package/dist/generators/html/templates/knowledgeSection.js +226 -0
  25. package/dist/generators/html/templates/streakSection.js +42 -0
  26. package/dist/generators/html/templates/timePatternsSection.js +110 -0
  27. package/dist/generators/html/utils/colorUtils.js +21 -0
  28. package/dist/generators/html/utils/commitMapBuilder.js +24 -0
  29. package/dist/generators/html/utils/dateRangeCalculator.js +57 -0
  30. package/dist/generators/html/utils/developerStatsCalculator.js +29 -0
  31. package/dist/generators/html/utils/scriptLoader.js +16 -0
  32. package/dist/generators/html/utils/styleLoader.js +18 -0
  33. package/dist/generators/html/utils/weekGrouper.js +28 -0
  34. package/dist/index.js +77 -0
  35. package/dist/types/index.js +2 -0
  36. package/dist/utils/achievementDefinitions.js +433 -0
  37. package/dist/utils/achievementEngine.js +170 -0
  38. package/dist/utils/commitQualityAnalyzer.js +368 -0
  39. package/dist/utils/fileHotspotAnalyzer.js +270 -0
  40. package/dist/utils/gitParser.js +125 -0
  41. package/dist/utils/htmlGenerator.js +449 -0
  42. package/dist/utils/impactAnalyzer.js +248 -0
  43. package/dist/utils/knowledgeDistributionAnalyzer.js +374 -0
  44. package/dist/utils/matrixGenerator.js +350 -0
  45. package/dist/utils/slideGenerator.js +313 -0
  46. package/dist/utils/streakCalculator.js +135 -0
  47. package/dist/utils/timePatternAnalyzer.js +305 -0
  48. package/dist/utils/wrappedDisplay.js +115 -0
  49. package/dist/utils/wrappedGenerator.js +377 -0
  50. package/dist/utils/wrappedHtmlGenerator.js +552 -0
  51. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # repo-wrapped
2
+
3
+ A CLI tool for generating Git repository analytics and visualizations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g repo-wrapped
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Generate Visualization
14
+
15
+ ```bash
16
+ # Current directory, current year
17
+ repo-wrapped generate
18
+
19
+ # Specific repository with HTML output
20
+ repo-wrapped generate /path/to/repo --html
21
+
22
+ # Analyze entire repository lifetime
23
+ repo-wrapped generate . --all --html
24
+
25
+ # Deep analysis with file-level knowledge distribution
26
+ repo-wrapped generate . --html --deep-analysis
27
+ ```
28
+
29
+ ### Options
30
+
31
+ | Flag | Description |
32
+ |------|-------------|
33
+ | `-y, --year <year>` | Year to analyze (default: current year) |
34
+ | `-m, --months <months>` | Number of months to show (1-12, default: 12) |
35
+ | `-a, --all` | Analyze entire repository lifetime |
36
+ | `--html` | Generate HTML report and open in browser |
37
+ | `--body-check` | Include commit body in quality scoring |
38
+ | `--deep-analysis` | Enable file-level analysis for knowledge distribution |
39
+
40
+ ### Year in Code Summary
41
+
42
+ ```bash
43
+ repo-wrapped wrapped 2024 . --html
44
+ ```
45
+
46
+ ## HTML Report Sections
47
+
48
+ - **Contribution Graph** - Interactive commit heatmap
49
+ - **Streak Analytics** - Current and longest streaks, active day percentage
50
+ - **Time Patterns** - Hourly/daily distribution, chronotype detection, burnout risk
51
+ - **Commit Quality** - Message quality scoring, conventional commits adherence
52
+ - **File Hotspots** - Most changed files, technical debt indicators
53
+ - **Achievements** - Progress-based badges and milestones
54
+ - **Impact Analysis** - Lines changed, churn metrics
55
+ - **Knowledge Distribution** - Bus factor risk, ownership analysis
56
+
57
+ ## Development
58
+
59
+ ```bash
60
+ npm install
61
+ npm run build
62
+ npm link
63
+ ```
64
+
65
+ ### Project Structure
66
+
67
+ ```
68
+ src/
69
+ ├── index.ts # CLI entry point
70
+ ├── commands/
71
+ │ └── generate.ts # Generate command
72
+ ├── types/
73
+ │ └── index.ts # TypeScript interfaces
74
+ ├── generators/
75
+ │ └── html/
76
+ │ ├── scripts/ # Client-side JS
77
+ │ ├── styles/ # CSS files
78
+ │ ├── templates/ # HTML section builders
79
+ │ └── utils/ # Build utilities
80
+ └── utils/
81
+ ├── gitParser.ts # Git log parsing
82
+ ├── htmlGenerator.ts # HTML report assembly
83
+ ├── streakCalculator.ts # Streak analytics
84
+ ├── timePatternAnalyzer.ts
85
+ ├── commitQualityAnalyzer.ts
86
+ ├── fileHotspotAnalyzer.ts
87
+ ├── achievementEngine.ts
88
+ ├── impactAnalyzer.ts
89
+ └── knowledgeDistributionAnalyzer.ts
90
+ ```
91
+
92
+ ## License
93
+
94
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const gitParser_1 = require("./utils/gitParser");
5
+ const matrixGenerator_1 = require("./utils/matrixGenerator");
6
+ const program = new commander_1.Command();
7
+ program
8
+ .command('generate-matrix <repoPath>')
9
+ .description('Generate a GitHub-like commit matrix for the specified repository')
10
+ .option('-y, --year <year>', 'Year to generate matrix for', String(new Date().getFullYear()))
11
+ .option('-m, --month <month>', 'Month to generate matrix for (1-12)', String(new Date().getMonth() + 1))
12
+ .action(async (repoPath, options) => {
13
+ try {
14
+ const commits = (0, gitParser_1.parseGitCommits)(repoPath);
15
+ const year = parseInt(options.year);
16
+ const month = parseInt(options.month);
17
+ const matrix = (0, matrixGenerator_1.generateCommitMatrix)(commits, year, month, repoPath);
18
+ console.log(matrix);
19
+ }
20
+ catch (error) {
21
+ console.error('Error generating commit matrix:', error);
22
+ }
23
+ });
24
+ program.parse(process.argv);
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.generateMatrix = void 0;
30
+ const chalk_1 = __importDefault(require("chalk"));
31
+ const child_process_1 = require("child_process");
32
+ const fs = __importStar(require("fs"));
33
+ const ora_1 = __importDefault(require("ora"));
34
+ const os = __importStar(require("os"));
35
+ const path = __importStar(require("path"));
36
+ const gitParser_1 = require("../utils/gitParser");
37
+ const htmlGenerator_1 = require("../utils/htmlGenerator");
38
+ const matrixGenerator_1 = require("../utils/matrixGenerator");
39
+ async function generateMatrix(repoPath, options) {
40
+ const spinner = (0, ora_1.default)('Analyzing git repository...').start();
41
+ try {
42
+ const absolutePath = path.resolve(repoPath);
43
+ const year = parseInt(options.year);
44
+ const monthsToShow = parseInt(options.months);
45
+ if (!options.all && (monthsToShow < 1 || monthsToShow > 12)) {
46
+ spinner.fail(chalk_1.default.red('Invalid months value. Please provide a value between 1 and 12.'));
47
+ process.exit(1);
48
+ }
49
+ spinner.text = 'Fetching commit history...';
50
+ const commits = (0, gitParser_1.parseGitCommits)(absolutePath);
51
+ if (commits.length === 0) {
52
+ spinner.warn(chalk_1.default.yellow('No commits found in the repository.'));
53
+ return;
54
+ }
55
+ spinner.text = 'Generating visualization...';
56
+ if (options.html) {
57
+ // Generate HTML output
58
+ const repoName = (0, gitParser_1.getRepositoryName)(absolutePath);
59
+ const repoUrl = (0, gitParser_1.getRepositoryUrl)(absolutePath);
60
+ const skipBodyCheck = !options.bodyCheck;
61
+ const deepAnalysis = options.deepAnalysis ?? false;
62
+ const html = (0, htmlGenerator_1.generateHTML)(commits, year, monthsToShow, absolutePath, repoName, repoUrl, skipBodyCheck, options.all, deepAnalysis);
63
+ const outputPath = path.join(os.tmpdir(), `git-wrapped-${Date.now()}.html`);
64
+ fs.writeFileSync(outputPath, html);
65
+ spinner.succeed(chalk_1.default.green('HTML file generated successfully!'));
66
+ console.log();
67
+ console.log(chalk_1.default.cyan('Opening in browser...'));
68
+ console.log(chalk_1.default.dim(`File: ${outputPath}`));
69
+ // Open in default browser
70
+ const command = process.platform === 'win32'
71
+ ? `start "" "${outputPath}"`
72
+ : process.platform === 'darwin'
73
+ ? `open "${outputPath}"`
74
+ : `xdg-open "${outputPath}"`;
75
+ (0, child_process_1.exec)(command);
76
+ }
77
+ else {
78
+ // Generate terminal output
79
+ const repoName = (0, gitParser_1.getRepositoryName)(absolutePath);
80
+ const repoUrl = (0, gitParser_1.getRepositoryUrl)(absolutePath);
81
+ const matrix = (0, matrixGenerator_1.generateCommitMatrix)(commits, year, monthsToShow, absolutePath, repoName, repoUrl);
82
+ spinner.succeed(chalk_1.default.green('✓ Analysis complete!'));
83
+ console.log(matrix);
84
+ console.log();
85
+ console.log(chalk_1.default.cyan('💡 Tip:'), chalk_1.default.dim('Run with'), chalk_1.default.white('--html'), chalk_1.default.dim('to see interactive details and per-developer analytics'));
86
+ console.log(chalk_1.default.dim(' Example:'), chalk_1.default.white(`git-wrapped generate ${repoPath} --html`));
87
+ }
88
+ }
89
+ catch (error) {
90
+ spinner.fail(chalk_1.default.red('Failed to generate visualization'));
91
+ console.error(chalk_1.default.red('\nError:'), error.message);
92
+ process.exit(1);
93
+ }
94
+ }
95
+ exports.generateMatrix = generateMatrix;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const gitParser_1 = require("../utils/gitParser");
5
+ const matrixGenerator_1 = require("../utils/matrixGenerator");
6
+ const program = new commander_1.Command();
7
+ program
8
+ .command('generate-matrix <repoPath>')
9
+ .description('Generate a GitHub-like commit visualization for the specified repository')
10
+ .option('-y, --year <year>', 'Year to generate visualization for', String(new Date().getFullYear()))
11
+ .option('-m, --month <month>', 'Month to generate visualization for (1-12)', String(new Date().getMonth() + 1))
12
+ .action(async (repoPath, options) => {
13
+ try {
14
+ const commits = (0, gitParser_1.parseGitCommits)(repoPath);
15
+ const year = parseInt(options.year);
16
+ const month = parseInt(options.month);
17
+ const matrix = (0, matrixGenerator_1.generateCommitMatrix)(commits, year, month, repoPath);
18
+ console.log(matrix);
19
+ }
20
+ catch (error) {
21
+ console.error('Error generating commit visualization:', error);
22
+ }
23
+ });
24
+ exports.default = program;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ANALYSIS_CHRONOTYPE_EMOJIS = exports.WRAPPED_CHRONOTYPE_LABELS = exports.WRAPPED_CHRONOTYPE_EMOJIS = void 0;
4
+ // Chronotype definitions for YearInCodeSummary (morning/day/evening/night)
5
+ exports.WRAPPED_CHRONOTYPE_EMOJIS = {
6
+ morning: '☀️',
7
+ day: '📅',
8
+ evening: '🌆',
9
+ night: '🌙'
10
+ };
11
+ exports.WRAPPED_CHRONOTYPE_LABELS = {
12
+ morning: 'Morning Person',
13
+ day: 'Daytime Coder',
14
+ evening: 'Evening Developer',
15
+ night: 'Night Owl'
16
+ };
17
+ // Chronotype definitions for TimePattern analysis (early-bird/night-owl/balanced/vampire)
18
+ exports.ANALYSIS_CHRONOTYPE_EMOJIS = {
19
+ 'early-bird': '🐦',
20
+ 'night-owl': '🦉',
21
+ 'vampire': '🧛',
22
+ 'balanced': '⚖️'
23
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONTRIBUTION_LEVELS = exports.GITHUB_COLORS = void 0;
4
+ // GitHub contribution colors
5
+ exports.GITHUB_COLORS = {
6
+ EMPTY: 'rgba(235, 237, 240, 0.1)',
7
+ LEVEL_1: '#9be9a8',
8
+ LEVEL_2: '#40c463',
9
+ LEVEL_3: '#30a14e',
10
+ LEVEL_4: '#216e39',
11
+ BORDER: '#21262d'
12
+ };
13
+ // Contribution thresholds
14
+ exports.CONTRIBUTION_LEVELS = {
15
+ LEVEL_1: 3,
16
+ LEVEL_2: 6,
17
+ LEVEL_3: 9
18
+ };
@@ -0,0 +1,18 @@
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
+ __exportStar(require("./colors"), exports);
18
+ __exportStar(require("./chronotypes"), exports);
@@ -0,0 +1,17 @@
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
+ __exportStar(require("./timeFormatter"), exports);
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatHourShort = exports.formatHour = void 0;
4
+ /**
5
+ * Formats an hour (0-23) to a human-readable AM/PM string
6
+ */
7
+ function formatHour(hour) {
8
+ if (hour === 0)
9
+ return '12:00 AM';
10
+ if (hour < 12)
11
+ return `${hour}:00 AM`;
12
+ if (hour === 12)
13
+ return '12:00 PM';
14
+ return `${hour - 12}:00 PM`;
15
+ }
16
+ exports.formatHour = formatHour;
17
+ /**
18
+ * Formats an hour (0-23) to a short AM/PM string (without :00)
19
+ */
20
+ function formatHourShort(hour) {
21
+ if (hour === 0)
22
+ return '12 AM';
23
+ if (hour < 12)
24
+ return `${hour} AM`;
25
+ if (hour === 12)
26
+ return '12 PM';
27
+ return `${hour - 12} PM`;
28
+ }
29
+ exports.formatHourShort = formatHourShort;
@@ -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,120 @@
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
+ })();
@@ -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
+ });