[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6E5WDVB8JS6JG6W39FTPR38":3,"skill-same:01M6E5WDVB8JS6JG6W39FTPR38":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":34,"linkOnly":35,"markdownContent":36,"markdownBody":37,"frontMatter":38,"refName":40,"commitSha":41,"skillDirectory":42,"forksCount":43,"repositoryHtmlUrl":44,"repositoryHomepageUrl":45,"ownerHtmlUrl":46,"ownerBlogUrl":6,"ownerDescription":47,"updatedAt":48,"install":49,"resources":61},"01M6E5WDVB8JS6JG6W39FTPR38","crap-analysis",null,"Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FAaronontheweb\u002Fdotnet-skills\u002Fcrap-analysis\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"analysis","crap analysis","change","code","risk","format","analyze","coverage","use","anti-patterns","complexity","patterns","code coverage","paths","reportgenerator","2026-11-10 10:12:30",false,"---\nname: crap-analysis\ndescription: Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.\ninvocable: true\n---\n\n# CRAP Score Analysis\n\n## When to Use This Skill\n\nUse this skill when:\n- Evaluating code quality and test coverage before changes\n- Identifying high-risk code that needs refactoring or testing\n- Setting up coverage collection for a .NET project\n- Prioritizing which code to test based on risk\n- Establishing coverage thresholds for CI\u002FCD pipelines\n\n---\n\n## What is CRAP?\n\n**CRAP Score = Complexity x (1 - Coverage)^2**\n\nThe CRAP (Change Risk Anti-Patterns) score combines cyclomatic complexity with test coverage to identify risky code.\n\n| CRAP Score | Risk Level | Action Required |\n|------------|------------|-----------------|\n| **\u003C 5** | Low | Well-tested, maintainable code |\n| **5-30** | Medium | Acceptable but watch complexity |\n| **> 30** | High | Needs tests or refactoring |\n\n### Why CRAP Matters\n\n- **High complexity + low coverage = danger**: Code that's hard to understand AND untested is risky to modify\n- **Complexity alone isn't enough**: A complex method with 100% coverage is safer than a simple method with 0%\n- **Focuses effort**: Prioritize testing on complex code, not simple getters\u002Fsetters\n\n### CRAP Score Examples\n\n| Method | Complexity | Coverage | Calculation | CRAP |\n|--------|------------|----------|-------------|------|\n| `GetUserId()` | 1 | 0% | 1 x (1 - 0)^2 | **1** |\n| `ParseToken()` | 54 | 52% | 54 x (1 - 0.52)^2 | **12.4** |\n| `ValidateForm()` | 20 | 0% | 20 x (1 - 0)^2 | **20** |\n| `ProcessOrder()` | 45 | 20% | 45 x (1 - 0.20)^2 | **28.8** |\n| `ImportData()` | 80 | 10% | 80 x (1 - 0.10)^2 | **64.8** |\n\n---\n\n## Coverage Collection Setup\n\n### coverage.runsettings\n\nCreate a `coverage.runsettings` file in your repository root. The **OpenCover format is required** for CRAP score calculation because it includes cyclomatic complexity metrics.\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\u003CRunSettings>\n  \u003CDataCollectionRunSettings>\n    \u003CDataCollectors>\n      \u003CDataCollector friendlyName=\"XPlat code coverage\">\n        \u003CConfiguration>\n          \u003C!-- OpenCover format includes cyclomatic complexity for CRAP scores -->\n          \u003CFormat>cobertura,opencover\u003C\u002FFormat>\n\n          \u003C!-- Exclude test and benchmark assemblies -->\n          \u003CExclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*\u003C\u002FExclude>\n\n          \u003C!-- Exclude generated code, obsolete members, and explicit exclusions -->\n          \u003CExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute\u003C\u002FExcludeByAttribute>\n\n          \u003C!-- Exclude source-generated files, Blazor generated code, and migrations -->\n          \u003CExcludeByFile>**\u002Fobj\u002F**\u002F*,**\u002F*.g.cs,**\u002F*.designer.cs,**\u002F*.razor.g.cs,**\u002F*.razor.css.g.cs,**\u002FMigrations\u002F**\u002F*\u003C\u002FExcludeByFile>\n\n          \u003C!-- Exclude test projects -->\n          \u003CIncludeTestAssembly>false\u003C\u002FIncludeTestAssembly>\n\n          \u003C!-- Optimization flags -->\n          \u003CSingleHit>false\u003C\u002FSingleHit>\n          \u003CUseSourceLink>true\u003C\u002FUseSourceLink>\n          \u003CSkipAutoProps>true\u003C\u002FSkipAutoProps>\n        \u003C\u002FConfiguration>\n      \u003C\u002FDataCollector>\n    \u003C\u002FDataCollectors>\n  \u003C\u002FDataCollectionRunSettings>\n\u003C\u002FRunSettings>\n```\n\n### Key Configuration Options\n\n| Option | Purpose |\n|--------|---------|\n| `Format` | Must include `opencover` for complexity metrics |\n| `Exclude` | Exclude test\u002Fbenchmark assemblies by pattern |\n| `ExcludeByAttribute` | Skip generated, obsolete, and explicitly excluded code (includes `ExcludeFromCodeCoverageAttribute`) |\n| `ExcludeByFile` | Skip source-generated files, Blazor components, and migrations |\n| `SkipAutoProps` | Don't count auto-properties as branches |\n\n---\n\n## ReportGenerator Installation\n\nInstall ReportGenerator as a local tool for generating HTML reports with Risk Hotspots.\n\n### Add to .config\u002Fdotnet-tools.json\n\n```json\n{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"dotnet-reportgenerator-globaltool\": {\n      \"version\": \"5.4.5\",\n      \"commands\": [\"reportgenerator\"],\n      \"rollForward\": false\n    }\n  }\n}\n```\n\nThen restore:\n\n```bash\ndotnet tool restore\n```\n\n### Or Install Globally\n\n```bash\ndotnet tool install --global dotnet-reportgenerator-globaltool\n```\n\n---\n\n## Collecting Coverage\n\n### Run Tests with Coverage Collection\n\n```bash\n# Clean previous results\nrm -rf coverage\u002F TestResults\u002F\n\n# Run unit tests with coverage\ndotnet test tests\u002FMyApp.Tests.Unit \\\n  --settings coverage.runsettings \\\n  --collect:\"XPlat Code Coverage\" \\\n  --results-directory .\u002FTestResults\n\n# Run integration tests (optional, adds to coverage)\ndotnet test tests\u002FMyApp.Tests.Integration \\\n  --settings coverage.runsettings \\\n  --collect:\"XPlat Code Coverage\" \\\n  --results-directory .\u002FTestResults\n```\n\n### Generate HTML Report\n\n```bash\ndotnet reportgenerator \\\n  -reports:\"TestResults\u002F**\u002Fcoverage.opencover.xml\" \\\n  -targetdir:\"coverage\" \\\n  -reporttypes:\"Html;TextSummary;MarkdownSummaryGithub\"\n```\n\n### Report Types\n\n| Type | Description | Output |\n|------|-------------|--------|\n| `Html` | Full interactive report | `coverage\u002Findex.html` |\n| `TextSummary` | Plain text summary | `coverage\u002FSummary.txt` |\n| `MarkdownSummaryGithub` | GitHub-compatible markdown | `coverage\u002FSummaryGithub.md` |\n| `Badges` | SVG badges for README | `coverage\u002Fbadge_*.svg` |\n| `Cobertura` | Merged Cobertura XML | `coverage\u002FCobertura.xml` |\n\n---\n\n## Reading the Report\n\n### Risk Hotspots Section\n\nThe HTML report includes a **Risk Hotspots** section showing methods sorted by complexity:\n\n- **Cyclomatic Complexity**: Number of independent paths through code (if\u002Felse, switch cases, loops)\n- **NPath Complexity**: Number of acyclic execution paths (exponential growth with nesting)\n- **Crap Score**: Calculated from complexity and coverage\n\n### Interpreting Results\n\n```\nRisk Hotspots\n─────────────\nMethod                          Complexity  Coverage  Crap Score\n──────────────────────────────────────────────────────────────────\nDataImporter.ParseRecord()      54          52%       12.4\nAuthService.ValidateToken()     32          0%        32.0   ← HIGH RISK\nOrderProcessor.Calculate()      28          85%       1.3\nUserService.CreateUser()        15          100%      0.0\n```\n\n**Action items:**\n- `ValidateToken()` has CRAP > 30 with 0% coverage - **test immediately or refactor**\n- `ParseRecord()` is complex but has decent coverage - acceptable\n- `CreateUser()` and `Calculate()` are well-tested - safe to modify\n\n---\n\n## Coverage Thresholds\n\n### Recommended Standards\n\n| Coverage Type | Target | Action |\n|---------------|--------|--------|\n| Line Coverage | > 80% | Good for most projects |\n| Branch Coverage | > 60% | Catches conditional logic |\n| CRAP Score | \u003C 30 | Maximum for new code |\n\n### Configuring Thresholds\n\nCreate `coverage.props` in your repository:\n\n```xml\n\u003CProject>\n  \u003CPropertyGroup>\n    \u003C!-- Coverage thresholds for CI enforcement -->\n    \u003CCoverageThresholdLine>80\u003C\u002FCoverageThresholdLine>\n    \u003CCoverageThresholdBranch>60\u003C\u002FCoverageThresholdBranch>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n---\n\n## CI\u002FCD Integration\n\n### GitHub Actions\n\n```yaml\nname: Coverage\n\non:\n  pull_request:\n    branches: [main, dev]\n\njobs:\n  coverage:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup .NET\n        uses: actions\u002Fsetup-dotnet@v4\n        with:\n          dotnet-version: '9.0.x'\n\n      - name: Restore tools\n        run: dotnet tool restore\n\n      - name: Run tests with coverage\n        run: |\n          dotnet test \\\n            --settings coverage.runsettings \\\n            --collect:\"XPlat Code Coverage\" \\\n            --results-directory .\u002FTestResults\n\n      - name: Generate report\n        run: |\n          dotnet reportgenerator \\\n            -reports:\"TestResults\u002F**\u002Fcoverage.opencover.xml\" \\\n            -targetdir:\"coverage\" \\\n            -reporttypes:\"Html;MarkdownSummaryGithub;Cobertura\"\n\n      - name: Upload coverage report\n        uses: actions\u002Fupload-artifact@v4\n        with:\n          name: coverage-report\n          path: coverage\u002F\n\n      - name: Add coverage to PR\n        uses: marocchino\u002Fsticky-pull-request-comment@v2\n        with:\n          path: coverage\u002FSummaryGithub.md\n```\n\n### Azure Pipelines\n\n```yaml\n- task: DotNetCoreCLI@2\n  displayName: 'Run tests with coverage'\n  inputs:\n    command: 'test'\n    arguments: '--settings coverage.runsettings --collect:\"XPlat Code Coverage\" --results-directory $(Build.SourcesDirectory)\u002FTestResults'\n\n- task: DotNetCoreCLI@2\n  displayName: 'Generate coverage report'\n  inputs:\n    command: 'custom'\n    custom: 'reportgenerator'\n    arguments: '-reports:\"$(Build.SourcesDirectory)\u002FTestResults\u002F**\u002Fcoverage.opencover.xml\" -targetdir:\"$(Build.SourcesDirectory)\u002Fcoverage\" -reporttypes:\"HtmlInline_AzurePipelines;Cobertura\"'\n\n- task: PublishCodeCoverageResults@2\n  displayName: 'Publish coverage'\n  inputs:\n    codeCoverageTool: 'Cobertura'\n    summaryFileLocation: '$(Build.SourcesDirectory)\u002Fcoverage\u002FCobertura.xml'\n```\n\n---\n\n## Quick Reference\n\n### One-Liner Commands\n\n```bash\n# Full analysis workflow\nrm -rf coverage\u002F TestResults\u002F && \\\ndotnet test --settings coverage.runsettings \\\n  --collect:\"XPlat Code Coverage\" \\\n  --results-directory .\u002FTestResults && \\\ndotnet reportgenerator \\\n  -reports:\"TestResults\u002F**\u002Fcoverage.opencover.xml\" \\\n  -targetdir:\"coverage\" \\\n  -reporttypes:\"Html;TextSummary\"\n\n# View summary\ncat coverage\u002FSummary.txt\n\n# Open HTML report (Linux)\nxdg-open coverage\u002Findex.html\n\n# Open HTML report (macOS)\nopen coverage\u002Findex.html\n\n# Open HTML report (Windows)\nstart coverage\u002Findex.html\n```\n\n### Project Standards\n\n| Metric | New Code | Legacy Code |\n|--------|----------|-------------|\n| Line Coverage | 80%+ | 60%+ (improve gradually) |\n| Branch Coverage | 60%+ | 40%+ (improve gradually) |\n| Maximum CRAP | 30 | Document exceptions |\n| High-risk methods | Must have tests | Add tests before modifying |\n\n---\n\n## What Gets Excluded\n\nThe recommended `coverage.runsettings` excludes:\n\n| Pattern | Reason |\n|---------|--------|\n| `[*.Tests]*` | Test assemblies aren't production code |\n| `[*.Benchmark]*` | Benchmark projects |\n| `[*.Migrations]*` | Database migrations (generated) |\n| `GeneratedCodeAttribute` | Source generators |\n| `CompilerGeneratedAttribute` | Compiler-generated code |\n| `ExcludeFromCodeCoverageAttribute` | Explicit developer opt-out |\n| `*.g.cs`, `*.designer.cs` | Generated files |\n| `*.razor.g.cs` | Blazor component generated code |\n| `*.razor.css.g.cs` | Blazor CSS isolation generated code |\n| `**\u002FMigrations\u002F**\u002F*` | EF Core migrations (auto-generated) |\n| `SkipAutoProps` | Auto-properties (trivial branches) |\n\n---\n\n## When to Update Thresholds\n\n**Lower thresholds temporarily for:**\n- Legacy codebases being modernized (document in README)\n- Generated code that can't be modified\n- Third-party wrapper code\n\n**Never lower thresholds for:**\n- \"It's too hard to test\" - refactor instead\n- \"We'll add tests later\" - add them now\n- New features - should meet standards from the start\n\n---\n\n## Additional Resources\n\n- **Coverlet Documentation**: https:\u002F\u002Fgithub.com\u002Fcoverlet-coverage\u002Fcoverlet\n- **ReportGenerator**: https:\u002F\u002Fgithub.com\u002Fdanielpalme\u002FReportGenerator\n- **CRAP Score Original Paper**: http:\u002F\u002Fwww.artima.com\u002Fweblogs\u002Fviewpost.jsp?thread=215899\n\n","# CRAP Score Analysis\n\n## When to Use This Skill\n\nUse this skill when:\n- Evaluating code quality and test coverage before changes\n- Identifying high-risk code that needs refactoring or testing\n- Setting up coverage collection for a .NET project\n- Prioritizing which code to test based on risk\n- Establishing coverage thresholds for CI\u002FCD pipelines\n\n---\n\n## What is CRAP?\n\n**CRAP Score = Complexity x (1 - Coverage)^2**\n\nThe CRAP (Change Risk Anti-Patterns) score combines cyclomatic complexity with test coverage to identify risky code.\n\n| CRAP Score | Risk Level | Action Required |\n|------------|------------|-----------------|\n| **\u003C 5** | Low | Well-tested, maintainable code |\n| **5-30** | Medium | Acceptable but watch complexity |\n| **> 30** | High | Needs tests or refactoring |\n\n### Why CRAP Matters\n\n- **High complexity + low coverage = danger**: Code that's hard to understand AND untested is risky to modify\n- **Complexity alone isn't enough**: A complex method with 100% coverage is safer than a simple method with 0%\n- **Focuses effort**: Prioritize testing on complex code, not simple getters\u002Fsetters\n\n### CRAP Score Examples\n\n| Method | Complexity | Coverage | Calculation | CRAP |\n|--------|------------|----------|-------------|------|\n| `GetUserId()` | 1 | 0% | 1 x (1 - 0)^2 | **1** |\n| `ParseToken()` | 54 | 52% | 54 x (1 - 0.52)^2 | **12.4** |\n| `ValidateForm()` | 20 | 0% | 20 x (1 - 0)^2 | **20** |\n| `ProcessOrder()` | 45 | 20% | 45 x (1 - 0.20)^2 | **28.8** |\n| `ImportData()` | 80 | 10% | 80 x (1 - 0.10)^2 | **64.8** |\n\n---\n\n## Coverage Collection Setup\n\n### coverage.runsettings\n\nCreate a `coverage.runsettings` file in your repository root. The **OpenCover format is required** for CRAP score calculation because it includes cyclomatic complexity metrics.\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\u003CRunSettings>\n  \u003CDataCollectionRunSettings>\n    \u003CDataCollectors>\n      \u003CDataCollector friendlyName=\"XPlat code coverage\">\n        \u003CConfiguration>\n          \u003C!-- OpenCover format includes cyclomatic complexity for CRAP scores -->\n          \u003CFormat>cobertura,opencover\u003C\u002FFormat>\n\n          \u003C!-- Exclude test and benchmark assemblies -->\n          \u003CExclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*\u003C\u002FExclude>\n\n          \u003C!-- Exclude generated code, obsolete members, and explicit exclusions -->\n          \u003CExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute\u003C\u002FExcludeByAttribute>\n\n          \u003C!-- Exclude source-generated files, Blazor generated code, and migrations -->\n          \u003CExcludeByFile>**\u002Fobj\u002F**\u002F*,**\u002F*.g.cs,**\u002F*.designer.cs,**\u002F*.razor.g.cs,**\u002F*.razor.css.g.cs,**\u002FMigrations\u002F**\u002F*\u003C\u002FExcludeByFile>\n\n          \u003C!-- Exclude test projects -->\n          \u003CIncludeTestAssembly>false\u003C\u002FIncludeTestAssembly>\n\n          \u003C!-- Optimization flags -->\n          \u003CSingleHit>false\u003C\u002FSingleHit>\n          \u003CUseSourceLink>true\u003C\u002FUseSourceLink>\n          \u003CSkipAutoProps>true\u003C\u002FSkipAutoProps>\n        \u003C\u002FConfiguration>\n      \u003C\u002FDataCollector>\n    \u003C\u002FDataCollectors>\n  \u003C\u002FDataCollectionRunSettings>\n\u003C\u002FRunSettings>\n```\n\n### Key Configuration Options\n\n| Option | Purpose |\n|--------|---------|\n| `Format` | Must include `opencover` for complexity metrics |\n| `Exclude` | Exclude test\u002Fbenchmark assemblies by pattern |\n| `ExcludeByAttribute` | Skip generated, obsolete, and explicitly excluded code (includes `ExcludeFromCodeCoverageAttribute`) |\n| `ExcludeByFile` | Skip source-generated files, Blazor components, and migrations |\n| `SkipAutoProps` | Don't count auto-properties as branches |\n\n---\n\n## ReportGenerator Installation\n\nInstall ReportGenerator as a local tool for generating HTML reports with Risk Hotspots.\n\n### Add to .config\u002Fdotnet-tools.json\n\n```json\n{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"dotnet-reportgenerator-globaltool\": {\n      \"version\": \"5.4.5\",\n      \"commands\": [\"reportgenerator\"],\n      \"rollForward\": false\n    }\n  }\n}\n```\n\nThen restore:\n\n```bash\ndotnet tool restore\n```\n\n### Or Install Globally\n\n```bash\ndotnet tool install --global dotnet-reportgenerator-globaltool\n```\n\n---\n\n## Collecting Coverage\n\n### Run Tests with Coverage Collection\n\n```bash\n# Clean previous results\nrm -rf coverage\u002F TestResults\u002F\n\n# Run unit tests with coverage\ndotnet test tests\u002FMyApp.Tests.Unit \\\n  --settings coverage.runsettings \\\n  --collect:\"XPlat Code Coverage\" \\\n  --results-directory .\u002FTestResults\n\n# Run integration tests (optional, adds to coverage)\ndotnet test tests\u002FMyApp.Tests.Integration \\\n  --settings coverage.runsettings \\\n  --collect:\"XPlat Code Coverage\" \\\n  --results-directory .\u002FTestResults\n```\n\n### Generate HTML Report\n\n```bash\ndotnet reportgenerator \\\n  -reports:\"TestResults\u002F**\u002Fcoverage.opencover.xml\" \\\n  -targetdir:\"coverage\" \\\n  -reporttypes:\"Html;TextSummary;MarkdownSummaryGithub\"\n```\n\n### Report Types\n\n| Type | Description | Output |\n|------|-------------|--------|\n| `Html` | Full interactive report | `coverage\u002Findex.html` |\n| `TextSummary` | Plain text summary | `coverage\u002FSummary.txt` |\n| `MarkdownSummaryGithub` | GitHub-compatible markdown | `coverage\u002FSummaryGithub.md` |\n| `Badges` | SVG badges for README | `coverage\u002Fbadge_*.svg` |\n| `Cobertura` | Merged Cobertura XML | `coverage\u002FCobertura.xml` |\n\n---\n\n## Reading the Report\n\n### Risk Hotspots Section\n\nThe HTML report includes a **Risk Hotspots** section showing methods sorted by complexity:\n\n- **Cyclomatic Complexity**: Number of independent paths through code (if\u002Felse, switch cases, loops)\n- **NPath Complexity**: Number of acyclic execution paths (exponential growth with nesting)\n- **Crap Score**: Calculated from complexity and coverage\n\n### Interpreting Results\n\n```\nRisk Hotspots\n─────────────\nMethod                          Complexity  Coverage  Crap Score\n──────────────────────────────────────────────────────────────────\nDataImporter.ParseRecord()      54          52%       12.4\nAuthService.ValidateToken()     32          0%        32.0   ← HIGH RISK\nOrderProcessor.Calculate()      28          85%       1.3\nUserService.CreateUser()        15          100%      0.0\n```\n\n**Action items:**\n- `ValidateToken()` has CRAP > 30 with 0% coverage - **test immediately or refactor**\n- `ParseRecord()` is complex but has decent coverage - acceptable\n- `CreateUser()` and `Calculate()` are well-tested - safe to modify\n\n---\n\n## Coverage Thresholds\n\n### Recommended Standards\n\n| Coverage Type | Target | Action |\n|---------------|--------|--------|\n| Line Coverage | > 80% | Good for most projects |\n| Branch Coverage | > 60% | Catches conditional logic |\n| CRAP Score | \u003C 30 | Maximum for new code |\n\n### Configuring Thresholds\n\nCreate `coverage.props` in your repository:\n\n```xml\n\u003CProject>\n  \u003CPropertyGroup>\n    \u003C!-- Coverage thresholds for CI enforcement -->\n    \u003CCoverageThresholdLine>80\u003C\u002FCoverageThresholdLine>\n    \u003CCoverageThresholdBranch>60\u003C\u002FCoverageThresholdBranch>\n  \u003C\u002FPropertyGroup>\n\u003C\u002FProject>\n```\n\n---\n\n## CI\u002FCD Integration\n\n### GitHub Actions\n\n```yaml\nname: Coverage\n\non:\n  pull_request:\n    branches: [main, dev]\n\njobs:\n  coverage:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup .NET\n        uses: actions\u002Fsetup-dotnet@v4\n        with:\n          dotnet-version: '9.0.x'\n\n      - name: Restore tools\n        run: dotnet tool restore\n\n      - name: Run tests with coverage\n        run: |\n          dotnet test \\\n            --settings coverage.runsettings \\\n            --collect:\"XPlat Code Coverage\" \\\n            --results-directory .\u002FTestResults\n\n      - name: Generate report\n        run: |\n          dotnet reportgenerator \\\n            -reports:\"TestResults\u002F**\u002Fcoverage.opencover.xml\" \\\n            -targetdir:\"coverage\" \\\n            -reporttypes:\"Html;MarkdownSummaryGithub;Cobertura\"\n\n      - name: Upload coverage report\n        uses: actions\u002Fupload-artifact@v4\n        with:\n          name: coverage-report\n          path: coverage\u002F\n\n      - name: Add coverage to PR\n        uses: marocchino\u002Fsticky-pull-request-comment@v2\n        with:\n          path: coverage\u002FSummaryGithub.md\n```\n\n### Azure Pipelines\n\n```yaml\n- task: DotNetCoreCLI@2\n  displayName: 'Run tests with coverage'\n  inputs:\n    command: 'test'\n    arguments: '--settings coverage.runsettings --collect:\"XPlat Code Coverage\" --results-directory $(Build.SourcesDirectory)\u002FTestResults'\n\n- task: DotNetCoreCLI@2\n  displayName: 'Generate coverage report'\n  inputs:\n    command: 'custom'\n    custom: 'reportgenerator'\n    arguments: '-reports:\"$(Build.SourcesDirectory)\u002FTestResults\u002F**\u002Fcoverage.opencover.xml\" -targetdir:\"$(Build.SourcesDirectory)\u002Fcoverage\" -reporttypes:\"HtmlInline_AzurePipelines;Cobertura\"'\n\n- task: PublishCodeCoverageResults@2\n  displayName: 'Publish coverage'\n  inputs:\n    codeCoverageTool: 'Cobertura'\n    summaryFileLocation: '$(Build.SourcesDirectory)\u002Fcoverage\u002FCobertura.xml'\n```\n\n---\n\n## Quick Reference\n\n### One-Liner Commands\n\n```bash\n# Full analysis workflow\nrm -rf coverage\u002F TestResults\u002F && \\\ndotnet test --settings coverage.runsettings \\\n  --collect:\"XPlat Code Coverage\" \\\n  --results-directory .\u002FTestResults && \\\ndotnet reportgenerator \\\n  -reports:\"TestResults\u002F**\u002Fcoverage.opencover.xml\" \\\n  -targetdir:\"coverage\" \\\n  -reporttypes:\"Html;TextSummary\"\n\n# View summary\ncat coverage\u002FSummary.txt\n\n# Open HTML report (Linux)\nxdg-open coverage\u002Findex.html\n\n# Open HTML report (macOS)\nopen coverage\u002Findex.html\n\n# Open HTML report (Windows)\nstart coverage\u002Findex.html\n```\n\n### Project Standards\n\n| Metric | New Code | Legacy Code |\n|--------|----------|-------------|\n| Line Coverage | 80%+ | 60%+ (improve gradually) |\n| Branch Coverage | 60%+ | 40%+ (improve gradually) |\n| Maximum CRAP | 30 | Document exceptions |\n| High-risk methods | Must have tests | Add tests before modifying |\n\n---\n\n## What Gets Excluded\n\nThe recommended `coverage.runsettings` excludes:\n\n| Pattern | Reason |\n|---------|--------|\n| `[*.Tests]*` | Test assemblies aren't production code |\n| `[*.Benchmark]*` | Benchmark projects |\n| `[*.Migrations]*` | Database migrations (generated) |\n| `GeneratedCodeAttribute` | Source generators |\n| `CompilerGeneratedAttribute` | Compiler-generated code |\n| `ExcludeFromCodeCoverageAttribute` | Explicit developer opt-out |\n| `*.g.cs`, `*.designer.cs` | Generated files |\n| `*.razor.g.cs` | Blazor component generated code |\n| `*.razor.css.g.cs` | Blazor CSS isolation generated code |\n| `**\u002FMigrations\u002F**\u002F*` | EF Core migrations (auto-generated) |\n| `SkipAutoProps` | Auto-properties (trivial branches) |\n\n---\n\n## When to Update Thresholds\n\n**Lower thresholds temporarily for:**\n- Legacy codebases being modernized (document in README)\n- Generated code that can't be modified\n- Third-party wrapper code\n\n**Never lower thresholds for:**\n- \"It's too hard to test\" - refactor instead\n- \"We'll add tests later\" - add them now\n- New features - should meet standards from the start\n\n---\n\n## Additional Resources\n\n- **Coverlet Documentation**: https:\u002F\u002Fgithub.com\u002Fcoverlet-coverage\u002Fcoverlet\n- **ReportGenerator**: https:\u002F\u002Fgithub.com\u002Fdanielpalme\u002FReportGenerator\n- **CRAP Score Original Paper**: http:\u002F\u002Fwww.artima.com\u002Fweblogs\u002Fviewpost.jsp?thread=215899\n\n",{"name":5,"invocable":39,"description":7},true,"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FAaronontheweb\u002Fdotnet-skills\u002Fcrap-analysis",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":50,"prompt":60,"zipAvailable":39},[51,54,57],{"tool":52,"command":53},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill crap-analysis",{"tool":55,"command":56},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill crap-analysis",{"tool":58,"command":59},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill crap-analysis","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FAaronontheweb\u002Fdotnet-skills\u002Fcrap-analysis\u002FSKILL.md",[],[63,77,87,96,102,109],{"skillNo":64,"name":65,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":66,"categories":67,"keywords":73,"publishedAt":34},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[68],{"categoryNo":69,"categoryName":70,"taxonomyType":71,"taxonomyTypeDesc":72},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[74,75,76],"model","model hierarchy","model hierarchy skill",{"skillNo":78,"name":79,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":83,"publishedAt":34},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[82],{"categoryNo":69,"categoryName":70,"taxonomyType":71,"taxonomyTypeDesc":72},[84,85,86],"MCP","zoom","zoom mcp",{"skillNo":88,"name":89,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":34},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[93,94,95,85],"sdk","zoom apps sdk","apps",{"skillNo":97,"name":98,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":101,"publishedAt":34},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[98],{"skillNo":103,"name":104,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":108,"publishedAt":34},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[107],{"categoryNo":69,"categoryName":70,"taxonomyType":71,"taxonomyTypeDesc":72},[104],{"skillNo":110,"name":111,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":34},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[111]]