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