Skip to content

crap4csharp: faithful C# port of crap4java (WIP)#5

Draft
parthopdas wants to merge 24 commits into
masterfrom
vibe/crap4csharp-port
Draft

crap4csharp: faithful C# port of crap4java (WIP)#5
parthopdas wants to merge 24 commits into
masterfrom
vibe/crap4csharp-port

Conversation

@parthopdas

Copy link
Copy Markdown
Collaborator

Running PR for the crap4csharp port — a high-fidelity C# port of the read-only Java tool crap4java. Driven task-by-task (T1–T16) via the hub-and-spoke agentic loop (Dave codes, Bhaskar verifies, Anders reviews, JARVIS orchestrates, Mr. Das decides). Authoritative plan: docs/features/crap4csharp-port.md; locked decisions: docs/decisions.md.

This is a WIP draft and will accumulate commits as tasks land. Do not merge until all slices are green and Mr. Das approves.

Progress

Slice Status
S1 — scaffolding ✅ T1 done (d3dd17f) — Release build 0/0, dotnet test green
S2 — pure core ⏳ next
S3 — adapters
S4 — composition + fail-fast + e2e

T1 landed

  • crap4csharp.sln + src/Crap4CSharp (net8.0 Exe, Roslyn Microsoft.CodeAnalysis.CSharp 4.14.0) + tests/Crap4CSharp.Tests (xUnit + coverlet + FluentAssertions pinned [7.0.0,8.0.0)), shared .targets imported, packages.lock.json committed.

