Skip to content

docs(dotnet): plan the .NET (C#) port - #42

Merged
aslakhellesoy merged 27 commits into
mainfrom
claude/plan-dotnet-port-q83reg
Jul 19, 2026
Merged

docs(dotnet): plan the .NET (C#) port#42
aslakhellesoy merged 27 commits into
mainfrom
claude/plan-dotnet-port-q83reg

Conversation

@aslakhellesoy

Copy link
Copy Markdown
Contributor

Planning docs for a .NET (C#) language port, produced with the adding-a-language-port skill. Planning only — no port code. Stacked on rename-to-varar (#39), so the diff is just these six docs and all coordinates use the post-rename Varar naming.

The mechanical decisions

  • Full pipeline port. The CLR shares no runtime with any existing port (JS, CPython, JVM, Ruby, Rust-native), so — like Python/Ruby/Rust — C# must reproduce all four conformance artifacts (var-doc/registry/plan/trace) × 15 bundles + the config corpus byte-for-byte; drift is unit-gated.
  • C# owns the engine; F# is a later facade over it (Kotlin-over-Java route, registry-only conformance). The CLR hosts C#/F# the way the JVM hosts Java/Kotlin, so the pairing is settled up front. Because the VSTest adapter is framework-neutral, F# should reuse it directly — needing only a facade + .steps.fs fixtures.
  • Static-language author-API forks (matching Java/Kotlin/Rust): injected Registrar, full-replacement immutable Value state (no runtime deep_freeze), and [CallerFilePath]/[CallerLineNumber] call-site capture for step source location.
  • Test integration = a custom VSTest adapter (ITestDiscoverer/ITestExecutor, auto-loaded via *.TestAdapter.dll) — the structural analog of Java's custom JUnit TestEngine, framework-neutral (works with dotnet test regardless of xUnit/NUnit/MSTest) and discovery-time, giving one selectable test per .md example. (ADR 0009, with the data-driven-[Theory] alternative rejected.)

Two de-risking findings

  • UTF-16 is expected free. C# string/char and Regex.Index are UTF-16 code-unit indexed like JS/JVM, so the Python port's conversion layer shouldn't recur — still gated on bundles 11-emoji-offsets/12-combining-marks rather than assumed.
  • cucumber-expressions parity, unlike Rust. The official Cucumber.CucumberExpressions 20.0.0 is on NuGet (Cucumber org, June 2026) — exact version parity, no hand-ported grammar or {float} gap.

Package shape (post-rename NuGet idiom)

Varar.Core · Varar (facade) · Varar.Config · Varar.Runner · Varar.TestAdapter

Files

  • doc/adr/0008-dotnet-port.md — .NET port (full pipeline; C# owns engine; F# later facade)
  • doc/adr/0009-dotnet-test-adapter-integration.md — VSTest adapter integration
  • doc/superpowers/specs/2026-07-19-dotnet-core-port-design.md — core + facade
  • doc/superpowers/specs/2026-07-19-dotnet-runner-adapter-design.md — config + runner + adapter
  • doc/superpowers/plans/2026-07-19-dotnet-core-port.md — core TDD plan (staged by the 4 artifacts)
  • doc/superpowers/plans/2026-07-19-dotnet-runner-adapters.md — runner/adapter + repo/release integration

Note: ADRs are numbered 0008/0009 assuming they land after Rust's 0007. Merging this before rename-to-varar (its base) is not intended.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj


Generated by Claude Code

Full-pipeline port on the CLR (no runtime sharing), gated on all four
conformance artifacts. C# owns the engine; F# is a later facade over it
(Kotlin-over-Java route, registry-only conformance). Coordinates use the
post-rename Varar scheme (NuGet Varar.*; varar.config.json; varar-examples).

- ADR 0008 (.NET port) + ADR 0009 (VSTest ITestDiscoverer/ITestExecutor adapter)
- core+facade and config+runner+adapter design specs
- two TDD task plans

Key forks (static-language precedent): injected Registrar, full-replacement
immutable Value state, [CallerFilePath]/[CallerLineNumber] call-site capture.
UTF-16 offsets expected free (C# strings are UTF-16); official
Cucumber.CucumberExpressions 20.0.0 gives exact version parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
claude added 2 commits July 19, 2026 09:25
…fsets

Sub-project 1, task T0 of the .NET (C#) port plan.

- dotnet/ workspace: Varar.sln + Varar.Core / Varar (facade) / Varar.Core.Tests
  skeleton projects; global.json, shared Directory.Build.props (nullable,
  warnings-as-errors), .gitignore.
- References Cucumber.CucumberExpressions 20.0.0 (exact cross-port parity).
- Confirms empirically that the library reports UTF-16 code-unit match offsets,
  so no code-point conversion layer is needed (CucumberOffsetTests). This
  resolves the plan's biggest open question early.

Environment note: pinned to net8.0 (the SDK available here) rather than the
plan's eventual net10.0; both are LTS and the dependency is netstandard2.0.

Build + tests + `dotnet format --verify-no-changes` all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 1, task T1 — the immutable data + serialization foundation the
pipeline is gated on.

- Value: closed record hierarchy (null/bool/int/float/string/list/map) with
  structural equality mirroring deep-equal.ts (Int(2) != Float(2.0), lists
  order-sensitive, maps order-insensitive). Port of the Rust Value model.
- CanonicalJson: hand-rolled serializer (UTF-16 key sort, 2-space indent, LF +
  trailing newline, raw non-ASCII, control chars \uXXXX, integral doubles as
  integers) — ported from canonical_json.rs rather than configured on
  System.Text.Json, for byte-exact control (as Java/Rust do). Gated by
  re-serializing 8 committed goldens (bundles 04/11/12/15) byte-for-byte.
- Span: source range in UTF-16 code units + 1-based line/col; port of span.ts.

25 tests green; dotnet format clean. Removed the T0 placeholder smoke test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
@aslakhellesoy
aslakhellesoy changed the base branch from rename-to-varar to main July 19, 2026 10:28
claude and others added 24 commits July 19, 2026 10:41
Sub-project 1, task T2 (core half) — the step/parameter-type registry the
registry.json gate (T4) projects from.

- Registry / StepRegistration / addStep / defineParameterType: port of
  registry.ts (immutable step list; duplicate-expression detection with both
  source positions; custom types tracked for the {name, regexp} projection).
- ParameterTypeRegistry + VararParameterType: the .NET cucumber-expressions
  package ships only IParameterType/IParameterTypeRegistry (no built-in
  registry), so var supplies its own, pre-loading int/float/double/word/string/
  anonymous from the package's own ParameterTypeConstants patterns for
  cross-port byte-parity. Each type also carries a Value transform (wired now,
  exercised at matching/T5).
- StepRole: StepKind + inferStepRole, port of step-role.ts.

The injected-Registrar author facade (DefineState/Stimulus/Sensor) is the next
increment. 35 tests green; dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 1, task T2 (facade half) — the authoring surface the .steps.cs
conformance fixtures (T4) are written against.

- Steps builder in the Varar facade: static Register(Registry) -> Registry entry
  point (injected-Registrar model, ADR 0008; no module-scope accumulator),
  folding stimulus/sensor/param into the core registry.
- Full-replacement state: a stimulus returns the whole next Value. Typed lambda
  overloads for 0/1/2 captures over Value (the C# analogue of Rust's
  IntoHandler); handlers fail by throwing.
- Source file/line captured via [CallerFilePath]/[CallerLineNumber]; the file's
  stem is the cross-port stepFile. Per-file state factories thread through the
  core Registry (ContextFactories) — what the TS facade keeps in a module global.

New Varar.Tests project. 41 tests green (6 facade + 35 core); dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
…e green

Sub-project 1, task T3 — the var-doc.json milestone. Ports the Markdown parse
pipeline and gates it byte-for-byte against the committed goldens across all 15
conformance bundles.

- Ast: the immutable block AST (heading/paragraph/list_item/blockquote/table/
  fence/thematic_break), Row/SegmentOffset/Example/VarDoc, RawLine + the
  scanner-plugin port. Port of ast.ts.
- Scanner: line-based block scanner (fences, tables, thematic breaks,
  blockquotes, headings, list items, paragraphs) with a JS-slice-compatible
  clamping helper. Port of scanner.ts.
- Structurer, TableCells, Parse: examples-under-scopes, row cell spans, and the
  parse entry point. Ports of structurer.ts / table-cells.ts / parse.ts.
- Conformance.ToVarDocArtifact: the var-doc.json projection to Value.

UTF-16 offsets verified free via bundles 11-emoji-offsets / 12-combining-marks
(part of the 15). 51 core tests green (15 bundles byte-for-byte); dotnet format
clean. Kept as chore(dotnet) per the plan (feat scopes wait for the NuGet
release target in sub-project 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 1, task T4 — the registry.json milestone.

- Authored conformance/bundles/*/**.steps.cs for all 15 bundles: injected-
  Registrar Register(Registry) fixtures with the same expressions, kinds, custom
  parameter types, and (best-effort) handlers as the .steps.rs siblings. Namespaced
  Varar.Corpus.B## to avoid the Varar.Core.Conformance name.
- Conformance.ToRegistryArtifact: projects each step to {expression,
  parameterTypeNames} (names read from the compiled expression's ParameterTypes,
  which cover order/duplicates/anonymous) plus custom {name, regexp}.
- Varar.Tests harness: compiles the corpus fixtures, maps bundle -> Register/State,
  and gates registry.json byte-for-byte across 15 bundles.
- HandlerException in the facade for the throw-to-fail model.

Two of four artifacts now green (var-doc + registry) x 15 bundles. 73 tests green;
dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
…e green

Sub-project 1, task T5 — the plan.json milestone. Ports matching and planning and
gates plan.json byte-for-byte across all 15 bundles.

- Matcher: findHits/resolveHits (port of matcher.ts). Scans each sentence with the
  cucumber regex (anchors stripped), then extracts per-parameter spans and values
  from cucumber's TreeRegexp Group tree (Group.Start/End are UTF-16, confirmed) —
  the .NET stand-in for the JS Argument.getValue()/group API the package omits.
  Ambiguity + overlap resolution ported 1:1.
- Sentences: sentence splitter with backtick/quote/number/abbreviation guards.
- Diagnostics: ambiguous-match / error-fence-without-step / drift.
- Plan: per-example planning — segment-map span lifting, header-bound table rows,
  error-fence expected-failure, table/doc-string attachment, example naming.
- Conformance.ToPlanArtifact: args as the raw source slice of each param span.

Three of four artifacts green (var-doc + registry + plan) x 15 bundles. 88 tests
green; dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 1, task T6 — the trace.json milestone, completing the core: all four
conformance artifacts (var-doc, registry, plan, trace) x 15 bundles now reproduce
the committed goldens byte-for-byte.

- Execute: the executor on the full-replacement state model (port of execute.rs /
  execute.ts). Per-example, per-stepfile state; stimulus replaces state wholesale;
  sensor return compared against its slots (inline params, then trailing table/doc
  string); header-bound row checks after the loop; error-fence expected-failure
  inversion. Handlers fail by throwing.
- Diff engine: cell-diff (compareRow/compareTable + renderCellValue), param-diff
  (compareParams with format rendering), doc-string-diff (compareDocString), and
  the structured errors (CellMismatch/DocStringMismatch/ReturnShape/UnexpectedPass).
- FailureAnchor + Conformance.ToTraceArtifact/toFailureArtifact: the inline trace
  projection (stepFile stem, failure kind + anchor + cells/diff).

Handlers authored in the T4 fixtures now execute; the return-based comparison
contract (bare returns, positional arrays, header-bound rows, doc strings) is
exercised against the goldens. 103 tests green; dotnet format clean. Drift (T7,
unit-gated) is the remaining tail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 1, task T7 — the drift feature (unit-gated, no golden), completing the
pure core.

- Hash: FNV-1a (32-bit) over UTF-16 code units → fnv1a:<8 hex>. Port of hash.ts.
- IBaselineStore: the varar.lock.json port interface (filesystem impl is the
  runner's, sub-project 2).
- Drift: Jaccard word-similarity re-identification (threshold 0.5), live-example
  derivation, detectDrift, reconcileDrift, and the varar.lock.json parser +
  its own insertion-order serializer (JSON.stringify(_,null,2)+"\n", paths
  sorted) — not the recursive-sort canonical JSON. Port of drift.ts.

Proven by translating hash.test.ts / drift.test.ts (27 tests), incl. the FNV-1a
known vectors. 130 tests green total; dotnet format clean.

Sub-project 1 done: all four conformance artifacts x 15 bundles byte-for-byte +
drift unit-gated. Remaining (sub-project 2): var-config reader, runner, VSTest
adapter, tree-sitter dialect, and repo/release integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 2, task P1 — the varar.config.json reader.

- Varar.Config: strict, fail-loud parser of the canonical
  { docs:{include,exclude}, steps, snippets, scannerPlugins } shape (port of
  config.ts): no docs/steps defaults, unknown keys / wrong types / non-string
  arrays are errors (message prefixed with the config path), missing file →
  empty. ParsedVarConfig + VarGlobs types; ToArtifact projection.
- Reproduces conformance/config/cases/* (8) byte-for-byte: golden.json via the
  core's canonical JSON, or an expect-error.txt case must throw. no-config-file
  uses the empty default.

87 core tests green (8 config cases + error cases); dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 2, task P2 — the imperative shell.

- Discovery: the shared glob→regex semantics (** / * / ? / /**/ ), a recursive
  file walk, and include/exclude filtering — ported to match every other runner.
- FileBaselineStore: the filesystem IBaselineStore (varar.lock.json read/write).
- Runner: PlanSpec (parse+plan), ExampleNames (innermost-heading dedup with [n]),
  RunExample (delegates to the core executor), LoadSteps (reflects and chains
  every static Register(Registry) in an assembly — the injected-Registrar
  discovery), and RenderFailure (reuses the core diff payloads, .md-anchored).

Runner-level drift reconciliation is exercised through the real FileBaselineStore.
103 core tests green; dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 2, task P3 — the dotnet test binding (ADR 0009).

- Varar.TestAdapter: a custom VSTest adapter. VararTestDiscoverer
  ([FileExtension(".dll")], [DefaultExecutorUri("executor://varar")]) loads the
  built test assembly, walks up to varar.config.json, globs the .md specs, plans
  each via the runner, and emits one TestCase per example — CodeFilePath/LineNumber
  point at the .md source line. VararTestExecutor runs each example through the
  core (state from the registry's DefineState factories) and reports pass/fail with
  the runner's .md-anchored failure render. Ships as Varar.TestAdapter.dll so
  dotnet test auto-loads it on package reference.
- samples/Varar.Sample: a minimal consumer (varar.config.json + counter.md +
  counter.steps.cs) proving auto-load — `dotnet test` discovers and runs
  "I increment. The count is 1" green with no user wiring.

Full solution green (155 tests); dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 2, task P4 — the per-language authoring surface.

- tree-sitter-dialects/csharp.ts: step-def query (s.Stimulus/s.Sensor invocations
  on a member access), custom-parameter-type query (s.Param with a plain or
  verbatim regexp), C# string decoding (escape sequences + @"" verbatim), and
  lambda handler-param extraction. Queries verified empirically against
  tree-sitter-c-sharp 0.23.5 and the 15 .steps.cs fixtures.
- Wired into tree-sitter-scanner (SPECS/EXTENSIONS + LanguageId 'csharp') and the
  grammar loaders (test loader + LSP node loader) and the VS Code bundler copy
  list; grammar dep added to language/lsp + knip ignores. The vitest/website
  playground loaders stay TypeScript-only by design.
- The shared scanner now lower-cases @function-name for the StepKind, so C#'s
  idiomatic PascalCase Stimulus/Sensor map to stimulus/sensor (a no-op for every
  existing port, whose method names are already lower-case).

Proven by tree-sitter-scanner-csharp.test.ts (extracts the right kinds +
verbatim/plain custom types). Not yet added to languages.json — that lands in
P5 with the docs/editor tabs the cross-language gates require. 95 language-package
tests green; biome + tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 2, task P5 (build axis).

- Makefile: a `dotnet` target (dotnet format --verify-no-changes + build + test
  of the solution, then the VSTest adapter smoke sample), threaded into `check`
  and documented in the header — mirroring each other port's gate.
- .github/workflows/dotnet.yml: sets up the .NET 8 SDK and runs the same gate on
  dotnet/** and conformance/** changes.

`make dotnet` green locally (155 solution tests + the adapter sample).

The website/editor axis (languages.json entry, docs code tabs, front-page editor
tabs, CM_LANGUAGE), the examples/csharp-* consumer, and the NuGet release target
remain — they land together since adding csharp to languages.json gates the
website build until its tabs/examples exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Sub-project 2, task P5 (examples axis).

- examples/csharp-vstest: a standalone consumer that runs the six shared Markdown
  specs (hello-var, deep-thought, roman-numerals, tables-and-docstrings, yahtzee,
  library) as `dotnet test` tests via the VSTest adapter — 30 examples, all green.
  The .md specs are symlinks to the typescript-vitest originals; the SUT
  (RomanNumerals, Yahtzee, Library with dates/money) lives in src/, the injected-
  Registrar step files in steps/*.steps.cs (full-replacement Value state, custom
  {date}/{money}/{title} parameter types). Project references the local build; a
  real project uses the published NuGet packages.
- Fix {string} transform: the .NET cucumber build captures {string} WITH its
  surrounding quotes (inner groups compiled non-capturing), so strip them before
  unescaping. The conformance corpus never compared a transformed {string} value
  (every {string} sensor returns nothing), so this gap only surfaced in the
  hello-var example. Conformance stays green.
- make dotnet + dotnet.yml now run the consumer (replacing the throwaway smoke
  sample); examples/README.md row added.

Solution 155 tests + example 30 examples green; dotnet format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Add C# to languages.json (the shared source of truth), the SiteLang union,
and the CodeMirror highlighter map, then wire the C# code tabs everywhere the
language-coverage drift gate requires: every `<Tabs syncKey="lang">` group in
the stimuli/sensors/custom-parameters/tables-and-doc-strings/get-started docs,
and a C# `<File>` in the three front-page editors (Deep Thought, Library,
Roman Numerals), sourced from examples/csharp-vstest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
… (P5)

Wire the .NET port into the release pipeline the same way Rust is wired: a
parked NuGet publish target (68-nuget.sh, gated by DOTNET_NUGET_ENABLED) with a
go-live checklist, kept in lock-step with the varar-examples sync — while parked
the csharp-* samples are excluded from the sync (their project references to
dotnet/ can't resolve until the packages are on NuGet) and pinned to the release
version once live. Add the C# port to the README build/coverage table (build
badge live, coverage n/a until coverlet is wired) and coverage.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
Update the adding-a-language-port skill overview: seven ports now reach full
behavioural parity (Rust and C# added), and C#/Rust are the precedents for a
full-pipeline port in a static language with an explicit Value model, an
injected registrar, and full-replacement state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
…src/ move

The library sample's domain import moved from './library' to '../src/library'
when the system-under-test was split into a production source set, but the
ts-diagnostics test still filtered unresolved-module noise for './library' only,
so the real import's noise leaked through and failed the type-check assertion.
Match the domain module by its '/library'' suffix instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZsEqbSdXCtqvKcs5cuMMj
…nd Roman editors

The interactive editors loaded the TypeScript steps and domain files at flat
paths, so the browser runner could not resolve the sample's `../src/library`
import and showed a red "Cannot import" banner. Give the files their real
`steps/…` + `src/…` layout so the relative import resolves exactly as it does
on disk; the in-browser language service now resolves it too. Tab labels are
unchanged (they render the basename).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XgoYfpakx2RwYwu6ztLPQc
…m, and keep step closures on the call line

Step files now expose `register(&mut Steps)` instead of the
`register(Registry) -> Registry` threading, dropping the
`Steps::from_registry(r)` / `s.into_registry()` bookends from every fixture and
harness — matching the Java/Kotlin injected-registrar model. `param_with_format`
is merged into a single `param(name, regexp, parse, Option<FormatFn>)`, the
one-`param` shape every other port already uses. A repo-root rustfmt.toml sets
`fn_call_width = 100` so `s.stimulus("expr", |state, arg| { … })` keeps the
expression and closure together on the call line instead of exploding one
argument per line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XgoYfpakx2RwYwu6ztLPQc
…PI surface minimal

Update the reference and tutorial C#/Rust snippets to the injected-builder
`Register`/`register` shape and the unified `param`, refresh the .NET port's
ADR / plans / specs, and add a "keep the public API surface minimal" principle
to the adding-a-language-port skill: expose only the author builder methods,
keep registry construction internal / crate-private, and prefer one method with
an optional argument over near-duplicate variants (the `param` lesson).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XgoYfpakx2RwYwu6ztLPQc
…t v3

Retarget from net8.0 to net10.0 (the current LTS) via Directory.Build.props and
global.json, pin the SDK to 10.0.302 in dotnet/.tool-versions, bump
Microsoft.NET.Test.Sdk and Microsoft.TestPlatform.ObjectModel to 18.8.1, and
migrate the test projects to xUnit v3 (xunit.v3.mtp-off 3.2.2 +
xunit.runner.visualstudio 3.1.5, OutputType=Exe) still on the VSTest runner.
Update the standalone example consumer, the CI workflow, the Makefile, and the
port README to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XgoYfpakx2RwYwu6ztLPQc
…nto Register, and shrink the public API

Author step files now expose `void Register(Steps s)` — the framework injects
the builder — instead of threading a Registry through `Steps.From(r)` /
`return s.ToRegistry()`, matching the Java/Kotlin/Rust injected-registrar model.
With that plumbing no longer author-facing, `Steps.From`/`ToRegistry`/ctor and
the Registry construction API (`Create`/`AddStep`/`DefineParameterType`/
`WithContextFactory`, `StepInput`/`ParameterTypeInput`) become `internal`,
exposed to the runner and tests via InternalsVisibleTo, so adapters receive a
Registry as an opaque token rather than building one by hand.

Also a behaviour-preserving idiomatic sweep across the port: collection
expressions, primary constructors, range / index-from-end operators,
`[GeneratedRegex]`, `char.IsAscii*`/`IsNullOrWhiteSpace`, and redundant-using
removal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XgoYfpakx2RwYwu6ztLPQc
…hecks

The per-language tree-sitter dialects are parallel by design — each new port
(C# most recently) adds a dialect whose param-extraction/query shape mirrors a
sibling's, which jscpd flags as a clone. Ruby and Python wrapped that shared
shape in inline `jscpd:ignore` blocks; generalise the exemption to the whole
`packages/language/src/tree-sitter-dialects/` folder so csharp.ts (vs java.ts)
and every future dialect are covered by one rule, and drop the now-redundant
inline markers (keeping their rationale as a plain comment).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XgoYfpakx2RwYwu6ztLPQc
@aslakhellesoy
aslakhellesoy merged commit 8b5c480 into main Jul 19, 2026
7 checks passed
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.

2 participants