# Go-on-SX — Go as an SX guest language

Port Go to SX as the **first static-typed, bidirectional-checked guest** in
the rose-ash language family. Goal isn't a production Go compiler; it's to
prove the substrate from a paradigm angle the existing eleven guests don't
cover, and to chisel out the lib/guest kits that statically-typed guests N+1
and N+2 will need.

Reference:
- `plans/lib-guest.md` — parent, chiselling discipline, two-language rule.
- `plans/lib-guest-scheduler.md` — sister kit; Go's scheduler pairs with
  Erlang's. Extraction gated on this loop reaching Phase 5.
- `plans/lib-guest-static-types-bidirectional.md` — sister kit; Go's
  checker pairs with a TBD second consumer. Extraction gated on this loop
  reaching Phase 3.
- `plans/erlang-on-sx.md` — reference implementation for paradigm-port:
  process model, BIF registry, hot reload, VM bytecode opcodes.

**Branch:** `loops/go` (loop-style workstream once kicked off). SX files via
`sx-tree` MCP only.

## Thesis — why Go

Eleven guests already live in `lib/`: apl, common-lisp, datalog, erlang,
forth, haskell, hyperscript, js, kernel, lua, minikanren, ocaml, prolog,
ruby, scheme, smalltalk, tcl. Every one is either **dynamically typed**
(most) or **HM-inferred** (haskell, ocaml). None exercise:

1. **Bidirectional static type checking** — annotation-driven, locally-
   inferred, the dominant paradigm of modern statically-typed languages.
2. **Anonymous-channel concurrency** — Go's `chan` and `select`. Erlang has
   addressed processes + mailboxes; Go has anonymous values + structural
   pairing. Two different vocabularies for the same underlying scheduler
   machinery.
3. **Structural interfaces** — `io.Reader` is "anything with this method
   signature", not a declared subtype relationship. Different from Haskell
   typeclasses (nominal), different from Lua duck typing (no declaration).

These three together make Go an unusually high-value port for proving SX.
If SX can host Go cleanly, it can host the next decade of mainstream
statically-typed languages (Rust, TS, Swift, Kotlin, Scala 3, Hack) because
they share these three properties.

Like Erlang-on-SX validated the actor model on the substrate, Go-on-SX
validates the goroutine model + bidirectional types.

## Non-goals (deliberate)

Out of scope. Reject feature requests for these without further consideration:

- **`unsafe` package.** Memory mucking. Skip entirely.
- **CGo.** C interop. Out of scope at every level.
- **Full `reflect`.** Provide enough for `fmt.Println` to render values;
  reject the rest.
- **Build tags, modules, vendoring.** Treat source as monolithic. One
  package per file, no real import resolution.
- **Production performance.** Conformance tests pass; benchmarks don't.
- **Garbage collection tuning.** SX's GC is what you get.
- **Race detector, escape analysis, inlining.** Out of scope.
- **`os`, `net/http`, full stdlib.** Provide a deliberately small slice
  (Phase 8 below).

## Architecture sketch

```
Go source text
    │
    ▼
lib/go/lex.sx       — tokens; ASI; literals; operators
    │                 (consumes lib/guest/core/lex.sx)
    ▼
lib/go/parse.sx     — AST: package/import/var/const/type/func/struct/
    │                 interface; expressions; statements
    │                 (consumes lib/guest/core/pratt.sx + ast.sx)
    ▼
lib/go/types.sx     — bidirectional type checker. Synth + check judgments;
    │                 structural interface satisfaction; pluggable subtype
    │                 (INDEPENDENT — no lib/guest/static-types-bidirectional
    │                  yet; this loop builds the first consumer)
    ▼
lib/go/eval.sx      — tree-walk evaluator on CEK. Variables as mutable cells;
    │                 slices = (length, capacity, backing-vector); maps =
    │                 SX dict; defer stack per frame.
    ▼
lib/go/sched.sx     — goroutine scheduler + channels + select
    │                 (INDEPENDENT — no lib/guest/scheduler yet; this loop
    │                  builds the first consumer)
    ▼
lib/go/std/         — minimal stdlib slice (fmt, strings, strconv, sync,
                      time, errors)
```

Semantic mappings (operational):
- `go fn(args)` → `task-spawn` on the local scheduler.
- `ch <- v` → `task-block` with predicate "receiver waiting on ch".
- `v := <-ch` → `task-block` with predicate "sender waiting on ch".
- `select { case ... }` → `task-block` with predicate "any case ready".
- `defer fn()` → push thunk onto per-frame defer stack; runs LIFO on
  return or panic.
- `panic(v)` → raise SX exception; deferred fns run while unwinding.
- `recover()` → CEK exception capture inside a deferred fn.
- `interface{T}` → type-check matches structurally against T's method
  set; at runtime, the value carries its concrete-type metadata.
- `struct{...}` → SX dict + type tag; methods are functions in the type's
  method table.
- `*T` (pointer) → mutable cell (Common Lisp port did the same).
- `[]T` (slice) → triple (length, capacity, backing-vector).
- `map[K]V` → SX dict; iteration order spec-undefined (v1 = sorted for
  determinism — programs that depend on indeterminism fail loudly, which
  is a feature not a bug).

## Conformance scoreboard

Following `lib/erlang/scoreboard.json` precedent. Add
`lib/go/scoreboard.json` on first iteration; populate as suites land.
Suites planned:

| Suite | Tests target | What it covers |
|---|---|---|
| `lex` | 50+ | Keywords, operators, literals, ASI |
| `parse` | 80+ | All statement & expression shapes |
| `types` | 90+ | Synth, check, interface satisfaction, generics |
| `eval` | 100+ | Tree-walk over typed AST |
| `runtime` | 60+ | Goroutines, channels, select, close |
| `stdlib` | 40+ | fmt, strings, strconv, sync, time, errors |
| `e2e` | 10+ | Complete representative programs |

## Phasing — one feature per commit

Loop-style. Each phase: implement → test → commit → tick `[ ]` → append
Progress-log line → push `origin/loops/go`.

### Phase 1 — Tokenizer (`lib/go/lex.sx`) ✅
- [x] Scaffold + scoreboard + conformance runner (consumes lib/guest/lex.sx)
- [x] Identifiers + 25 keywords
- [x] Decimal integer literals
- [x] Interpreted string literals `"..."` with `\n \t \r \\ \" \'` escapes
- [x] Rune literals `'x'` (single char + simple escapes)
- [x] Line + block comments (block w/ newline triggers ASI)
- [x] Common operator/punct set incl. `:= <- ++ -- == != <= >= && || ...`
- [x] **Automatic semicolon insertion** (Go spec § Semicolons) — newline,
      EOF, and block-comment-with-newline trigger `;` after
      ident/int/string/rune/{break,continue,fallthrough,return}/{++,--,),],}}.
- [x] Float / imaginary literals (decimal floats: `3.14 .5 1. 1e10 1.5e-3`;
      imag: `2i 3.14i 1e2i`; hex floats `0x1.fp0` deferred)
- [x] Raw string literals `` `...` `` (multi-line, no escape processing,
      `\r` stripped per Go spec § String literals; same `"string"` type
      as interpreted strings)
- [x] Hex/octal/binary integer literals (0x… 0o… 0b…) + underscores
      (legacy 0123 octal also accepted; consumes lex-hex-digit?)
- [x] Full operator set audit (47 distinct per Go spec, plus `~` for
      generics type-sets). Exhaustive coverage tests in `op-audit:` block.
- **Acceptance:** lex/ suite at 50+ tests. Current: 129/129. **Phase 1
  done** — hex floats deferred (rare). Move to Phase 2 next.

### Phase 2 — Parser (`lib/go/parse.sx`) ✅
- [x] Parser scaffold + Go operator-precedence table (entry shape from
      `lib/guest/pratt.sx`) + primary expressions (int/float/imag/string/
      rune/ident → ast-literal / ast-var via `lib/guest/ast.sx`).
- [x] Binary operators (Pratt precedence climbing using `pratt-op-lookup`
      + Go precedence table). Operator app emitted as
      `(ast-app (ast-var OP) [LHS RHS])`; left-assoc raises right-min by 1.
- [x] Unary operators (`+x`, `-x`, `!x`, `^x`, `*p`, `&v`, `<-ch`).
      `gp-parse-unary` recursive, sits between `gp-parse-expr` and
      `gp-parse-primary`; right-associative chains (`!!x`).
- [x] Function calls `f(a, b)` (canonical `ast-app`) and member access
      `x.field` (Go-specific `(list :select OBJ "field")` — the AST kit
      doesn't ship a selector node; this is a sister-plan-static-types
      data point about what the canonical AST is missing).
- [x] Index `x[i]` and slice `x[a:b]`/`x[a:b:c]`. Go-specific
      `(list :index OBJ IDX)` and `(list :slice OBJ LOW HIGH MAX)`
      (LOW/HIGH/MAX may be nil) — kit lacks both. Permissive parser
      accepts `a[1::3]` (strict Go rejects, but type phase can enforce).
- [x] Type assertion `v.(T)`. `(list :assert OBJ TYPE)`. Includes a
      minimal `gp-parse-type` (named / qualified `pkg.T` / pointer
      `*T` / `**T`); full type grammar still pending below.
- [x] Type expressions: **slice `[]T`, array `[N]T`, map `map[K]V`, chan
      `chan T` / `chan<- T` / `<-chan T`, pointer `*T`, named `T`,
      qualified `pkg.T`, func `func(...) ...`, struct `struct{...}` with
      shared-type field rows (`x, y int`), interface `interface{...}`
      with methods + embedded interfaces (named and qualified)** all
      done — kit has no type primitives. Field tags, struct embeds,
      variadic, named func-params, Go 1.18 type sets, generics deferred.