Open decisions for Mr. Das (non-blocking for S2)

  • C1 — single production assembly (convention-based layering) vs Core/Adapters/Exe split. Anders recommends single assembly.
  • C2 — CA1707 (warnings-as-errors) forbids underscores in member names; keep PascalCase test names everywhere (recommended) vs suppress CA1707 for tests/**.

Co-authored-by: Copilot

parthopdas and others added 2 commits July 26, 2026 12:24
Add crap4csharp.sln, src/Crap4CSharp (net8.0 Exe with Roslyn Microsoft.CodeAnalysis.CSharp 4.14.0) and tests/Crap4CSharp.Tests (xUnit + coverlet.collector + FluentAssertions pinned [7.0.0,8.0.0)), importing the shared Crap4CSharp(.Tests).Common.targets. Empty build + dotnet test are green in Release (warnings-as-errors); packages.lock.json committed for deterministic restore.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… Das

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service

Copy link
Copy Markdown

@parthopdas please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.

  1. Definitions.
    “Code” means the computer software code, whether in human-readable or machine-executable form,
    that is delivered by You to Microsoft under this Agreement.
    “Project” means any of the projects owned or managed by Microsoft and offered under a license
    approved by the Open Source Initiative (www.opensource.org).
    “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
    Project, including but not limited to communication on electronic mailing lists, source code control
    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
    discussing and improving that Project, but excluding communication that is conspicuously marked or
    otherwise designated in writing by You as “Not a Submission.”
    “Submission” means the Code and any other copyrightable material Submitted by You, including any
    associated comments and documentation.
  2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
    described in Section 4 below), Submit to any Project.
  3. Originality of Work. You represent that each of Your Submissions is entirely Your original work.
    Should You wish to Submit materials that are not Your original work, You may Submit them separately
    to the Project if You (a) retain all copyright and license information that was in the materials as You
    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
    containing materials of a third party:” followed by the names of the third party and any licenses or other
    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
    guidelines concerning Submissions.
  4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
    Submission is made in the course of Your work for an employer or Your employer has intellectual
    property rights in Your Submission by contract or applicable law, You must secure permission from Your
    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
    Agreement will refer to You and the employer collectively. If You change employers in the future and
    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
    and secure permission from the new employer before Submitting those Submissions.
  5. Licenses.
  • Copyright License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
    Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
    the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
    parties.
  • Patent License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
    Your patent claims that are necessarily infringed by the Submission or the combination of the
    Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
    import or otherwise dispose of the Submission alone or with the Project.
  • Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
    No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
    granted by implication, exhaustion, estoppel or otherwise.
  1. Representations and Warranties. You represent that You are legally entitled to grant the above
    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
    have disclosed under Section 3). You represent that You have secured permission from Your employer to
    make the Submission in cases where Your Submission is made in the course of Your work for Your
    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
  2. Notice to Microsoft. You agree to notify Microsoft in writing of any facts or circumstances of which
    You later become aware that would make Your representations in this Agreement inaccurate in any
    respect.
  3. Information about Submissions. You agree that contributions to Projects and information about
    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
    information that You submit with Your Submission.
  4. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and
    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
    defenses of lack of personal jurisdiction and forum non-conveniens.
  5. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
    supersedes any and all prior agreements, understandings or communications, written or oral, between
    the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

parthopdas and others added 7 commits July 26, 2026 12:57
Faithful C# port of the five crap4java domain types into the pure core: CliMode (PascalCase enum), CliArguments (IReadOnlyList), CoverageData (+exact CoveragePercent, total==0 -> 0.0), MethodDescriptor (+TypeName, the approved coverage-key-FQN departure), MethodMetrics (Double -> double?). Adds CoverageDataTests (ratio + total==0) and removes the throwaway scaffolding sanity test per decision C3.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ch-items W1-W5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Faithful port of crap4java CrapScore (final class + private ctor -> public static class): CRAP = CC^2*(1-coverage)^3 + CC, null coverage -> null. CrapScoreTests carries all four Java oracles with identical tolerances (5.0/30.0/18.648/null). Cube via multiplication (not Math.Pow) preserves bit-identical results vs the Java oracle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Faithful port of crap4java ReportFormatter: title/header/separator(88)/data rows, stable sort (scored before N/A, higher CRAP first), formatCoverage/formatCrap N/A handling. Approved determinism departures applied: InvariantCulture everywhere (CA1305) and explicit \n for ALL terminators (replacing Java's platform %n in data rows). Tests: golden exact-match, sort-order, and a stability guard using a 17-entry N/A tie-set (List<T>.Sort is a stable insertion sort at <=16, unstable at >=17, so the guard genuinely bites).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crap4java CliArgumentsParser: precedence empty->AllSrc, --help wins,
--changed (throws ArgumentException if combined with files)->ChangedSrc,
else->ExplicitFiles. Ordinal comparisons; unknown --flags ignored.
IllegalArgumentException->ArgumentException (verbatim message preserved).
CA1062-forced ArgumentNullException.ThrowIfNull(args) guard: behavior-
preserving fail-fast analog of Java NPE-on-null. 7 tests assert Mode and
FileArgs separately (W2: C# records do reference equality on IReadOnlyList).
16 tests green, 0/0 Release.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update task table: T3 (47c1a1c), T4 (f830f0e), T5 (1b7833b) -> Done.
Status line: S2 complete, 16 tests green, paused at boundary.
Mark W2 done (T5 honored it). Add Anders's T5 watch-items W6-W10 for the
T14/T15 CLI seam. Record open decision D-T5 (CA1062 ThrowIfNull idiom) for
Mr. Das's ruling before S3.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d pointer

Slim .github/copilot-instructions.md to a lean agent-process playbook: remove
the duplicated Fidelity contract (CRAP formula/threshold/departures) and the
Ratified conventions (C1/C2) sections; replace with a one-line pointer to
docs/decisions.md. Refine guardrail #10 to split durable project-design facts
(-> decisions.md) from agent-process facts (-> .github/agents/), never global
Copilot Memory.

decisions.md becomes the single source of truth for design: make the CRAP
formula CC^2*(1-coverage)^3+CC and threshold 8.0 explicit (new CRAP formula
section, previously only implied); add C1 (single production assembly) and C2
(CA1707/PascalCase test names) as ratified conventions; 3 approved departures
retained. Add thin root AGENTS.md pointer for cross-tool discovery. Zero design
duplication across files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@parthopdas
parthopdas force-pushed the vibe/crap4csharp-port branch from 3b67cd0 to 9b16ab3 Compare July 26, 2026 21:23
parthopdas and others added 15 commits July 26, 2026 14:50
Mr. Das ruled W1 = RENAME. Drop JaCoCo instruction terminology: this port's
coverage adapter is Coverlet -> Cobertura LINE counters. Rename record fields
MissedInstructions/CoveredInstructions -> MissedLines/CoveredLines and reframe
the doc comment; CoveragePercent math byte-identical, 16/16 tests green, 0/0
Release. Record as departure #4 in docs/decisions.md (naming counterpart to R1,
not a new behavioral departure). Feature doc: mark W1 resolved, add T10
counter-derivation watch-item W11.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mr. Das ruled D-T5 = below-threshold: no standing-policy line for the CA1062
ThrowIfNull idiom; treat as non-noteworthy. Record D-T9 (open, escalated):
drop sourcePath/sourceUri javac plumbing + adopt Parse(string source); blocks
T9 pending ruling. Anders recommends Option A (drop both).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Faithful port of crap4java SourceFileFinder: resolve projectRoot/src, empty on
missing, recursive walk, EndsWith(".cs") not a glob (mirrors Java endsWith,
dodges Windows 8.3 quirk), absolute paths. Two approved C# adaptations per the
T7 plan: ordinal sort (StringComparer.Ordinal) for cross-OS determinism, and
segment-exact bin/obj build-output exclusion (any depth; keeps Robin lookalike).
5 tests: faithful port + missing-src + bin/obj-exclusion + ordinal-order +
only-.cs. 21/21 green, 0/0 Release.

Design docs (Anders): decisions.md departure #3 extended (ordinal sort), new
departure #5 (bin/obj exclusion, load-bearing since T12 repopulates bin/obj),
C2 clarification (CA1707 scope = externally-visible + test methods; private
fields use _camelCase). Feature file watch-items W12/W13/W14.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cutor)

Faithful port of crap4java CommandExecutor/ProcessCommandExecutor: sync
int Run(IReadOnlyList<string> command, string workingDirectory); launches
command[0] with the rest via ArgumentList (UseShellExecute=false), sets the
working directory, and returns the child ExitCode. Single test oracle exit==7,
mirroring Java /bin/sh -c "exit 7".

Ratified I-series "async stdout drain" idiom is the C# analog of Java
.inheritIO(): both stdout+stderr are redirected and drained asynchronously
(OutputDataReceived/ErrorDataReceived + BeginOutputReadLine/BeginErrorReadLine,
written through to Console) with Start -> Begin*ReadLine -> WaitForExit ordering,
so a full pipe buffer cannot deadlock and child output stays user-visible.
ThrowIfNull guards are the below-threshold CA1062 idiom (D-T5).

Test is cross-platform (Windows cmd.exe /c exit 7; else /bin/sh -c exit 7),
identical exit-7 oracle both branches — ruled below-threshold by Anders (no
departure). Anders added watch-item W15: ICommandExecutor is exit-code-only;
T8 (git) keeps its own capturing process and must fix Java's latent full-pipe
deadlock rather than reuse this seam.

22/22 tests green, 0 warnings / 0 errors in Release. Also backfills the task
table: T6 Done, T7 Done (1b90d24).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Faithful port of crap4java ChangedFileDetector: ChangedCSharpFiles runs
git -C <root> status --porcelain, parses each porcelain line
(substring(3).Trim -> RenameTarget -> .cs filter -> absolute normalized path),
ordinal-sorts, and returns IReadOnlyList<string>. ChangedCSharpFilesUnderSrc
applies a segment-aware under-<root>/src filter (Path.startsWith semantics, not
a naive prefix). Public IsCandidateLine / RenameTarget helpers unit-tested.
Non-zero git exit -> InvalidOperationException (Java IllegalStateException) whose
message carries git's stderr (e.g. "not a git repository"). All 5 Java tests
ported faithfully with a cross-platform RunGit setup helper (invokes git
directly instead of Java's sh -c).

Uses its OWN Process, not ICommandExecutor (W15: this is an exit-code-only seam;
git needs captured stdout). Applies the W15 deadlock fix in prod + test: both
StandardOutput/StandardError ReadToEndAsync start BEFORE WaitForExit, so pipes
drain concurrently and cannot wedge -- deliberately NOT Java's
waitFor()-then-readAllBytes() ordering.

Adaptations (all confirmed by Anders as below-threshold / covered): ordinal sort
under decisions.md departure #3 (now extended to name ChangedFileDetector);
redirectErrorStream -> stdout+stderr merge for the error message; sh -c ->
direct-git test-infra swap. Anders added W16 (T14 ChangedSrc must call the
UnderSrc variant) and logged D-T8 (integration-trait / build-test.md doc-reality
gap) as an OPEN decision for Mr. Das.

27/27 tests green (deterministic x2), 0 warnings / 0 errors in Release. Also
backfills the task table: T6 da02028; T8 Done.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ckfill T8 SHA

Plan update ruled by Mr. Das — add the two closing slices to the feature file:

- S5 (depends S4): independent clean-room evaluation. Task T17 — a neutral
  evaluator sub-agent on gpt-5.6-sol judges the delivered port against the
  verbatim ## Requirements block; report-only verdict. Status: Pending (deferred).
- S6 (depends S4, S5): real-world dogfooding. Task T18 — run crap4csharp on
  crap4csharp + ../mutate4csharp + ../dry4csharp; capture CRAP reports; summarize
  crappy methods. Status: Pending (deferred).

Also:
- R6: coverlet.collector + resolvable .sln prerequisite; sibling repos can't be
  modified in place — S6 may surface tool gaps. (Noted, not solved now.)
- D-S6 (resolved): siblings are read-only (guardrail #2); S6 runs against COPIES
  in a scratch/temp workspace outside all source repos, never writing into them.
- Backfill T8 commit SHA (c023ef8) into its task-table row.

Docs-only change; no code/build impact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Faithful port of crap4java CliApplication.moduleRootFor, with the marker swapped
pom.xml -> .sln (primary) + .csproj (fallback). Single upward walk (inclusive of
start), file argument normalized to its parent dir; any .sln up the chain beats a
nearer .csproj; else nearest .csproj; else the start directory.

- public static class ModuleRootResolver { public static string Resolve(string startDirectory); }
- Marker probe mirrors SourceFileFinder discipline: EnumerateFiles + EndsWith(Ordinal),
  not a *.sln glob (dodges 8.3/.slnf/.slnx quirks); .slnx/.slnf never match .sln.
- ArgumentNullException.ThrowIfNull guard; nullable on; no internal; no suppressions.
- 12 tests (linchpin PrefersSolutionOverNearerProjectFile). Suite 27 -> 39 green;
  dotnet build -c Release 0/0 (warnings-as-errors); deterministic x2 (Bhaskar).

B1 (departure candidate, staged as decisions.md #6 pending Mr. Das): the walk is
unbounded (climbs to filesystem root, fallback = start dir) vs Java's
workspace-bounded walk / workspaceRoot fallback. Anders recommends approve-as-specced.

Docs: feature-file T13 row -> Done; W17 (T14 resolve-once vs multi-module grouping,
Mr. Das call at T14) + W18 (nonexistent-path normalization, below threshold) recorded;
D-T13/B1 logged as open. Departure #6 NOT landed in decisions.md until ruled.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mr. Das approved D-T13/B1. ModuleRootResolver climbs unbounded to the filesystem
root and falls back to the start directory (nearest .sln -> .csproj -> start),
in place of crap4java's workspace-bounded moduleRootFor / workspaceRoot fallback —
the C# CLI has no Maven-style workspace concept. T13 shipped as-is (e4d1329); no
rework. Recorded as departure #6 in docs/decisions.md; D-T13 marked RESOLVED in
the feature file.

Docs-only; no code/build impact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…en TypeName)

Faithful Roslyn port of crap4java JavaMethodParser. Mr. Das ruled D-T9 Option A:
adopt Parse(string source); drop the 2 javac file-URI plumbing tests (no C#
analog); port the other 7 faithfully.

- public static class CSharpMethodParser { public static IReadOnlyList<MethodDescriptor> Parse(string source); }
- Syntax-only (CSharpSyntaxTree.ParseText, LanguageVersion.Latest; no compilation/
  SemanticModel) -- parse, don't bind. ArgumentNullException.ThrowIfNull guard.
- ComplexityWalker: private sealed nested CSharpSyntaxWalker, _complexity seeded 1,
  fresh per method, counts the augmented node set from docs/decisions.md (loops, if,
  catch, ?:, switch labels, &&/||/??, ??=, switch-expression arms, pattern and/or/not,
  catch filter, when); prunes nested types; descends into lambdas + local functions
  (departure #2). CC oracles reproduce Java exactly (alpha3/beta4/score10/nested13/
  switched4/stable1/helper1).
- Methods only (bodied MethodDeclarationSyntax, document order, bare Name); ctors/
  accessors/local-fns/lambdas excluded structurally. TypeName emitted in the FROZEN
  canonical form (dotted ns FQN + per-level backtick arity; global ns -> bare).
- 17 tests (7 faithful ports + 10 new). Suite 39 -> 56 green; build -c Release 0/0
  (warnings-as-errors); deterministic x2 (Bhaskar). Anders review GREEN.

Docs: decisions.md records the frozen TypeName reciprocal (T9 emit / T10 produce /
T11 consume). Feature file: W-T9a (T10 NormalizeTypeName -> frozen form, pin to real
coverlet sample), W-T9b (skip compiler-generated names), W-T9c (map key shape);
D-T9 -> RESOLVED. Back-filled T13 SHA (e4d1329).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…; behavioral XXE)

Final task of Slice S3. C# analog of crap4java JacocoCoverageParser, re-hosted on
Coverlet -> Cobertura per-method <line> counters (departure #4, line-based
MissedLines/CoveredLines). Implemented strictly to Anders's locked contract;
Bhaskar PASS (68/68 deterministic x2, Release 0/0); Anders review GREEN.

- public static class CoberturaCoverageParser { Parse(string?), NormalizeTypeName,
  IsCompilerGeneratedMethod }. File-scoped ns, nullable on, no internal, no suppressions.
- Frozen TypeName reciprocal (LOAD-BEARING): NormalizeTypeName =
  Replace('/','.').Replace('+','.') keeping backtick arity -> byte-for-byte equal to
  T9 CSharpMethodParser.TypeNameOf. Pinned by test 11 (D-table) + test 10 (real coverlet
  sample). D-T10b RESOLVED (keep arity); R3 normalization pin CLOSED.
- Secure XML (D-T10a RESOLVED): DtdProcessing.Ignore + XmlResolver=null; external
  entities never resolved. Java's white-box configuresSecureFactoryFeatures replaced by
  BEHAVIORAL XXE test N10 (canary + file:// entity, asserts throw + canary never read).
  DOCTYPE-tolerance test ports faithfully under Ignore. Not a new behavioral departure.
- Walk class->methods->method->lines->line only; class-level <lines> aggregate skipped
  (no double-count); <conditions> ignored. Key = TypeName#method:minChildLine. Skip
  predicate rules 1-4 + W-T10c (synthetic MoveNext skipped via angle-bracket class; real
  MoveNext kept). Edges faithful to Java: null/missing/empty -> empty ordinal map (no
  throw); malformed -> InvalidOperationException("Unable to parse Cobertura XML...").
- 12 tests (3 faithful Java ports + empty-report/null/missing/malformed + real-coverlet
  R3 pin + skip-predicate + D-table + behavioral XXE). Suite 56 -> 68 green.

Docs: decisions.md gains the T10 register (D-T10a/b RESOLVED, R3 CLOSED, W-T10a/b/c) +
"T10 discharged" note on the frozen reciprocal. Feature file: W-T10a/b/c watch-items,
D-T10a/b RESOLVED, T10 -> Done. Back-filled T9 SHA (400d49d). S3 COMPLETE.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l->S6, dogfood->S7

Mr. Das added a closing slice. There are now THREE closing slices, in order:
- S5 (NEW) Module & test resolution finalization (depends S4): resolve the
  file->module->test model (options a keep locked .sln-first / b adopt
  mutate4csharp nearest-.csproj + <Project>.Tests/.UnitTests / c hybrid, per
  ../mutate4csharp README "Module & Test Resolution"); Mr. Das rules WHEN THE
  SLICE RUNS; then rework ModuleRootResolver (T13) + shape T12/T14. Locked now:
  keep running ALL module tests (crap4java parity), NO unit-only [Trait] filter
  (distinct from the open D-T8 dev-loop tagging question).
- S6 Independent clean-room evaluation (gpt-5.6-sol; formerly S5; spec unchanged;
  depends S5).
- S7 Real-world dogfooding (formerly S6, the finale; spec unchanged; depends S6).

Tasks renumbered: T17=S5 module resolution (new, first), T18=S6 eval (former T17
verbatim), T19=S7 dogfood (former T18 verbatim). R6 + D-S6 + ordering note keep
their stable labels; internal slice/task refs updated S6->S7 / T18->T19. Added an
engineering watch-item: keep T12/T14 decoupled behind the ModuleRootResolver seam
so the later S5 model swap is cheap.

NO code / resolution-model change now: the port keeps driving on the locked
.sln-first model through S3->S4; S5 is the only place the model is revisited.
Docs-only; Anders authored the restructure, JARVIS landed it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…policy note

Fold Mr. Das's S4-GO rulings into the design docs (docs-only, no code impact):

- decisions.md: add approved departure #7 (resolve-once — single module root
  per run via ModuleRootResolver, no crap4java §6 module-group loop; supersedes
  watch-item W17); add D-T8 (NO TRAITS) to Ratified conventions; cross-ref
  resolve-once from the Test-parity note.
- features/crap4csharp-port.md: W17 -> RESOLVED (resolve-once); D-T8 -> RESOLVED
  (no traits); trim S5 slice + T17 to resolution-model-only (grouping closed by
  #7); reconcile D-S5 open-D-T8 pointer.
- README.md: add "## Test policy (agentic dev loop)" — unit-by-default; a test is
  unit unless explicitly marked integration; full suite needs explicit approval.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Faithful 1:1 port of crap4java CrapAnalyzer — the flat S4 composition layer
that reads the changed files and drives both parsers (T9 CSharpMethodParser,
T10 CoberturaCoverageParser), composing CrapScore + MethodMetrics.

- Exact->nearest-line->N/A coverage lookup keyed on the FROZEN reciprocal
  TypeName#method:line form (byte-for-byte aligned with T10; trailing ":" is
  load-bearing). InvariantCulture line formatting (departure #3); Ordinal
  comparisons throughout.
- N/A discipline (W4 / departure #1): a missing lookup yields null, never 0.0;
  0%-present vs absent preserved end-to-end.
- Stable OrderBy(...).ThenByDescending(...), N/A last (not List.Sort).
- Resolve-once (departure #7): flat — no module-grouping loop (a T14 concern).
- Public tested helpers LookupCoverage/NearestCoverage/ParseTrailingLine
  (internal banned, C1); ExactCoverage private (least privilege).
- Drops Java classNameFromSource + dead projectRoot param (D-T9; behaviour
  migrated to T9). 17 tests: P1-P9 faithful ports + C1-C8 C#-specific.

Build 0/0 Release (warnings-as-errors); 85/85 tests green, deterministic x2.
docs/decisions.md: add T11 register (frozen-consumer discharged, D-T11a/b,
W-T11a/b/c). Backfill T10 SHA 87488ce; flip T11 -> Done.

Verified by Bhaskar (PASS); design-reviewed by Anders (GREEN).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… tests

Faithful port of crap4java CoverageRunner (delete-stale -> run-once ->
throw-on-nonzero) re-hosted on coverlet, plus a C#-specific
CoverageReportLocator (JaCoCo's report path was a fixed constant; coverlet
writes coverage.cobertura.xml into a random GUID subdir, so a locator is new).

- CoverageRunner: public sealed; ctor takes ICommandExecutor (T6 seam reused);
  const ResultsDirectoryName="coverage"; GenerateCoverage(projectRoot) deletes
  the stale coverage/ dir, runs ["dotnet","test","--collect:XPlat Code
  Coverage","--results-directory","coverage"] once at projectRoot (verbatim
  cwd, no normalization), throws InvalidOperationException("Coverage command
  failed with exit N") on non-zero exit (byte-identical to Java).
- CoverageReportLocator: public static; Locate(projectRoot) does an ordinal-
  first single-pick of one coverage.cobertura.xml (departure #8), null when
  absent. No mtime logic (deterministic, departure #3).
- Resolve-once (#7): both take a pre-resolved projectRoot; neither calls
  ModuleRootResolver (load-bearing S5/T17 swap seam, D-T12b).
- Fail-fast gate stays in T14: runner throws; locator returns null.
- 10 tests (RecordingExecutor fake; no real dotnet test): 5 runner + 5 locator.

Build 0/0 Release (warnings-as-errors); 95/95 tests green, deterministic x2.

docs/decisions.md: add approved departure #8 (single-pick coverage report,
Mr. Das-ruled; aggregation deferred to S5/T17) + T12 register (D-T12b/e/f
load-bearing invariants). docs/features: expand S5/T17 scope to include
multi-report coverage aggregation; backfill T11 SHA debf9de; flip T12 -> Done.

Verified by Bhaskar (PASS); design-reviewed by Anders (GREEN).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Faithful 1:1 port of crap4java CliApplication/Main (execute/parseArguments/
filesForMode/explicitFiles/maxCrap/thresholdExceeded), module-loop removed
(departure #7 resolve-once). Composes shipped types only; no new behavioral
departure beyond the approved set #1-#8.

- Fail-fast gate (departure #1), three greppable stderr anchors:
  1) Locate -> null  => "No coverage report was produced" => exit 1
  2) Parse(report).Count == 0 => "contained no coverage data" => exit 1
     (checks coverageMap.Count, not metrics)
  3) CoverageRunner throw propagates ("Coverage command failed with exit")
     => realized as exit 1 at T15
- Exit-code table owned by T14: 0 (help / no files / max<=8.0),
  1 (parse error / fail-fast / propagated), 2 (MaxCrap>8.0 strict ->
  "CRAP threshold exceeded: {max:F1} > 8.0", InvariantCulture).
- Double-parse locked (option A); Analyze(map) overload deferred (Mr. Das).
- Execute does NOT catch runner/parser throw (Java parity); T15 Program.Main
  bare-catch -> exit 1 realizes it (D-T14d).

17 parity tests (fake ICommandExecutor, no real dotnet test). 112 tests green
x2, 0/0 Release. Bhaskar PASS, Anders green. T14 register + tracker updated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant