
Crap Analysis
- 425 installs
- 1.1k repo stars
- Updated July 3, 2026
- aaronontheweb/dotnet-skills
crap-analysis is a .NET skill that computes CRAP scores from cyclomatic complexity and code coverage to prioritize high-risk methods needing tests or refactors before merge or release.
About
crap-analysis is an aaronontheweb dotnet-skills skill that calculates CRAP (Change Risk Anti-Patterns) scores using the formula Complexity × (1 − Coverage)² to expose risky .NET methods. It expects OpenCover-format coverage feeds and ReportGenerator Risk Hotspots output showing cyclomatic complexity alongside untested paths. Developers invoke crap-analysis while evaluating quality before changes, setting CI coverage thresholds, or deciding which classes deserve tests first. The skill supports pre-merge reviews, release readiness checks, and pipeline gates where high complexity with low coverage signals refactor or test work. It is invocable directly in agent workflows when coverage artifacts already exist or when agents help configure collection for a .NET solution.
- CRAP score calculation
- Complexity plus coverage risk
- Hotspot prioritization
- .NET test gap surfacing
- Refactor guidance
Crap Analysis by the numbers
- 425 all-time installs (skills.sh)
- Ranked #253 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill crap-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 425 |
|---|---|
| repo stars | ★ 1.1k |
| Last updated | July 3, 2026 |
| Repository | aaronontheweb/dotnet-skills ↗ |
How do you prioritize risky untested .NET code?
Compute CRAP scores from complexity and coverage to prioritize risky .NET methods that need tests or refactors before merge or release.
Who is it for?
.NET developers running OpenCover and ReportGenerator who need data-driven test prioritization before merge or release.
Skip if: Non-.NET codebases or teams without coverage collection tooling who cannot produce OpenCover or Risk Hotspot artifacts.
When should I use this skill?
A developer asks to analyze .NET coverage, compute CRAP scores, identify Risk Hotspots, or set CI coverage thresholds.
What you get
CRAP-scored method rankings, Risk Hotspot reports, coverage threshold guidance, and prioritized test/refactor targets.
- CRAP-ranked method list
- Risk Hotspot analysis
- coverage threshold recommendations
By the numbers
- Uses CRAP formula: Complexity × (1 − Coverage)²
- Integrates OpenCover coverage format with ReportGenerator Risk Hotspots
Files
CRAP Score Analysis
When to Use This Skill
Use this skill when:
- Evaluating code quality and test coverage before changes
- Identifying high-risk code that needs refactoring or testing
- Setting up coverage collection for a .NET project
- Prioritizing which code to test based on risk
- Establishing coverage thresholds for CI/CD pipelines
---
What is CRAP?
CRAP Score = Complexity x (1 - Coverage)^2
The CRAP (Change Risk Anti-Patterns) score combines cyclomatic complexity with test coverage to identify risky code.
| CRAP Score | Risk Level | Action Required |
|---|---|---|
| < 5 | Low | Well-tested, maintainable code |
| 5-30 | Medium | Acceptable but watch complexity |
| > 30 | High | Needs tests or refactoring |
Why CRAP Matters
- High complexity + low coverage = danger: Code that's hard to understand AND untested is risky to modify
- Complexity alone isn't enough: A complex method with 100% coverage is safer than a simple method with 0%
- Focuses effort: Prioritize testing on complex code, not simple getters/setters
CRAP Score Examples
| Method | Complexity | Coverage | Calculation | CRAP |
|---|---|---|---|---|
GetUserId() | 1 | 0% | 1 x (1 - 0)^2 | 1 |
ParseToken() | 54 | 52% | 54 x (1 - 0.52)^2 | 12.4 |
ValidateForm() | 20 | 0% | 20 x (1 - 0)^2 | 20 |
ProcessOrder() | 45 | 20% | 45 x (1 - 0.20)^2 | 28.8 |
ImportData() | 80 | 10% | 80 x (1 - 0.10)^2 | 64.8 |
---
Coverage Collection Setup
coverage.runsettings
Create a coverage.runsettings file in your repository root. The OpenCover format is required for CRAP score calculation because it includes cyclomatic complexity metrics.
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<!-- OpenCover format includes cyclomatic complexity for CRAP scores -->
<Format>cobertura,opencover</Format>
<!-- Exclude test and benchmark assemblies -->
<Exclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*</Exclude>
<!-- Exclude generated code, obsolete members, and explicit exclusions -->
<ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>
<!-- Exclude source-generated files, Blazor generated code, and migrations -->
<ExcludeByFile>**/obj/**/*,**/*.g.cs,**/*.designer.cs,**/*.razor.g.cs,**/*.razor.css.g.cs,**/Migrations/**/*</ExcludeByFile>
<!-- Exclude test projects -->
<IncludeTestAssembly>false</IncludeTestAssembly>
<!-- Optimization flags -->
<SingleHit>false</SingleHit>
<UseSourceLink>true</UseSourceLink>
<SkipAutoProps>true</SkipAutoProps>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>Key Configuration Options
| Option | Purpose |
|---|---|
Format | Must include opencover for complexity metrics |
Exclude | Exclude test/benchmark assemblies by pattern |
ExcludeByAttribute | Skip generated, obsolete, and explicitly excluded code (includes ExcludeFromCodeCoverageAttribute) |
ExcludeByFile | Skip source-generated files, Blazor components, and migrations |
SkipAutoProps | Don't count auto-properties as branches |
---
ReportGenerator Installation
Install ReportGenerator as a local tool for generating HTML reports with Risk Hotspots.
Add to .config/dotnet-tools.json
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-reportgenerator-globaltool": {
"version": "5.4.5",
"commands": ["reportgenerator"],
"rollForward": false
}
}
}Then restore:
dotnet tool restoreOr Install Globally
dotnet tool install --global dotnet-reportgenerator-globaltool---
Collecting Coverage
Run Tests with Coverage Collection
# Clean previous results
rm -rf coverage/ TestResults/
# Run unit tests with coverage
dotnet test tests/MyApp.Tests.Unit \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
# Run integration tests (optional, adds to coverage)
dotnet test tests/MyApp.Tests.Integration \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResultsGenerate HTML Report
dotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;TextSummary;MarkdownSummaryGithub"Report Types
| Type | Description | Output |
|---|---|---|
Html | Full interactive report | coverage/index.html |
TextSummary | Plain text summary | coverage/Summary.txt |
MarkdownSummaryGithub | GitHub-compatible markdown | coverage/SummaryGithub.md |
Badges | SVG badges for README | coverage/badge_*.svg |
Cobertura | Merged Cobertura XML | coverage/Cobertura.xml |
---
Reading the Report
Risk Hotspots Section
The HTML report includes a Risk Hotspots section showing methods sorted by complexity:
- Cyclomatic Complexity: Number of independent paths through code (if/else, switch cases, loops)
- NPath Complexity: Number of acyclic execution paths (exponential growth with nesting)
- Crap Score: Calculated from complexity and coverage
Interpreting Results
Risk Hotspots
─────────────
Method Complexity Coverage Crap Score
──────────────────────────────────────────────────────────────────
DataImporter.ParseRecord() 54 52% 12.4
AuthService.ValidateToken() 32 0% 32.0 ← HIGH RISK
OrderProcessor.Calculate() 28 85% 1.3
UserService.CreateUser() 15 100% 0.0Action items:
ValidateToken()has CRAP > 30 with 0% coverage - test immediately or refactorParseRecord()is complex but has decent coverage - acceptableCreateUser()andCalculate()are well-tested - safe to modify
---
Coverage Thresholds
Recommended Standards
| Coverage Type | Target | Action |
|---|---|---|
| Line Coverage | > 80% | Good for most projects |
| Branch Coverage | > 60% | Catches conditional logic |
| CRAP Score | < 30 | Maximum for new code |
Configuring Thresholds
Create coverage.props in your repository:
<Project>
<PropertyGroup>
<!-- Coverage thresholds for CI enforcement -->
<CoverageThresholdLine>80</CoverageThresholdLine>
<CoverageThresholdBranch>60</CoverageThresholdBranch>
</PropertyGroup>
</Project>---
CI/CD Integration
GitHub Actions
name: Coverage
on:
pull_request:
branches: [main, dev]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Restore tools
run: dotnet tool restore
- name: Run tests with coverage
run: |
dotnet test \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
- name: Generate report
run: |
dotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;MarkdownSummaryGithub;Cobertura"
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
- name: Add coverage to PR
uses: marocchino/sticky-pull-request-comment@v2
with:
path: coverage/SummaryGithub.mdAzure Pipelines
- task: DotNetCoreCLI@2
displayName: 'Run tests with coverage'
inputs:
command: 'test'
arguments: '--settings coverage.runsettings --collect:"XPlat Code Coverage" --results-directory $(Build.SourcesDirectory)/TestResults'
- task: DotNetCoreCLI@2
displayName: 'Generate coverage report'
inputs:
command: 'custom'
custom: 'reportgenerator'
arguments: '-reports:"$(Build.SourcesDirectory)/TestResults/**/coverage.opencover.xml" -targetdir:"$(Build.SourcesDirectory)/coverage" -reporttypes:"HtmlInline_AzurePipelines;Cobertura"'
- task: PublishCodeCoverageResults@2
displayName: 'Publish coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Build.SourcesDirectory)/coverage/Cobertura.xml'---
Quick Reference
One-Liner Commands
# Full analysis workflow
rm -rf coverage/ TestResults/ && \
dotnet test --settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults && \
dotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;TextSummary"
# View summary
cat coverage/Summary.txt
# Open HTML report (Linux)
xdg-open coverage/index.html
# Open HTML report (macOS)
open coverage/index.html
# Open HTML report (Windows)
start coverage/index.htmlProject Standards
| Metric | New Code | Legacy Code |
|---|---|---|
| Line Coverage | 80%+ | 60%+ (improve gradually) |
| Branch Coverage | 60%+ | 40%+ (improve gradually) |
| Maximum CRAP | 30 | Document exceptions |
| High-risk methods | Must have tests | Add tests before modifying |
---
What Gets Excluded
The recommended coverage.runsettings excludes:
| Pattern | Reason |
|---|---|
[*.Tests]* | Test assemblies aren't production code |
[*.Benchmark]* | Benchmark projects |
[*.Migrations]* | Database migrations (generated) |
GeneratedCodeAttribute | Source generators |
CompilerGeneratedAttribute | Compiler-generated code |
ExcludeFromCodeCoverageAttribute | Explicit developer opt-out |
*.g.cs, *.designer.cs | Generated files |
*.razor.g.cs | Blazor component generated code |
*.razor.css.g.cs | Blazor CSS isolation generated code |
**/Migrations/**/* | EF Core migrations (auto-generated) |
SkipAutoProps | Auto-properties (trivial branches) |
---
When to Update Thresholds
Lower thresholds temporarily for:
- Legacy codebases being modernized (document in README)
- Generated code that can't be modified
- Third-party wrapper code
Never lower thresholds for:
- "It's too hard to test" - refactor instead
- "We'll add tests later" - add them now
- New features - should meet standards from the start
---
Additional Resources
- Coverlet Documentation: https://github.com/coverlet-coverage/coverlet
- ReportGenerator: https://github.com/danielpalme/ReportGenerator
- CRAP Score Original Paper: http://www.artima.com/weblogs/viewpost.jsp?thread=215899
Related skills
How it compares
Pick crap-analysis over generic lint rules when you need coverage-weighted risk ranking tied to OpenCover and ReportGenerator artifacts.
FAQ
What is the CRAP formula in crap-analysis?
crap-analysis uses CRAP Score = Complexity × (1 − Coverage)². Higher scores flag .NET methods combining high cyclomatic complexity with low test coverage as refactor or test priorities.
What coverage formats does crap-analysis require?
crap-analysis expects OpenCover-format coverage data and ReportGenerator Risk Hotspots output listing cyclomatic complexity and untested paths. Agents use those artifacts to rank risky methods before merge.
When should .NET teams run crap-analysis?
.NET teams should run crap-analysis before significant changes, during release readiness reviews, or while configuring CI coverage gates. The skill prioritizes which methods need tests when time is limited.