crap4csharp: faithful C# port of crap4java (WIP)#5
Draft
parthopdas wants to merge 24 commits into
Draft
Conversation
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>
|
@parthopdas please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
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
force-pushed
the
vibe/crap4csharp-port
branch
from
July 26, 2026 21:23
3b67cd0 to
9b16ab3
Compare
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
d3dd17f) — Release build 0/0,dotnet testgreenT1 landed
crap4csharp.sln+src/Crap4CSharp(net8.0 Exe, RoslynMicrosoft.CodeAnalysis.CSharp4.14.0) +tests/Crap4CSharp.Tests(xUnit + coverlet + FluentAssertions pinned[7.0.0,8.0.0)), shared.targetsimported,packages.lock.jsoncommitted.Open decisions for Mr. Das (non-blocking for S2)
tests/**.Co-authored-by: Copilot