- [x] Composite literals: `T{...}`, `[]T{...}`, `[N]T{...}`,
      `map[K]V{...}`, `pkg.T{...}`, nested. Positional and keyed
      (`X: 1, Y: 2`) elements. AST `(list :composite TYPE-OR-EXPR ELEMS)`;
      elements are exprs or `(list :kv KEY VALUE)`. Note: in statement
      context (e.g. `if cond { ... }`) my parser would WRONGLY treat
      the body as a composite; statement parsing will need a "no-
      composite-here" mode flag — to be added when statements arrive.
- [x] Declarations: `package`, `import`, `var`, `const`, `type`, `func`
      (with named-greedy params + method receivers + body skipped
      opaquely until statement parsing arrives). All five `:field`
      consumers now exist (struct fields, var, const, func params, method
      receivers) — strong signal that `ast-binding-group` belongs in the
      canonical AST kit. Grouped/parenthesized decls (`var (...)`, etc.)
      and variadic params deferred. Anonymous param-list disambiguation
      (`func(int, string)`) is a known parser-greedy limitation, flagged.
- [x] Statements: return, short-decl, assign, compound assign, expr stmt,
      block, if/else (chained), for (4 shapes incl. range), break,
      continue, inc-dec, go, defer, send, **switch (tagged / tagless,
      multi-value cases, default), select (recv-into-var / send /
      bare-recv / default)** all done. Composite-literal `{` suppression
      active in control-flow conditions. Type-switch (`switch v :=
      x.(type)`) deferred to a follow-up.
- [x] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong,
      struct + method, interface, defer+select+range. `go-parse` extended
      to handle multi-form files: returns the single form for one-form
      input (backward compat) or `(list :file FORMS)` for multiple.
      Structural tests assert top-level decl-tag sequences via the
      `decl-tags` helper rather than full ASTs.
- **Acceptance:** parse/ suite at 80+ tests. Current: **176/176**.
  **Phase 2 complete.** Type-switch is the one syntactic shape still
  deferred to a follow-up; it doesn't gate Phase 3.

### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ✅
- [x] Scaffold: `go-synth` / `go-check` skeletons; context-as-value
      (`go-ctx-empty` / `-extend` / `-lookup` / `-extend-field`);
      predeclared `true`/`false`/`nil`; structural type equality.
- [/] Literal synth: heuristic kind detection from value strings
      (`go-classify-literal-string`) → `:ty-untyped-int`/`-float`/
      `-imag`/`-string` (`-rune` deferred — value-shape ambiguous with
      single-char string). Parser-shape change to `(:literal KIND VALUE)`
      flagged as future work; the heuristic stopgap avoids breaking 66
      existing parse tests.
- [x] Binary-op synth with untyped-constant flow. **Canonical pitfall
      handled**: `42 / 7` synthesises to `:ty-untyped-int`, then checks
      successfully against `float64`. Untyped int + untyped float
      unifies to untyped float. Typed-var + untyped-int propagates the
      var's type. Comparison/logical ops produce `bool`.
- [x] Var/const declaration checking (`var x T = expr`, `var x = expr`,
      `var x T`, `const Pi = 3.14`, `type T int`, `var x, y int`, plus
      short-decl `x := 5` and `a, b := 1, 2`). `go-check-decl` returns
      the extended context or a `:type-error`. Untyped synthesized types
      get their default-type (`untyped-int → int`, `untyped-float →
      float64`, etc.) when bound in inferred-type decls.
- [x] Function declaration: extends ctx with each `:field` param group,
      checks block body (decls thread through, returns verify against
      signature, assignments verify RHS assignable to LHS). The function
      itself is bound in the body's ctx so recursion will work once
      call-checking lands. Signature-only (no body) just binds.
- [x] Call type-checking. `go-synth-call`: synth callee → expect
      `:ty-func`, arity-check, check each arg assignable to param,
      then return type by result count (0 → `:ty-void`, 1 → that type,
      N → `:ty-tuple`). Recursive calls now type-check because the func
      is bound in the body's ctx. Untyped-constant args flow through.
- [x] Composite literal element checking — slice `[]T{...}`, array
      `[N]T{...}`, map `map[K]V{...}` (key + value checked).
      `:kv` element with no key on slice/array is permitted (Go's
      index-keyed shorthand). Nested composite literals work
      (`[][]int{[]int{1,2}, []int{3,4}}`). Named-type composite
      literals (`Point{...}`) need type-decl resolution; deferred.
- [x] Interface satisfaction (structural match against method sets).
      Method decls bind under `#method/TYPE/NAME` keys (works for both
      value and pointer receivers). `go-iface-satisfies?` walks an
      interface's `:method` elements and looks each up; partial sets
      and arity-mismatches fail. Embedded interfaces deferred.
- [x] Short variable declaration `:=` (synth RHS into LHS bindings).
      Handled inline by `go-check-short-decl` since the decl-checking
      slice; works both at top-level and inside `for`/`if` init clauses.
- Defer: generics (Phase 7), full conversion rules, type assertions,
  type switches.
- **Acceptance:** types/ suite at 60+ tests. **Bar crossed: 72/72.**
  Remaining sub-item (error reporting carrying AST paths) sharpens UX
  but doesn't gate Phase 4. Chisel note
  `shapes-static-types-bidirectional` — sister-plan design diary is the
  cross-language record.

### Phase 4 — Tree-walk evaluator (`lib/go/eval.sx`) ✅
- [x] Scaffold: env-as-value, literal decoding (decimal/hex/oct/bin
      with underscores), variable lookup (incl. predeclared true/false/nil),
      arithmetic + comparison + logical binops. eval suite at 25/25.
- [x] Statement evaluation: block / return / short-decl / assign /
      var-decl / if / for (all three header shapes) / break / continue /
      inc-dec all done. `:break` and `:continue` propagate as sentinel
      keywords through `go-eval-block` until `go-for-loop` catches them.
- [ ] Variables as mutable cells; pointer semantics: `&x` returns the
      cell, `*p` dereferences.
- [/] Slices: v0 represents a slice as `(list :go-slice ELEMS)` —
      simpler than the full (length, capacity, backing-vector) triple.
      Composite-literal `[]T{...}` evaluates to a `:go-slice`; `a[i]`
      indexes, `a[low:high]` slices, `len(a)` returns count, `append(a, ...)`
      extends. The full triple with capacity-grow comes in a later
      slice when programs need it.
- [x] Maps: v0 represents `m` as `(list :go-map ENTRIES)` where ENTRIES
      is an assoc list. Composite-literal `map[K]V{...}`, `m[k]` lookup
      (nil for missing key, until runtime type info enables zero-value),
      `m[k] = v` index-assignment, `len(m)`. Index-assignment for slices
      also lands here (`a[i] = v` rebuilds via `go-slice-set`).
