Add Go as a full pipeline port#47
Conversation
…nformance) Adds the `go/` workspace (module github.com/varar-dev/varar-go) with the varcore package: offsets (UTF-16 conversion layer), span, value model, canonical JSON serializer, AST, table-cell parsing, scanner, structurer, parse, and the var-doc conformance projection. All 15 bundles reproduce golden/var-doc.json byte-for-byte, including the emoji/combining-mark offset-fidelity bundles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Adds the varcore expression/registry/handler/step-kind layer over the official cucumber-expressions-go library (built-ins byte-identical to the reference; our own value transforms applied), the `varar` author facade (injected Steps builder, full-replacement state, runtime.Caller source capture), Go step fixtures for all 15 bundles in the shared corpus, and the registry conformance gate. All 15 bundles reproduce golden/registry.json byte-for-byte. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Adds the sentences splitter, the matcher (unanchored scan + greedy non-overlap resolution + ambiguity detection), planner diagnostics, RowCheck, the planner (block planning, span lifting, table/doc-string attachment, error-fence expected-failure, header-bound row expansion), and the plan conformance projection. All 15 bundles reproduce golden/plan.json byte-for-byte. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Adds the failure model (StepError/StepFailure), doc-string/param/cell diffs, failure anchoring, the executor (full-replacement state, panic-recovery as the assertion channel, sensor return-shape/slot comparison, header-bound row checks, error-fence inversion), and the trace conformance projection + RunConformance. All 15 bundles now reproduce all four goldens (var-doc/registry/plan/trace) byte-for-byte — the core pipeline is conformance-complete. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…ated) Adds FNV-1a source hashing over UTF-16 code units, Jaccard word-similarity drift detection, the insertion-ordered varar.lock.json serializer/parser, and the BaselineStore port + ReconcileDrift, with the hash/drift unit tests translated from the shared TS/Rust vectors (drift has no conformance golden). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Adds the varconfig package: a strict, fail-loud reader for the canonical
{ docs: {include, exclude}, steps, snippets, scannerPlugins } shape (missing
file → empty, unknown keys / wrong types / invalid JSON fail loudly). All 8
config conformance cases reproduce their golden byte-for-byte (or raise).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…ore) Adds the varrunner package: the shared glob→regex spec discovery, plan/run helpers, example display-name de-duplication, human-readable failure rendering, and the filesystem varar.lock.json BaselineStore — pure shell over varcore, with the runner unit tests translated from the shared suite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…ate) Adds the vargotest package: Run enumerates every Markdown example matched by varar.config.json as a Go subtest (t.Run), rendering failures anchored to the .md, plus a drift gate that reconciles varar.lock.json (VAR_UPDATE=1 accepts drift). The enumeration core (Collect) is unit-tested for the passing, drift-reported, and update-mode-accepts-drift paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
ADR 0010 records Go as a full pipeline port (author-API fork points, the UTF-16 offset layer, and the cucumber-expressions-go stale-library exception); ADR 0011 records the go-test subtest-per-example integration. Adds the core and runner+adapter design specs plus the task plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…, release Wires the Go port into the monorepo scaffolding: the `make go` gate (threaded into `check`) and the `make coverage` step (go test -coverpkg → lcov via scripts/gocover-to-lcov.sh); the README coverage row + coverage.json; the .github/workflows/go.yml CI; the standalone examples/go-gotest consumer project (6 specs, 30 examples, custom parameter types, header-bound rows, drift baseline) + examples/README row; and the parked release wiring (GO_MODULES_ENABLED gate, 69-go-modules.sh, the varar-examples go pin/exclude, and the go consumer scope in lint-commits/cliff). Also fixes runner spec discovery to follow symlinks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…Go tabs Wires Go into the shared authoring/LSP/website layer: the tree-sitter dialect (tree-sitter-dialects/go.ts, verified against tree-sitter-go 0.25.0 — its extraction matches TypeScript byte-for-byte across all 15 bundles), the scanner SPECS/EXTENSIONS + LanguageId union, both grammar loaders and the VS Code bundler + knip ignore blocks (tree-sitter-go), the CodeMirror `go` highlighter and SiteLang union, the front-page Editor Go <File> tabs, and a Go <TabItem> in every docs lang-tab group. languages.json gains the Go entry. The language-coverage drift gate and extraction/scanner tests pass; the website builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Code reviewFound 1 issue:
varar/examples/go-gotest/steps.go Lines 3 to 5 in 96f29c8 varar/examples/go-gotest/README.md Lines 26 to 28 in 96f29c8 API simplification (requested: godog comparison)Separately, on the ask to look for a simpler, more idiomatic API against godog. Four observations, roughly in order of value-for-effort: 1. Lines 26 to 42 in 96f29c8 2. Package clauses don't match their directories. Lines 13 to 15 in 96f29c8 3. Missing panicking 4. godog-style typed handlers — worth a follow-up, not this PR. ADR 0010 justifies the fixed The Note on what didn't make the cut: several candidate bugs were investigated and dropped as pre-existing rather than Go-specific — the unterminated-fence 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
…e skill The website's example-projects CardGrid had drifted four ports behind — Ruby, Rust, C# and Go were all missing, because no test covers that page. Adds a card for each. The parked ports (Rust, C#, Go) are excluded from the varar-examples sync until their registry publishing goes live, so their "Browse" links point at the monorepo's examples/ directory rather than 404ing on varar-examples. Also adds the page to the adding-a-language-port skill's repo-integration checklist, with the parked-vs-live link rule, so a future port doesn't repeat it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…returns Three review items, all cheap now and breaking once the module is tagged: 1. Package clauses now match their directories (`package core` in go/core, etc., instead of `varcore`/`varconfig`/`varrunner`/`vargotest`). Consumers write a bare `gotest.Run(...)` with no import alias. Locals that would shadow the `config` package were renamed to `cfg`. 2. `HandlerReturn` (a bespoke tri-state struct) is replaced by the idiomatic Go pair `func(state Value, args []Value) (*Value, error)`: `(nil, nil)` is no assertion, `(&v, nil)` a value, `(nil, err)` a failure — panicking still works, recovered into the same channel. Drops one exported type and three constructors (`Returns`/`NoReturn`/`Fails`) for one helper, `Ptr`. Authors can now use `errors.New`/`fmt.Errorf` and any error-returning assertion library. 3. Adds panicking `Must*` Value accessors plus `CloneMap`, so consumers stop hand-rolling `asInt`/`asStr`/`smap` wrappers — the example project now uses them and its assertion paths return errors instead of panicking. Also fixes the example's doc comment and README, which described step files as `steps_*.go` when they are `*.steps.go`. ADR 0010's handler-shape rationale is corrected: it had ruled out arity inference by citing Rust's compile-time trait dispatch, but godog does it with runtime reflection — so the fixed shape is a deliberate choice, not a language limitation, and a reflection-based second form is recorded as a follow-up in the port plan. All four conformance artifacts still reproduce byte-for-byte; the 30 example specs and the website docs Go tabs are updated to the new API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…form
Adds varar.Sensor1/2/3 and Stimulus0/1/2: each expression capture (and any
trailing doc string) arrives as a plain Go value, and a sensor returns one value
per slot — the same contract TypeScript spells as a tuple return
(`(_state, n) => [n, n * n]`), spelled in Go as multiple return values:
varar.Sensor2(s, "The square of {int} is {int}.",
func(state varar.Value, n, square int) (int, int, error) { return n, n * n, nil })
Implemented with generics rather than godog-style reflection, which checks
strictly more at compile time than reflection can: a return type that doesn't
match its slot, a handler arity that doesn't match the constructor, and an
unsupported parameter type are all compile errors, where godog defers each to
run time. The two runtime paths that remain — a slot count only the expression
knows, and a slot whose kind doesn't match the declared type — fail the step
with a message naming the slot.
Additive: the explicit func(state, args []Value) (*Value, error) form stays the
primitive and the escape hatch for whole-table slots, header-bound rows (which
compare by column, not positionally), and custom parameter types that parse to a
map. Both forms now appear in the conformance corpus — bundles 02/04/06/14 are
authored in the typed form and still reproduce their goldens byte-for-byte, and
the example project mixes the two.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
The typed constructors are package-level functions (Go allows no type parameters on methods), so the builder is the first argument and the expression the second, and the arity rides in the name — neither of which the Go tree-sitter dialect matched, so bundles authored in the typed form extracted zero steps. Adds a second query pattern for that shape, plus an optional `normalizeKind` hook on LanguageSpec (defaulting to today's lower-casing) that Go uses to strip the trailing arity digit, so `Sensor2` yields `sensor`. Extraction conformance covers both forms across all 15 bundles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
…constructors
Replaces the generic Sensor1/Sensor2/… family with a single Sensor/Stimulus that
accepts a handler whose parameters are plain Go values — the first the state,
the rest the step's slots — with a sensor returning one value per slot:
s.Sensor("The square of {int} is {int}.",
func(state varar.Value, n, square int) (int, int, error) { return n, n * n, nil })
The generics version checked more at compile time, but Go has no variadic
generics, so the arity had to live in the name, and numbered functions are not a
Go idiom — reflection is how Go does variable arity (it is what godog uses). The
checking that is lost is largely recovered by validating the handler signature
eagerly at REGISTRATION: a non-func, a missing/!Value state parameter, an
unsupported slot type, a non-error last result, or a sensor whose results don't
match its slots all panic when the suite wires up. Only the two document-
dependent checks are deferred to match time — the slot count the expression
produced, and a slot whose runtime kind doesn't match the declared type — and
both fail the step with a message naming the slot.
The reflection lives entirely in the facade; varar-core stays reflection-free.
The raw func(state, args []Value) (*Value, error) form is still accepted under
the same name, for whole-table slots, header-bound rows (compared by column, not
positionally), and custom types that parse to a map.
This also reverts the tree-sitter dialect's second query pattern and the
LanguageSpec.normalizeKind hook added for the numbered names: with one method
name the expression is the first argument again, so the original query matches
and no shared-code seam is needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Java hands a handler a LocalDate directly, because its core is Object-based and
a custom parameter type's transform result flows through untouched. Go's core
uses a closed Value union, so a {date} slot could previously only surface as
varar.Value, leaving the handler to unpack it.
Adds the json.Unmarshaler-style pair: a type implementing ValueDecoder (and,
for a sensor slot, ValueEncoder) can be a step parameter directly.
func (d *Date) DecodeVarValue(v varar.Value) error { … }
func (d Date) EncodeVarValue() varar.Value { … }
s.Stimulus("borrowed {title}, due back on {date}",
func(state varar.Value, title string, due Date) (varar.Value, error) { … })
Slot conversion also now goes by reflect Kind rather than exact type, so a named
primitive (`type Celsius int64`) works with no interface at all. Registration
rejects a parameter that cannot be decoded, and a sensor result that cannot be
encoded back for comparison, both with a message naming the type.
varar-core is untouched — all of this lives in the facade, so the core stays
reflection-free and knows nothing about author types. The library example now
takes `due Date` / `returned Date` / `on Date`, and Date's decode/encode pair is
the single place that knows how a {date} maps to a Value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
varar.Value was half the remaining author-facing API surface — 62 of 123
occurrences in the example and fixtures were the state (parameter, return, and
factory). Steps is now generic in it, so a project uses its own struct:
type Ctx struct{ Loans []Loan; Fee int }
func Register(s *varar.Steps[Ctx]) {
s.Stimulus("borrowed {title}, due back on {date}",
func(ctx Ctx, title string, due Date) (Ctx, error) { … })
}
varar-core now treats the state as an opaque `any`: it threads it between a
file's steps and replaces it wholesale, but never compares, serializes, or
inspects it — no conformance artifact contains the state, so this changes no
golden. Steps[varar.Value] keeps the dynamic behaviour, which is what the
conformance fixtures use (they exercise the schemaless path on purpose).
The raw []Value form now rejects a stimulus whose state type isn't Value, since
its *Value result cannot be that state — previously it silently became the
state, which the roman-numerals bundle caught.
In the example project, varar.Value drops from 123 occurrences to 18: only the
custom parameter type parse/format, whole-table slots, and header-bound rows
still mention it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Follows the skill's "keep the public API surface minimal". After the Steps[C]
change, varar.Value still appeared in four places in author code; all four now
have a natural Go spelling:
- custom parameter types are keyed on the author's own type —
s.Param("date", `…`,
func(g []string) Date { return ParseDate(g[0]) },
func(d Date) (string, bool) { return FormatDate(d), true })
- whole-table slots arrive as [][]string, returning []map[string]string
- header-bound rows arrive and return as map[string]string
- plain structs map to a value map by exported field name, the way
encoding/json does, so a domain type needs no interface at all
(Date's DecodeVarValue/EncodeVarValue are deleted)
ValueDecoder/ValueEncoder remain as the override for types where field-mapping
isn't the right shape, and Steps[varar.Value] still gives dynamic state — the
conformance fixtures use it deliberately, to keep exercising the schemaless
path.
The example project now mentions varar.Value zero times, down from 123 before
this series. Registration rejects a genuinely unmappable slot type (a channel)
with a message naming it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133q818sjdNmKi5Qc3ZYk56
Adds Go as a complete Varar port, structured exactly like the other ports and proven against the shared conformance corpus (no fresh tests). Module
github.com/varar-dev/varar-gorooted atgo/, withcore·varar(facade) ·config·runner·gotestpackages.Conformance (byte-for-byte)
var-doc(parse),registry,plan,trace(execute), incl. the emoji/combining-mark offset bundles via a UTF-16 conversion layer.conformance/config/cases.Key decisions (ADR 0010 / 0011)
Stepsbuilder, full-replacementValuestate,runtime.Callersource capture, single variadic handler shape — the static-language pattern (Rust/C#).cucumber-expressions-golibrary (stale at v6.2.0, but its{int}/{word}/{string}regexps are byte-identical to the reference) with Varar's own value transforms — documented as an exception since there is no 20.0.0-line Go release yet.go testintegration = onet.Runsubtest per Markdown example (no compile-time codegen needed, unlike Rust'slibtest-mimic).Integration
make go(threaded intocheck), coverage → lcov (80.4%),.github/workflows/go.yml, README/coverage.jsonrow.examples/go-goteststandalone consumer project — 6 specs, 30 examples all pass, with custom parameter types, header-bound rows, and a committed drift baseline.gohighlighter;SiteLang; front-page Editor tabs; a Go<TabItem>in every docs lang-tab group;languages.json.GO_MODULES_ENABLEDgate,69-go-modules.sh, varar-examples pin,cliff/lint-commitsscope) mirroring the Rust/C# pattern.Verification
make go✓ ·pnpm check(biome/typecheck/knip/tests/jscpd) ✓ ·pnpm --filter @varar/website... build✓ · commit-lint ✓🤖 Generated with Claude Code