specweave 1.0.239 → 1.0.241

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 (161) hide show
  1. package/CLAUDE.md +31 -30
  2. package/README.md +1 -1
  3. package/bin/specweave.js +16 -0
  4. package/dist/plugins/specweave-ado/lib/ado-permission-gate.d.ts.map +1 -1
  5. package/dist/plugins/specweave-ado/lib/ado-permission-gate.js +17 -2
  6. package/dist/plugins/specweave-ado/lib/ado-permission-gate.js.map +1 -1
  7. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +7 -0
  8. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-feature-sync.js +53 -0
  10. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  11. package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -1
  12. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +17 -2
  13. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -1
  14. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts +1 -0
  15. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts.map +1 -1
  16. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js +7 -3
  17. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js.map +1 -1
  18. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts.map +1 -1
  19. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js +27 -19
  20. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js.map +1 -1
  21. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts +8 -0
  22. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts.map +1 -1
  23. package/dist/plugins/specweave-testing/lib/playwright-routing.js +10 -7
  24. package/dist/plugins/specweave-testing/lib/playwright-routing.js.map +1 -1
  25. package/dist/src/adapters/agents-md-generator.js +1 -1
  26. package/dist/src/adapters/agents-md-generator.js.map +1 -1
  27. package/dist/src/adapters/claude/README.md +1 -1
  28. package/dist/src/adapters/claude-md-generator.js +1 -1
  29. package/dist/src/adapters/claude-md-generator.js.map +1 -1
  30. package/dist/src/cli/commands/init.d.ts.map +1 -1
  31. package/dist/src/cli/commands/init.js +10 -1
  32. package/dist/src/cli/commands/init.js.map +1 -1
  33. package/dist/src/cli/commands/refresh-marketplace.d.ts.map +1 -1
  34. package/dist/src/cli/commands/refresh-marketplace.js +7 -67
  35. package/dist/src/cli/commands/refresh-marketplace.js.map +1 -1
  36. package/dist/src/cli/commands/team.d.ts +20 -0
  37. package/dist/src/cli/commands/team.d.ts.map +1 -0
  38. package/dist/src/cli/commands/team.js +101 -0
  39. package/dist/src/cli/commands/team.js.map +1 -0
  40. package/dist/src/cli/helpers/init/claude-settings-env.d.ts +16 -0
  41. package/dist/src/cli/helpers/init/claude-settings-env.d.ts.map +1 -0
  42. package/dist/src/cli/helpers/init/claude-settings-env.js +44 -0
  43. package/dist/src/cli/helpers/init/claude-settings-env.js.map +1 -0
  44. package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -1
  45. package/dist/src/cli/helpers/init/plugin-installer.js +9 -13
  46. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  47. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  48. package/dist/src/cli/helpers/issue-tracker/index.js +12 -6
  49. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  50. package/dist/src/cli/helpers/issue-tracker/types.d.ts +2 -0
  51. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  52. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  53. package/dist/src/core/increment/discipline-checker.js +1 -1
  54. package/dist/src/core/increment/discipline-checker.js.map +1 -1
  55. package/dist/src/core/increment/status-commands.d.ts.map +1 -1
  56. package/dist/src/core/increment/status-commands.js +7 -0
  57. package/dist/src/core/increment/status-commands.js.map +1 -1
  58. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +2 -2
  59. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  60. package/dist/src/core/lazy-loading/llm-plugin-detector.js +63 -25
  61. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  62. package/dist/src/core/reflection/reflect-handler.js +2 -2
  63. package/dist/src/core/reflection/reflect-handler.js.map +1 -1
  64. package/dist/src/core/session/handoff-context.js +2 -2
  65. package/dist/src/core/session/handoff-context.js.map +1 -1
  66. package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
  67. package/dist/src/sync/ado-reconciler.js +21 -2
  68. package/dist/src/sync/ado-reconciler.js.map +1 -1
  69. package/dist/src/sync/engine.d.ts.map +1 -1
  70. package/dist/src/sync/engine.js +2 -0
  71. package/dist/src/sync/engine.js.map +1 -1
  72. package/dist/src/sync/github-reconciler.d.ts.map +1 -1
  73. package/dist/src/sync/github-reconciler.js +52 -26
  74. package/dist/src/sync/github-reconciler.js.map +1 -1
  75. package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
  76. package/dist/src/sync/jira-reconciler.js +16 -3
  77. package/dist/src/sync/jira-reconciler.js.map +1 -1
  78. package/dist/src/sync/providers/ado.d.ts.map +1 -1
  79. package/dist/src/sync/providers/ado.js +4 -2
  80. package/dist/src/sync/providers/ado.js.map +1 -1
  81. package/dist/src/sync/providers/github.d.ts.map +1 -1
  82. package/dist/src/sync/providers/github.js +11 -0
  83. package/dist/src/sync/providers/github.js.map +1 -1
  84. package/dist/src/sync/providers/jira.d.ts.map +1 -1
  85. package/dist/src/sync/providers/jira.js +14 -2
  86. package/dist/src/sync/providers/jira.js.map +1 -1
  87. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  88. package/dist/src/sync/sync-coordinator.js +31 -6
  89. package/dist/src/sync/sync-coordinator.js.map +1 -1
  90. package/dist/src/utils/auto-install.js +4 -4
  91. package/dist/src/utils/auto-install.js.map +1 -1
  92. package/package.json +2 -2
  93. package/plugins/FINAL-AUDIT-RECOMMENDATIONS.md +3 -3
  94. package/plugins/SKILLS-VS-AGENTS.md +1 -1
  95. package/plugins/specweave/PLUGIN.md +0 -2
  96. package/plugins/specweave/commands/export-skills.md +1 -1
  97. package/plugins/specweave/commands/role-orchestrator.md +1 -1
  98. package/plugins/specweave/hooks/log-decision.sh +6 -0
  99. package/plugins/specweave/hooks/stop-auto-v5.sh +17 -1
  100. package/plugins/specweave/hooks/stop-reflect.sh +16 -2
  101. package/plugins/specweave/hooks/stop-sync.sh +17 -9
  102. package/plugins/specweave/hooks/user-prompt-submit.sh +119 -35
  103. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +52 -26
  104. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  105. package/plugins/specweave/scripts/read-grill-context.sh +149 -0
  106. package/plugins/specweave/skills/code-review/SKILL.md +608 -0
  107. package/plugins/specweave/skills/done/SKILL.md +1 -1
  108. package/plugins/specweave/skills/grill/SKILL.md +91 -0
  109. package/plugins/specweave/skills/performance/SKILL.md +6 -0
  110. package/plugins/specweave/skills/security/SKILL.md +7 -0
  111. package/plugins/specweave/skills/security-patterns/SKILL.md +6 -0
  112. package/plugins/specweave/skills/tdd-orchestrator/SKILL.md +1 -1
  113. package/plugins/specweave/skills/team-build/SKILL.md +1 -1
  114. package/plugins/specweave/skills/team-orchestrate/SKILL.md +1 -1
  115. package/plugins/specweave/skills/tech-lead/SKILL.md +7 -0
  116. package/plugins/specweave-ado/lib/ado-permission-gate.js +18 -2
  117. package/plugins/specweave-ado/lib/ado-permission-gate.ts +19 -2
  118. package/plugins/specweave-frontend/skills/frontend/SKILL.md +138 -2
  119. package/plugins/specweave-frontend/skills/i18n-expert/SKILL.md +989 -0
  120. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +23 -1
  121. package/plugins/specweave-github/lib/github-feature-sync.js +41 -0
  122. package/plugins/specweave-github/lib/github-feature-sync.ts +62 -0
  123. package/plugins/specweave-infrastructure/PLUGIN.md +2 -1
  124. package/plugins/specweave-infrastructure/skills/gcp-deep-dive/SKILL.md +1172 -0
  125. package/plugins/specweave-infrastructure/skills/observability/SKILL.md +6 -0
  126. package/plugins/specweave-infrastructure/skills/opentelemetry/SKILL.md +6 -0
  127. package/plugins/specweave-jira/lib/jira-permission-gate.js +18 -2
  128. package/plugins/specweave-jira/lib/jira-permission-gate.ts +19 -2
  129. package/plugins/specweave-mobile/PLUGIN.md +1 -2
  130. package/plugins/specweave-mobile/README.md +13 -12
  131. package/plugins/specweave-mobile/skills/capacitor-ionic/SKILL.md +4 -18
  132. package/plugins/specweave-mobile/skills/deep-linking-push/SKILL.md +4 -22
  133. package/plugins/specweave-mobile/skills/expo/SKILL.md +4 -24
  134. package/plugins/specweave-mobile/skills/mobile-testing/SKILL.md +4 -22
  135. package/plugins/specweave-mobile/skills/react-native-expert/SKILL.md +404 -47
  136. package/plugins/specweave-testing/PLUGIN.md +3 -11
  137. package/plugins/specweave-testing/lib/playwright-cli-detector.js +8 -3
  138. package/plugins/specweave-testing/lib/playwright-cli-detector.ts +8 -3
  139. package/plugins/specweave-testing/lib/playwright-cli-runner.js +25 -20
  140. package/plugins/specweave-testing/lib/playwright-cli-runner.ts +24 -19
  141. package/plugins/specweave-testing/lib/playwright-routing.js +1 -6
  142. package/plugins/specweave-testing/lib/playwright-routing.ts +11 -8
  143. package/plugins/specweave-testing/skills/accessibility-testing/SKILL.md +998 -0
  144. package/plugins/specweave-testing/skills/e2e-testing/SKILL.md +29 -28
  145. package/plugins/specweave-testing/skills/mutation-testing/SKILL.md +769 -0
  146. package/plugins/specweave-testing/skills/performance-testing/SKILL.md +961 -0
  147. package/plugins/specweave-testing/skills/qa-engineer/SKILL.md +2 -0
  148. package/plugins/specweave/.specweave/logs/decisions.jsonl +0 -12
  149. package/plugins/specweave/.specweave/logs/reflect/reflect.log +0 -8
  150. package/plugins/specweave/.specweave/logs/stop-auto.log +0 -6
  151. package/plugins/specweave/.specweave/logs/stop-sync.log +0 -10
  152. package/plugins/specweave/.specweave/state/dashboard.json +0 -43
  153. package/plugins/specweave/skills/infrastructure/SKILL.md +0 -86
  154. package/plugins/specweave/skills/qa-lead/SKILL.md +0 -77
  155. package/plugins/specweave-mobile/skills/mobile-architect/SKILL.md +0 -30
  156. package/plugins/specweave-testing/commands/e2e-setup.md +0 -1103
  157. package/plugins/specweave-testing/commands/test-coverage.md +0 -983
  158. package/plugins/specweave-testing/commands/test-generate.md +0 -1160
  159. package/plugins/specweave-testing/commands/test-init.md +0 -413
  160. package/plugins/specweave-testing/commands/ui-automate.md +0 -182
  161. package/plugins/specweave-testing/commands/ui-inspect.md +0 -82
