repo-wrapped 0.0.6 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/agents/complete.agent.md +257 -0
- package/.github/agents/feature-scaffold.agent.md +248 -0
- package/.github/agents/jsdoc.agent.md +243 -0
- package/.github/agents/plan.agent.md +202 -0
- package/.github/agents/spec-writer.agent.md +169 -0
- package/.github/agents/test-writer.agent.md +169 -0
- package/.stylelintrc.json +27 -0
- package/README.md +94 -94
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +446 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +446 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +7039 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/commands/generate.js +262 -5
- package/dist/config/defaults.js +158 -0
- package/dist/config/index.js +10 -0
- package/dist/features/achievements/data/achievements.json +284 -0
- package/dist/features/achievements/engine.js +140 -0
- package/dist/features/achievements/evaluators.js +246 -0
- package/dist/features/achievements/helpers.js +58 -0
- package/dist/features/achievements/index.js +57 -0
- package/dist/features/achievements/loader.js +88 -0
- package/dist/features/achievements/template.js +155 -0
- package/dist/features/achievements/types.js +7 -0
- package/dist/features/commit-quality/analyzer.js +378 -0
- package/dist/features/commit-quality/analyzer.test.js +484 -0
- package/dist/features/commit-quality/index.js +28 -0
- package/dist/features/commit-quality/template.js +114 -0
- package/dist/features/commit-quality/types.js +2 -0
- package/dist/features/comparison/analyzer.js +222 -0
- package/dist/features/comparison/index.js +28 -0
- package/dist/features/comparison/template.js +119 -0
- package/dist/features/comparison/types.js +2 -0
- package/dist/features/contribution-graph/index.js +9 -0
- package/dist/features/contribution-graph/template.js +89 -0
- package/dist/features/events/index.js +31 -0
- package/dist/features/events/parser.js +253 -0
- package/dist/features/events/template.js +113 -0
- package/dist/features/events/types.js +2 -0
- package/dist/features/executive-summary/generator.js +275 -0
- package/dist/features/executive-summary/index.js +27 -0
- package/dist/features/executive-summary/template.js +80 -0
- package/dist/features/executive-summary/types.js +2 -0
- package/dist/features/gaps/analyzer.js +298 -0
- package/dist/features/gaps/analyzer.test.js +517 -0
- package/dist/features/gaps/index.js +27 -0
- package/dist/features/gaps/template.js +190 -0
- package/dist/features/gaps/types.js +2 -0
- package/dist/features/impact/analyzer.js +248 -0
- package/dist/features/impact/index.js +26 -0
- package/dist/features/impact/template.js +118 -0
- package/dist/features/impact/types.js +2 -0
- package/dist/features/index.js +40 -0
- package/dist/features/knowledge/analyzer.js +385 -0
- package/dist/features/knowledge/index.js +26 -0
- package/dist/features/knowledge/template.js +239 -0
- package/dist/features/knowledge/types.js +2 -0
- package/dist/features/streaks/calculator.js +184 -0
- package/dist/features/streaks/calculator.test.js +366 -0
- package/dist/features/streaks/index.js +36 -0
- package/dist/features/streaks/template.js +41 -0
- package/dist/features/streaks/types.js +9 -0
- package/dist/features/team/analyzer.js +316 -0
- package/dist/features/team/index.js +30 -0
- package/dist/features/team/template.js +146 -0
- package/dist/features/team/types.js +2 -0
- package/dist/features/time-patterns/analyzer.js +319 -0
- package/dist/features/time-patterns/analyzer.test.js +278 -0
- package/dist/features/time-patterns/index.js +37 -0
- package/dist/features/time-patterns/template.js +109 -0
- package/dist/features/time-patterns/types.js +9 -0
- package/dist/features/velocity/analyzer.js +257 -0
- package/dist/features/velocity/analyzer.test.js +383 -0
- package/dist/features/velocity/index.js +27 -0
- package/dist/features/velocity/template.js +189 -0
- package/dist/features/velocity/types.js +2 -0
- package/dist/generators/html/scripts/knowledge.js +17 -0
- package/dist/generators/html/styles/base.css +10 -6
- package/dist/generators/html/styles/components.css +121 -1
- package/dist/generators/html/styles/knowledge.css +21 -0
- package/dist/generators/html/styles/leaddev.css +1335 -0
- package/dist/generators/html/styles/strategic-insights.css +1337 -0
- package/dist/generators/html/templates/commitQualitySection.js +28 -2
- package/dist/generators/html/templates/comparisonSection.js +119 -0
- package/dist/generators/html/templates/eventsSection.js +113 -0
- package/dist/generators/html/templates/executiveSummarySection.js +80 -0
- package/dist/generators/html/templates/gapSection.js +190 -0
- package/dist/generators/html/templates/impactSection.js +8 -6
- package/dist/generators/html/templates/knowledgeSection.js +16 -2
- package/dist/generators/html/templates/teamSection.js +146 -0
- package/dist/generators/html/templates/velocitySection.js +189 -0
- package/dist/generators/html/types.js +7 -0
- package/dist/generators/html/utils/analysisRunner.js +93 -0
- package/dist/generators/html/utils/cardBuilder.js +47 -0
- package/dist/generators/html/utils/contextBuilder.js +54 -0
- package/dist/generators/html/utils/htmlDocumentBuilder.js +396 -0
- package/dist/generators/html/utils/kpiBuilder.js +76 -0
- package/dist/generators/html/utils/sectionWrapper.js +71 -0
- package/dist/generators/html/utils/styleLoader.js +2 -1
- package/dist/html/analysisRunner.js +93 -0
- package/dist/html/htmlDocumentBuilder.js +396 -0
- package/dist/html/index.js +29 -0
- package/dist/html/shared/colorUtils.js +61 -0
- package/dist/html/shared/commitMapBuilder.js +23 -0
- package/dist/html/shared/components/cardBuilder.js +47 -0
- package/dist/html/shared/components/index.js +18 -0
- package/dist/html/shared/components/kpiBuilder.js +76 -0
- package/dist/html/shared/components/sectionWrapper.js +71 -0
- package/dist/html/shared/contextBuilder.js +54 -0
- package/dist/html/shared/dateRangeCalculator.js +56 -0
- package/dist/html/shared/developerStatsCalculator.js +28 -0
- package/dist/html/shared/index.js +39 -0
- package/dist/html/shared/scriptLoader.js +15 -0
- package/dist/html/shared/scripts/export.js +125 -0
- package/dist/html/shared/scripts/knowledge.js +137 -0
- package/dist/html/shared/scripts/modal.js +68 -0
- package/dist/html/shared/scripts/navigation.js +156 -0
- package/dist/html/shared/scripts/tabs.js +18 -0
- package/dist/html/shared/scripts/tooltip.js +21 -0
- package/dist/html/shared/styleLoader.js +18 -0
- package/dist/html/shared/styles/achievements.css +387 -0
- package/dist/html/shared/styles/base.css +822 -0
- package/dist/html/shared/styles/components.css +1511 -0
- package/dist/html/shared/styles/knowledge.css +242 -0
- package/dist/html/shared/styles/strategic-insights.css +1337 -0
- package/dist/html/shared/weekGrouper.js +27 -0
- package/dist/html/types.js +7 -0
- package/dist/index.js +54 -21
- package/dist/test/helpers/commitFactory.js +166 -0
- package/dist/test/helpers/dateUtils.js +101 -0
- package/dist/test/helpers/index.js +29 -0
- package/dist/test/setup.js +17 -0
- package/dist/test/smoke.test.js +94 -0
- package/dist/types/achievements.js +7 -0
- package/dist/types/analysis.js +7 -0
- package/dist/types/core.js +7 -0
- package/dist/types/index.js +38 -0
- package/dist/types/options.js +7 -0
- package/dist/types/shared.js +7 -0
- package/dist/types/strategic.js +7 -0
- package/dist/types/summary.js +7 -0
- package/dist/utils/achievementDefinitions.js +22 -22
- package/dist/utils/analyzerContextBuilder.js +124 -0
- package/dist/utils/commitQualityAnalyzer.js +13 -2
- package/dist/utils/emptyResults.js +95 -0
- package/dist/utils/eventAnnotationParser.js +253 -0
- package/dist/utils/executiveSummaryGenerator.js +275 -0
- package/dist/utils/fileHotspotAnalyzer.js +4 -12
- package/dist/utils/gapAnalyzer.js +298 -0
- package/dist/utils/gitParser.test.js +363 -0
- package/dist/utils/htmlGenerator.js +126 -450
- package/dist/utils/impactAnalyzer.js +20 -19
- package/dist/utils/knowledgeDistributionAnalyzer.js +32 -27
- package/dist/utils/matrixGenerator.js +13 -13
- package/dist/utils/rangeComparisonAnalyzer.js +222 -0
- package/dist/utils/streakCalculator.js +77 -27
- package/dist/utils/teamAnalyzer.js +316 -0
- package/dist/utils/timePatternAnalyzer.js +18 -3
- package/dist/utils/velocityAnalyzer.js +257 -0
- package/dist/utils/wrappedGenerator.js +8 -8
- package/package.json +74 -55
- package/vitest.config.ts +46 -0
- package/dist/cli.js +0 -24
- package/dist/commands/index.js +0 -24
|
@@ -59,10 +59,10 @@ exports.ACHIEVEMENTS = [
|
|
|
59
59
|
id: 'champion',
|
|
60
60
|
name: 'Champion',
|
|
61
61
|
emoji: '🏆',
|
|
62
|
-
description: 'Reached
|
|
62
|
+
description: 'Reached 200 commits',
|
|
63
63
|
category: 'milestone',
|
|
64
|
-
tier: '
|
|
65
|
-
criteria: { type: 'commit-count', threshold:
|
|
64
|
+
tier: 'gold',
|
|
65
|
+
criteria: { type: 'commit-count', threshold: 200, comparator: '>=' },
|
|
66
66
|
progress: 0,
|
|
67
67
|
isUnlocked: false,
|
|
68
68
|
isSecret: false
|
|
@@ -71,10 +71,10 @@ exports.ACHIEVEMENTS = [
|
|
|
71
71
|
id: 'master',
|
|
72
72
|
name: 'Master',
|
|
73
73
|
emoji: '💫',
|
|
74
|
-
description: 'Reached
|
|
74
|
+
description: 'Reached 300 commits',
|
|
75
75
|
category: 'milestone',
|
|
76
76
|
tier: 'platinum',
|
|
77
|
-
criteria: { type: 'commit-count', threshold:
|
|
77
|
+
criteria: { type: 'commit-count', threshold: 300, comparator: '>=' },
|
|
78
78
|
progress: 0,
|
|
79
79
|
isUnlocked: false,
|
|
80
80
|
isSecret: false
|
|
@@ -83,10 +83,10 @@ exports.ACHIEVEMENTS = [
|
|
|
83
83
|
id: 'legend',
|
|
84
84
|
name: 'Legend',
|
|
85
85
|
emoji: '🌟',
|
|
86
|
-
description: 'Reached
|
|
86
|
+
description: 'Reached 500 commits',
|
|
87
87
|
category: 'milestone',
|
|
88
88
|
tier: 'legendary',
|
|
89
|
-
criteria: { type: 'commit-count', threshold:
|
|
89
|
+
criteria: { type: 'commit-count', threshold: 500, comparator: '>=' },
|
|
90
90
|
progress: 0,
|
|
91
91
|
isUnlocked: false,
|
|
92
92
|
isSecret: false
|
|
@@ -98,7 +98,7 @@ exports.ACHIEVEMENTS = [
|
|
|
98
98
|
id: 'consistency',
|
|
99
99
|
name: 'Consistency',
|
|
100
100
|
emoji: '📅',
|
|
101
|
-
description: 'Maintained a 3-day
|
|
101
|
+
description: 'Maintained a 3-day business day streak',
|
|
102
102
|
category: 'time',
|
|
103
103
|
tier: 'bronze',
|
|
104
104
|
criteria: { type: 'streak', threshold: 3, comparator: '>=' },
|
|
@@ -110,10 +110,10 @@ exports.ACHIEVEMENTS = [
|
|
|
110
110
|
id: 'week-warrior',
|
|
111
111
|
name: 'Week Warrior',
|
|
112
112
|
emoji: '⚡',
|
|
113
|
-
description: 'Maintained a
|
|
113
|
+
description: 'Maintained a 5-day business day streak (full week)',
|
|
114
114
|
category: 'time',
|
|
115
115
|
tier: 'silver',
|
|
116
|
-
criteria: { type: 'streak', threshold:
|
|
116
|
+
criteria: { type: 'streak', threshold: 5, comparator: '>=' },
|
|
117
117
|
progress: 0,
|
|
118
118
|
isUnlocked: false,
|
|
119
119
|
isSecret: false
|
|
@@ -122,10 +122,10 @@ exports.ACHIEVEMENTS = [
|
|
|
122
122
|
id: 'streak-on-fire',
|
|
123
123
|
name: 'Streak On Fire',
|
|
124
124
|
emoji: '🔥',
|
|
125
|
-
description: 'Maintained a
|
|
125
|
+
description: 'Maintained a 10-day business day streak (2 weeks)',
|
|
126
126
|
category: 'time',
|
|
127
127
|
tier: 'gold',
|
|
128
|
-
criteria: { type: 'streak', threshold:
|
|
128
|
+
criteria: { type: 'streak', threshold: 10, comparator: '>=' },
|
|
129
129
|
progress: 0,
|
|
130
130
|
isUnlocked: false,
|
|
131
131
|
isSecret: false
|
|
@@ -134,10 +134,10 @@ exports.ACHIEVEMENTS = [
|
|
|
134
134
|
id: 'unstoppable',
|
|
135
135
|
name: 'Unstoppable',
|
|
136
136
|
emoji: '💪',
|
|
137
|
-
description: 'Maintained a
|
|
137
|
+
description: 'Maintained a 20-day business day streak (1 month)',
|
|
138
138
|
category: 'time',
|
|
139
139
|
tier: 'platinum',
|
|
140
|
-
criteria: { type: 'streak', threshold:
|
|
140
|
+
criteria: { type: 'streak', threshold: 20, comparator: '>=' },
|
|
141
141
|
progress: 0,
|
|
142
142
|
isUnlocked: false,
|
|
143
143
|
isSecret: false
|
|
@@ -146,10 +146,10 @@ exports.ACHIEVEMENTS = [
|
|
|
146
146
|
id: 'iron-will',
|
|
147
147
|
name: 'Iron Will',
|
|
148
148
|
emoji: '🦾',
|
|
149
|
-
description: 'Maintained a
|
|
149
|
+
description: 'Maintained a 40-day business day streak (2 months)',
|
|
150
150
|
category: 'time',
|
|
151
151
|
tier: 'platinum',
|
|
152
|
-
criteria: { type: 'streak', threshold:
|
|
152
|
+
criteria: { type: 'streak', threshold: 40, comparator: '>=' },
|
|
153
153
|
progress: 0,
|
|
154
154
|
isUnlocked: false,
|
|
155
155
|
isSecret: false
|
|
@@ -158,10 +158,10 @@ exports.ACHIEVEMENTS = [
|
|
|
158
158
|
id: 'code-machine',
|
|
159
159
|
name: 'Code Machine',
|
|
160
160
|
emoji: '👾',
|
|
161
|
-
description: 'Maintained a
|
|
161
|
+
description: 'Maintained a 60-day business day streak (3 months)',
|
|
162
162
|
category: 'time',
|
|
163
163
|
tier: 'legendary',
|
|
164
|
-
criteria: { type: 'streak', threshold:
|
|
164
|
+
criteria: { type: 'streak', threshold: 60, comparator: '>=' },
|
|
165
165
|
progress: 0,
|
|
166
166
|
isUnlocked: false,
|
|
167
167
|
isSecret: false
|
|
@@ -230,7 +230,7 @@ exports.ACHIEVEMENTS = [
|
|
|
230
230
|
id: 'early-bird',
|
|
231
231
|
name: 'Early Bird',
|
|
232
232
|
emoji: '🌅',
|
|
233
|
-
description: '
|
|
233
|
+
description: '20+ commits before 9am',
|
|
234
234
|
category: 'time',
|
|
235
235
|
tier: 'silver',
|
|
236
236
|
criteria: {
|
|
@@ -240,7 +240,7 @@ exports.ACHIEVEMENTS = [
|
|
|
240
240
|
const hour = new Date(c.date).getHours();
|
|
241
241
|
return hour >= 5 && hour < 9;
|
|
242
242
|
}).length;
|
|
243
|
-
return earlyCommits >=
|
|
243
|
+
return earlyCommits >= 20;
|
|
244
244
|
}
|
|
245
245
|
},
|
|
246
246
|
progress: 0,
|
|
@@ -251,7 +251,7 @@ exports.ACHIEVEMENTS = [
|
|
|
251
251
|
id: 'night-owl',
|
|
252
252
|
name: 'Night Owl',
|
|
253
253
|
emoji: '🦉',
|
|
254
|
-
description: '
|
|
254
|
+
description: '20+ commits after 9pm',
|
|
255
255
|
category: 'time',
|
|
256
256
|
tier: 'silver',
|
|
257
257
|
criteria: {
|
|
@@ -261,7 +261,7 @@ exports.ACHIEVEMENTS = [
|
|
|
261
261
|
const hour = new Date(c.date).getHours();
|
|
262
262
|
return hour >= 21 || hour < 5;
|
|
263
263
|
}).length;
|
|
264
|
-
return lateCommits >=
|
|
264
|
+
return lateCommits >= 20;
|
|
265
265
|
}
|
|
266
266
|
},
|
|
267
267
|
progress: 0,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utility functions for building and working with AnalyzerContext.
|
|
4
|
+
*
|
|
5
|
+
* @module analyzerContextBuilder
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.isAnalyzerContext = isAnalyzerContext;
|
|
9
|
+
exports.buildContext = buildContext;
|
|
10
|
+
exports.getOption = getOption;
|
|
11
|
+
exports.computeFileChanges = computeFileChanges;
|
|
12
|
+
exports.withOptions = withOptions;
|
|
13
|
+
/**
|
|
14
|
+
* Type guard to check if an argument is an AnalyzerContext.
|
|
15
|
+
*
|
|
16
|
+
* @param arg - Value to check
|
|
17
|
+
* @returns True if the argument is an AnalyzerContext
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* function analyze(arg1: CommitData[] | AnalyzerContext, ...rest: unknown[]) {
|
|
22
|
+
* if (isAnalyzerContext(arg1)) {
|
|
23
|
+
* // Use arg1.commits, arg1.dateRange, etc.
|
|
24
|
+
* } else {
|
|
25
|
+
* // Use positional parameters
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function isAnalyzerContext(arg) {
|
|
31
|
+
return (typeof arg === 'object' &&
|
|
32
|
+
arg !== null &&
|
|
33
|
+
'commits' in arg &&
|
|
34
|
+
'dateRange' in arg &&
|
|
35
|
+
typeof arg.dateRange === 'object' &&
|
|
36
|
+
arg.dateRange !== null &&
|
|
37
|
+
'start' in arg.dateRange &&
|
|
38
|
+
'end' in arg.dateRange);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build an AnalyzerContext from individual parameters.
|
|
42
|
+
*
|
|
43
|
+
* @param commits - Array of commits to analyze
|
|
44
|
+
* @param startDate - Start of the analysis period
|
|
45
|
+
* @param endDate - End of the analysis period
|
|
46
|
+
* @param repoPath - Path to the repository (optional)
|
|
47
|
+
* @param options - Additional analyzer options (optional)
|
|
48
|
+
* @returns A fully constructed AnalyzerContext
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const ctx = buildContext(commits, startDate, endDate, '/path/to/repo', { windowWeeks: 8 });
|
|
53
|
+
* const velocity = analyzeVelocity(ctx);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function buildContext(commits, startDate, endDate, repoPath = '', options) {
|
|
57
|
+
return {
|
|
58
|
+
commits,
|
|
59
|
+
dateRange: {
|
|
60
|
+
start: startDate,
|
|
61
|
+
end: endDate,
|
|
62
|
+
},
|
|
63
|
+
repoPath,
|
|
64
|
+
options,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get an option value from context with a default fallback.
|
|
69
|
+
*
|
|
70
|
+
* @param ctx - The analyzer context
|
|
71
|
+
* @param key - The option key to retrieve
|
|
72
|
+
* @param defaultValue - Default value if option is not set
|
|
73
|
+
* @returns The option value or the default
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const windowWeeks = getOption(ctx, 'windowWeeks', 4);
|
|
78
|
+
* const thresholdDays = getOption(ctx, 'thresholdDays', 3);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
function getOption(ctx, key, defaultValue) {
|
|
82
|
+
return ctx.options?.[key] ?? defaultValue;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Compute file change data from commits (cached).
|
|
86
|
+
*
|
|
87
|
+
* This is an expensive operation that should only be done once
|
|
88
|
+
* and then shared across analyzers via the context.
|
|
89
|
+
*
|
|
90
|
+
* @param _repoPath - Path to the git repository
|
|
91
|
+
* @param _commits - Commits to analyze for file changes
|
|
92
|
+
* @returns Map of file paths to change data
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* This is a placeholder for future implementation.
|
|
96
|
+
* Currently returns an empty map.
|
|
97
|
+
*/
|
|
98
|
+
function computeFileChanges(_repoPath, _commits) {
|
|
99
|
+
// TODO: Implement git log --name-only parsing
|
|
100
|
+
// This would be called once and shared across impact/knowledge analyzers
|
|
101
|
+
return new Map();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Clone a context with merged options.
|
|
105
|
+
*
|
|
106
|
+
* @param ctx - Original context
|
|
107
|
+
* @param additionalOptions - Options to merge
|
|
108
|
+
* @returns New context with merged options
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const velocityCtx = withOptions(ctx, { windowWeeks: 8 });
|
|
113
|
+
* const gapCtx = withOptions(ctx, { thresholdDays: 5, excludeWeekends: true });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
function withOptions(ctx, additionalOptions) {
|
|
117
|
+
return {
|
|
118
|
+
...ctx,
|
|
119
|
+
options: {
|
|
120
|
+
...ctx.options,
|
|
121
|
+
...additionalOptions,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.analyzeCommitQuality = analyzeCommitQuality;
|
|
4
4
|
exports.getQualityRating = getQualityRating;
|
|
5
5
|
exports.getQualityLevel = getQualityLevel;
|
|
6
|
+
const analyzerContextBuilder_1 = require("./analyzerContextBuilder");
|
|
6
7
|
const CONVENTIONAL_COMMIT_REGEX = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?:\s.+/;
|
|
7
8
|
const CODE_HYGIENE_PATTERNS = {
|
|
8
9
|
quickFix: /fix(ed)?\s+(typo|spelling|grammar)/i,
|
|
@@ -24,8 +25,18 @@ const IMPERATIVE_STARTERS = [
|
|
|
24
25
|
'improve', 'optimize', 'enhance', 'change', 'move', 'rename', 'extract',
|
|
25
26
|
'merge', 'bump', 'revert', 'document', 'test', 'upgrade', 'downgrade'
|
|
26
27
|
];
|
|
27
|
-
function analyzeCommitQuality(
|
|
28
|
-
|
|
28
|
+
function analyzeCommitQuality(ctxOrCommits, options = {}) {
|
|
29
|
+
// Normalize to internal variables
|
|
30
|
+
let commits;
|
|
31
|
+
let skipBodyCheck;
|
|
32
|
+
if ((0, analyzerContextBuilder_1.isAnalyzerContext)(ctxOrCommits)) {
|
|
33
|
+
commits = ctxOrCommits.commits;
|
|
34
|
+
skipBodyCheck = (0, analyzerContextBuilder_1.getOption)(ctxOrCommits, 'skipBodyCheck', false);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
commits = ctxOrCommits;
|
|
38
|
+
skipBodyCheck = options.skipBodyCheck ?? false;
|
|
39
|
+
}
|
|
29
40
|
if (commits.length === 0) {
|
|
30
41
|
return getEmptyQuality();
|
|
31
42
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Centralized empty result factories for all analyzers
|
|
4
|
+
* Provides consistent default values when there's no data to analyze
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getEmptyImpactAnalysis = getEmptyImpactAnalysis;
|
|
8
|
+
exports.getEmptyVelocityAnalysis = getEmptyVelocityAnalysis;
|
|
9
|
+
exports.getEmptyGapAnalysis = getEmptyGapAnalysis;
|
|
10
|
+
exports.getEmptyKnowledgeDistribution = getEmptyKnowledgeDistribution;
|
|
11
|
+
exports.getEmptyFileHotspotAnalysis = getEmptyFileHotspotAnalysis;
|
|
12
|
+
const date_fns_1 = require("date-fns");
|
|
13
|
+
/**
|
|
14
|
+
* Creates an empty impact analysis result
|
|
15
|
+
*/
|
|
16
|
+
function getEmptyImpactAnalysis() {
|
|
17
|
+
return {
|
|
18
|
+
overallScore: 0,
|
|
19
|
+
scoreBreakdown: {
|
|
20
|
+
coreContributions: 0,
|
|
21
|
+
featureWork: 0,
|
|
22
|
+
maintenanceWork: 0,
|
|
23
|
+
documentationWork: 0,
|
|
24
|
+
},
|
|
25
|
+
topImpactFiles: [],
|
|
26
|
+
impactTrend: 'stable',
|
|
27
|
+
insights: ['No commit data available for analysis']
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates an empty velocity analysis result
|
|
32
|
+
*/
|
|
33
|
+
function getEmptyVelocityAnalysis() {
|
|
34
|
+
return {
|
|
35
|
+
timeline: [],
|
|
36
|
+
overallTrend: 'stable',
|
|
37
|
+
trendPercentage: 0,
|
|
38
|
+
averageCommitsPerWeek: 0,
|
|
39
|
+
peakWeek: { weekStart: new Date(), commits: 0 },
|
|
40
|
+
lowestWeek: { weekStart: new Date(), commits: 0 },
|
|
41
|
+
anomalies: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates an empty gap analysis result
|
|
46
|
+
*/
|
|
47
|
+
function getEmptyGapAnalysis(startDate, endDate) {
|
|
48
|
+
const totalDays = (0, date_fns_1.differenceInDays)(endDate, startDate) + 1;
|
|
49
|
+
return {
|
|
50
|
+
gaps: [{
|
|
51
|
+
start: startDate,
|
|
52
|
+
end: endDate,
|
|
53
|
+
durationDays: totalDays,
|
|
54
|
+
commitBefore: null,
|
|
55
|
+
commitAfter: null,
|
|
56
|
+
possibleType: 'blocker',
|
|
57
|
+
}],
|
|
58
|
+
totalGapDays: totalDays,
|
|
59
|
+
percentageOfPeriodInGaps: 100,
|
|
60
|
+
longestGap: null,
|
|
61
|
+
averageGapLength: totalDays,
|
|
62
|
+
gapFrequency: 0,
|
|
63
|
+
patterns: [],
|
|
64
|
+
riskLevel: 'critical',
|
|
65
|
+
riskFactors: ['No commits found in the analysis period'],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates an empty knowledge distribution result
|
|
70
|
+
*/
|
|
71
|
+
function getEmptyKnowledgeDistribution() {
|
|
72
|
+
return {
|
|
73
|
+
directories: [],
|
|
74
|
+
busFactorRisk: {
|
|
75
|
+
overall: 0,
|
|
76
|
+
level: 'low',
|
|
77
|
+
criticalPaths: []
|
|
78
|
+
},
|
|
79
|
+
knowledgeSilos: [],
|
|
80
|
+
sharedKnowledge: [],
|
|
81
|
+
recommendations: ['No commit data available for analysis']
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates an empty file hotspot analysis result
|
|
86
|
+
*/
|
|
87
|
+
function getEmptyFileHotspotAnalysis() {
|
|
88
|
+
return {
|
|
89
|
+
topFiles: [],
|
|
90
|
+
topDirectories: [],
|
|
91
|
+
technicalDebt: [],
|
|
92
|
+
ownershipRisks: [],
|
|
93
|
+
totalFilesAnalyzed: 0
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadEventsFile = loadEventsFile;
|
|
37
|
+
exports.parseEventsInline = parseEventsInline;
|
|
38
|
+
exports.correlateEventsWithMetrics = correlateEventsWithMetrics;
|
|
39
|
+
exports.getEventTypeEmoji = getEventTypeEmoji;
|
|
40
|
+
exports.getEventTypeColor = getEventTypeColor;
|
|
41
|
+
exports.formatEventsInsights = formatEventsInsights;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const date_fns_1 = require("date-fns");
|
|
44
|
+
const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
|
|
45
|
+
/**
|
|
46
|
+
* Load events from a JSON file
|
|
47
|
+
*/
|
|
48
|
+
function loadEventsFile(filePath) {
|
|
49
|
+
try {
|
|
50
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
// Validate and transform
|
|
53
|
+
const events = (parsed.events || []).map((event) => ({
|
|
54
|
+
date: new Date(event.date),
|
|
55
|
+
label: event.label,
|
|
56
|
+
type: validateEventType(event.type),
|
|
57
|
+
description: event.description,
|
|
58
|
+
}));
|
|
59
|
+
return { events };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error.code === 'ENOENT') {
|
|
63
|
+
throw new Error(`Events file not found: ${filePath}`);
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Failed to parse events file: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse inline events JSON
|
|
70
|
+
*/
|
|
71
|
+
function parseEventsInline(jsonStr) {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(jsonStr);
|
|
74
|
+
const events = (parsed.events || [parsed]).map((event) => ({
|
|
75
|
+
date: new Date(event.date),
|
|
76
|
+
label: event.label,
|
|
77
|
+
type: validateEventType(event.type),
|
|
78
|
+
description: event.description,
|
|
79
|
+
}));
|
|
80
|
+
return { events };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new Error(`Failed to parse inline events JSON: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function validateEventType(type) {
|
|
87
|
+
const validTypes = ['disruption', 'attrition', 'growth', 'release', 'incident', 'external', 'custom'];
|
|
88
|
+
if (validTypes.includes(type)) {
|
|
89
|
+
return type;
|
|
90
|
+
}
|
|
91
|
+
return 'custom';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Calculate correlation between events and metrics changes
|
|
95
|
+
*/
|
|
96
|
+
function correlateEventsWithMetrics(events, commits, windowDays = 14, skipBodyCheck = true) {
|
|
97
|
+
return events.map(event => ({
|
|
98
|
+
...event,
|
|
99
|
+
correlation: calculateEventCorrelation(event, commits, windowDays, skipBodyCheck),
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
function calculateEventCorrelation(event, commits, windowDays, skipBodyCheck) {
|
|
103
|
+
const eventDate = event.date;
|
|
104
|
+
// Before period
|
|
105
|
+
const beforeStart = (0, date_fns_1.subDays)(eventDate, windowDays);
|
|
106
|
+
const beforeEnd = (0, date_fns_1.subDays)(eventDate, 1);
|
|
107
|
+
const beforeCommits = commits.filter(c => {
|
|
108
|
+
const d = new Date(c.date);
|
|
109
|
+
return d >= beforeStart && d <= beforeEnd;
|
|
110
|
+
});
|
|
111
|
+
// After period
|
|
112
|
+
const afterStart = (0, date_fns_1.addDays)(eventDate, 1);
|
|
113
|
+
const afterEnd = (0, date_fns_1.addDays)(eventDate, windowDays);
|
|
114
|
+
const afterCommits = commits.filter(c => {
|
|
115
|
+
const d = new Date(c.date);
|
|
116
|
+
return d >= afterStart && d <= afterEnd;
|
|
117
|
+
});
|
|
118
|
+
const metricsBefore = calculatePeriodMetrics(beforeCommits, beforeStart, beforeEnd, skipBodyCheck);
|
|
119
|
+
const metricsAfter = calculatePeriodMetrics(afterCommits, afterStart, afterEnd, skipBodyCheck);
|
|
120
|
+
// Calculate changes
|
|
121
|
+
const velocityChange = calculatePercentageChange(metricsBefore.commitsPerWeek ?? 0, metricsAfter.commitsPerWeek ?? 0);
|
|
122
|
+
const qualityChange = calculatePercentageChange(metricsBefore.qualityScore ?? 0, metricsAfter.qualityScore ?? 0);
|
|
123
|
+
const activeAuthorsChange = (metricsAfter.totalAuthors ?? 0) - (metricsBefore.totalAuthors ?? 0);
|
|
124
|
+
// Generate assessment
|
|
125
|
+
const assessment = generateAssessment(event, velocityChange, qualityChange, activeAuthorsChange);
|
|
126
|
+
return {
|
|
127
|
+
metricsBefore,
|
|
128
|
+
metricsAfter,
|
|
129
|
+
impact: {
|
|
130
|
+
velocityChange,
|
|
131
|
+
qualityChange,
|
|
132
|
+
activeAuthorsChange,
|
|
133
|
+
},
|
|
134
|
+
assessment,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function calculatePeriodMetrics(commits, startDate, endDate, skipBodyCheck) {
|
|
138
|
+
const days = (0, date_fns_1.differenceInDays)(endDate, startDate) + 1;
|
|
139
|
+
const weeks = Math.max(1, days / 7);
|
|
140
|
+
const totalCommits = commits.length;
|
|
141
|
+
const commitsPerWeek = Math.round((totalCommits / weeks) * 10) / 10;
|
|
142
|
+
const totalAuthors = new Set(commits.map(c => c.author)).size;
|
|
143
|
+
const qualityResult = commits.length > 0
|
|
144
|
+
? (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits, { skipBodyCheck })
|
|
145
|
+
: { overallScore: 0 };
|
|
146
|
+
return {
|
|
147
|
+
totalCommits,
|
|
148
|
+
commitsPerWeek,
|
|
149
|
+
totalAuthors,
|
|
150
|
+
qualityScore: Math.round(qualityResult.overallScore * 10) / 10,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function calculatePercentageChange(before, after) {
|
|
154
|
+
if (before === 0) {
|
|
155
|
+
return after > 0 ? 100 : 0;
|
|
156
|
+
}
|
|
157
|
+
return Math.round(((after - before) / before) * 1000) / 10;
|
|
158
|
+
}
|
|
159
|
+
function generateAssessment(event, velocityChange, qualityChange, authorChange) {
|
|
160
|
+
const parts = [];
|
|
161
|
+
const eventLabel = `"${event.label}"`;
|
|
162
|
+
if (velocityChange <= -30) {
|
|
163
|
+
parts.push(`velocity dropped ${Math.abs(velocityChange)}%`);
|
|
164
|
+
}
|
|
165
|
+
else if (velocityChange >= 30) {
|
|
166
|
+
parts.push(`velocity increased ${velocityChange}%`);
|
|
167
|
+
}
|
|
168
|
+
if (qualityChange <= -20) {
|
|
169
|
+
parts.push(`quality decreased ${Math.abs(qualityChange)}%`);
|
|
170
|
+
}
|
|
171
|
+
else if (qualityChange >= 20) {
|
|
172
|
+
parts.push(`quality improved ${qualityChange}%`);
|
|
173
|
+
}
|
|
174
|
+
if (authorChange < 0) {
|
|
175
|
+
parts.push(`${Math.abs(authorChange)} fewer active contributor(s)`);
|
|
176
|
+
}
|
|
177
|
+
else if (authorChange > 0) {
|
|
178
|
+
parts.push(`${authorChange} more active contributor(s)`);
|
|
179
|
+
}
|
|
180
|
+
if (parts.length === 0) {
|
|
181
|
+
return `No significant metric changes detected after ${eventLabel}`;
|
|
182
|
+
}
|
|
183
|
+
return `After ${eventLabel}: ${parts.join(', ')}`;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get emoji for event type
|
|
187
|
+
*/
|
|
188
|
+
function getEventTypeEmoji(type) {
|
|
189
|
+
const emojis = {
|
|
190
|
+
disruption: '⚡',
|
|
191
|
+
attrition: '👋',
|
|
192
|
+
growth: '🌱',
|
|
193
|
+
release: '🚀',
|
|
194
|
+
incident: '🔥',
|
|
195
|
+
external: '📅',
|
|
196
|
+
custom: '📌',
|
|
197
|
+
};
|
|
198
|
+
return emojis[type] || '📌';
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get color for event type (for HTML)
|
|
202
|
+
*/
|
|
203
|
+
function getEventTypeColor(type) {
|
|
204
|
+
const colors = {
|
|
205
|
+
disruption: '#ff6b6b',
|
|
206
|
+
attrition: '#ffa94d',
|
|
207
|
+
growth: '#69db7c',
|
|
208
|
+
release: '#4dabf7',
|
|
209
|
+
incident: '#ff8787',
|
|
210
|
+
external: '#868e96',
|
|
211
|
+
custom: '#9775fa',
|
|
212
|
+
};
|
|
213
|
+
return colors[type] || '#9775fa';
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Format events for display
|
|
217
|
+
*/
|
|
218
|
+
function formatEventsInsights(events) {
|
|
219
|
+
const insights = [];
|
|
220
|
+
if (events.length === 0) {
|
|
221
|
+
return ['No events annotated'];
|
|
222
|
+
}
|
|
223
|
+
insights.push(`📅 ${events.length} Event(s) Annotated`);
|
|
224
|
+
insights.push('');
|
|
225
|
+
// Sort by date
|
|
226
|
+
const sortedEvents = [...events].sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
227
|
+
for (const event of sortedEvents) {
|
|
228
|
+
const emoji = getEventTypeEmoji(event.type);
|
|
229
|
+
const dateStr = (0, date_fns_1.format)(event.date, 'MMM d, yyyy');
|
|
230
|
+
insights.push(`${emoji} ${dateStr}: ${event.label}`);
|
|
231
|
+
if (event.description) {
|
|
232
|
+
insights.push(` ${event.description}`);
|
|
233
|
+
}
|
|
234
|
+
if (event.correlation) {
|
|
235
|
+
const { impact } = event.correlation;
|
|
236
|
+
const impactParts = [];
|
|
237
|
+
if (Math.abs(impact.velocityChange) >= 10) {
|
|
238
|
+
impactParts.push(`velocity ${impact.velocityChange >= 0 ? '+' : ''}${impact.velocityChange}%`);
|
|
239
|
+
}
|
|
240
|
+
if (Math.abs(impact.qualityChange) >= 10) {
|
|
241
|
+
impactParts.push(`quality ${impact.qualityChange >= 0 ? '+' : ''}${impact.qualityChange}%`);
|
|
242
|
+
}
|
|
243
|
+
if (impact.activeAuthorsChange !== 0) {
|
|
244
|
+
impactParts.push(`${impact.activeAuthorsChange >= 0 ? '+' : ''}${impact.activeAuthorsChange} contributors`);
|
|
245
|
+
}
|
|
246
|
+
if (impactParts.length > 0) {
|
|
247
|
+
insights.push(` 📊 Impact: ${impactParts.join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
insights.push('');
|
|
251
|
+
}
|
|
252
|
+
return insights;
|
|
253
|
+
}
|