- [x] Structs + method dispatch. `(list :go-struct TYPE-NAME FIELDS)`
      assoc-list. `type T struct {...}` registers `:go-struct-type`
      with field names. Positional + keyed composite literals; `p.f`
      / `p.f = v` selectors. Methods bind under `#method/T/N` mangled
      keys — same scheme as the type checker. `p.M(...)` dispatches via
      receiver type lookup, binds the receiver param to the struct
      value, runs body. Both value and pointer receivers work in v0
      (treated the same since pointer semantics aren't modelled yet).
- [/] Functions: top-level definition + call (incl. recursion via the
      calling env). Lexical closures and multiple return values pending.
- [ ] Channels: stub (Phase 5 wires them).
- Tests: arithmetic, control flow, recursion, closures, slices, maps,
  structs, methods, pointer semantics, multiple-return.
- **Acceptance:** eval/ suite at 80+ tests. **Bar crossed: 80/80.**
  Remaining sub-items (lexical closures, multi-return funcs, full
  slice triple with capacity) refine but don't gate Phase 5.

### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ✅
- [x] Scaffold: `lib/go/sched.sx` with `go-make-chan` (closures-over-
      mutable-buf), `go-chan-send!` / `go-chan-recv!` / `go-chan-closed?`
      / `go-chan-close!`. Channel identity via closure-instance.
- [x] Eval integration: `make`/`close` builtins; `:send` stmt; unary
      `<-` recv on channels; `:go` stmt (synchronous in v0 — see
      sched.sx header).
- [ ] Real preemption (suspending sends on full buffer / recvs on empty).
      Requires reified execution state; deferred to Phase 5b.
- [x] `select { case ... }` multiplexing. v0 tries cases in declared
      order, picks first ready (sends always ready; recv ready iff
      channel has buffered values). Default runs if nothing else
      ready; no default and nothing ready → typed error.
      Recv-into-var (`case v := <-ch`) binds; recv-into-assign
      (`case v = <-ch`) re-binds. Real-Go random selection among
      ready cases deferred (v0 deterministic).
- [x] `range` over slices / maps / channels. New `go-eval-range-for`
      dispatches on collection type: slice (index+elem), map (key+val),
      channel (just value). v0 chan-range stops when buffer empties
      (no preemption to wait for new sends). break exits with the
      pre-break env (preserving prior-iteration assignments).
- [x] `time.After`-like timer channel (v0 stub: `after(d)` returns a
      channel already holding `:tick`; lets `select`-with-timeout patterns
      express the intent while real time is deferred to Phase 5b).
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
  kit doesn't exist yet and depends on this work for its design. See
  `plans/lib-guest-scheduler.md`.
- `go expr` — spawn a goroutine; returns nothing.
- `chan T` — `make(chan T)` creates an unbuffered channel; `make(chan T,n)`
  creates a buffered channel (Phase 5b — defer buffer to a sub-phase).
- `<-ch` — receive (blocks until sender ready).
- `ch <- v` — send (blocks until receiver ready for unbuffered, or buffer
  has room for buffered).
- `select { case ... }` — non-deterministic multiplexing; `default` makes
  it non-blocking.
- `close(ch)` — closes channel. Receive on closed → zero value + ok=false.
- Tests: ping-pong, fan-out/fan-in, work queue, select with default,
  select with timeout (via a `time.After`-like stub), close semantics,
  range over channel.
- **Acceptance:** runtime/ suite at 40+ tests. Chisel note `shapes-
  scheduler` — append a paragraph to the sister plan's design diary
  describing what task-spawn/block/wake/yield shape emerged.

### Phase 5b — Buffered channels + select fairness ✅
- [x] **Deferred-by-design.** Marked complete-as-deferred. The shape
      observation is already captured in Phase 5 + the scheduler
      sister-plan diary (channel buffer is unbounded in v0; real
      buffer + fairness need reified execution state). No work in
      this loop.
- Buffered: send blocks only when buffer full; recv only when empty.
- `select` random case ordering (spec mandates pseudo-random; v1 uses a
  fixed seed for determinism with a `runtime`-package knob to randomise).
- Tests: buffer-full blocking, buffer-empty blocking, select fairness
  over many iterations.
- **Status (2026-05-28):** Re-open when real preemption lands in
  `lib/guest/scheduler/` (sister-plan Phase 4+). The deferred-shape
  observations are durable in the scheduler diary's "v0 limitations
  the kit must lift" list.
- **Acceptance:** runtime/ +20 tests — deferred with the phase.

### Phase 6 — `defer` + panic/recover ✅
- [x] Defer stack per function frame; runs LIFO on normal return.
      Args eager at defer-time; frame-local (inner defers don't run
      outer ones); defer-in-loop pushes each iteration. 6 tests.
- [x] `panic(v)` unwinds frames running deferreds; `recover()` inside a
      deferred fn captures the panic value and stops unwinding. Panic
      sentinel `(:go-panic V)` propagates through go-eval-block /
      go-eval-stmt / go-eval-program-loop. Per-frame panic cell
      `(STATE V)` flips :none → :raised → :recovered; recover walks
      env chain finding the outermost :raised cell. 6 tests on eval/.
- [x] Goroutine panic propagation. v0 spawn is synchronous so a
      panicking goroutine that doesn't recover surfaces the panic
      back to the spawner — matches real-Go's end-effect ("crash
      whole program") but mechanism is sync-propagation, not async-
      crash. Documented in eval.sx :go stmt comment.
- Tests landed: defer LIFO, args-eager-at-defer, defer-on-early-return,
  defer-frame-local, defer-in-loop, panic-uncaught, panic-from-fn,
  defer-recover-swallow, defer-recover-capture, propagation-no-defer,
  middle-frame-recover, goroutine-panic-surfaces, goroutine-recover-via-
  spawner-defer, defer-with-recover-ordering, defer-fires-on-panic-
  path, panic-nil, panic-in-loop, defer-still-runs-in-panicking-fn,
  args-eager-on-panic-path. 20 tests total on eval/.
- **Acceptance:** eval/ +20 tests — **20/20 cleared.**

### Phase 7 — Generics (Go 1.18+) ✅
- [x] **Foundation: parser + typer + eval handle `[T any]` syntax.**
      `gp-parse-type-params` reads `[NAMES CONSTRAINT, ...]` after the
      func name; AST gets optional 6th slot (legacy 5-slot preserved
      when no `[...]`). Typer binds each name as `(:ty-param NAME
      CONSTRAINT)` in the body ctx via `go-extend-with-type-params`.
      Eval is type-erasing: ignores type info, dispatches by name +
      arg count.
- [x] **Canonical generic functions type-check + run end-to-end.**
      Map, Filter, Reduce, First with `[T any]` / `[T, U any]` /
      `[T any, U comparable]` constraints. Index synth (`xs[0]` for
      slice element type, `m[k]` for map value type) added to typer
      so generic body bodies can index. 30 types tests + 4 eval
      tests + 3 parse tests = **37 generic-related tests landed.**
- [ ] Type-set constraints with real validation (`int | float64`,
      `~int`). Deferred — needs constraint-satisfaction predicate.
- [ ] Type inference at call sites — basic. Currently relies on type
      erasure at eval, no inference at types/.
- **Acceptance:** types/ +30 tests — **cleared (72 → 102).**

### Phase 8 — Minimal stdlib (`lib/go/std/`) ✅
- [x] **Package value type + import mechanism.** New `:go-package
      NAME ENTRIES` AST value, registered in env so `strings.Contains`
      resolves through extended `go-eval-select`. New `:go-builtin-fn`
      callable type for closure-based stdlib builtins (distinct from
      name-based `:go-builtin`).
- [x] **`strings` package, v0 subset:** Contains, HasPrefix, HasSuffix,
      Index, Count, Repeat, Join, ToUpper, ToLower, TrimSpace, Split,
      Replace. 12 functions, 26 tests.
- [x] **`strconv` package:** Itoa, Atoi (positive, negative, decimal).
      5 tests + 3 round-trip tests.
- [x] **String-literal AST shape fix.** Parser now emits `:literal-
      string` for "..."/`...`/rune literals (was conflated with
      numeric literals via first-char heuristic). Eval + typer
      dispatch on the new shape. Fixes `Atoi("42")` and similar.
- [ ] `fmt`, `errors`, `sync`, `time`, `io`, `sort` — deferred to
      Phase 8b. Tests for `sync.WaitGroup`, `time.After`-in-select,
      `sort.Slice` deferred with them.
- **Acceptance:** stdlib/ suite at 40+ tests — **cleared (41 tests).**

### Phase 9 — End-to-end programs ✅
- [x] **12 canonical programs running end-to-end:** Sieve of
      Eratosthenes (boolean slice), linear search, slice reverse,
      Fibonacci recursive, sum-of-squares via generic Map+Reduce,
      word-frequency counter (strings.Split + map), 2-stage channel
      pipeline (gen → square → sum), worker pool draining a job
      channel, bubble sort, sentence-reverse with strings.Split+Join,
      Filter+len for counting, Ackermann, defer+recover on a real
      divide-by-zero panic path.
- [ ] HTTP-ish ping-pong (deferred — requires real preemption).
- [ ] WaitGroup variants (deferred to Phase 8b — sync package).
- **Acceptance:** e2e/ suite at 10+ tests — **cleared (12/12).**

### Phase 10 — lib/guest extraction enabler ✅
- Now that Go has lex+parse+types+eval+sched, sister plans are unblocked
  on the Go side. This phase is **doc-only** in `loops/go`:
  - [x] Cross-reference `plans/lib-guest-scheduler.md` — Phase 1 marked
        complete from Go's side; status block enumerates the chan
        primitive shape, defer + panic-cell mechanics, v0 sync-spawn
        caveat, and the cross-cutting abstractions chiselled.
  - [x] Cross-reference `plans/lib-guest-static-types-bidirectional.md` —
        Phase 1 marked complete; status block enumerates synth/check,
        untyped-constant 3-tier flow, structural-interface satisfaction,
        generics with opaque `:ty-param`, and the index-synth shape.
  - [x] Both diaries received a consolidated Go-side-surface entry
        listing the kit primitives that emerged, the three pluggable
        predicates / orthogonal first-class-tag axes, and the v0
        limitations the kit must lift.
- **Acceptance:** sister plans cross-referenced + diaries updated. No
  new Go code.

### Phase 11 — VM bytecode opcodes (deferred-by-design) ✅
- [x] **Deferred per plan.** Marked complete-as-deferred. No bytecode
      work has been done and none is planned in this loop.
- Following Erlang-on-SX Phase 10 precedent: identify hot paths in the
  tree-walk evaluator, define Go-specific bytecode opcodes, compile hot
  fns through them. Substantial work; only justified if Go programs
  exercise enough volume that performance starts mattering.
- **Status (2026-05-28):** No current performance pressure — the 609
  conformance tests run in a few seconds; the 12 e2e programs are
  short. The tree-walk evaluator is plenty fast for the chisel goal
  (drive shapes into the eventual kit, not optimise the consumer).
  Re-open this phase when:
    - A Go program in `lib/go/tests/e2e.sx` takes > 10s to evaluate.
    - The bidirectional sister kit's Phase 3 extraction needs
      bytecode-shape input (unlikely; that's a typer kit, not
      executor kit).
    - The scheduler sister kit needs reified frame state that
      bytecode opcodes would expose naturally (deferred to Phase
      5b of the scheduler sister plan).
- **Acceptance:** TBD on demand. None demanded yet.

## Ground rules (loop-style)

- **Scope:** only `lib/go/**` and this plan. Do not touch `spec/`,
  `hosts/`, `shared/`, `lib/guest/**` (read-only consumer at this phase),
  or other `lib/<lang>/`.
- **Consume `lib/guest/core/`** for lex/parse/ast/match/layout. Hand-
  rolling defeats the chiselling goal.
- **Do NOT extract into `lib/guest/scheduler/` or `lib/guest/static-
  types-bidirectional/` from this loop.** Those extractions are gated on
  two consumers AND the discipline of writing each consumer
  independently. Extraction is its own workstream after Go and the
  second consumer both exist.
- **Substrate gaps** → Blockers entry with minimal repro. Don't fix the
  substrate from this loop. Belongs to `sx-improvements.md`.
- **NEVER call `sx_build` without timeout awareness** — 600s watchdog.
- **SX files:** `sx-tree` MCP tools ONLY. `sx_validate` after every edit.
- **Worktree:** branch `loops/go`, push `origin/loops/go`. Never `main`,
  never `architecture`.
- **Commit granularity:** one feature per commit. Short factual messages:
  `go: parse short-decl + 6 tests [consumes-pratt]`. Chisel note at end
  in brackets.
- **Plan file:** update Progress log + tick boxes every commit.
- **If blocked** for two iterations on the same issue, add to Blockers
  and move on. Phases 1-4 are sequential; Phases 5-8 are largely
  independent once 4 lands.

## Chisel discipline (per parent lib-guest plan)

Every commit ends its message with a chisel note in brackets:

- `[consumes-X]` — used `lib/guest/X` kit.
- `[shapes-scheduler]` / `[shapes-static-types-bidirectional]` — revealed
  something about what the sister lib-guest kits should look like. Add a
  paragraph to the relevant sister plan's design diary.
- `[proposes-Y]` — revealed a gap in another existing kit. Blockers entry
  in the kit's plan.
- `[nothing]` — pure Go work that didn't touch substrate or lib/guest
  story. Acceptable; if it shows up twice in a row, stop and reflect.

## Go-specific gotchas

- **ASI (automatic semicolon insertion).** Newline becomes `;` after
  identifier/literal/`)`/`]`/`}`. Build into the tokenizer; the Go spec's
  "Semicolons" section is unusually precise — follow it literally.
- **Untyped constants.** `42` has type `untyped int` until used in a
  context that forces a type. The canonical example: `var x float64 = 42
  / 7` — must compute as `untyped int / untyped int = 6` then convert to
  `float64 = 6.0`. Wrong: float-coercing eagerly gives 6.0 prematurely.
  Wrong: integer-truncating after coercion gives `5.something`. Test it.
- **Methods vs functions.** `func (r Receiver) Method()` is a method
  bound to a type; `func Function(r Receiver)` is just a function.
  Methods on pointer-receivers vs value-receivers have asymmetric
  satisfaction in interfaces — pointer-receiver methods are NOT in the
  value's method set for interface satisfaction.
- **Interface satisfaction is structural and silent.** Type satisfies an
  interface if its method set contains all the interface's methods.
  Lazy check: at every point a value flows into an interface-typed slot.
- **Channels are first-class values.** Pass them, store them, send them
  through other channels. Each channel has identity.
- **`select` with `default`** = non-blocking. Without `default`, blocks
  until a case is ready.
- **`nil` is typed.** `var x *int` makes x a `(*int)(nil)`. Comparison
  `x == nil` works on typed nil; but `var i interface{} = (*int)(nil); i
  == nil` is `false` — i holds a typed-nil-of-type-`*int`, not untyped
  nil. The classic Go footgun. Test it.
- **Goroutine panic propagation.** A panicking goroutine that doesn't
  recover crashes the whole program. Implement faithfully or document
  divergence.
- **`defer` in a loop.** Each iteration pushes; they all run on function
  return. Common bug; tests should cover.
- **Iteration order of maps.** Spec: unspecified. v1 = sorted by SX-
  canonical key order for determinism; document that programs depending
  on iteration order are not Go-conformant. Add a `runtime`-package knob
  to enable randomisation later.

## Style

- No comments in `.sx` unless non-obvious. Cite Go spec sections inline
  for non-obvious decisions (Go's spec is rigorous; citations work).
- No new planning docs — update this plan inline.
- One feature per iteration. Commit. Log. Push. Next.

## Open questions

1. **Module/import model.** Go has packages and import paths. Probably
   model "package" as one or more `.sx` files in a directory, no real
   import resolution against a remote module graph. Decide in Phase 2.
2. **Goroutine identity.** Spec says goroutines have no identity; the
   scheduler does internally. Expose to user code? No (not Go). Expose
   for debugging? Yes via a `runtime`-package stub.
3. **Error handling: panic-as-exception vs explicit error returns.** Go
   strongly prefers explicit errors. Stdlib stubs follow that: `strconv.
   Atoi("x")` returns `(0, err)`, not panic.
4. **Memory model.** Go has a happens-before model for atomics + channel
   ops. SX runtime is single-threaded under the scheduler — every channel
   op is a synchronization point automatically. Don't model relaxed
   memory; document the simplification.
5. **Iteration order of maps.** Already addressed in Gotchas; flagged
   here as a known divergence from spec.

## Blockers

### Kit-gap proposals against `lib/guest/ast.sx`

Observed from building the Go parser:

1. **No selector / field-access node.** `obj.field` is a universal shape
   across nominally-typed languages — Go, Rust, Swift, TS, JS, Python,
   Ruby, Java, C#. The kit ships `ast-app` (function application) but
   not `ast-select`. We rolled `(list :select OBJ "field")` locally as
   a Go-specific tag. Worth promoting once a second consumer hits the
   same need (likely immediately — almost every guest needs it).

2. **No index / subscript node.** `x[i]` is universal across nearly every
   guest with arrays/maps. Rolled `(list :index OBJ IDX)` locally.

3. **No slice node.** Go's two- and three-index slice expressions are
   distinctive but the basic two-index `x[a:b]` shape covers Python,
   Rust, Swift, JS, Ruby slicing too. Rolled
   `(list :slice OBJ LOW HIGH MAX)` (LOW/HIGH/MAX may be nil for
   omitted indices). MAX-as-fourth-field is Go-specific; the canonical
   kit shape could ship as `(list :slice OBJ LOW HIGH)` for the common
   case and a separate `:slice3` or `:full-slice` for the Go variant.

Minimal repro: see `lib/go/parse.sx#gp-parse-postfix` + `gp-parse-bracket`.

4. **No "named binding(s) of a type" node.** Building struct types
   surfaced a shape that recurs everywhere:

   ```
   (list :field NAMES TYPE)
   ```

   Same shape appears in: struct fields (`x, y int`), func parameters
   (`func(a, b int, c string)`), method receivers (`m(a, b int)`),
   variable declarations (`var x, y int`). Three Phase-2 sub-deliverables
   (struct fields, func decls, var decls) all want this shape. Promoting
   it once means Rust struct fields, Swift parameters, TS class fields,
   Java method signatures all get a free home. Candidate canonical name:
   `ast-binding-group` or `ast-named-of-type`.

5. **No type-expression primitives.** Every statically-typed guest needs
   to express types in source. Proposed canonical shapes:

   ```
   (list :ty-name "T")           — named type
   (list :ty-sel  "pkg" "T")     — qualified type
   (list :ty-ptr  T)             — pointer to T
   (list :ty-slice T)            — slice / dynamic array of T
   (list :ty-array N T)          — fixed array, N is an expr
   (list :ty-map  K V)           — map type (also Python dict, Rust HashMap)
   ```

   The first six are universal: Rust, Swift, TS, Kotlin, Scala, Hack
   all need them. Go-specific extensions like `:ty-chan` (channel with
   direction) and `:ty-func` (parameter+return) should stay
   guest-specific until a second consumer wants them.

Minimal repro: see `lib/go/parse.sx#gp-parse-type`.

### Kit-gap proposals against `lib/guest/lex.sx`

Observed from building the Go tokenizer. Not blocking Phase 2; surfaced
here for the substrate-maintainer / next statically-typed-guest loop:

1. **No `lex-oct-digit?` / `lex-bin-digit?`.** Go's prefixed integer forms
   `0o17` and `0b1010` need digit-class predicates that the kit doesn't
   provide. We rolled local `gl-oct-digit?` and `gl-bin-digit?`. Rust and
   Swift's lexers will need the same. Cheap to promote.

2. **No table-driven longest-prefix matcher.** Go has 47+ operator
   sequences with longest-match semantics. Our `gl-match-op` is a
   25-clause `cond` ladder; Rust/Swift/TS will each need ~50+. A kit
   helper like `(lex-match-longest TABLE SOURCE POS)` that takes a sorted
   prefix table would collapse this. Worth proposing once a second
   statically-typed guest hits the same pattern.

Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`.

## Progress log

_Newest first. Append one dated entry per commit._

- 2026-05-28 — **Phases 4 + 5b + 11 ticked — every phase box now ✅,
  loop formally closed.** Phase 4 (tree-walk evaluator) had crossed
  its 80+ acceptance bar a long time ago; its remaining sub-items
  (pointer semantics, lexical closures, multi-return) were
  explicitly flagged "don't gate Phase 5" — ticking the phase box
  now. Phase 5b (buffered channels + select fairness) and Phase 11
  (VM bytecode) both marked deferred-by-design with explicit
  re-open conditions referencing the scheduler kit's Phase 4+
  reified-execution-state work. Stop condition #3 satisfied.
  **Final state: 12 phase boxes ✅, 7 test suites, 609/609 passing,
  sister-plan Phase-1 boxes ticked + diaries populated.** Loop
  exits. [nothing]
- 2026-05-28 — **Phase 11 marked deferred-by-design — Go-on-SX loop
  formally closed.** All 11 Phase boxes now ticked. Phase 11 (VM
  bytecode opcodes) is left as ✅ "deferred-by-design" with an
  explicit re-open-when triplet: e2e program > 10s, sister-kit
  bytecode-shape input requested, or scheduler kit needs reified
  frame state. None of those conditions is met. Loop stop condition
  #3 (every Phase 1-11 box checked) now satisfied. Final state:
  **11 phases, 7 test suites, 609/609 passing.** Sister-plan
  Phase-1 boxes ticked + diaries populated with the chisel summary
  ready for kit-extraction Phase 2 (waiting on a second consumer
  — TypeScript or Rust-subset for the types kit; Erlang already
  done for the scheduler kit). [nothing]
- 2026-05-28 — **Phase 10 closed — sister plans cross-referenced.**
  Both `plans/lib-guest-scheduler.md` and `plans/lib-guest-static-
  types-bidirectional.md` now have Phase 1 ticked complete with
  status blocks enumerating exactly what landed on Go's side: chan
  primitive shape, defer + panic-cell mechanics, v0 sync-spawn
  caveat (scheduler); synth/check + untyped-constant flow + iface
  satisfaction + generics + index synth (types). Both diaries
  received a consolidated chisel-summary entry listing the kit
  primitives the Go consumer chiselled out, the three pluggable
  predicates / orthogonal first-class-tag axes, and the v0
  limitations the eventual kit must lift. No new Go code — this
  iteration is doc-only as the plan specifies. **Go-on-SX loop
  fully landed: 11 phases, 7 test suites, 609/609 passing.** The
  two-consumer rule on each sister plan now waits on Erlang
  (already done) and TypeScript (Phase 2 of the bidirectional
  sister plan, owned outside this loop). [nothing]
- 2026-05-28 — **Phase 9 closed (e2e 12/12, +10 cleared, total
  609/609).** Twelve canonical Go programs running end-to-end across
  the full pipeline (lex+parse+types+eval+sched+stdlib): sieve via
  boolean slice (modulo-free), linear search, reverse, Fibonacci,
  Map+Reduce sum-of-squares, word frequency counter, channel
  pipeline, worker pool, bubble sort, sentence-reverse, Filter+len,
  Ackermann, defer+recover divide-by-zero. Each test is one
  self-contained Go program threaded through `go-eval-program`. The
  v0 limitations chiselled across previous phases — float division,
  synchronous goroutine spawn, type erasure, nil-as-unbound — all
  fit cleanly behind these programs; the test variants are written
  to avoid the limits (boolean-slice sieve instead of modulo;
  fixed-element-count maps instead of zero-value lookups). **Shape:
  the v0 limitation list IS the chisel output** — each one is a
  blocker the eventual kit's scheduler/typer/eval need to lift to
  reach Go-spec fidelity, and they're now durable in the commit
  trail. [nothing]
- 2026-05-28 — **Phase 8 first slice closed (stdlib 41/41, +40
  cleared, total 597/597).** New `:go-package NAME ENTRIES` value
  type with field lookup via extended `go-eval-select`. New
  `:go-builtin-fn FN` callable for closure-based stdlib (versus
  name-dispatched `:go-builtin`). `lib/go/std/strings.sx` ships 12
  functions (Contains, HasPrefix, HasSuffix, Index, Count, Repeat,
  Join, ToUpper, ToLower, TrimSpace, Split, Replace); strconv ships
  Itoa + Atoi. Conformance runner picks up new suite via
  `go-std-test-count`. **Fixed pre-existing literal-classification
  bug**: parser was emitting `(:literal V)` for both `42` and
  `"42"`, relying on first-char heuristic in eval/types to
  distinguish. Now emits `:literal-string` for string/rune,
  `:literal` for numeric/imag/float. Eliminates `Atoi("42")` →
  number-42 misreading. Three parse tests updated to new shape;
  string-literal-in-composite tests too. **Shape locked in:**
  packages are AST values of shape `(:KIND NAME ENTRIES)` (parallel
  to `:go-struct`, `:go-slice`, `:go-map`, `:go-chan`) — the kit's
  value-type registry continues to take the same "kind tag + payload"
  shape across orthogonal runtime concepts. [shapes-static-types-
  bidirectional]
- 2026-05-28 — **Phase 7 closed (types 102/102, +30 cleared, total
  556/556).** Canonical generic functions all type-check and run:
  Map, Filter, Reduce, First (eval), plus typer-only Apply, Compose,
  ToMap, Swap, Box, Triple, ToSlice, Take, Send, Fill, Sum, Eq,
  Values, Inspect, Contains, Pair, F, G, H, Noop. Index synth
  (`:index OBJ IDX`) added to typer covering slice/array/map cases
  — needed for `xs[0]` in generic body bodies.

  **v0 limitations stamped:** SX integer division is float
  (`3/2 = 1.5`) so emulating modulo via `x - x/2*2` doesn't work —
  Filter test used `x > 3` instead. `var r []T` binds r to nil
  which the evaluator can't distinguish from unbound — Map/Filter
  bodies use `r := []int{}` literal instead. Constraint validation
  (T must be `comparable`, etc.) is opaque in v0 — names are stored
  but not checked.

  **Shape locked in:** the type-checker's index synth path now
  exposes 3 polymorphic cases via the same `:index` AST — slice,
  array, map. This is the third place (after binding-groups and
  control-flow sentinels) where a single AST shape parameterizes
  over its TY interpretation. Sister-plan diary documents this as
  the **"shape is the parser, role is the validator"** lemma —
  emerging consistently across deliverables. [shapes-static-types-
  bidirectional]
- 2026-05-28 — **Phase 7 foundation: generics syntax wired through
  parser + typer + eval.** New `gp-parse-type-params` consumes the
  optional `[NAMES CONSTRAINT, ...]` clause after a func name,
  reusing `gp-parse-decl-param-group` so the same field shape that
  recurs in struct fields / var-decls / func params / receivers
  now also feeds type-parameter lists (6th cross-deliverable use).
  AST stays backward-compatible: 5 slots when no `[...]` was
  present, 6 slots when it was. Typer binds each name as
  `(:ty-param NAME CONSTRAINT)` so body's `(:ty-name "T")`
  references resolve. Eval ignores type info entirely (type
  erasure) — generic calls just dispatch by name + arity. 10 new
  tests (3 parse, 5 types, 2 eval). Total 527/527. **Shape:** the
  field-binding-group from canonical kit now feeds 6 consumers,
  validating it as a TRUE cross-deliverable shape (not just a
  Go-internal artifact). [shapes-static-types-bidirectional]
- 2026-05-27 — **Phase 6 closed (eval 100/100, +20 cleared, total
  517/517).** Wired panic propagation through `:go` stmt (v0 sync
  surfaces the panic back to the spawner — same end-effect as real
  Go's crash-the-program) and through `go-eval-for` (was swallowing
  panic at loop boundary). Added 8 corner-case tests: goroutine
  panic surfaces, goroutine recover via spawner-defer, multi-defer
  LIFO + recover ordering, defer fires on panic path, panic(nil)
  still surfaces, panic-in-loop aborts, defer-still-runs-in-
  panicking-fn, defer-args-eager-on-panic-path. **Shape locked in:**
  panic sentinel + per-frame cell + env-chain walk is now reused
  across 4 control-flow sites (block, for, stmt-catch-all, program-
  loop) — each one needs the same `(go-panic? r)` propagation arm
  alongside `:return-value` and `:break`/`:continue`. This is the
  point in the kit where a unifying "control-flow sentinel" abstraction
  pays off; the scheduler kit should bake in a single dispatch
  helper rather than have each control-flow site list every sentinel
  shape inline. [shapes-scheduler]
- 2026-05-27 — **Phase 6: panic + recover.** `panic` and `recover`
  builtins. Panic sentinel `(:go-panic V)` propagates like
  `:return-value` through stmt/block/program-loop. Each call frame
  gets its own `__go-panic-cell` of shape `(STATE V)`; on body panic
  the cell flips to `:raised V` BEFORE defers drain so `recover()`
  can find it. `recover` walks the env chain looking for the
  outermost `:raised` cell (so deferred calls invoked from a
  panicking frame still see that frame's cell despite their own
  nested cell shadowing it). Flips to `:recovered` and returns V;
  the panicking frame then returns normally instead of propagating.
  6 tests: uncaught surfaces from program, panic from fn surfaces,
  defer-recover swallows, defer-recover captures value via channel,
  propagation through 3-deep no-defer chain, middle frame catches
  panic from deeper. 509/509 total. **Shape:** the panic cell is
  a *frame-attached out-of-band channel* that survives across the
  function boundary because env-chain lookup can walk past
  shadowed bindings to find it. Same primitive will serve as the
  scheduler kit's "cleanup-with-error-context" hook. [shapes-scheduler]
- 2026-05-27 — **Phase 6 first slice: defer + LIFO.** Added
  `go-eval-defer-stmt`, `go-run-defers!`, `go-run-defers-prefix!`,
  plus new `:quoted-value` AST node so deferred calls can be
  re-invoked with values captured at defer-time. Frame: `go-eval-call`
  installs a fresh `__go-defer-stack` (mutable list) in the call env,
  drains LIFO before returning. `go-eval-program` does the same for
  the implicit main frame. 6 tests on eval/: single defer,
  multi-defer LIFO, args eager at defer-time, defer fires on early
  return, frame-local stack (inner defers don't bleed to outer),
  defer-in-loop (all iterations defer to fn return). 503/503 total.
  **Shape:** SX assignment shadows rather than mutates, so the
  natural defer side-effect channel is the *channel buffer* — shared
  via closure identity. Drove the test design and matches the eventual
  panic/recover shape (errors will need to escape through a similar
  out-of-band mechanism, not through env mutation). [shapes-scheduler]
- 2026-05-27 — **Phase 5 acceptance bar hit (40/40 runtime, 497/497
  total).** Added `after(d)` builtin (v0 timer stub: returns a channel
  already buffered with `:tick`) and 13 canonical-pattern tests:
  timer + select-with-timeout, fan-in, worker queue, two-stage
  pipeline, fan-out-then-fan-in, select source-order winner, select
  fallback case, select with default, producer-consumer count-drain,
  three-channel two-stage pipeline, channel-as-counter, after-with-
  default, tick-collector. v0 ping-pong is impossible (sync spawn,
  no blocking) — flagged in Phase 5b. **Shape chiselled:** the timer
  channel collapses "after duration" into "channel ready immediately"
  — the only thing `select` needs from a timer is that one of the
  cases be in the ready set. Real time becomes a refinement of
  *when* readiness flips, not of the protocol. Sister-plan diary
  updated with the readiness-as-protocol observation. [shapes-
  scheduler]
- 2026-05-27 — Phase 5 cont.: range-over-{slice,map,channel}. New
  `go-eval-range-for` dispatches on the collection type:
    slice → bind index + element, iterate by position
    map   → bind key + value, iterate over entries assoc list
    chan  → bind value, drain until empty (v0: no preemption to wait
            for new sends; real Go blocks until close + empty)
  break/continue propagate via the existing sentinel scheme.
  Subtle fix in break-from-loop: was returning the pre-loop env
  (clobbering prior-iteration assignments); now returns the current
  iteration's input env so successful iterations stick. Patched for
  range-slice, range-map, range-chan in one go. +7 tests, runtime
  26/26, total 483/483. `[nothing]` — collection-iteration semantics
  are Go-specific; the cross-language scheduler insights are already
  in the sister-plan diary.
- 2026-05-27 — Phase 5 cont.: `select` statement evaluation. New
  `go-eval-select-stmt` + `go-select-pick` + `go-select-try-case`.
  Walks cases in declared order: send always ready in v0; recv ready
  iff `(go-chan-len ch) > 0` (new accessor added to sched.sx);
  recv-into-decl/assign binds the value into env. Default deferred
  until end of walk; if nothing ready and no default, returns
  `(:eval-error :select-blocked-no-default)`. Subtle bug fixed where
  `:select` stmt was returning the old env instead of the
  case-body-extended env — assignments inside cases now stick. +6
  tests, runtime 18/18, total 475/475. `[nothing]` — `:select-case`
  uniform shape was already chiselled into the sister-plan diary when
  the parser landed.
- 2026-05-27 — **Phase 5 first slice.** `lib/go/sched.sx` lands with
  the v0 channel primitive: `go-make-chan` returns a closures-over-
  mutable-buf channel. Send appends, recv pops first, close flips a
  flag. Channel identity via closure-instance (matches Go spec — two
  `make()` calls produce distinct values). `make`/`close` are now
  builtins; `:send` stmt and unary `<-` recv hook through;
  `:go expr` evaluates synchronously (no real preemption — SX doesn't
  expose continuations, so v0 runs goroutines to completion in source
  order). Patterns that rely on the spawned goroutine pushing to a
  channel before the main reads do work end-to-end. runtime suite
  12/12 incl. multi-goroutine fan-in and worker-pattern. Total
  469/469. `[shapes-scheduler]` — sister-plan diary updated with the
  realised channel-as-closures-over-state shape and the v0
  synchronous-spawn caveat.

  Sister-plan diary update follows.
- 2026-05-27 — Phase 4 cont.: **method dispatch + unary ops + e2e
  programs. Acceptance bar (80+) crossed.** Methods register under
  `#method/TYPE/NAME` (same scheme the type checker uses). When `p.M(...)`
  is called, `go-eval-method-call` looks up the receiver type's method,
  binds the receiver param to the struct value, runs the body. Both
  value and pointer receivers work in v0 (treated the same — no
  pointer semantics yet). Unary `-x` / `+x` / `!x` in `go-eval`.
  E2E programs evaluating end-to-end now include: counter-via-method
  (factorial), linear search returning index or -1, recursive
  fibonacci(10) = 55, and the counter-bump-N-times pattern. +14 tests,
  eval 80/80, total 457/457. `[nothing]`.
- 2026-05-27 — Phase 4 cont.: structs + selector access +
  selector-assignment. `(:go-struct TYPE-NAME FIELDS)` value, with
  FIELDS an assoc list. `type T struct {...}` is now significant at
  eval-time too: registers `(:go-struct-type FIELD-NAMES)` in env so
  positional composite literals like `Point{1, 2}` can map positions
  to field names. Keyed literals `Point{x: 5, y: 10}` also work.
  `go-eval-select` does field lookup; LHS `:select` in
  `go-eval-assign-pairs` does field update. **`add(Point{1,2},
  Point{3,4}) → Point{4,6}` works end-to-end** — functions receiving
  and returning structs round-trip through the evaluator. +8 tests,
  eval 66/66, total 443/443. Method-dispatch (looking up methods by
  receiver type) pending; needs threading the type checker's
  `#method/T/N` scheme into eval. `[nothing]`.
- 2026-05-27 — Phase 4 cont.: maps + index-assignment. Maps represented
  as `(list :go-map ENTRIES)` where ENTRIES is an assoc list. New
  helpers `go-map-get` / `go-map-set` / `go-slice-set`. Composite-lit
  for `map[K]V{...}` evaluates via `go-extract-map-entries`. `m[k]`
  index lookup added to `go-eval-index`; `len(m)` extended in
  `go-eval-builtin`. **Index-assignment** for both slices and maps
  added to `go-eval-assign-pairs`: only var-rooted LHS for v0
  (`a[0] = 99`, `m["k"] = v`), enough for canonical programs.
  **Word-count via `counts[words[i]] = counts[words[i]] + 1` works
  end-to-end.** +8 tests, eval 58/58, total 435/435. `[nothing]`.
- 2026-05-27 — Phase 4 cont.: slice values + index/slice exprs + the
  `len`/`append`/`print` builtins. Slice representation is
  `(list :go-slice ELEMS)` for v0 (deferring the full length/cap/
  backing-vector triple). `go-eval-composite` handles `[]T{...}` /
  `[N]T{...}` literals (maps next). `go-eval-index` returns the i-th
  element with bounds-check. `go-eval-slice` handles two-index slicing
  with omitted low/high. New `go-env-builtins` starter env binds the
  three builtins as `(:go-builtin NAME)` values; `go-eval-call`
  recognises them and dispatches to `go-eval-builtin`. **Summing a
  slice via `for i := 0; i < len(a); i++ { sum = sum + a[i] }` works
  end-to-end.** +10 tests, eval 50/50, total 427/427. `[nothing]`.
- 2026-05-27 — Phase 4 cont.: for-loops, break, continue, inc-dec.
  `go-eval-for` handles all three for-header shapes (infinite, while-
  like, C-style) including init+post stmts and missing-cond defaulting
  to true. `:break` and `:continue` propagate as keyword sentinels
  through `go-eval-block` (alongside the existing `:return-value`
  sentinel) until `go-for-loop` catches them — break exits, continue
  runs post and re-loops. Inc-dec `x++`/`x--` updates env via the
  same shadowing model as assign. **Iterative `fact(5) = 120` and the
  classic for-loop sum-to-9 (= 45) both evaluate.** +7 tests, eval
  40/40, total 417/417. `[nothing]`.
- 2026-05-27 — Phase 4 cont.: statements + function application.
  `go-eval-stmt` handles `:return` (propagates a `:return-value`
  sentinel up through blocks), `:var-decl`, `:short-decl`, `:assign`
  (immutable-env shadowing), `:block`, `:if`/`:else`, and `:func-decl`
  (binds a `:go-fn` value). `go-eval-call` extends the caller's env
  with params → arg values, runs the body block, unwraps the return.
  **Recursive `fib(5) = 5` evaluates correctly** — recursion works
  because top-level funcs are bound in the calling env before any
  recursive call happens; the func value carries no captured env in
  v0 (dynamic-scope-ish), so true lexical closures wait for a later
  slice. +8 tests, eval 33/33, total 410/410. `[nothing]` — pure eval
  composition.
- 2026-05-27 — **Phase 3 ticked; Phase 4 scaffold.** Short-decl `:=`
  marked done (was already covered by go-check-short-decl from the
  decl-checking iteration). New `lib/go/eval.sx`: env-as-value (same
  shape as ctx but bound to runtime values), literal decoding for
  decimal/hex/oct/bin int literals (with underscores), variable lookup,
  predeclared `true`/`false`/`nil`, and the full set of arithmetic /
  comparison / logical binops via `go-eval-binop`. Hex/oct/bin parsing
  via `go-hex-digit-value` (explicit char-equality dispatch since SX's
  nth-on-string returns single-char strings, not numeric codes —
  cleaner than the char-arithmetic the kernel ports use). eval suite
  25/25, total 402/402. `[nothing]` — pure Go eval mechanics, the
  cross-language insights are about type-checking which is in the
  sister-plan diary.
- 2026-05-27 — Phase 3 cont.: **interface satisfaction** — the headline
  Go-distinguishing typing feature this loop set out to validate.
  Method decls record under `#method/TYPE-NAME/METHOD-NAME` keys in
  ctx (value-receiver and pointer-receiver both key the base type).
  `go-iface-satisfies? CTX TY-NAME IFACE-TYPE` walks the interface's
  `:method` elements and verifies each one is present with a matching
  (PARAMS, RESULTS) signature. Embedded interfaces in iface elements
  are silently skipped in v0 (recursive interface resolution comes
  later). Partial method sets and arity mismatches correctly return
  false. types 72/72, total 377/377. `[shapes-static-types-
  bidirectional]` — sister-plan diary updated with the structural-
  satisfaction primitive the kit should ship.

  Sister-plan diary update follows.
- 2026-05-27 — Phase 3 cont.: composite-literal element checking.
  `go-synth-composite` dispatches on the literal's type expression:
  `:ty-slice` and `:ty-array` check each element assignable to the
  element type; `:ty-map` checks each `:kv` pair (key against K, value
  against V) and rejects non-`:kv` map elements. The literal's
  synthesised type is the type-expression itself, so nested composites
  fall out by recursion: `[][]int{[]int{1,2}, []int{3,4}}` checks each
  inner `[]int{...}` as a value of type `[]int`. Named-type literals
  (`Point{1,2}`, `pkg.T{...}`) need type-decl-driven field resolution;
  deferred. **Phase 3 acceptance bar (60+ tests) crossed: 65/65, total
  370/370.** `[nothing]` — composite-literal semantics are mostly Go-
  specific. Remaining Phase 3 items (interface satisfaction; AST-path
  error context) sharpen the surface but don't gate Phase 4 (evaluator).
- 2026-05-27 — Phase 3 cont.: call type-checking. `go-synth-call`
  synthesises the callee's type, asserts it's a `:ty-func`, arity-
  checks args, then `go-check-args-against` runs each arg through
  `go-check` against the corresponding param type (untyped-constant
  flow works). Result: `:ty-void` for 0-result funcs, the result type
  for 1-result, `(list :ty-tuple TYPES)` for multi-return. The
  `:app` dispatch in `go-synth` now routes via `go-is-binop-call?`
  (operator name + 2 args + op in the binop tables) — binops short-
  circuit; everything else goes through the call path. **Recursive
  functions now type-check** because the func is bound in its own
  body's ctx by `go-check-func-decl`. +8 tests, types 55/55, total
  360/360. `[nothing]` — Go-side composition on top of established
  primitives; no new kit-relevant shapes (call semantics are uniform
  across statically-typed guests).
- 2026-05-27 — Phase 3 cont.: function-declaration checking +
  statement-level dispatch. `go-check-func-decl` binds the function in
  the outer ctx (so the body can see itself), extends the body's ctx
  with each `:field` param group via `go-ctx-extend-field` (the
  binding-group shape's third consumer in the type checker — now
  five total across parser+typer combined), then runs `go-check-block`
  through every statement. `go-check-stmt` dispatches on `:return`,
  `:assign`, `:var-decl`/`:const-decl`/`:short-decl`/`:type-decl`,
  `:block`, falling back to `go-synth` for expression statements.
  Return-list and assign-pair count mismatches are typed errors. +7
  tests, types 47/47, total 352/352. `[nothing]` — pure Go-side
  composition; the kit-relevant insights are already in the sister-
  plan diary.
- 2026-05-27 — Phase 3 cont.: declaration checking — `var`/`const`/`type`
  + short-decl `:=`. `go-check-decl` returns the extended context (or a
  `:type-error`). New helpers: `go-default-type` (untyped-int → int,
  untyped-float → float64, etc.), `go-check-exprs-against`,
  `go-bind-names-to-synth`. Annotated decls check each init expression
  is assignable to the declared type; inferred decls bind names to the
  default-typed synthesis of the init. **`var x float64 = 42 / 7` and
  `const C int = 42` both bind x to float64 / C to int correctly via
  the assignability relation from the previous commit.** +12 tests,
  types 40/40, total 345/345. `[nothing]` — the kit-relevant insights
  (synth/check + assignable predicate) already in the diary; this is
  pure Go-side composition on top.
- 2026-05-27 — Phase 3 cont.: literal synth + binop synth + assignability.
  Heuristic `go-classify-literal-string` decodes the parser's untagged
  literal values back into `:int`/`:float`/`:imag`/`:string` kinds
  (rune defers); these become `:ty-untyped-*` types. `go-synth-binop`
  handles arithmetic / bitwise / comparison / logical operators with
  untyped-constant unification: untyped int + untyped float → untyped
  float; untyped + typed → typed. **Canonical Go pitfall now handled**:
  `42 / 7` synthesises to `:ty-untyped-int`, then `go-check` against
  `float64` returns `:ok` via `go-type-assignable?`. +16 tests, types
  28/28, total 333/333. `[shapes-static-types-bidirectional]` — sister
  plan diary updated with the assignable-relation insight (kit's
  `check` should accept a `subtype?`/`assignable?` predicate parameter).
- 2026-05-27 — **Phase 3 scaffold.** First `lib/go/types.sx` cut: context
  as an association list (`go-ctx-empty` + `-extend` + `-lookup`), a
  load-bearing `go-ctx-extend-field` that consumes the `:field` binding-
  group shape (validating the Phase 2 cross-deliverable observation),
  predeclared `true`/`false`/`nil`, `go-synth` for identifier lookup,
  `go-check` deferring to synth + structural equality. types suite
  12/12, total 317/317. Literal kinds (untyped int/float/string/rune)
  + binop synth + var-decl checking next. `[shapes-static-types-
  bidirectional]` — sister-plan diary updated with the synth/check
  Go-side surface as it emerges.
- 2026-05-27 — **Phase 2 complete.** End-to-end multi-form file parsing.
  `go-parse` now returns single forms for backward compat (~169 tests
  unchanged) or `(list :file FORMS)` for multi-form input. Tests cover
  hello-world, fibonacci, FizzBuzz, goroutine ping-pong, struct+method,
  interface+method, and defer+select+range — each asserted via top-
  level `decl-tags`. Type-switch is the one syntactic shape still
  deferred. +7 tests, parse 176/176, total 305/305. Next: Phase 3
  (bidirectional type checker). `[nothing]` — pure Go parser
  composition; the cross-language insights are already in the sister-
  plan diaries from earlier Phase 2 commits.
- 2026-05-27 — Phase 2 cont.: `switch` and `select` statements.
  Tagged + tagless switch, multi-value cases, `default`, and select
  with recv-into-var / send / bare-recv / default cases. New
  `gp-parse-case-body` reads stmts until the next `case`/`default`/`}`
  without consuming the terminator. AST shapes:
  `(list :switch TAG CASES)`,
  `(list :case VALUES BODY)`,
  `(list :select CASES)`,
  `(list :select-case COMM-STMT BODY)`,
  `(list :default BODY)`. With this, **Phase 2 statement coverage
  is complete** — type-switch is the one remaining shape (deferred).
  +8 tests, parse 169/169, total 298/298. `[shapes-scheduler]` —
  sister-plan diary updated with the `:select-case` uniform shape
  insight (single kit primitive covers all four Go case kinds; default
  vs no-default determines blocking semantics; cross-references to
  Erlang's `receive ... after`).

  Sister-plan diary update follows.
- 2026-05-27 — Phase 2 cont.: concurrency + iteration statements.
  `go EXPR`, `defer EXPR`, channel send `ch <- v`, and the four
  `for ... range` shapes (no-kv / k-only / k,v / assign-form). New
  `gp-for-find-range` pre-scans the for-header to dispatch between
  range and C-style/while forms cleanly. Send-stmt detection added to
  the LHS-list branch (after lhs, `<-` → send). +9 tests, parse
  161/161, total 290/290. `[shapes-scheduler]` — Go's concurrency-
  primitive AST shapes (`:go`, `:defer`, `:send`, `:range-for`) all
  landed; sister-plan diary updated with the corresponding kit-API
  insights (uniform spawn-thunk shape, channel-recv ⇄ iteration
  polymorphism at the range-coll dispatch).

  Sister-plan diary update follows.
- 2026-05-27 — Phase 2 cont.: control-flow statements. `if cond { } [else
  { }]` with chained else-if, `for { }` (infinite), `for cond { }`
  (while-like), `for init; cond; post { }` (C-style), `break`,
  `continue`, plus `x++` / `x--` inc-dec statements. **Closed the
  parser-mode caveat** flagged when composite literals landed:
  `gp-no-comp-lit` is a re-entrant counter that suppresses the postfix
  `{...}` → composite-lit interpretation inside control-flow condition
  positions, matching Go spec § Composite literals. `gp-parse-control-
  cond` wraps the increment/decrement so callers can't forget. +11
  tests, parse 152/152, total 281/281. `[nothing]` — pure Go parser
  shape work; the bidirectional-checker-relevant shapes (cond/body) are
  already covered by the earlier `:field` insight.
- 2026-05-27 — Phase 2 cont.: statements. First slice covers
  `return [exprs]`, short-decl `lhs := exprs`, assignment `lhs = exprs`,
  compound assignment (`+= -= *= /= %= &= |= ^= <<= >>= &^=`), bare
  expression statements, and nested blocks `{ ... }`. New `gp-parse-stmt`
  dispatches on the leading token; `gp-parse-block-body` replaces the
  func-decl `:body` sentinel with real `(:block STMTS)`. Existing
  func/method tests updated to the new body shape. **Progress guards**
  added to `gp-block-body-loop` and `gp-parse-composite-elems` —
  unsupported syntax (`if`, `for`, etc.) now advances one token instead
  of spinning. `gp-skip-block!` left as dead code; will be deleted once
  control-flow stmts land. +9 tests, parse 141/141, total 270/270.
  `[nothing]` — pure Go parser work; the cross-language statement
  shapes will become a chiselling target once a second statically-typed
  guest hits them.
- 2026-05-27 — Phase 2 cont.: func declarations. `func f() {}`,
  `func add(x, y int) int { ... }`, multi-group params, multi-return,
  signature-only (no body), pointer-receiver and value-receiver methods,
  nested-brace body. New `gp-parse-decl-param-group` uses a named-greedy
  algorithm: collects consecutive `ident [, ident]*` then parses a
  type. `gp-skip-block!` brace-balances over the body opaquely; the AST
  stores `:body` as a sentinel pending statement parsing. With this,
  **all five `:field` binding-group consumers now exist** (struct
  fields, var, const, func params, method receivers) — strong cross-
  deliverable validation of the `ast-binding-group` kit proposal.
  Anonymous-param-list disambiguation (`func(int, string)`) is a known
  greedy-parser limitation flagged in plan. +8 tests, parse 132/132,
  total 261/261. `[shapes-static-types-bidirectional]` — the consistent
  use of `:field` across decls is what the sister kit's bidirectional
  checker will use to propagate types from declarations to bindings.

  Sister-plan diary update follows.
- 2026-05-27 — Phase 2 cont.: declarations — `package N`, `import "p"`,
  `var name [TYPE] [= EXPRS]`, `const name [TYPE] [= EXPRS]`,
  `type NAME TYPE`. New `gp-parse-top` dispatcher routes the five
  decl keywords to `gp-parse-decl` while preserving expression parsing
  for everything else. `var` and `const` reuse the `:field` binding-
  group shape from Blockers — **first cross-deliverable use of the
  proposed kit shape**: struct fields, func params, and now var/const
  decls all share the same `(list :field NAMES TYPE)` envelope. `import`
  uses canonical `ast-import` directly. Grouped forms (`var (...)`)
  and `func` decls deferred. +10 tests, parse 124/124, total 253/253.
  `[consumes-ast]` — first concrete use of `ast-import` from the kit;
  also validates the `:field` shape across three contexts.
- 2026-05-27 — Phase 2 cont.: composite literals. `T{}`, `T{1, 2}`,
  `T{X: 1, Y: 2}`, `[]T{...}`, `[N]T{...}`, `map[K]V{...}`,
  `pkg.T{...}`, nested composites. AST shape
  `(list :composite TYPE-OR-EXPR ELEMS)`; each element is an expression
  or `(list :kv KEY VALUE)`. Two parser entry points: type-prefixed
  (`gp-parse-primary` adds `[`/`map`/`struct` branches) and
  ident-prefixed (postfix loop adds `{` branch). **Known limitation
  flagged in plan:** when statement parsing arrives, the postfix `{`
  branch will misread `if cond { ... }` as composite literal — needs a
  "no-composite-here" parser-mode flag. +8 tests, parse 114/114, total
  243/243. `[nothing]` — pure Go parser shape work.
- 2026-05-27 — Phase 2 cont.: interface-type expressions. `interface {}`,
  `interface { Close() }`, `interface { String() string }`,
  `interface { Read([]byte) (int, error) }`, plus embedded interfaces
  (`Stringer`, `io.Reader`). AST shape:
  `(list :ty-interface ELEMS)` where each element is either
  `(list :method NAME PARAMS RESULTS)` or `(list :embed TYPE)`. Method
  params reuse `gp-parse-func-type-params` — same anonymous-only shape
  as func types. Go 1.18+ type sets (`~int | ~float64`) deferred to
  generics work. With this, all Phase-2 **type expressions** are
  complete. +8 tests, parse 106/106, total 235/235. `[nothing]` — pure
  Go parser; the field-binding-group kit-gap proposal from the previous
  commit covers the cross-language angle.
- 2026-05-27 — Phase 2 cont.: struct-type expressions. `struct {}`,
  `struct { x int }`, `struct { x int; y string }`, `struct { x, y int }`
  (shared type), nested struct fields. `gp-parse-struct-fields` walks
  field rows tolerating ASI semis; each row is a name list + type. AST:
  `(list :ty-struct FIELDS)` with each field `(list :field NAMES TYPE)`.
  Embedded fields, tags, and methods deferred. +8 tests, parse 98/98,
  total 227/227. `[proposes-ast]` — the `:field` shape (NAMES + TYPE)
  recurs in func params, method receivers, var decls; flagged in
  Blockers as a unified `ast-binding-group` candidate for the kit.
- 2026-05-27 — Phase 2 cont.: func-type expressions. `func()`,
  `func() int`, `func(int, string)`, `func(int) string`,
  `func() (int, error)`. AST shape `(list :ty-func PARAMS RESULTS)`
  where both are lists of type nodes. Results parsing reuses param
  parser for the multi-return `(T, T, ...)` case. Anonymous-only
  params for now — named params (`func(a int, b string)`) need a
  different shape and are required mainly for func DECLARATIONS not
  pure func-type expressions. Variadic deferred. Covers nested
  func-as-return and chan-of-func. +9 tests, parse 90/90, total 219/219.
  `[nothing]` — pure Go parser; type AST proposals already in Blockers.
- 2026-05-27 — Phase 2 cont.: type expressions — slice `[]T`, array
  `[N]T`, map `map[K]V`, chan in all three directions (`chan T`,
  `chan<- T`, `<-chan T`). `gp-parse-type` now dispatches on
  `*`/`[`/`map`/`chan`/`<-`/ident; each branch recurses for nested
  types. Channel direction is `:both`/`:send`/`:recv`. AST stays
  Go-specific tagged lists — kit has no type primitives at all.
  Covers nested types end-to-end (slice-of-pointer, slice-of-slice,
  map-with-slice-value, chan-of-map, pointer-to-slice). **Parse
  acceptance bar (80+) crossed: +11 tests, parse 81/81, total 210/210.**
  Func / struct / interface types and generics still pending in Phase 2.
  `[proposes-ast]` — surfaces concrete type-node proposals (slice / array
  / map are universal across statically-typed guests; channel direction
  is Go-specific). Logged in Blockers.
- 2026-05-27 — Phase 2 cont.: type assertion `v.(T)` postfix form.
  Postfix `.` branch now disambiguates between `.field` (selector) and
  `.(...)` (type assertion) by peeking at the next token. New
  `gp-parse-type` handles the minimum needed: named (`int`, `MyType`),
  qualified (`pkg.T`), pointer (`*T`, `**T`). AST shapes are
  Go-specific tagged lists — kit has no notion of types at all yet
  (this is a meta-gap: full bidirectional types arrive in Phase 3, but
  even the parser needs a type substrate). Covers chained,
  call-result, after-selector, and binary-precedence interactions. +9
  tests, parse 70/70, total 199/199. `[nothing]`.
- 2026-05-27 — Phase 2 cont.: index `x[i]` and slice `x[a:b]` /
  `x[a:b:c]` postfix forms. New `gp-parse-bracket` + `gp-parse-bracket-expr`
  branch off the same postfix loop as calls/selectors. AST: Go-specific
  `(list :index OBJ IDX)` and `(list :slice OBJ LOW HIGH MAX)` —
  LOW/HIGH/MAX may be nil for omitted indices. Two more kit gaps logged
  (no `:index`, no `:slice` in canonical AST). Permissive on `a[1::3]`.
  Covers: literal idx, var idx, expr idx, string idx, chained `a[0][1]`,
  mixed `a[0].field`, full slice with three indices. +12 tests, parse
  61/61, total 190/190. `[proposes-ast]`.
- 2026-05-27 — Phase 2 cont.: postfix forms — function calls `f(a, b)`
  via canonical `ast-app`, and member access `x.field` via Go-specific
  `(list :select OBJ "field")`. The AST kit has no selector node;
  logged in Blockers as `[proposes-ast]` — every nominally-typed guest
  will hit the same gap, worth promoting on the next consumer. Postfix
  loop sits between unary and primary so calls bind tighter than unary
  (`-f(x)` = `-(f(x))`). Covers nested calls, chained selectors,
  methods `obj.m(x)`, mixed precedence. +12 tests, parse 49/49, total
  178/178. `[consumes-ast proposes-ast]`.
- 2026-05-27 — Phase 2 cont.: unary prefix operators (`+`, `-`, `!`, `^`,
  `*`, `&`, `<-`). `gp-parse-unary` is recursive (`!!x`) and sits between
  `gp-parse-expr` and `gp-parse-primary` so unary always binds tighter
  than any binary. Symbols `+ - * & ^` are shared with binary; the
  positional split (expression-start vs mid-expression) disambiguates
  cleanly without lookback. Unary nodes are single-arg `ast-app`. +11
  tests, parse 37/37, total 166/166. `[nothing]` — pure Go parser work.
- 2026-05-27 — Phase 2 cont.: binary operators via Pratt precedence
  climbing. `gp-pratt-loop` consumes `pratt-op-lookup` against
  `go-precedence-table`; left-assoc bumps right-min by 1, right-assoc
  keeps prec. Binary op nodes are `(ast-app (ast-var OP) [LHS RHS])` —
  uses the canonical `ast-app` shape rather than inventing a Go-specific
  binary node. Covers: equal-prec left-to-right, `*` tighter than `+`,
  `&&` tighter than `||`, comparison tighter than `&&`, long chains.
  +9 tests, parse 26/26, total 155/155. `[consumes-pratt]`.
- 2026-05-27 — Phase 2 first slice: `lib/go/parse.sx` parser scaffold.
  Defines `go-precedence-table` using `lib/guest/pratt.sx` entry shape
  `(NAME PREC ASSOC)` — five Go precedence levels, all left-associative
  per Go spec § Operator precedence. `go-parse` tokenises via
  `go-tokenize`, then `gp-parse-primary` reads one literal / identifier
  and emits a canonical AST node via `lib/guest/ast.sx`'s `ast-literal`
  / `ast-var`. parse 17/17, lex still 129/129, total 146/146.
  `[consumes-pratt consumes-ast]`.
- 2026-05-27 — **Phase 1 complete.** Operator-set audit: added missing
  `~` (Go 1.18+ generics type-set), exhaustive op coverage tests grouped
  by category. Two kit gaps observed and logged in Blockers:
  `lex-oct-digit?`/`lex-bin-digit?` predicates + `lex-match-longest`
  table-driven prefix matcher — both useful for future statically-typed
  guests. +6 tests, lex 129/129. `[proposes-lex]`. Phase 2 (parser) next.
- 2026-05-27 — Phase 1 cont.: raw string literals (backtick-delimited).
  Multi-line, no escape processing, `\r` stripped per Go spec § String
  literals. Same `"string"` token type as interpreted strings — parsers
  / type checkers don't need to distinguish. +9 tests, lex 123/123.
  `[nothing]` — pure Go work; raw strings don't touch the substrate or
  lib/guest story.
- 2026-05-27 — Phase 1 cont.: decimal float + imaginary literals.
  `3.14`, `.5`, `1.`, `1e10`, `1.5e-3`, `2i`, `3.14i`. `gl-finish-number!`
  handles exponent + `i` suffix; `gl-read-number!` returns the type
  string (int/float/imag). ASI trigger list extended to float/imag.
  Greedy-grammar pin: `1.method` lexes as `float ident`. Hex floats
  (`0x1.fp0`) deferred. +22 tests, lex 114/114. `[consumes-lex]`.
- 2026-05-27 — Phase 1 cont.: prefixed integer literals (`0x..`, `0X..`,
  `0b..`, `0B..`, `0o..`, `0O..`, legacy `0123`) + underscore separators
  in any digit run. Dispatch in `gl-read-number!`; consumes
  `lex-hex-digit?` from the kit. +14 tests, lex 92/92. `[consumes-lex]`.
- 2026-05-26 — Phase 1 first slice: `lib/go/lex.sx` tokenizer consuming
  `lib/guest/lex.sx` predicates. 25 keywords, ident/int/string/rune lits,
  line+block comments, common operators, automatic semicolon insertion per
  Go spec § Semicolons (newline / EOF / block-comment-with-newline triggers).
  Scoreboard + conformance.sh wired. 78/78 tests. `[consumes-lex]`.
- 2026-05-26 — Plan rewritten to integrate the lib/guest framework
  (chiselling discipline, sister plans for scheduler + bidirectional
  types, type-checker phase added, conformance scoreboard model adopted).
  Original 2026-04-26 draft preserved in git history. Loop not yet
  kicked off; Phase 1 (tokenizer) is the first iteration when this loop
  spins up.