@@ -1,983 +0,0 @@
1
- ---
2
- description: Analyze test coverage, identify gaps, generate reports, and provide actionable quality metrics.
3
- ---
4
-
5
- # /sw-testing:test-coverage
6
-
7
- Comprehensive test coverage analysis, reporting, and quality metrics for modern test suites.
8
-
9
- You are an expert test coverage analyst who provides actionable insights and quality metrics.
10
-
11
- ## Your Task
12
-
13
- Analyze test coverage, identify gaps, generate reports, and provide recommendations for improving test quality.
14
-
15
- ### 1. Coverage Tools Stack
16
-
17
- **Vitest Coverage (v8/istanbul)**:
18
- - Line coverage
19
- - Branch coverage
20
- - Function coverage
21
- - Statement coverage
22
- - Per-file analysis
23
- - HTML/LCOV/JSON reports
24
-
25
- **Additional Tools**:
26
- - Codecov integration
27
- - SonarQube analysis
28
- - Custom coverage badges
29
- - Historical trending
30
- - Coverage gates
31
-
32
- ### 2. Vitest Coverage Configuration
33
-
34
- **vitest.config.ts** (Comprehensive Coverage):
35
- ```typescript
36
- import { defineConfig } from 'vitest/config';
37
-
38
- export default defineConfig({
39
- test: {
40
- coverage: {
41
- provider: 'v8', // or 'istanbul'
42
- reporter: [
43
- 'text', // Console output
44
- 'text-summary', // Summary in console
45
- 'html', // HTML report
46
- 'lcov', // LCOV format (for Codecov)
47
- 'json', // JSON format
48
- 'json-summary', // Summary JSON
49
- 'clover', // Clover XML
50
- ],
51
-
52
- // Files to include
53
- include: ['src/**/*.{ts,tsx,js,jsx}'],
54
-
55
- // Files to exclude
56
- exclude: [
57
- 'node_modules/',
58
- 'dist/',
59
- 'build/',
60
- 'coverage/',
61
- '**/*.d.ts',
62
- '**/*.config.*',
63
- '**/*.setup.*',
64
- '**/mockData/**',
65
- '**/types/**',
66
- '**/__tests__/**',
67
- '**/*.test.*',
68
- '**/*.spec.*',
69
- ],
70
-
71
- // Coverage thresholds (fail if below)
72
- thresholds: {
73
- lines: 80,
74
- functions: 80,
75
- branches: 80,
76
- statements: 80,
77
-
78
- // Per-file thresholds
79
- perFile: true,
80
-
81
- // Auto-update thresholds
82
- autoUpdate: false,
83
-
84
- // Threshold enforcement
85
- 100: false, // Don't require 100%
86
- },
87
-
88
- // Report all files (even untested)
89
- all: true,
90
-
91
- // Skip coverage for specific files
92
- ignoreClassMethods: ['toString', 'toJSON'],
93
-
94
- // Clean coverage directory before run
95
- clean: true,
96
-
97
- // Watermarks for coloring
98
- watermarks: {
99
- statements: [50, 80],
100
- functions: [50, 80],
101
- branches: [50, 80],
102
- lines: [50, 80],
103
- },
104
- },
105
- },
106
- });
107
- ```
108
-
109
- ### 3. Coverage Analysis Script
110
-
111
- **scripts/analyze-coverage.ts**:
112
- ```typescript
113
- import fs from 'fs';
114
- import path from 'path';
115
- import chalk from 'chalk';
116
-
117
- interface FileCoverage {
118
- lines: { total: number; covered: number; skipped: number; pct: number };
119
- statements: { total: number; covered: number; skipped: number; pct: number };
120
- functions: { total: number; covered: number; skipped: number; pct: number };
121
- branches: { total: number; covered: number; skipped: number; pct: number };
122
- }
123
-
124
- interface CoverageSummary {
125
- total: FileCoverage;
126
- [file: string]: FileCoverage;
127
- }
128
-
129
- export class CoverageAnalyzer {
130
- private coveragePath: string;
131
- private summary: CoverageSummary;
132
-
133
- constructor(coveragePath = 'coverage/coverage-summary.json') {
134
- this.coveragePath = coveragePath;
135
- this.summary = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));
136
- }
137
-
138
- // Overall coverage summary
139
- printSummary() {
140
- const { total } = this.summary;
141
-
142
- console.log(chalk.bold('\n📊 Coverage Summary\n'));
143
- console.log(this.formatCoverageLine('Lines', total.lines));
144
- console.log(this.formatCoverageLine('Statements', total.statements));
145
- console.log(this.formatCoverageLine('Functions', total.functions));
146
- console.log(this.formatCoverageLine('Branches', total.branches));
147
- }
148
-
149
- // Files with low coverage
150
- findLowCoverageFiles(threshold = 80) {
151
- const lowCoverageFiles: Array<{
152
- file: string;
153
- type: string;
154
- coverage: number;
155
- gap: number;
156
- }> = [];
157
-
158
- Object.entries(this.summary).forEach(([file, data]) => {
159
- if (file === 'total') return;
160
-
161
- const checks = [
162
- { type: 'lines', pct: data.lines.pct },
163
- { type: 'functions', pct: data.functions.pct },
164
- { type: 'branches', pct: data.branches.pct },
165
- { type: 'statements', pct: data.statements.pct },
166
- ];
167
-
168
- checks.forEach(({ type, pct }) => {
169
- if (pct < threshold) {
170
- lowCoverageFiles.push({
171
- file: this.shortenPath(file),
172
- type,
173
- coverage: pct,
174
- gap: threshold - pct,
175
- });
176
- }
177
- });
178
- });
179
-
180
- return lowCoverageFiles.sort((a, b) => b.gap - a.gap);
181
- }
182
-
183
- // Uncovered files
184
- findUncoveredFiles() {
185
- const uncovered: string[] = [];
186
-
187
- Object.entries(this.summary).forEach(([file, data]) => {
188
- if (file === 'total') return;
189
-
190
- if (data.statements.pct === 0) {
191
- uncovered.push(this.shortenPath(file));
192
- }
193
- });
194
-
195
- return uncovered;
196
- }
197
-
198
- // Files with perfect coverage
199
- findPerfectCoverage() {
200
- const perfect: string[] = [];
201
-
202
- Object.entries(this.summary).forEach(([file, data]) => {
203
- if (file === 'total') return;
204
-
205
- const isPerfect =
206
- data.lines.pct === 100 &&
207
- data.functions.pct === 100 &&
208
- data.branches.pct === 100 &&
209
- data.statements.pct === 100;
210
-
211
- if (isPerfect) {
212
- perfect.push(this.shortenPath(file));
213
- }
214
- });
215
-
216
- return perfect;
217
- }
218
-
219
- // Coverage trends (requires historical data)
220
- analyzeTrends(previousCoveragePath: string) {
221
- const previousSummary: CoverageSummary = JSON.parse(
222
- fs.readFileSync(previousCoveragePath, 'utf-8')
223
- );
224
-
225
- const trends = {
226
- lines: this.summary.total.lines.pct - previousSummary.total.lines.pct,
227
- statements: this.summary.total.statements.pct - previousSummary.total.statements.pct,
228
- functions: this.summary.total.functions.pct - previousSummary.total.functions.pct,
229
- branches: this.summary.total.branches.pct - previousSummary.total.branches.pct,
230
- };
231
-
232
- console.log(chalk.bold('\n📈 Coverage Trends\n'));
233
-
234
- Object.entries(trends).forEach(([type, change]) => {
235
- const arrow = change > 0 ? '↗' : change < 0 ? '↘' : '→';
236
- const color = change > 0 ? chalk.green : change < 0 ? chalk.red : chalk.gray;
237
- console.log(
238
- `${type.padEnd(12)} ${color(arrow)} ${color(`${change > 0 ? '+' : ''}${change.toFixed(2)}%`)}`
239
- );
240
- });
241
- }
242
-
243
- // Generate coverage report
244
- generateReport() {
245
- const lowCoverage = this.findLowCoverageFiles();
246
- const uncovered = this.findUncoveredFiles();
247
- const perfect = this.findPerfectCoverage();
248
-
249
- console.log(chalk.bold('\n🎯 Coverage Analysis Report\n'));
250
-
251
- // Summary
252
- this.printSummary();
253
-
254
- // Perfect coverage
255
- if (perfect.length > 0) {
256
- console.log(chalk.bold.green(`\n✓ Perfect Coverage (${perfect.length} files)`));
257
- perfect.slice(0, 5).forEach((file) => {
258
- console.log(chalk.green(` • ${file}`));
259
- });
260
- if (perfect.length > 5) {
261
- console.log(chalk.gray(` ... and ${perfect.length - 5} more`));
262
- }
263
- }
264
-
265
- // Uncovered files
266
- if (uncovered.length > 0) {
267
- console.log(chalk.bold.red(`\n✗ Uncovered Files (${uncovered.length})`));
268
- uncovered.slice(0, 10).forEach((file) => {
269
- console.log(chalk.red(` • ${file}`));
270
- });
271
- if (uncovered.length > 10) {
272
- console.log(chalk.gray(` ... and ${uncovered.length - 10} more`));
273
- }
274
- }
275
-
276
- // Low coverage files
277
- if (lowCoverage.length > 0) {
278
- console.log(chalk.bold.yellow(`\n⚠ Low Coverage Areas (${lowCoverage.length})`));
279
- lowCoverage.slice(0, 10).forEach(({ file, type, coverage, gap }) => {
280
- console.log(
281
- chalk.yellow(
282
- ` • ${file} - ${type}: ${coverage.toFixed(1)}% (gap: ${gap.toFixed(1)}%)`
283
- )
284
- );
285
- });
286
- if (lowCoverage.length > 10) {
287
- console.log(chalk.gray(` ... and ${lowCoverage.length - 10} more`));
288
- }
289
- }
290
-
291
- // Recommendations
292
- this.printRecommendations(lowCoverage, uncovered);
293
- }
294
-
295
- // Recommendations
296
- private printRecommendations(lowCoverage: any[], uncovered: string[]) {
297
- console.log(chalk.bold('\n💡 Recommendations\n'));
298
-
299
- if (uncovered.length > 0) {
300
- console.log(chalk.yellow('1. Add tests for uncovered files'));
301
- console.log(` Priority: ${uncovered.slice(0, 3).join(', ')}`);
302
- }
303
-
304
- if (lowCoverage.length > 0) {
305
- const topGap = lowCoverage[0];
306
- console.log(chalk.yellow(`2. Improve ${topGap.type} coverage in ${topGap.file}`));
307
- console.log(` Current: ${topGap.coverage.toFixed(1)}%, Target: 80%`);
308
- }
309
-
310
- const { total } = this.summary;
311
- if (total.branches.pct < 80) {
312
- console.log(chalk.yellow('3. Focus on branch coverage'));
313
- console.log(' Add tests for conditional logic and edge cases');
314
- }
315
-
316
- if (total.functions.pct < 80) {
317
- console.log(chalk.yellow('4. Increase function coverage'));
318
- console.log(' Test all exported functions and methods');
319
- }
320
- }
321
-
322
- // Helpers
323
- private formatCoverageLine(label: string, data: FileCoverage['lines']) {
324
- const percentage = data.pct.toFixed(2);
325
- const color =
326
- data.pct >= 80 ? chalk.green : data.pct >= 50 ? chalk.yellow : chalk.red;
327
-
328
- return `${label.padEnd(12)} ${color(percentage.padStart(6))}% ${chalk.gray(
329
- `(${data.covered}/${data.total})`
330
- )}`;
331
- }
332
-
333
- private shortenPath(file: string) {
334
- return file.replace(process.cwd(), '').replace(/^\//, '');
335
- }
336
- }
337
-
338
- // CLI usage
339
- if (require.main === module) {
340
- const analyzer = new CoverageAnalyzer();
341
- analyzer.generateReport();
342
- }
343
- ```
344
-
345
- ### 4. Coverage Badge Generation
346
-
347
- **scripts/generate-coverage-badge.ts**:
348
- ```typescript
349
- import fs from 'fs';
350
- import path from 'path';
351
-
352
- interface CoverageSummary {
353
- total: {
354
- lines: { pct: number };
355
- };
356
- }
357
-
358
- export function generateCoverageBadge() {
359
- const summary: CoverageSummary = JSON.parse(
360
- fs.readFileSync('coverage/coverage-summary.json', 'utf-8')
361
- );
362
-
363
- const coverage = summary.total.lines.pct;
364
- const color = coverage >= 80 ? 'brightgreen' : coverage >= 50 ? 'yellow' : 'red';
365
-
366
- const badge = `https://img.shields.io/badge/coverage-${coverage.toFixed(0)}%25-${color}`;
367
-
368
- // Update README.md
369
- const readme = fs.readFileSync('README.md', 'utf-8');
370
- const updatedReadme = readme.replace(
371
- /!\[Coverage\]\(.*?\)/,
372
- `![Coverage](${badge})`
373
- );
374
-
375
- fs.writeFileSync('README.md', updatedReadme);
376
- console.log(`✓ Updated coverage badge: ${coverage.toFixed(2)}%`);
377
- }
378
- ```
379
-
380
- ### 5. Coverage Gates
381
-
382
- **scripts/enforce-coverage-gates.ts**:
383
- ```typescript
384
- import fs from 'fs';
385
- import chalk from 'chalk';
386
-
387
- interface CoverageThresholds {
388
- lines: number;
389
- statements: number;
390
- functions: number;
391
- branches: number;
392
- }
393
-
394
- export class CoverageGate {
395
- private summary: any;
396
- private thresholds: CoverageThresholds;
397
-
398
- constructor(
399
- summaryPath = 'coverage/coverage-summary.json',
400
- thresholds: CoverageThresholds = {
401
- lines: 80,
402
- statements: 80,
403
- functions: 80,
404
- branches: 80,
405
- }
406
- ) {
407
- this.summary = JSON.parse(fs.readFileSync(summaryPath, 'utf-8'));
408
- this.thresholds = thresholds;
409
- }
410
-
411
- enforce(): boolean {
412
- const { total } = this.summary;
413
- const failures: string[] = [];
414
-
415
- console.log(chalk.bold('\n🚦 Coverage Gate Enforcement\n'));
416
-
417
- Object.entries(this.thresholds).forEach(([metric, threshold]) => {
418
- const actual = total[metric].pct;
419
- const passed = actual >= threshold;
420
-
421
- const status = passed ? chalk.green('✓') : chalk.red('✗');
422
- const color = passed ? chalk.green : chalk.red;
423
-
424
- console.log(
425
- `${status} ${metric.padEnd(12)} ${color(
426
- `${actual.toFixed(2)}%`
427
- )} ${chalk.gray(`(threshold: ${threshold}%)`)}`
428
- );
429
-
430
- if (!passed) {
431
- failures.push(`${metric}: ${actual.toFixed(2)}% < ${threshold}%`);
432
- }
433
- });
434
-
435
- if (failures.length > 0) {
436
- console.log(chalk.bold.red('\n✗ Coverage gate failed!'));
437
- console.log(chalk.red('\nFailures:'));
438
- failures.forEach((failure) => console.log(chalk.red(` • ${failure}`)));
439
- return false;
440
- }
441
-
442
- console.log(chalk.bold.green('\n✓ Coverage gate passed!'));
443
- return true;
444
- }
445
- }
446
-
447
- // CLI usage
448
- if (require.main === module) {
449
- const gate = new CoverageGate();
450
- const passed = gate.enforce();
451
- process.exit(passed ? 0 : 1);
452
- }
453
- ```
454
-
455
- ### 6. Diff Coverage Analysis
456
-
457
- **scripts/analyze-diff-coverage.ts**:
458
- ```typescript
459
- import { execSync } from 'child_process';
460
- import fs from 'fs';
461
- import chalk from 'chalk';
462
-
463
- export class DiffCoverageAnalyzer {
464
- private baseBranch: string;
465
-
466
- constructor(baseBranch = 'main') {
467
- this.baseBranch = baseBranch;
468
- }
469
-
470
- // Get changed files
471
- getChangedFiles(): string[] {
472
- const output = execSync(`git diff ${this.baseBranch} --name-only`, {
473
- encoding: 'utf-8',
474
- });
475
-
476
- return output
477
- .split('\n')
478
- .filter((file) => file.endsWith('.ts') || file.endsWith('.tsx'))
479
- .filter((file) => file.startsWith('src/'));
480
- }
481
-
482
- // Get coverage for changed files
483
- analyzeDiffCoverage() {
484
- const changedFiles = this.getChangedFiles();
485
- const coverage = JSON.parse(
486
- fs.readFileSync('coverage/coverage-summary.json', 'utf-8')
487
- );
488
-
489
- console.log(chalk.bold('\n📝 Diff Coverage Analysis\n'));
490
- console.log(chalk.gray(`Base branch: ${this.baseBranch}\n`));
491
-
492
- const results = changedFiles.map((file) => {
493
- const fullPath = `${process.cwd()}/${file}`;
494
- const fileCoverage = coverage[fullPath];
495
-
496
- if (!fileCoverage) {
497
- return {
498
- file,
499
- covered: false,
500
- lines: 0,
501
- statements: 0,
502
- functions: 0,
503
- branches: 0,
504
- };
505
- }
506
-
507
- return {
508
- file,
509
- covered: true,
510
- lines: fileCoverage.lines.pct,
511
- statements: fileCoverage.statements.pct,
512
- functions: fileCoverage.functions.pct,
513
- branches: fileCoverage.branches.pct,
514
- };
515
- });
516
-
517
- // Print results
518
- results.forEach((result) => {
519
- if (!result.covered) {
520
- console.log(chalk.red(`✗ ${result.file} - No coverage`));
521
- } else {
522
- const avgCoverage =
523
- (result.lines + result.statements + result.functions + result.branches) / 4;
524
- const color = avgCoverage >= 80 ? chalk.green : chalk.yellow;
525
-
526
- console.log(
527
- `${color('✓')} ${result.file} - ${color(`${avgCoverage.toFixed(1)}%`)}`
528
- );
529
- console.log(
530
- chalk.gray(
531
- ` Lines: ${result.lines.toFixed(1)}%, Functions: ${result.functions.toFixed(1)}%, Branches: ${result.branches.toFixed(1)}%`
532
- )
533
- );
534
- }
535
- });
536
-
537
- // Summary
538
- const uncovered = results.filter((r) => !r.covered).length;
539
- const lowCoverage = results.filter(
540
- (r) =>
541
- r.covered &&
542
- (r.lines < 80 || r.statements < 80 || r.functions < 80 || r.branches < 80)
543
- ).length;
544
-
545
- console.log(chalk.bold('\n📊 Diff Coverage Summary\n'));
546
- console.log(`Total changed files: ${results.length}`);
547
- console.log(chalk.red(`Uncovered: ${uncovered}`));
548
- console.log(chalk.yellow(`Low coverage: ${lowCoverage}`));
549
- console.log(
550
- chalk.green(`Good coverage: ${results.length - uncovered - lowCoverage}`)
551
- );
552
-
553
- return uncovered === 0 && lowCoverage === 0;
554
- }
555
- }
556
-
557
- // CLI usage
558
- if (require.main === module) {
559
- const analyzer = new DiffCoverageAnalyzer();
560
- const passed = analyzer.analyzeDiffCoverage();
561
- process.exit(passed ? 0 : 1);
562
- }
563
- ```
564
-
565
- ### 7. Uncovered Lines Reporter
566
-
567
- **scripts/report-uncovered-lines.ts**:
568
- ```typescript
569
- import fs from 'fs';
570
- import chalk from 'chalk';
571
-
572
- interface UncoveredRange {
573
- start: number;
574
- end: number;
575
- }
576
-
577
- interface FileCoverageDetail {
578
- path: string;
579
- statementMap: Record<string, any>;
580
- s: Record<string, number>;
581
- branchMap: Record<string, any>;
582
- b: Record<string, number[]>;
583
- fnMap: Record<string, any>;
584
- f: Record<string, number>;
585
- }
586
-
587
- export class UncoveredLinesReporter {
588
- private coverageData: Record<string, FileCoverageDetail>;
589
-
590
- constructor(coveragePath = 'coverage/coverage-final.json') {
591
- this.coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));
592
- }
593
-
594
- reportUncoveredLines(targetFile?: string) {
595
- const files = targetFile
596
- ? [targetFile]
597
- : Object.keys(this.coverageData).filter((f) => !f.includes('node_modules'));
598
-
599
- console.log(chalk.bold('\n🔍 Uncovered Lines Report\n'));
600
-
601
- files.forEach((file) => {
602
- const coverage = this.coverageData[file];
603
- if (!coverage) return;
604
-
605
- const uncoveredStatements = this.getUncoveredStatements(coverage);
606
- const uncoveredBranches = this.getUncoveredBranches(coverage);
607
- const uncoveredFunctions = this.getUncoveredFunctions(coverage);
608
-
609
- if (
610
- uncoveredStatements.length === 0 &&
611
- uncoveredBranches.length === 0 &&
612
- uncoveredFunctions.length === 0
613
- ) {
614
- return; // Skip files with perfect coverage
615
- }
616
-
617
- console.log(chalk.bold(this.shortenPath(file)));
618
-
619
- if (uncoveredStatements.length > 0) {
620
- console.log(chalk.yellow(' Uncovered statements:'));
621
- uncoveredStatements.forEach((range) => {
622
- console.log(chalk.gray(` Lines ${range.start}-${range.end}`));
623
- });
624
- }
625
-
626
- if (uncoveredBranches.length > 0) {
627
- console.log(chalk.yellow(' Uncovered branches:'));
628
- uncoveredBranches.forEach(({ line, type }) => {
629
- console.log(chalk.gray(` Line ${line} (${type})`));
630
- });
631
- }
632
-
633
- if (uncoveredFunctions.length > 0) {
634
- console.log(chalk.yellow(' Uncovered functions:'));
635
- uncoveredFunctions.forEach(({ name, line }) => {
636
- console.log(chalk.gray(` ${name} (line ${line})`));
637
- });
638
- }
639
-
640
- console.log();
641
- });
642
- }
643
-
644
- private getUncoveredStatements(coverage: FileCoverageDetail): UncoveredRange[] {
645
- const uncovered: UncoveredRange[] = [];
646
-
647
- Object.entries(coverage.s).forEach(([key, count]) => {
648
- if (count === 0) {
649
- const statement = coverage.statementMap[key];
650
- uncovered.push({
651
- start: statement.start.line,
652
- end: statement.end.line,
653
- });
654
- }
655
- });
656
-
657
- return uncovered;
658
- }
659
-
660
- private getUncoveredBranches(coverage: FileCoverageDetail) {
661
- const uncovered: Array<{ line: number; type: string }> = [];
662
-
663
- Object.entries(coverage.b).forEach(([key, branches]) => {
664
- const branch = coverage.branchMap[key];
665
- branches.forEach((count, idx) => {
666
- if (count === 0) {
667
- uncovered.push({
668
- line: branch.loc.start.line,
669
- type: branch.type,
670
- });
671
- }
672
- });
673
- });
674
-
675
- return uncovered;
676
- }
677
-
678
- private getUncoveredFunctions(coverage: FileCoverageDetail) {
679
- const uncovered: Array<{ name: string; line: number }> = [];
680
-
681
- Object.entries(coverage.f).forEach(([key, count]) => {
682
- if (count === 0) {
683
- const fn = coverage.fnMap[key];
684
- uncovered.push({
685
- name: fn.name || '(anonymous)',
686
- line: fn.loc.start.line,
687
- });
688
- }
689
- });
690
-
691
- return uncovered;
692
- }
693
-
694
- private shortenPath(file: string) {
695
- return file.replace(process.cwd(), '').replace(/^\//, '');
696
- }
697
- }
698
-
699
- // CLI usage
700
- if (require.main === module) {
701
- const reporter = new UncoveredLinesReporter();
702
- reporter.reportUncoveredLines();
703
- }
704
- ```
705
-
706
- ### 8. Coverage Comparison Tool
707
-
708
- **scripts/compare-coverage.ts**:
709
- ```typescript
710
- import fs from 'fs';
711
- import chalk from 'chalk';
712
-
713
- interface CoverageSummary {
714
- total: {
715
- lines: { pct: number };
716
- statements: { pct: number };
717
- functions: { pct: number };
718
- branches: { pct: number };
719
- };
720
- }
721
-
722
- export function compareCoverage(
723
- beforePath: string,
724
- afterPath: string = 'coverage/coverage-summary.json'
725
- ) {
726
- const before: CoverageSummary = JSON.parse(fs.readFileSync(beforePath, 'utf-8'));
727
- const after: CoverageSummary = JSON.parse(fs.readFileSync(afterPath, 'utf-8'));
728
-
729
- console.log(chalk.bold('\n📊 Coverage Comparison\n'));
730
-
731
- const metrics = ['lines', 'statements', 'functions', 'branches'] as const;
732
-
733
- metrics.forEach((metric) => {
734
- const beforePct = before.total[metric].pct;
735
- const afterPct = after.total[metric].pct;
736
- const diff = afterPct - beforePct;
737
-
738
- const arrow = diff > 0 ? '↗' : diff < 0 ? '↘' : '→';
739
- const color = diff > 0 ? chalk.green : diff < 0 ? chalk.red : chalk.gray;
740
-
741
- console.log(
742
- `${metric.padEnd(12)} ${beforePct.toFixed(2)}% ${arrow} ${afterPct.toFixed(2)}% ${color(
743
- `(${diff > 0 ? '+' : ''}${diff.toFixed(2)}%)`
744
- )}`
745
- );
746
- });
747
-
748
- // Recommendation
749
- if (after.total.lines.pct < before.total.lines.pct) {
750
- console.log(chalk.bold.red('\n⚠ Coverage decreased! Consider adding tests.'));
751
- } else if (after.total.lines.pct > before.total.lines.pct) {
752
- console.log(chalk.bold.green('\n✓ Coverage improved!'));
753
- } else {
754
- console.log(chalk.gray('\n→ Coverage unchanged.'));
755
- }
756
- }
757
- ```
758
-
759
- ### 9. CI/CD Integration
760
-
761
- **GitHub Actions (.github/workflows/coverage.yml)**:
762
- ```yaml
763
- name: Coverage
764
-
765
- on:
766
- push:
767
- branches: [main, develop]
768
- pull_request:
769
- branches: [main]
770
-
771
- jobs:
772
- coverage:
773
- runs-on: ubuntu-latest
774
-
775
- steps:
776
- - uses: actions/checkout@v4
777
- with:
778
- fetch-depth: 0 # Needed for diff coverage
779
-
780
- - uses: actions/setup-node@v4
781
- with:
782
- node-version: '20'
783
- cache: 'npm'
784
-
785
- - name: Install dependencies
786
- run: npm ci
787
-
788
- - name: Run tests with coverage
789
- run: npm run test:coverage
790
-
791
- - name: Analyze coverage
792
- run: npm run coverage:analyze
793
-
794
- - name: Enforce coverage gates
795
- run: npm run coverage:gate
796
-
797
- - name: Analyze diff coverage
798
- if: github.event_name == 'pull_request'
799
- run: npm run coverage:diff
800
-
801
- - name: Upload coverage to Codecov
802
- uses: codecov/codecov-action@v3
803
- with:
804
- files: ./coverage/lcov.info
805
- flags: unittests
806
- name: codecov-umbrella
807
- fail_ci_if_error: true
808
-
809
- - name: Upload coverage reports
810
- uses: actions/upload-artifact@v3
811
- with:
812
- name: coverage-report
813
- path: |
814
- coverage/
815
- !coverage/**/*.json
816
-
817
- - name: Comment PR with coverage
818
- if: github.event_name == 'pull_request'
819
- uses: romeovs/lcov-reporter-action@v0.3.1
820
- with:
821
- lcov-file: ./coverage/lcov.info
822
- github-token: ${{ secrets.GITHUB_TOKEN }}
823
- delete-old-comments: true
824
-
825
- - name: Generate coverage badge
826
- if: github.ref == 'refs/heads/main'
827
- run: npm run coverage:badge
828
- ```
829
-
830
- ### 10. Package Scripts
831
-
832
- ```json
833
- {
834
- "scripts": {
835
- "test:coverage": "vitest run --coverage",
836
- "coverage:analyze": "tsx scripts/analyze-coverage.ts",
837
- "coverage:gate": "tsx scripts/enforce-coverage-gates.ts",
838
- "coverage:diff": "tsx scripts/analyze-diff-coverage.ts",
839
- "coverage:uncovered": "tsx scripts/report-uncovered-lines.ts",
840
- "coverage:compare": "tsx scripts/compare-coverage.ts",
841
- "coverage:badge": "tsx scripts/generate-coverage-badge.ts",
842
- "coverage:report": "npm run test:coverage && npm run coverage:analyze",
843
- "coverage:html": "open coverage/index.html"
844
- },
845
- "devDependencies": {
846
- "@vitest/coverage-v8": "^1.0.4",
847
- "chalk": "^5.3.0",
848
- "tsx": "^4.7.0"
849
- }
850
- }
851
- ```
852
-
853
- ### 11. SonarQube Integration
854
-
855
- **sonar-project.properties**:
856
- ```properties
857
- sonar.projectKey=my-project
858
- sonar.projectName=My Project
859
- sonar.projectVersion=1.0.0
860
-
861
- # Source
862
- sonar.sources=src
863
- sonar.tests=tests
864
-
865
- # Language
866
- sonar.language=ts
867
- sonar.sourceEncoding=UTF-8
868
-
869
- # Coverage
870
- sonar.javascript.lcov.reportPaths=coverage/lcov.info
871
- sonar.coverage.exclusions=**/*.test.ts,**/*.spec.ts,**/mockData/**
872
-
873
- # Quality gates
874
- sonar.qualitygate.wait=true
875
- ```
876
-
877
- ### 12. Coverage Dashboard HTML
878
-
879
- **scripts/generate-coverage-dashboard.ts**:
880
- ```typescript
881
- import fs from 'fs';
882
-
883
- export function generateDashboard() {
884
- const summary = JSON.parse(
885
- fs.readFileSync('coverage/coverage-summary.json', 'utf-8')
886
- );
887
-
888
- const html = `
889
- <!DOCTYPE html>
890
- <html>
891
- <head>
892
- <title>Coverage Dashboard</title>
893
- <style>
894
- body { font-family: system-ui; padding: 20px; max-width: 1200px; margin: 0 auto; }
895
- .metric { display: inline-block; margin: 10px; padding: 20px; border-radius: 8px; }
896
- .high { background: #d4edda; }
897
- .medium { background: #fff3cd; }
898
- .low { background: #f8d7da; }
899
- h1 { color: #333; }
900
- .percentage { font-size: 2em; font-weight: bold; }
901
- </style>
902
- </head>
903
- <body>
904
- <h1>Test Coverage Dashboard</h1>
905
- <div class="metrics">
906
- ${generateMetricCard('Lines', summary.total.lines.pct)}
907
- ${generateMetricCard('Statements', summary.total.statements.pct)}
908
- ${generateMetricCard('Functions', summary.total.functions.pct)}
909
- ${generateMetricCard('Branches', summary.total.branches.pct)}
910
- </div>
911
- </body>
912
- </html>
913
- `;
914
-
915
- fs.writeFileSync('coverage/dashboard.html', html);
916
- }
917
-
918
- function generateMetricCard(name: string, pct: number) {
919
- const className = pct >= 80 ? 'high' : pct >= 50 ? 'medium' : 'low';
920
- return `
921
- <div class="metric ${className}">
922
- <div>${name}</div>
923
- <div class="percentage">${pct.toFixed(1)}%</div>
924
- </div>
925
- `;
926
- }
927
- ```
928
-
929
- ### 13. Best Practices
930
-
931
- **Coverage Goals**:
932
- - 80%+ overall coverage (all metrics)
933
- - 90%+ for critical paths
934
- - 100% for utility functions
935
- - No uncovered files
936
-
937
- **What to Focus On**:
938
- - Business logic (highest priority)
939
- - Error handling
940
- - Edge cases
941
- - Conditional branches
942
- - Integration points
943
-
944
- **What to Skip**:
945
- - Type definitions
946
- - Configuration files
947
- - Mock data
948
- - Test utilities
949
- - Build artifacts
950
-
951
- **Quality over Quantity**:
952
- - Meaningful tests > high coverage
953
- - Test behavior, not implementation
954
- - Focus on user journeys
955
- - Verify error scenarios
956
- - Check accessibility
957
-
958
- ## Workflow
959
-
960
- 1. Ask about coverage requirements and current state
961
- 2. Run coverage analysis with Vitest
962
- 3. Generate comprehensive coverage report
963
- 4. Identify low coverage areas and gaps
964
- 5. Analyze diff coverage for PRs
965
- 6. Report uncovered lines with specific locations
966
- 7. Enforce coverage gates
967
- 8. Generate badges and dashboards
968
- 9. Set up CI/CD integration
969
- 10. Provide actionable recommendations
970
- 11. Track coverage trends over time
971
-
972
- ## When to Use
973
-
974
- - Checking current test coverage
975
- - Identifying coverage gaps
976
- - Enforcing coverage standards
977
- - Setting up CI/CD coverage gates
978
- - Analyzing coverage trends
979
- - Generating coverage reports
980
- - Creating coverage dashboards
981
- - Improving code quality
982
-
983
- Achieve comprehensive test coverage with actionable insights!