Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d3dd17f
T1: scaffold solution and projects (S1)
parthopdas Jul 26, 2026
0bd8c8e
docs(T1): mark T1 done (d3dd17f); record open decisions C1/C2 for Mr.…
parthopdas Jul 26, 2026
545c000
T2: port domain types (S2)
parthopdas Jul 26, 2026
bc87843
docs(T2): mark T2 done (545c000); record C1/C2 conventions and S2 wat…
parthopdas Jul 26, 2026
47c1a1c
T3: port CrapScore + parity oracles (S2)
parthopdas Jul 26, 2026
f830f0e
T4: port ReportFormatter + golden/stability tests (S2)
parthopdas Jul 26, 2026
1b7833b
T5: CliArgumentsParser with 7 parity tests
parthopdas Jul 26, 2026
d28070c
docs: S2 boundary bookkeeping (T3/T4/T5 done, watch-items W6-W10)
parthopdas Jul 26, 2026
9b16ab3
docs: repoint playbook to decisions.md; move C1/C2; add root AGENTS.m…
parthopdas Jul 26, 2026
b46d073
W1: rename CoverageData fields -> MissedLines/CoveredLines
parthopdas Jul 26, 2026
9f28cdb
docs: close D-T5 (below-threshold); log open decision D-T9
parthopdas Jul 26, 2026
1b90d24
T7: port SourceFileFinder (src/*.cs, exclude bin/obj, ordinal sort)
parthopdas Jul 26, 2026
da02028
T6: port command-execution seam (ICommandExecutor + ProcessCommandExe…
parthopdas Jul 26, 2026
c023ef8
T8: port ChangedFileDetector (git status --porcelain change detection)
parthopdas Jul 26, 2026
25f3089
docs: append S5 (clean-room eval) + S6 (dogfooding) finale slices; ba…
parthopdas Jul 27, 2026
e4d1329
T13: ModuleRootResolver (nearest .sln > .csproj > start; unbounded walk)
parthopdas Jul 27, 2026
d0784b3
docs: ratify B1 unbounded module-root walk as departure #6
parthopdas Jul 27, 2026
400d49d
T9: CSharpMethodParser + ComplexityWalker (Roslyn; augmented CC; froz…
parthopdas Jul 27, 2026
87488ce
T10: CoberturaCoverageParser (secure XML; frozen-reciprocal normalize…
parthopdas Jul 27, 2026
17a3c3c
docs: insert S5 (module & test resolution finalization); renumber eva…
parthopdas Jul 27, 2026
14de208
docs: ratify resolve-once (#7) + no-traits (D-T8); trim S5; add test-…
parthopdas Jul 27, 2026
debf9de
T11: CrapAnalyzer (exact->nearest coverage lookup) + 17 parity tests
parthopdas Jul 27, 2026
9041c2c
T12: CoverageRunner (dotnet test --collect) + CoverageReportLocator +…
parthopdas Jul 27, 2026
6e8456c
T14: CliApplication composition + fail-fast gate (exit 0/1/2)
parthopdas Jul 27, 2026
1f7581e
T15: Program entry point + CoverageException + spawn e2e tests
parthopdas Jul 27, 2026
ff422bd
T16: finalize README to shipped behavior; close S4 (S1-S4 complete)
parthopdas Jul 27, 2026
4e41c62
T20: broaden Program.Main catch-all for exit-code parity
parthopdas Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,11 @@ dotnet_diagnostic.SA1135.severity = none

# IDE0305: Simplify collection initialization
dotnet_diagnostic.IDE0305.severity = warning

# CA1031: Do not catch general exception types. Scoped relaxation for the CLI entry point ONLY.
# Program.Main is the top-level boundary that ports Java's `main throws Exception`: it deliberately
# catches every escaping exception and converts it to process exit 1 (.NET, unlike the JVM, does not
# guarantee exit 1 on an unhandled exception). This is the sole sanctioned catch-all; no other file is
# affected, and no #pragma / [SuppressMessage] is used. Ruled by Mr. Das (T20).
[Program.cs]
dotnet_diagnostic.CA1031.severity = none
31 changes: 11 additions & 20 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ Address the human as **Mr. Das** (an alt of Iron Man), "Sir", or something simil
## What this repo is

`crap4csharp` is a high-fidelity C# port of the Java tool `crap4java` (read-only sibling at
`../crap4java`). It is a CRAP-metric analyzer for **C# projects**: Roslyn for parsing + cyclomatic
complexity, Coverlet → Cobertura for coverage, `dotnet test` / MSBuild as the driver. Design intent,
locked decisions, and the task plan live in `docs/decisions.md` and `docs/features/<feature>.md`.
`../crap4java`) — a CRAP-metric analyzer for **C# projects**. Project design (intent, locked choices,
the CRAP formula) lives in `docs/decisions.md`; per-feature plans in `docs/features/<feature>.md`.

## Golden rules (guardrails)

Expand All @@ -31,24 +30,16 @@ locked decisions, and the task plan live in `docs/decisions.md` and `docs/featur
5. Never deploy.
6. Stop and ask when a task needs a product/architecture decision. That call belongs to Mr. Das.
7. Mr. Das can invoke any agent on demand.
8. Tests are fidelity-first: every `crap4java` test has a faithful C# counterpart asserting the same
behavior. Beyond parity, add fine-grained unit tests for business logic and integration tests only
8. Fidelity-first: preserve `crap4java`'s behavior, and give every `crap4java` test a faithful C#
counterpart asserting the same behavior — except where `docs/decisions.md` records an approved
departure. Beyond parity, add fine-grained unit tests for business logic and integration tests only
for critical paths — don't overdo it. Avoid timing-sensitive tests.
9. Never use the `internal` access modifier on any C# construct — use the least-privilege
alternative; if it is a must, flag it.
10. Record durable facts in the relevant `.github/agents/<agent>.md` (or this file if cross-cutting),
not global Copilot Memory.
10. Record durable facts by kind: project-design decisions/conventions → `docs/decisions.md`;
agent-process facts → the relevant `.github/agents/<agent>.md` (or this playbook if
cross-cutting). Never global Copilot Memory.

## Fidelity contract (this port)

- Preserve `crap4java`'s class decomposition, CRAP formula (`CC² · (1 − coverage)³ + CC`, threshold
`8.0`), CLI contract, report format, and exit codes. Adapt only the ecosystem adapters (Java parser
→ Roslyn, JaCoCo → Cobertura, Maven → dotnet).
- **Approved deliberate departures** from the Java tool (see `docs/decisions.md`):
- **Fail fast** on no-tests-run / no-coverage-produced for a module → non-zero exit (not the Java
warn-and-continue with `N/A`). Per-method `N/A` is unchanged.
- **Richer cyclomatic complexity** — counts switch-expression arms, `??`/`??=`, pattern
`and`/`or`/`not`, and every `when` guard, in addition to the Java decision set. CC is therefore
not numerically comparable to `crap4java` on such constructs.
- **Coverage key** by Roslyn enclosing-type FQN; **nullable** reference types enabled;
**InvariantCulture** + explicit `\n` in the report.
Project design — the CRAP formula, approved departures, the complexity node set, and ratified
conventions (assembly structure, test naming) — lives in `docs/decisions.md`; reload it before
working.
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refer `.github/copilot-instructions.md`.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ files.

## Coverage Pipeline

For each invocation, per module (nearest `.sln`, else `.csproj`, else the project root):
Each invocation resolves a **single module root** (nearest `.sln`, else `.csproj`, else the project
root) and runs coverage **exactly once** there:

1. Delete stale coverage artifacts:
- `coverage/`
2. Run `dotnet test --collect:"XPlat Code Coverage" --results-directory coverage`
3. Read the produced `coverage.cobertura.xml`
3. Read the produced `coverage.cobertura.xml` (one is picked when several exist — see Notes)
4. Analyze the selected C# files

## Build and Test
Expand All @@ -37,6 +38,17 @@ For each invocation, per module (nearest `.sln`, else `.csproj`, else the projec
dotnet test
```

## Test policy (agentic dev loop)

Tight agentic development loops run the **unit tests by default** for fast feedback.

- **Unit-test convention:** a test is treated as a **unit test unless it is explicitly marked an
integration test** (e.g. `[Trait("Category", "Integration")]`).
- crap4csharp marks **none** of its tests, so **all** of them are treated as unit tests and run by
default — including the process- and `git`-touching tests.
- Running the **full** suite (including any long-running tests) requires **explicit user approval**,
because slow tests delay the tight loop.

## Run

Build:
Expand Down Expand Up @@ -82,4 +94,10 @@ dotnet run --project src/Crap4CSharp -c Release -- project-a project-b
- **Fail fast:** if a module runs no tests or produces no coverage, `crap4csharp` exits non-zero
rather than continuing — a deliberate, stricter departure from `crap4java`. A method simply absent
from an otherwise-populated report is still reported as `N/A`.
- **Multi-test-project coverage (interim limitation):** `dotnet test --collect` emits one
`coverage.cobertura.xml` per test project, and crap4csharp deterministically picks a single
(ordinal-first) report. On a solution with multiple test projects, methods covered only by the
non-picked projects have no matching entry and resolve to `N/A` — scoring as if uncovered and
inflating their CRAP. Multi-report aggregation is deferred; single-test-project layouts (the common
case) are unaffected.
- Report output is sorted by CRAP descending, with `N/A` at the bottom.
36 changes: 36 additions & 0 deletions crap4csharp.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED496051-9E7A-453C-9A26-247CB424CE32}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Crap4CSharp", "src\Crap4CSharp\Crap4CSharp.csproj", "{63C6F08D-6D93-4A2E-9A08-4B9159DB0D13}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A013CBD6-7643-4A36-A131-357A886B0B9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Crap4CSharp.Tests", "tests\Crap4CSharp.Tests\Crap4CSharp.Tests.csproj", "{693BA54C-6870-4D71-BE43-7DD723E24BE5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{63C6F08D-6D93-4A2E-9A08-4B9159DB0D13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63C6F08D-6D93-4A2E-9A08-4B9159DB0D13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63C6F08D-6D93-4A2E-9A08-4B9159DB0D13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63C6F08D-6D93-4A2E-9A08-4B9159DB0D13}.Release|Any CPU.Build.0 = Release|Any CPU
{693BA54C-6870-4D71-BE43-7DD723E24BE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{693BA54C-6870-4D71-BE43-7DD723E24BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{693BA54C-6870-4D71-BE43-7DD723E24BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{693BA54C-6870-4D71-BE43-7DD723E24BE5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{63C6F08D-6D93-4A2E-9A08-4B9159DB0D13} = {ED496051-9E7A-453C-9A26-247CB424CE32}
{693BA54C-6870-4D71-BE43-7DD723E24BE5} = {A013CBD6-7643-4A36-A131-357A886B0B9B}
EndGlobalSection
EndGlobal
Loading
Loading