@cratis/pi 7.101.0 → 7.103.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/package/corpus/harnesses/copilot/instructions/github-actions.instructions.md +17 -0
- package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
- package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
- package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
- package/package/corpus/hooks/README.md +11 -1
- package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
- package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
- package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
- package/package/corpus/rules/github-actions.md +17 -0
- package/package/corpus/rules/profiles.md +10 -1
- package/package/corpus/rules/pull-requests.md +2 -0
- package/package/corpus/rules/secrets.md +4 -0
- package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
- package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
- package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
- package/package/corpus/skills/binlog-generation/LICENSE +23 -0
- package/package/corpus/skills/binlog-generation/NOTICE +22 -0
- package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
- package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
- package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
- package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
- package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
- package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
- package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
- package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
- package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
- package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
- package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
- package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
- package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
- package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
- package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/LICENSE +2 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/SKILL.md +201 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/build-once-and-shard.md +158 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/caching.md +86 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/flaky-and-hung-jobs.md +70 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/publish-pipelines.md +67 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/required-checks-and-path-filters.md +117 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/runner-sizing-and-images.md +65 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/security-and-pinning.md +73 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/sources.md +33 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/test-setup-cost.md +82 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/ci-timings.sh +356 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1003.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1007.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1010.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/runs.json +102 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/LICENSE +2 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/SKILL.md +244 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/docker-images.md +86 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/dotnet-nuget.md +87 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/go-jvm-python-elixir.md +83 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/npm-yarn.md +78 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/other-environments.md +60 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/worked-examples.md +97 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/cratis-local-packages.sh +778 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/npm-restore-check.cjs +109 -0
- package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
- package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
- package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
- package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
- package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
- package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
- package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
- package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
- package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
- package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
- package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
- package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
- package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
- package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
- package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
- package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
- package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
- package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
- package/package/corpus/skills/dump-collect/LICENSE +23 -0
- package/package/corpus/skills/dump-collect/NOTICE +40 -0
- package/package/corpus/skills/dump-collect/SKILL.md +97 -0
- package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
- package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
- package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
- package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
- package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
- package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
- package/package/corpus/skills/extension-points/LICENSE +23 -0
- package/package/corpus/skills/extension-points/NOTICE +18 -0
- package/package/corpus/skills/extension-points/SKILL.md +248 -0
- package/package/corpus/skills/filter-syntax/LICENSE +23 -0
- package/package/corpus/skills/filter-syntax/NOTICE +17 -0
- package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
- package/package/corpus/skills/including-generated-files/LICENSE +23 -0
- package/package/corpus/skills/including-generated-files/NOTICE +17 -0
- package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
- package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
- package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
- package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
- package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
- package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
- package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
- package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
- package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
- package/package/corpus/skills/msbuild/LICENSE +23 -0
- package/package/corpus/skills/msbuild/NOTICE +55 -0
- package/package/corpus/skills/msbuild/SKILL.md +117 -0
- package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
- package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
- package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
- package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
- package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
- package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
- package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
- package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
- package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
- package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
- package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
- package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
- package/package/corpus/skills/orleans/LICENSE +21 -0
- package/package/corpus/skills/orleans/NOTICE +64 -0
- package/package/corpus/skills/orleans/SKILL.md +140 -0
- package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
- package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
- package/package/corpus/skills/orleans/references/examples.md +66 -0
- package/package/corpus/skills/orleans/references/grain-api.md +784 -0
- package/package/corpus/skills/orleans/references/grains.md +75 -0
- package/package/corpus/skills/orleans/references/hosting.md +71 -0
- package/package/corpus/skills/orleans/references/implementation.md +591 -0
- package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
- package/package/corpus/skills/orleans/references/patterns.md +811 -0
- package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
- package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
- package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
- package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
- package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
- package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
- package/package/corpus/skills/platform-detection/LICENSE +23 -0
- package/package/corpus/skills/platform-detection/NOTICE +17 -0
- package/package/corpus/skills/platform-detection/SKILL.md +230 -0
- package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
- package/package/corpus/skills/profiling/LICENSE +21 -0
- package/package/corpus/skills/profiling/NOTICE +50 -0
- package/package/corpus/skills/profiling/SKILL.md +144 -0
- package/package/corpus/skills/profiling/references/commands.md +439 -0
- package/package/corpus/skills/profiling/references/patterns.md +586 -0
- package/package/corpus/skills/profiling/references/profiling.md +190 -0
- package/package/corpus/skills/run-tests/LICENSE +23 -0
- package/package/corpus/skills/run-tests/NOTICE +19 -0
- package/package/corpus/skills/run-tests/SKILL.md +305 -0
- package/package/corpus/skills/target-authoring/LICENSE +23 -0
- package/package/corpus/skills/target-authoring/NOTICE +17 -0
- package/package/corpus/skills/target-authoring/SKILL.md +165 -0
- package/package/corpus/skills/vectorization/LICENSE +23 -0
- package/package/corpus/skills/vectorization/NOTICE +17 -0
- package/package/corpus/skills/vectorization/SKILL.md +189 -0
- package/package/profile-catalog.json +87 -1
- package/package.json +3 -2
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Explicit Risk/CRAP Analysis
|
|
2
|
+
|
|
3
|
+
Read this file only when the user explicitly asks for project-wide risk
|
|
4
|
+
hotspots, CRAP scores, complexity-weighted priorities, or refactoring safety.
|
|
5
|
+
Do not read it for supplied-excerpt interpretation, plateau diagnosis, or target
|
|
6
|
+
arithmetic alone.
|
|
7
|
+
|
|
8
|
+
## Compute the risk data
|
|
9
|
+
|
|
10
|
+
Resolve the scripts relative to this skill's `SKILL.md`:
|
|
11
|
+
|
|
12
|
+
```powershell
|
|
13
|
+
& "<skill-directory>/scripts/Compute-CrapScores.ps1" `
|
|
14
|
+
-CoberturaPath @(<all Cobertura paths>) `
|
|
15
|
+
-CrapThreshold <crap_threshold> `
|
|
16
|
+
-TopN <top_n>
|
|
17
|
+
|
|
18
|
+
& "<skill-directory>/scripts/Extract-MethodCoverage.ps1" `
|
|
19
|
+
-CoberturaPath @(<all Cobertura paths>) `
|
|
20
|
+
-CoverageThreshold <line_threshold> `
|
|
21
|
+
-BranchThreshold <branch_threshold> `
|
|
22
|
+
-Filter below-threshold
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`Compute-CrapScores.ps1` emits aggregate line/branch coverage, method counts,
|
|
26
|
+
flagged counts, and sorted hotspots. `Extract-MethodCoverage.ps1` emits every
|
|
27
|
+
below-threshold method. Use both for explicit project-wide risk work.
|
|
28
|
+
|
|
29
|
+
CRAP is:
|
|
30
|
+
|
|
31
|
+
`CRAP(m) = complexity² × (1 − lineCoverage)³ + complexity`
|
|
32
|
+
|
|
33
|
+
A method at 100% coverage therefore has CRAP equal to its complexity. Use 30 as
|
|
34
|
+
the default flagged threshold; treat 15–30 as moderate rather than catastrophic.
|
|
35
|
+
|
|
36
|
+
## Scale the output
|
|
37
|
+
|
|
38
|
+
- Show the top 3 actual risk hotspots by default.
|
|
39
|
+
- Exclude fully covered low-risk methods from the hotspot table.
|
|
40
|
+
- State how many additional methods exceeded the threshold instead of listing
|
|
41
|
+
them all.
|
|
42
|
+
- Honor a user-supplied count up to 10. Exceed 10 only when explicitly requested.
|
|
43
|
+
- For five or fewer below-threshold members, name all of them. For larger sets,
|
|
44
|
+
show the top hotspots and summarize the remaining count and range.
|
|
45
|
+
- Give 1–3 recommendations ordered by expected risk reduction.
|
|
46
|
+
|
|
47
|
+
## Prioritize
|
|
48
|
+
|
|
49
|
+
- **HIGH** — both CRAP and coverage exceed their risk thresholds.
|
|
50
|
+
- **MED** — either CRAP or coverage exceeds its threshold.
|
|
51
|
+
- **LOW** — below coverage threshold but complexity is at most 2.
|
|
52
|
+
|
|
53
|
+
Prefer complex uncovered critical paths (authentication, payment, data access,
|
|
54
|
+
error handling). Deprioritize trivial getters, generated code, migrations, and
|
|
55
|
+
configuration glue.
|
|
56
|
+
|
|
57
|
+
Do not project a CRAP reduction from an arbitrary target without showing the
|
|
58
|
+
assumption. Recalculate with the stated projected method coverage.
|
|
59
|
+
|
|
60
|
+
Do not generate tests during analysis. Recommend focused test cases;
|
|
61
|
+
implementation is a separate follow-up.
|
|
62
|
+
|
|
63
|
+
## Style
|
|
64
|
+
|
|
65
|
+
- Lead with the risk verdict, not setup narration.
|
|
66
|
+
- Quantify recommendations only from actual line/coverage evidence.
|
|
67
|
+
- Use one compact hotspot table. Do not append the full report template unless
|
|
68
|
+
the user requested a report.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Output Format
|
|
2
|
+
|
|
3
|
+
Read and use this template only when the user explicitly requests a full
|
|
4
|
+
markdown coverage report. Normal interpretation, plateau, arithmetic, and
|
|
5
|
+
hotspot requests use the scaled response contract in `SKILL.md` instead.
|
|
6
|
+
|
|
7
|
+
For an explicit full report, copy the template below **verbatim** for all fixed elements (headings, table headers, emoji, symbols). Only replace `<placeholder>` values with actual data. Do not substitute emoji with text equivalents, do not change `·` to `-`, do not change `×` to `x`, and do not drop section emoji prefixes.
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
# Coverage Analysis - <ProjectName>
|
|
11
|
+
|
|
12
|
+
| Metric | Value |
|
|
13
|
+
|--------|-------|
|
|
14
|
+
| **Date** | <YYYY-MM-DD> |
|
|
15
|
+
| **Line Coverage** | <N>% |
|
|
16
|
+
| **Branch Coverage** | <N>% |
|
|
17
|
+
| **Risk Hotspots** | <N> (CRAP > <crap_threshold>) |
|
|
18
|
+
| **Tests** | <N> passed · <N> failed |
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
| Metric | Value | Threshold | Status |
|
|
23
|
+
|--------|-------|-----------|--------|
|
|
24
|
+
| **Line Coverage** | <N>% | <line_threshold>% | ✅ / ❌ |
|
|
25
|
+
| **Branch Coverage** | <N>% | <branch_threshold>% | ✅ / ❌ |
|
|
26
|
+
| **Methods Analyzed** | <N> | — | — |
|
|
27
|
+
| **Risk Hotspots** | <N> | 0 | ✅ / ⚠️ |
|
|
28
|
+
| **Test Result** | <Passed / N tests failed> | — | ✅ / ⚠️ |
|
|
29
|
+
|
|
30
|
+
> Coverage collected from **<N> of <M> test project(s)**.
|
|
31
|
+
> Outputs saved to: `<coverageDir>/` (markdown summary + raw Cobertura XML).
|
|
32
|
+
> *If HTML/CSV generation ran:* additional reports are at `<coverageDir>/reports/`.
|
|
33
|
+
|
|
34
|
+
If any coverage provider package was added to test projects, include this note after the summary:
|
|
35
|
+
|
|
36
|
+
> ℹ️ **Coverage provider package updates**
|
|
37
|
+
> - `coverlet.collector` added to `<K>` project(s): `<TestProject1.csproj>`, `<TestProject2.csproj>`
|
|
38
|
+
> - `Microsoft.Testing.Extensions.CodeCoverage` added to `<M>` project(s): `<TestProject3.csproj>`
|
|
39
|
+
>
|
|
40
|
+
> To revert: `git checkout -- <path-to-each-modified-csproj>`
|
|
41
|
+
|
|
42
|
+
If all test projects already had a coverage provider, omit this note.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🔥 Risk Hotspots (Top <N> by CRAP Score)
|
|
47
|
+
|
|
48
|
+
Methods flagged as high-risk: complex code with low test coverage that is dangerous to change.
|
|
49
|
+
|
|
50
|
+
| Rank | Method | Class | File | Complexity | Coverage | CRAP Score |
|
|
51
|
+
|------|--------|-------|------|-----------|---------|-----------|
|
|
52
|
+
| 1 | `<method>` | `<class>` | `<file>` | <N> | <N>% | **<score>** |
|
|
53
|
+
| … | … | … | … | … | … | … |
|
|
54
|
+
|
|
55
|
+
> **CRAP Score** = `Complexity² × (1 − Coverage)³ + Complexity`.
|
|
56
|
+
> Scores above <crap_threshold> are flagged. A score ≤ 5 is considered safe.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 📋 Coverage Gaps by File
|
|
61
|
+
|
|
62
|
+
Files below the line or branch coverage threshold, ordered by uncovered lines descending:
|
|
63
|
+
|
|
64
|
+
| File | Line Coverage | Branch Coverage | Uncovered Lines | Priority |
|
|
65
|
+
|------|--------------|----------------|----------------|---------|
|
|
66
|
+
| `<file>` | <N>% | <N>% | <N> | 🔴 HIGH / 🟡 MED / 🟢 LOW |
|
|
67
|
+
| … | … | … | … | … |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 💡 Recommendations
|
|
72
|
+
|
|
73
|
+
1. **Write tests for the top risk hotspot first** — `<method>` in `<class>` has a CRAP score of <N> (complexity <N>, <N>% coverage). Reducing it to 80% coverage would drop the score to ~<projected>.
|
|
74
|
+
2. **Focus on `<file>`** — <N> uncovered lines, below threshold. <Brief reasoning.>
|
|
75
|
+
3. **<Up to 5 actionable items total, ordered by expected risk reduction.>**
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 📁 Reports
|
|
80
|
+
|
|
81
|
+
| Report | Path |
|
|
82
|
+
|--------|------|
|
|
83
|
+
| Markdown summary (this file) | `<coverageDir>/coverage-analysis.md` |
|
|
84
|
+
| Raw Cobertura XML | `<coberturaXmlPathsUsedForAnalysis>` |
|
|
85
|
+
| HTML (browsable) | `<coverageDir>/reports/index.html` *or* `Not generated (optional — request HTML reports to enable)` |
|
|
86
|
+
| Text summary | `<coverageDir>/reports/Summary.txt` *or* `Not generated` |
|
|
87
|
+
| GitHub markdown | `<coverageDir>/reports/SummaryGithub.md` *or* `Not generated` |
|
|
88
|
+
| CSV data | `<coverageDir>/reports/Summary.csv` *or* `Not generated` |
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If ReportGenerator has not run, mark the HTML/Text/GitHub-markdown/CSV rows as `Not generated (optional — request HTML reports to enable)`. Do not invent paths for files that have not been produced. For **Raw Cobertura XML**, list the actual XML file path(s) used in analysis (for from-scratch runs this is typically under `<coverageDir>/raw/`; for existing-data runs this may be under `TestResults/` or another user-supplied location).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Coverage Analysis — ReportGenerator HTML/CSV reports
|
|
2
|
+
|
|
3
|
+
Read this file **only** when the user explicitly asked for HTML/CSV reports and
|
|
4
|
+
the direct coverage/risk answer has already been delivered.
|
|
5
|
+
|
|
6
|
+
## Verify or install ReportGenerator
|
|
7
|
+
|
|
8
|
+
```powershell
|
|
9
|
+
$rgAvailable = $false
|
|
10
|
+
$rgCommand = Get-Command reportgenerator -ErrorAction SilentlyContinue
|
|
11
|
+
if ($rgCommand) {
|
|
12
|
+
$rgAvailable = $true
|
|
13
|
+
Write-Host "RG_INSTALLED:already-present"
|
|
14
|
+
} else {
|
|
15
|
+
$rgToolPath = Join-Path "<COVERAGE_DIR>" ".tools"
|
|
16
|
+
# Cratis: Downloads or installs a tool: requires the user's approval; prefer a pinned version (`dotnet tool install <tool> --version <x>`)
|
|
17
|
+
dotnet tool install dotnet-reportgenerator-globaltool --tool-path $rgToolPath
|
|
18
|
+
if ($LASTEXITCODE -eq 0) {
|
|
19
|
+
$env:PATH = "$rgToolPath$([System.IO.Path]::PathSeparator)$env:PATH"
|
|
20
|
+
$rgCommand = Get-Command reportgenerator -ErrorAction SilentlyContinue
|
|
21
|
+
if ($rgCommand) {
|
|
22
|
+
$rgAvailable = $true
|
|
23
|
+
Write-Host "RG_INSTALLED:true (tool-path: $rgToolPath)"
|
|
24
|
+
} else {
|
|
25
|
+
Write-Host "RG_INSTALLED:false"
|
|
26
|
+
Write-Host "RG_INSTALL_ERROR:reportgenerator-not-available"
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
Write-Host "RG_INSTALLED:false"
|
|
30
|
+
Write-Host "RG_INSTALL_ERROR:reportgenerator-not-available"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
Write-Host "RG_AVAILABLE:$rgAvailable"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If installation fails (no internet), keep `RG_AVAILABLE:false`, leave the existing user-facing summary as the final output, and note that HTML reports were skipped.
|
|
37
|
+
|
|
38
|
+
## Step 7: Generate HTML/CSV reports
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
$reportsDir = Join-Path "<COVERAGE_DIR>" "reports"
|
|
42
|
+
if ($rgAvailable) {
|
|
43
|
+
reportgenerator `
|
|
44
|
+
-reports:"<semicolon-separated COBERTURA paths>" `
|
|
45
|
+
-targetdir:$reportsDir `
|
|
46
|
+
-reporttypes:"Html;TextSummary;MarkdownSummaryGithub;CsvSummary" `
|
|
47
|
+
-title:"Coverage Report" `
|
|
48
|
+
-tag:"coverage-analysis-skill"
|
|
49
|
+
|
|
50
|
+
Get-Content (Join-Path $reportsDir "Summary.txt") -ErrorAction SilentlyContinue
|
|
51
|
+
} else {
|
|
52
|
+
Write-Host "REPORTGENERATOR_SKIPPED:true"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
After report generation completes successfully, you may follow up with a short
|
|
57
|
+
message pointing the user to the generated HTML report (one paragraph, no need
|
|
58
|
+
to repeat the summary).
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Coverage Analysis — setup and discovery
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Step 1: Locate the solution or project
|
|
6
|
+
- Step 2: Create the output directory
|
|
7
|
+
- Step 2b: Discover or accept existing Cobertura XML (required for the existing-data path)
|
|
8
|
+
- Step 2c: Recommend ignoring `TestResults/`
|
|
9
|
+
|
|
10
|
+
Read this file only when the user did not supply usable coverage evidence and
|
|
11
|
+
the request requires workspace discovery or fresh collection. Do not read or run
|
|
12
|
+
these probes for a supplied excerpt or valid Cobertura path.
|
|
13
|
+
|
|
14
|
+
## Step 1: Locate the solution or project
|
|
15
|
+
|
|
16
|
+
Given the user's path (default: current directory), find the entry point.
|
|
17
|
+
If the user or the repository's documented coverage command selects a specific
|
|
18
|
+
`.sln`, `.slnx`, `.slnf`, or `.csproj`, pass that exact file as `$root`; do not
|
|
19
|
+
replace it with a discovered alternative. Directory discovery stops on multiple
|
|
20
|
+
solutions or, when no solution exists, multiple projects. Request an explicit
|
|
21
|
+
entry point rather than selecting the first.
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
$root = "<user-or-repository-selected-file-or-directory>"
|
|
25
|
+
$requested = Get-Item -LiteralPath $root -ErrorAction Stop
|
|
26
|
+
$entry = $null
|
|
27
|
+
if (-not $requested.PSIsContainer) {
|
|
28
|
+
if ($requested.Extension -notin @('.sln', '.slnx', '.slnf', '.csproj')) {
|
|
29
|
+
throw "Unsupported entry point: $($requested.FullName)"
|
|
30
|
+
}
|
|
31
|
+
$entry = $requested
|
|
32
|
+
$root = $requested.DirectoryName
|
|
33
|
+
} else {
|
|
34
|
+
$root = $requested.FullName
|
|
35
|
+
$candidates = @(Get-ChildItem -LiteralPath $root -File -Recurse -Depth 2 -ErrorAction Stop |
|
|
36
|
+
Where-Object { $_.Extension -in @('.sln', '.slnx') -and $_.FullName -notmatch '[/\\](obj|bin)[/\\]' })
|
|
37
|
+
if ($candidates.Count -eq 0) {
|
|
38
|
+
$candidates = @(Get-ChildItem -LiteralPath $root -Filter "*.csproj" -File -Recurse -Depth 2 -ErrorAction Stop |
|
|
39
|
+
Where-Object { $_.FullName -notmatch '[/\\](obj|bin)[/\\]' })
|
|
40
|
+
}
|
|
41
|
+
if ($candidates.Count -gt 1) {
|
|
42
|
+
throw "Ambiguous entry point under $root. Specify one .sln, .slnx, .slnf, or .csproj: $($candidates.FullName -join ', ')"
|
|
43
|
+
}
|
|
44
|
+
if ($candidates.Count -eq 1) { $entry = $candidates[0] }
|
|
45
|
+
}
|
|
46
|
+
if ($entry) {
|
|
47
|
+
$entryType = if ($entry.Extension -eq '.csproj') { 'Project' } else { 'Solution' }
|
|
48
|
+
Write-Host "ENTRY_TYPE:$entryType"; Write-Host "ENTRY:$($entry.FullName)"
|
|
49
|
+
} else {
|
|
50
|
+
Write-Host "ENTRY_TYPE:NotFound"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Test projects: search the requested directory, then its containing Git root only.
|
|
54
|
+
$searchRoots = @($root)
|
|
55
|
+
$gitOutput = @(git -C $root rev-parse --show-toplevel 2>&1)
|
|
56
|
+
if ($LASTEXITCODE -eq 0) {
|
|
57
|
+
$gitRoot = [string]$gitOutput[0]
|
|
58
|
+
} else {
|
|
59
|
+
$gitError = $gitOutput | Out-String
|
|
60
|
+
if ($gitError -notmatch 'not a git repository \(or any of the parent directories\): \.git') {
|
|
61
|
+
throw "Git root lookup failed: $gitError"
|
|
62
|
+
}
|
|
63
|
+
$gitRoot = $null
|
|
64
|
+
}
|
|
65
|
+
if ($gitRoot) { $gitRoot = [System.IO.Path]::GetFullPath($gitRoot) }
|
|
66
|
+
if ($gitRoot -and $gitRoot -ne $root) {
|
|
67
|
+
$sep = [System.IO.Path]::DirectorySeparatorChar
|
|
68
|
+
$prefix = $gitRoot.TrimEnd($sep) + $sep
|
|
69
|
+
if (-not $root.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
70
|
+
throw "Git root is not a containing directory: $gitRoot"
|
|
71
|
+
}
|
|
72
|
+
$searchRoots += $gitRoot
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Get-SolutionProjects($solution) {
|
|
76
|
+
$text = Get-Content -LiteralPath $solution.FullName -Raw -ErrorAction Stop
|
|
77
|
+
if ($solution.Extension -eq '.slnx') {
|
|
78
|
+
$xml = [xml]$text
|
|
79
|
+
if ($xml.DocumentElement.Name -ne 'Solution') { throw "Invalid solution: $($solution.FullName)" }
|
|
80
|
+
$paths = @($xml.SelectNodes('//Project[@Path]') | ForEach-Object { $_.Path })
|
|
81
|
+
} elseif ($solution.Extension -eq '.sln') {
|
|
82
|
+
if ($text -notmatch 'Microsoft Visual Studio Solution File, Format Version') {
|
|
83
|
+
throw "Invalid solution: $($solution.FullName)"
|
|
84
|
+
}
|
|
85
|
+
$paths = @([regex]::Matches($text, '(?m)^Project\("[^"]+"\)\s*=\s*"[^"]*",\s*"([^"]+)",') |
|
|
86
|
+
ForEach-Object { $_.Groups[1].Value } |
|
|
87
|
+
Where-Object { [System.IO.Path]::GetExtension($_) -eq '.csproj' })
|
|
88
|
+
} else {
|
|
89
|
+
throw "Unsupported underlying solution: $($solution.FullName)"
|
|
90
|
+
}
|
|
91
|
+
foreach ($path in $paths) {
|
|
92
|
+
if ([System.IO.Path]::GetExtension($path) -eq '.csproj') {
|
|
93
|
+
$relative = $path.Replace('\', [System.IO.Path]::DirectorySeparatorChar)
|
|
94
|
+
[System.IO.Path]::GetFullPath((Join-Path $solution.DirectoryName $relative))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Scope before reading/classifying projects: solution membership, filter membership,
|
|
100
|
+
# or the single project that the selected test command actually targets.
|
|
101
|
+
if ($entry) {
|
|
102
|
+
if ($entry.Extension -eq '.csproj') {
|
|
103
|
+
$projectPaths = @($entry.FullName)
|
|
104
|
+
} elseif ($entry.Extension -eq '.slnf') {
|
|
105
|
+
$filter = Get-Content -LiteralPath $entry.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
106
|
+
if (-not $filter.solution.path -or $null -eq $filter.solution.projects) {
|
|
107
|
+
throw "Invalid solution filter: $($entry.FullName)"
|
|
108
|
+
}
|
|
109
|
+
$solutionPath = $filter.solution.path.Replace('\', [System.IO.Path]::DirectorySeparatorChar)
|
|
110
|
+
$solution = Get-Item -LiteralPath (Join-Path $entry.DirectoryName $solutionPath) -ErrorAction Stop
|
|
111
|
+
$members = @(Get-SolutionProjects $solution)
|
|
112
|
+
$projectPaths = @($filter.solution.projects | ForEach-Object {
|
|
113
|
+
$relative = $_.Replace('\', [System.IO.Path]::DirectorySeparatorChar)
|
|
114
|
+
$project = Get-Item -LiteralPath (Join-Path $solution.DirectoryName $relative) -ErrorAction Stop
|
|
115
|
+
if ($members -notcontains $project.FullName) { throw "Filtered project is not in the solution: $($project.FullName)" }
|
|
116
|
+
$project.FullName
|
|
117
|
+
})
|
|
118
|
+
} else {
|
|
119
|
+
$projectPaths = @(Get-SolutionProjects $entry)
|
|
120
|
+
}
|
|
121
|
+
$projects = @($projectPaths | Select-Object -Unique | ForEach-Object {
|
|
122
|
+
Get-Item -LiteralPath $_ -ErrorAction Stop
|
|
123
|
+
})
|
|
124
|
+
$testProjects = @($projects | Where-Object {
|
|
125
|
+
(Select-String -LiteralPath $_.FullName -Pattern 'Microsoft\.NET\.Test\.Sdk|xunit|nunit|MSTest\.TestAdapter|"MSTest"|MSTest\.TestFramework|TUnit' -Quiet -ErrorAction Stop) -or
|
|
126
|
+
$_.Name -match '(?i)(test|spec)'
|
|
127
|
+
})
|
|
128
|
+
} else {
|
|
129
|
+
$testProjects = @()
|
|
130
|
+
foreach ($sr in $searchRoots) {
|
|
131
|
+
$projects = @(Get-ChildItem -LiteralPath $sr -Filter "*.csproj" -File -Recurse -Depth 5 -ErrorAction Stop |
|
|
132
|
+
Where-Object { $_.FullName -notmatch '[/\\](obj|bin)[/\\]' })
|
|
133
|
+
$testProjects = @($projects | Where-Object {
|
|
134
|
+
Select-String -LiteralPath $_.FullName -Pattern 'Microsoft\.NET\.Test\.Sdk|xunit|nunit|MSTest\.TestAdapter|"MSTest"|MSTest\.TestFramework|TUnit' -Quiet -ErrorAction Stop
|
|
135
|
+
})
|
|
136
|
+
if ($testProjects.Count -eq 0) {
|
|
137
|
+
$testProjects = @($projects | Where-Object { $_.Name -match '(?i)(test|spec)' })
|
|
138
|
+
}
|
|
139
|
+
if ($testProjects.Count -gt 0) {
|
|
140
|
+
if ($sr -ne $root) { Write-Host "SEARCHED:$sr" }
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
Write-Host "TEST_PROJECTS:$($testProjects.Count)"
|
|
146
|
+
$testProjects | ForEach-Object { Write-Host "TEST_PROJECT:$($_.FullName)" }
|
|
147
|
+
|
|
148
|
+
# Project-system classification controls whether the automatic dotnet/provider path is safe.
|
|
149
|
+
$classicTestProjects = @($testProjects | Where-Object {
|
|
150
|
+
$text = Get-Content -LiteralPath $_.FullName -Raw -ErrorAction Stop
|
|
151
|
+
$hasSdk = $text -match '<Project[^>]+\bSdk\s*=' -or $text -match '<Sdk\b'
|
|
152
|
+
$hasPackagesConfig = Test-Path (Join-Path $_.DirectoryName "packages.config")
|
|
153
|
+
$hasClassicSignals = $text -match '\bToolsVersion\s*=' -or
|
|
154
|
+
$text -match 'Microsoft\.(Common\.props|CSharp\.targets)' -or
|
|
155
|
+
$text -match '<Compile\s+Include='
|
|
156
|
+
$hasPackagesConfig -or (-not $hasSdk -and $hasClassicSignals)
|
|
157
|
+
})
|
|
158
|
+
Write-Host "CLASSIC_TEST_PROJECTS:$($classicTestProjects.Count)"
|
|
159
|
+
$classicTestProjects | ForEach-Object { Write-Host "CLASSIC_TEST_PROJECT:$($_.FullName)" }
|
|
160
|
+
$sdkTestProjects = @($testProjects | Where-Object {
|
|
161
|
+
$classicTestProjects.FullName -notcontains $_.FullName
|
|
162
|
+
})
|
|
163
|
+
Write-Host "SDK_TEST_PROJECTS:$($sdkTestProjects.Count)"
|
|
164
|
+
$sdkTestProjects | ForEach-Object { Write-Host "SDK_TEST_PROJECT:$($_.FullName)" }
|
|
165
|
+
|
|
166
|
+
# Resolve the test output root (where coverage-analysis artifacts will be written)
|
|
167
|
+
if ($testProjects.Count -eq 0) {
|
|
168
|
+
if ($gitRoot) {
|
|
169
|
+
$testOutputRoot = $gitRoot
|
|
170
|
+
} else {
|
|
171
|
+
$testOutputRoot = $root
|
|
172
|
+
}
|
|
173
|
+
} elseif ($testProjects.Count -eq 1) {
|
|
174
|
+
$testOutputRoot = $testProjects[0].DirectoryName
|
|
175
|
+
} else {
|
|
176
|
+
# Multiple test projects — find their deepest common parent directory
|
|
177
|
+
$dirs = $testProjects | ForEach-Object { $_.DirectoryName }
|
|
178
|
+
$common = $dirs[0]
|
|
179
|
+
foreach ($d in $dirs[1..($dirs.Count-1)]) {
|
|
180
|
+
$sep = [System.IO.Path]::DirectorySeparatorChar
|
|
181
|
+
while (-not $d.StartsWith("$common$sep", [System.StringComparison]::OrdinalIgnoreCase) -and $d -ne $common) {
|
|
182
|
+
$prevCommon = $common
|
|
183
|
+
$common = Split-Path $common -Parent
|
|
184
|
+
# Terminate if we can no longer move up (at filesystem root or no parent)
|
|
185
|
+
if ([string]::IsNullOrEmpty($common) -or $common -eq $prevCommon) {
|
|
186
|
+
$common = $null
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if ([string]::IsNullOrEmpty($common)) {
|
|
192
|
+
# Fallback when no common parent directory exists (e.g., projects on different drives)
|
|
193
|
+
if ($gitRoot) {
|
|
194
|
+
$testOutputRoot = $gitRoot
|
|
195
|
+
} else {
|
|
196
|
+
$testOutputRoot = $root
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
$testOutputRoot = $common
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
Write-Host "TEST_OUTPUT_ROOT:$testOutputRoot"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- If `ENTRY_TYPE:NotFound` and SDK-style test projects were found → use the test projects directly as `dotnet test` entry points.
|
|
206
|
+
- If `ENTRY_TYPE:NotFound` and classic test projects were found → use only the repository's documented coverage command; do not infer `dotnet test`.
|
|
207
|
+
- If `ENTRY_TYPE:NotFound` and no test projects found → stop: `No .sln, .slnx, or test projects found under <path>. Provide the path to your .NET solution, filter, or project.`
|
|
208
|
+
- A missing/unreadable requested path, failed lookup, or ambiguous entry point is a discovery blocker, not measured zero coverage or evidence of an empty suite. Do not continue collection after a discovery error.
|
|
209
|
+
- Discovery is an inventory, not permission to expand collection scope. Pass the
|
|
210
|
+
exact selected entry point to `run-tests` and collect only its requested
|
|
211
|
+
project graph. Do not replace a selected `.csproj` or `.slnf` with every test
|
|
212
|
+
project found under the containing Git root. Apply the classic-project safety
|
|
213
|
+
rules below within that selected graph.
|
|
214
|
+
- Selected-entry arrays contain only the projects targeted by that entry:
|
|
215
|
+
solution members, filter members, or one explicit project. Transitive build
|
|
216
|
+
dependencies are not additional test-run targets. For a generated or
|
|
217
|
+
nonstandard entry graph that these formats cannot represent, stop and use
|
|
218
|
+
the repository's evaluated target inventory rather than scanning siblings.
|
|
219
|
+
- If `TEST_PROJECTS:0` and `EXISTING_COBERTURA_COUNT` > 0 (Step 2b) → continue with existing Cobertura XML analysis (no `dotnet test` run).
|
|
220
|
+
- If `TEST_PROJECTS:0` and `EXISTING_COBERTURA_COUNT` == 0 → stop: `No test projects found (expected projects with 'Test' or 'Spec' in the name), and no existing Cobertura XML was provided. Add a test project or provide a Cobertura file path.`
|
|
221
|
+
- If `CLASSIC_TEST_PROJECTS` is nonzero and no existing Cobertura XML is found,
|
|
222
|
+
search scripts/CI/docs for a repository-owned coverage command. Use it if it
|
|
223
|
+
emits Cobertura.
|
|
224
|
+
- If classic projects are the only test projects and no repository command
|
|
225
|
+
exists, stop: `Classic non-SDK or packages.config test project detected. The
|
|
226
|
+
automatic SDK-style coverage-provider path would modify this project
|
|
227
|
+
incorrectly. Run the repository's supported coverage workflow and provide its
|
|
228
|
+
Cobertura XML.`
|
|
229
|
+
This is a hard stop: do not create or run a temporary SDK project against the
|
|
230
|
+
classic source, because its coverage would belong to the substitute assembly,
|
|
231
|
+
not the requested test project.
|
|
232
|
+
- In a mixed solution, run automatic collection only for `SDK_TEST_PROJECTS`.
|
|
233
|
+
Never run the solution entry point if it would include classic projects.
|
|
234
|
+
Clearly label the result partial until repository-owned Cobertura data for the
|
|
235
|
+
classic projects is also available.
|
|
236
|
+
|
|
237
|
+
## Step 2: Create the output directory
|
|
238
|
+
|
|
239
|
+
```powershell
|
|
240
|
+
$coverageDir = Join-Path $testOutputRoot "TestResults" "coverage-analysis"
|
|
241
|
+
if (Test-Path $coverageDir) { Remove-Item $coverageDir -Recurse -Force }
|
|
242
|
+
New-Item -ItemType Directory -Path $coverageDir -Force | Out-Null
|
|
243
|
+
Write-Host "COVERAGE_DIR:$coverageDir"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This step only manages the `TestResults/coverage-analysis/` subdirectory (skill-owned outputs). It must never delete user-supplied Cobertura files — those live one level up at `TestResults/coverage.cobertura.xml` (or wherever the user pointed). If the user provided a path that *is* `TestResults/coverage-analysis/...`, copy the file aside before this step recreates the directory.
|
|
247
|
+
|
|
248
|
+
## Step 2b: Discover or accept existing Cobertura XML (required for the existing-data path)
|
|
249
|
+
|
|
250
|
+
If the user supplied a Cobertura XML path explicitly, use it. Otherwise probe well-known locations and any path the user mentioned:
|
|
251
|
+
|
|
252
|
+
```powershell
|
|
253
|
+
# 1. Honor a user-supplied path first (highest priority)
|
|
254
|
+
$coberturaFiles = @()
|
|
255
|
+
if ($userSuppliedCoberturaPath) {
|
|
256
|
+
$coberturaFiles = @(Get-Item -LiteralPath $userSuppliedCoberturaPath -ErrorAction Stop)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# 2. Otherwise scan TestResults/ at the repo/test root for any *.cobertura.xml
|
|
260
|
+
if ($coberturaFiles.Count -eq 0) {
|
|
261
|
+
$searchPaths = @(
|
|
262
|
+
(Join-Path $testOutputRoot "TestResults"),
|
|
263
|
+
(Join-Path $root "TestResults")
|
|
264
|
+
) | Where-Object { $_ -and (Test-Path $_) } | Select-Object -Unique
|
|
265
|
+
foreach ($sp in $searchPaths) {
|
|
266
|
+
$found = @(Get-ChildItem -LiteralPath $sp -Filter "*.cobertura.xml" -Recurse -ErrorAction Stop |
|
|
267
|
+
Where-Object { $_.FullName -notmatch '[/\\]coverage-analysis[/\\]raw[/\\]' })
|
|
268
|
+
if ($found.Count -gt 0) { $coberturaFiles = $found; break }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
Write-Host "EXISTING_COBERTURA_COUNT:$($coberturaFiles.Count)"
|
|
273
|
+
$coberturaFiles | ForEach-Object { Write-Host "EXISTING_COBERTURA:$($_.FullName)" }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- If `EXISTING_COBERTURA_COUNT` > 0 → skip fresh collection and analyze these paths.
|
|
277
|
+
- If `EXISTING_COBERTURA_COUNT` == 0 and all test projects are SDK-style →
|
|
278
|
+
invoke `run-tests` to select the repository-compatible platform/provider
|
|
279
|
+
command and collect Cobertura.
|
|
280
|
+
- If `EXISTING_COBERTURA_COUNT` == 0 and only classic/packages.config projects
|
|
281
|
+
exist → use a repository-owned coverage command that emits Cobertura;
|
|
282
|
+
otherwise stop with the message above.
|
|
283
|
+
- If `EXISTING_COBERTURA_COUNT` == 0 and both classic and SDK-style projects
|
|
284
|
+
exist → ask `run-tests` to collect only for `SDK_TEST_PROJECTS` and mark the
|
|
285
|
+
result partial until classic-project Cobertura is available.
|
|
286
|
+
|
|
287
|
+
## Step 2c: Recommend ignoring `TestResults/`
|
|
288
|
+
|
|
289
|
+
```powershell
|
|
290
|
+
$pattern = "**/TestResults/"
|
|
291
|
+
$gitRoot = (git -C $testOutputRoot rev-parse --show-toplevel 2>$null)
|
|
292
|
+
if ($gitRoot) { $gitRoot = [System.IO.Path]::GetFullPath($gitRoot) }
|
|
293
|
+
if ($gitRoot) {
|
|
294
|
+
$gitignorePath = Join-Path $gitRoot ".gitignore"
|
|
295
|
+
$alreadyIgnored = $false
|
|
296
|
+
if (Test-Path $gitignorePath) {
|
|
297
|
+
$alreadyIgnored = (Select-String -Path $gitignorePath -Pattern '^\s*(\*\*/)?TestResults/?\s*$' -Quiet)
|
|
298
|
+
}
|
|
299
|
+
if ($alreadyIgnored) {
|
|
300
|
+
Write-Host "GITIGNORE_RECOMMENDATION:already-present"
|
|
301
|
+
} else {
|
|
302
|
+
Write-Host "GITIGNORE_RECOMMENDATION:$pattern"
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
Write-Host "GITIGNORE_RECOMMENDATION:$pattern"
|
|
306
|
+
}
|
|
307
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Compute CRAP = complexity^2 * (1 - line coverage)^3 + complexity.
|
|
4
|
+
.DESCRIPTION
|
|
5
|
+
Missing complexity or executable lines yield unknown, never complexity=1/coverage=0.
|
|
6
|
+
Legacy output retains OVERALL_*, TOTAL_METHODS, FLAGGED_METHODS and HOTSPOTS markers
|
|
7
|
+
only when all requested measurements are representable. -OutputFormat json-v1
|
|
8
|
+
emits one provenance-bearing JSON document. Diagnostics go to stderr.
|
|
9
|
+
Thresholds and hotspot ordering use raw scores; only displayed scores are rounded.
|
|
10
|
+
Exit codes: 0 complete, 2 invalid input, 3 incomplete/unrepresentable evidence.
|
|
11
|
+
#>
|
|
12
|
+
param(
|
|
13
|
+
[Parameter(Mandatory)][string[]]$CoberturaPath,
|
|
14
|
+
[ValidateRange(0,[int]::MaxValue)][int]$CrapThreshold = 30,
|
|
15
|
+
[ValidateRange(1,[int]::MaxValue)][int]$TopN = 10,
|
|
16
|
+
[ValidateSet('legacy', 'json-v1')][string]$OutputFormat = 'legacy'
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
. "$PSScriptRoot\CoverageData.ps1"
|
|
21
|
+
} catch {
|
|
22
|
+
$diagnostic = @{ code = 'DEPENDENCY_UNAVAILABLE'; severity = 'error'; message = $_.Exception.Message; path = "$PSScriptRoot\CoverageData.ps1" }
|
|
23
|
+
[Console]::Error.WriteLine("$($diagnostic.code): $($diagnostic.message)")
|
|
24
|
+
if ($OutputFormat -eq 'json-v1') {
|
|
25
|
+
@{
|
|
26
|
+
schema_version = '1'; tool = 'Compute-CrapScores'; tool_version = '1.0'; status = 'error'
|
|
27
|
+
scope = @{}; inputs = @(); diagnostics = @($diagnostic); data = @{}
|
|
28
|
+
} | ConvertTo-Json -Depth 5
|
|
29
|
+
}
|
|
30
|
+
exit 2
|
|
31
|
+
}
|
|
32
|
+
$scope = @{ identity = 'reported-module-file-class-signature'; source_revision = $null; top = $TopN; crap_threshold = $CrapThreshold }
|
|
33
|
+
try {
|
|
34
|
+
$coverage = Get-CoverageData $CoberturaPath
|
|
35
|
+
} catch {
|
|
36
|
+
$diagnostics = @([pscustomobject]@{ code = 'INVALID_INPUT'; severity = 'error'; message = $_.Exception.Message })
|
|
37
|
+
Write-CoverageDiagnostics $diagnostics
|
|
38
|
+
if ($OutputFormat -eq 'json-v1') { Write-CoverageJson 'Compute-CrapScores' 'error' $scope @() $diagnostics @{} }
|
|
39
|
+
exit 2
|
|
40
|
+
}
|
|
41
|
+
foreach ($method in $coverage.Methods) {
|
|
42
|
+
$score = if ($null -ne $method.Complexity -and $method.LineCoverageStatus -eq 'exact') {
|
|
43
|
+
[math]::Pow($method.Complexity, 2) * [math]::Pow(1 - $method.CoveredLines / [double]$method.TotalLines, 3) + $method.Complexity
|
|
44
|
+
} else { $null }
|
|
45
|
+
$method | Add-Member -NotePropertyName CrapScore -NotePropertyValue $score
|
|
46
|
+
}
|
|
47
|
+
$ranked = @($coverage.Methods | Where-Object { $null -ne $_.CrapScore } |
|
|
48
|
+
Sort-Object -Property @{Expression='CrapScore';Descending=$true}, Class, Method, File, Signature)
|
|
49
|
+
$hotspots = @($ranked | Select-Object -First $TopN)
|
|
50
|
+
$unknown = @($coverage.Methods | Where-Object { $null -eq $_.CrapScore }).Count
|
|
51
|
+
$coverage.Summary.flagged_methods = @($ranked | Where-Object { $_.CrapScore -gt $CrapThreshold }).Count
|
|
52
|
+
$coverage.Summary.unknown_crap_methods = $unknown
|
|
53
|
+
foreach ($method in $ranked) {
|
|
54
|
+
$method.CrapScore = [math]::Round($method.CrapScore, 2)
|
|
55
|
+
}
|
|
56
|
+
foreach ($metric in @('line', 'branch')) {
|
|
57
|
+
if ($coverage.Summary["overall_${metric}_coverage_status"] -in @('unknown', 'bounded')) {
|
|
58
|
+
Add-CoverageDiagnostic $coverage.Diagnostics 'AGGREGATE_UNKNOWN' "Overall $metric coverage cannot be derived from overlapping report counters." ''
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
$partial = -not $coverage.Methods.Count -or $unknown -gt 0 -or
|
|
62
|
+
$coverage.Summary.overall_line_coverage_status -in @('unknown', 'bounded') -or
|
|
63
|
+
$coverage.Summary.overall_branch_coverage_status -in @('unknown', 'bounded')
|
|
64
|
+
Write-CoverageDiagnostics $coverage.Diagnostics
|
|
65
|
+
if ($OutputFormat -eq 'json-v1') {
|
|
66
|
+
Write-CoverageJson 'Compute-CrapScores' $(if ($partial) { 'partial' } else { 'complete' }) $scope $coverage.Inputs $coverage.Diagnostics @{
|
|
67
|
+
methods = $coverage.Methods; hotspots = $hotspots; summary = $coverage.Summary
|
|
68
|
+
}
|
|
69
|
+
} elseif (-not $partial) {
|
|
70
|
+
$line = if ($coverage.Summary.overall_line_coverage_status -eq 'not-applicable') { 0 } else { $coverage.Summary.overall_line_coverage }
|
|
71
|
+
$branch = if ($coverage.Summary.overall_branch_coverage_status -eq 'not-applicable') { 0 } else { $coverage.Summary.overall_branch_coverage }
|
|
72
|
+
Write-Output "OVERALL_LINE_COVERAGE:$line"
|
|
73
|
+
Write-Output "OVERALL_BRANCH_COVERAGE:$branch"
|
|
74
|
+
Write-Output "TOTAL_METHODS:$($coverage.Methods.Count)"
|
|
75
|
+
Write-Output "FLAGGED_METHODS:$($coverage.Summary.flagged_methods)"
|
|
76
|
+
$legacy = @($hotspots | Select-Object Class, Method, Signature, File, TotalLines, CoveredLines, LineCoverage, Complexity, CrapScore)
|
|
77
|
+
Write-Output "HOTSPOTS:$(ConvertTo-Json -InputObject $legacy -Compress)"
|
|
78
|
+
}
|
|
79
|
+
if ($partial) { exit 3 }
|
|
80
|
+
exit 0
|