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