Skip to content

Commit 5343c81

Browse files
authored
Merge pull request #12 from BuddhiLW/staging
clj-parity: macroexpand-all, when-not expansion; bash-e2e → cljw-native suite migration
2 parents a0216c5 + b82fb1f commit 5343c81

55 files changed

Lines changed: 1438 additions & 1979 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.dev/accepted_divergences.yaml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ accepted:
259259
finite dividend gives a 0 quotient and both runtimes return NaN (verified
260260
2026-08-17 against the clj oracle).
261261
derives_from: "F-005 (numeric tower: single internal f64, one clean float quot/rem/mod path) + AD-008 (cljw's cleaner numeric surface where clj throws)"
262-
pin: "test/e2e/phase14_quot_rem_mod_tower.sh — quot_nan/rem_nan/mod_nan_d/mod_nan_v (NaN family) + quot_inf/rem_inf/mod_inf/quot_ninf (+/-Inf-dividend family) lock cljw's non-finite result (clj throws)"
262+
pin: "test/clj/suites/numeric_tower_test.clj — the `non-finite-operands` deftest: quot_nan/rem_nan/mod_nan_d/mod_nan_v (NaN family) + quot_inf/rem_inf/mod_inf/quot_ninf (+/-Inf-dividend family) lock cljw's non-finite result (clj throws). Each assertion carries its original case name as a trailing comment."
263263
- id: "AD-019"
264264
summary: "Java arrays are type-erased: element type hints (`^\"[B\"`, `^ints`, `^bytes`) are advisory only, and a typed-array constructor / `aset` does not coerce or type-check non-byte/short/char elements."
265265
example: '(let [^"[B" b (byte-array 1)] (aset b 0 65) (aget b 0)) => 65 (a non-byte stored into a byte-typed array, no error); (class (object-array 0)) => cljw "array" / clj "[Ljava.lang.Object;"'
@@ -617,7 +617,7 @@ accepted:
617617
`when`/`if-not`/`when-not` forms ARE made to byte-match clj; only the
618618
recursive-cascade cases keep cljw's full-expand shape.
619619
derives_from: "F-011 (behavioural equivalence is the contract; the macros run identically — only the intermediate macroexpand-1 FORM differs; cljw's Zig transforms lower eagerly in one step). Functionally-equivalent expansion form, like ClojureScript's macroexpansions vs JVM clj."
620-
pin: "test/e2e/phase14_when_if_not.sh — `cond_expand_full` locks (macroexpand-1 '(cond a 1 :else 2)) => (if a 1 (if :else 2 nil)) (cljw's full form), and `when_expand_multi`/`when_expand_single` lock when's byte-match with clj"
620+
pin: "test/clj/suites/control_macros_test.clj — `cond-expands-to-the-full-nested-if` locks (macroexpand-1 '(cond a 1 :else 2)) => (if a 1 (if :else 2 nil)) (cljw's full form), and `when-expands-like-clj`/`when-not-expands-like-clj` lock when's and when-not's byte-match with clj"
621621
- id: "AD-041"
622622
summary: "clojure.test's FAIL/ERROR ` (file:line)` suffix points at the DEFTEST var's line (from Var source meta, D-563b) where clj points at the failing assertion's line (JVM stack frame); and `report :error` prints the exception's value instead of clj's JVM cause trace. Names, expected/actual, context, and counts all match clj."
623623
example: "(deftest a (is (= 1 2))) in f.clj: cljw `FAIL in (a) (f.clj:LINE-OF-DEFTEST)` / clj `FAIL in (a) (f.clj:LINE-OF-IS)`. An erroring test: cljw ` actual: #error {…}` (the ex value) / clj a multi-line `clojure.lang.ExceptionInfo … at …` cause trace."
@@ -686,7 +686,7 @@ accepted:
686686
accepted divergence. `displayClassName` (runtime/class_name.zig) is the SSOT;
687687
the names are NOT `instance?` targets (no `clojure.lang.*` FQCN to resolve).
688688
derives_from: "ADR-0059 (no JVM Class) + AD-003 (simple class name, no JVM FQCN) — extended to callables/seq-views/refs which clj backs with generated/reify classes."
689-
pin: "test/e2e/phase14_class_type.sh — class_fn_anon/class_fn_builtin => Fn, class_multifn => MultiFn, class_lazyseq/range/chunkseq/future/agent/atom/ref/volatile/delay/reduced/tvec, and class_fn_isa_ifn (the IFn isa? still resolves through the renamed callable classes)."
689+
pin: "test/clj/suites/class_type_test.clj — the `class-of-callables-and-refs` deftest: anon fn/builtin => Fn, multifn => MultiFn, lazyseq/range/chunkseq/future/agent/atom/ref/volatile/delay/reduced/tvec, and the class-level `(isa? (class +) clojure.lang.IFn)` (the IFn isa? still resolves through the renamed callable classes)."
690690
- id: "AD-045"
691691
summary: "`send-via` / `set-agent-send-executor!` / `set-agent-send-off-executor!` RAISE (`value_error`) instead of dispatching on / configuring a user-supplied executor — cljw has no `java.util.concurrent.ExecutorService` type, so there is no executor to pass or configure. clj runs these for real against its global agent thread pools."
692692
example: '(send-via my-executor a inc) / (set-agent-send-executor! my-executor) => cljw raises "ClojureWasm has no configurable executor — agents run on a per-agent worker …" / clj dispatches-via / mutates the pool.'
@@ -1109,4 +1109,4 @@ accepted:
11091109
matches clj, so no code that was portable has been broken.
11101110
example: '(class (seq [1 2 3])) => cljw ArraySeq / clj clojure.lang.PersistentVector$ChunkedSeq. Also `(class (seq (into-array [1 2])))` would be ArraySeq in BOTH — the one backing for which the shared name is exact.'
11111111
derives_from: "AD-003 (simple class name for cljw-native types) + F-004 A14 slot reservation + the F-005 family-collapse precedent."
1112-
pin: "test/e2e/vector_seq_view.sh — class_name."
1112+
pin: "test/clj/suites/vector_seq_view_test.clj — the `the-class-name-is-the-reserved-slot-name` deftest (case class_name), asserting (str (class (seq [1 2]))) => \"ArraySeq\"."

‎.dev/handover.md‎

Lines changed: 77 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,100 @@
11
# Session handover
2-
3-
> ≤ 100 lines. Driving doc; framing per
4-
> [`.claude/rules/handover_framing.md`](../.claude/rules/handover_framing.md).
2+
> ≤100 lines. Driving doc; framing per `.claude/rules/handover_framing.md`.
53
64
## Resume contract
75

86
- **HEAD**: `staging` is the WIP/integration branch (`git log` = SSOT); a
97
release is cut by PR `staging`→`main` (the merge push auto-bumps the patch).
10-
Per-commit = smoke locally; commit **and** push. **First commit MUST be**
11-
`[CLJW-META-MAP]` (`20260828224509-23d5e92c`) — atom/ref/agent `:meta` ctor
12-
must reject a non-map (verified live 2026-08-28; TRIVIAL, extract one
13-
`requireMetaMap`). Then the g5 cheap chain `[CLJW-HIER-GUARDS]` →
14-
`[CLJW-FUTURE-BINDINGS]`, and `[CLJW-COMPLIANCE-REMEASURE]`
15-
(`20260828224528-20f90bb5`, HIGH) to refresh the STALE 2026-08-21 red list
16-
(memory `20260828224435-02f356e5`; g-notes g1..g6 are the root-cause map, and
17-
the runtime-`def`-interns-wrong-ns root `[CLJW-DEF-NS]` clears several reds at
18-
once — memory `20260828224443-05ce9bfa`). **3 source commits ride since the
19-
last full gate — run `bash scripts/run_gate.sh` before the 5-commit ceiling.**
20-
OR cut **v1.11.2** first (staging→main; `[Unreleased]` now has
21-
range-heap-Long + empty + contains?-array + prior). **Forbidden this
22-
session**: `git rebase`/`cherry-pick`/
23-
`commit --amend` (classifier-blocked in auto-mode — use FORWARD commits on a
24-
fresh branch); killing co-tenant JVMs to free build memory (see the build-OOM
25-
memory). **CI runs ONE configuration everywhere** — PR, push and dispatch all
26-
run the identical `test/run_all.sh --serial-e2e` that `scripts/run_gate.sh`
27-
runs; `check_gate_parity.sh` fails if a tier comes back.
28-
- **zwasm** = tag pin **v2.5.0** (`278587f6`, FINAL), living at
29-
`github.com/zwasm/zwasm` under separate maintainership since 2026-08-12; the
30-
dep URL names it directly rather than riding the transfer redirect.
31-
Co-development (the `dogfooding_handover` mailbox,
32-
`.dev/zwasm_capabilities.md`'s refresh duty) is RETIRED.
33-
- **This repository is a MAINTAINED FORK.** Upstream `clojurewasm/ClojureWasm`
34-
(chaploud) stopped at v1.10.1 and invited forks under EPL-2.0. **`origin` is
35-
this fork** and `main` tracks it. The archived source is the `clojurewasm`
36-
remote, deliberately hobbled: push URL `no_push`, and
37-
`remote.clojurewasm.tagOpt=--no-tags` so a fetch can never deliver its
38-
`v1.10.1` over ours — that ref is what the Homebrew tap resolves, and losing
39-
it would repoint every `brew install`. Tap = `BuddhiLW/homebrew-tap`. PERF
40-
CAMPAIGN (D-450) stays **stopped** — findings remain in the row as a record.
41-
- **TAG CLASH — `v1.10.1` names two different commits.** Ours is `50b85214`
42-
(what `brew install buddhilw/tap/cljw` installs); upstream's is `8fa46d97`.
43-
Both were cut the same day from different trees, and since the upstream merge
44-
both are reachable in one object graph, so that NAME no longer identifies a
45-
commit. Do NOT fetch upstream's tags into this repo — it would overwrite the
46-
ref the tap resolves. Releases continue at v1.10.2 and up, which collide with
47-
nothing, since upstream cut nothing after v1.10.1.
48-
- **Anything built from the `v1.10.0` tag rides the zwasm transfer redirect**,
49-
which holds only while nothing claims the vacated `clojurewasm/zwasm` name.
50-
Build from v1.10.1 or later.
51-
- **Release path is automated and proven.** Every push to `main` that touches
52-
`src/**` cuts the next patch itself: `preflight` bumps `build.zig.zon`,
53-
stamps `## [Unreleased]` into a dated version heading, tags, builds both
54-
native artifacts and bumps the tap (`tap` job, deploy key `TAP_DEPLOY_KEY`).
55-
A patch whose `[Unreleased]` body is EMPTY is not stamped — so write the
56-
changelog entry in the same landing as the source.
57-
- **Test layers 6/7/8 are OPEN** (ADR-0186): golden (`test/golden/`, gated),
58-
properties (`src/testing/prop_*.zig`, fixed seed — sweep with
59-
`-Dprop-seed`/`-Dprop-iters`), mutation (`scripts/mutation/run.sh`, on
60-
demand, worktree-isolated, NEVER gated). The mutation `--oracle` defaults to
61-
`unit`, which never builds the CLI — a file whose behaviour IS what the
62-
program prints needs `--oracle unit+golden` or every rendering path scores
63-
as unconstrained. First task **D-577**: rule out equivalence before writing
64-
a test for a survivor (`.dev/mutation_equivalent.jsonl` holds the proofs).
65-
- **CI fires on push, on PRs and on tags here** — verified, not assumed.
66-
**Unreleased on main**: see CHANGELOG `[Unreleased]`.
67-
- **Release-process hazard**: cutting the GitHub release BY HAND before the
68-
tag workflow runs breaks the artifact upload (zwasm #160). The matrix-leg
69-
`gh release view || gh release create` TOCTOU is FIXED — a failed create is
70-
accepted iff the release exists afterwards.
8+
Per-commit = smoke; commit **and** push.
9+
- **FIRST MOVE ON RESUME: spawn a cljw nREPL and work through it**
10+
(`mcp__hive__code cider spawn repl_type="cljw"`, then `cider eval`). Reap by
11+
scoped pattern per `orphan_prevention.md` rule 4; never before a wrap.
12+
- **First commit on resume MUST be**: the next unit of
13+
`[CLJW-E2E-TO-SUITES]` (`20260831204206-0eed020c`) — port the next stateless
14+
bash e2e to `test/clj/suites/`. That card carries the method AND the three
15+
hazards (state, process-bound cases, dangling pin/ledger pointers); read it
16+
before deleting any script.
17+
- **Gate state 2026-08-31: FULL gate GREEN (436 passed, 0 failed, 0 skipped,
18+
907s); cadence counter reset.**
19+
- **Pick the smoke selector by COVERAGE, not topic**: grep the e2e tier for the
20+
file you changed and name that step. A bare `--smoke` on a `walk.clj` change
21+
ran zero walk e2e and shipped a stale stub assertion for 11 commits
22+
(memory `20260831212130-2668f443`). Respect ADR-0107's 5-commit ceiling; it
23+
bounds how far such a miss travels.
24+
- **Forbidden this session**: `git rebase`/`cherry-pick`/`commit --amend`
25+
(classifier-blocked in auto-mode — use FORWARD commits); killing co-tenant
26+
JVMs/`cljw` processes to free memory (7 `bb-mcp.core` co-tenants are normal).
27+
- **CI runs ONE configuration everywhere** — PR, push and dispatch all run the
28+
same `test/run_all.sh --serial-e2e` as `run_gate.sh`; `check_gate_parity.sh`
29+
fails if a tier comes back.
7130
- **Forbidden**: bare `zig build test` without `-Dwasm`; bare `zig build` for a
7231
probe (use ReleaseSafe); a concurrent build during a gate (the FULL gate runs
7332
`--serial-e2e`, ALONE). `.claude/**` edits + cross-repo publishes may hit the
74-
auto-mode block — surface to the user. **D-549 is user-LOCKED**, **D-560
75-
trigger-gated**. External publishes need `test -s` + read-back guards.
33+
auto-mode block — surface to the user. **D-549 user-LOCKED**, **D-560
34+
trigger-gated**; external publishes need `test -s` + read-back guards.
35+
36+
## Project invariants (stable)
37+
38+
- **zwasm** = tag pin **v2.5.0** (`278587f6`, FINAL) at `github.com/zwasm/zwasm`,
39+
separately maintained; co-development RETIRED (`.dev/zwasm_capabilities.md`).
40+
- **This repository is a MAINTAINED FORK.** Upstream `clojurewasm/ClojureWasm`
41+
(chaploud) stopped at v1.10.1 and invited forks under EPL-2.0. `origin` is
42+
this fork; `main` tracks it. The archive is the `clojurewasm` remote,
43+
deliberately hobbled (push URL `no_push`, `tagOpt=--no-tags`) so a fetch can
44+
never deliver its `v1.10.1` over ours — that ref is what the Homebrew tap
45+
resolves. Tap = `BuddhiLW/homebrew-tap`.
46+
- **TAG CLASH — `v1.10.1` names two commits.** Ours is `50b85214` (what `brew
47+
install buddhilw/tap/cljw` gets); upstream's is `8fa46d97`. Do NOT fetch
48+
upstream tags. Releases continue at v1.10.2+, which collide with nothing.
49+
- **Release path is automated.** Every push to `main` touching `src/**` cuts
50+
the next patch: `preflight` bumps `build.zig.zon`, stamps `## [Unreleased]`
51+
into a dated heading, tags, builds both native artifacts, bumps the tap
52+
(`TAP_DEPLOY_KEY`). An EMPTY `[Unreleased]` body is not stamped — write the
53+
changelog entry in the same landing as the source.
54+
- **Release hazard**: cutting the GitHub release BY HAND before the tag
55+
workflow runs breaks artifact upload (zwasm #160). The matrix-leg
56+
`gh release view || gh release create` TOCTOU is FIXED.
57+
- **PERF CAMPAIGN (D-450) stays stopped** — findings remain in the row.
7658

7759
## Current state (details = CHANGELOG + git log)
7860

7961
- **Issues, PRs and Discussions are all OPEN here.** CONTRIBUTING exempts
80-
outside contributors from the loop's own conventions: a contributed commit
81-
is taken as-is with authorship preserved and any missing `Smell-audited:`
82-
line amended in. Each `scripts/check_*.sh` header is its own SSOT.
83-
- **Active unit — clojure.core compliance drain** (jank `clojure-test-suite`,
84-
248 `.cljc`; drain-plan `private/notes/compliance/drain-plan-2026-08-22.md`,
85-
batches B1-B8; baseline is pre-fix, re-measure via the fixed harness ADR-0191).
86-
**v1.11.1 SHIPPED** (net serve + unix sockets + assocEx + numeric + interop;
87-
its 3 SSOT-ledger CI failures fixed — see the smoke-drift memory). Landed on
88-
`staging` since, unreleased (4-5 clj-parity fixes, cards done): **int?**
89-
fixed-precision + heap-Long origin (B7); **take** float/`##Inf`/BigInt count
90-
(B3-adjacent); **disj/disj!/dissoc!** variadic (B1); **(String. …offset len
91-
[charset])** range ctors; **string/replace** map/IFn replacement (B4, WIP).
92-
Deferred with design memos: **[CLJW-DEF-NS]** (Capture-by-Var, multi-backend +
93-
AOT — memory `20260825160952-2e68811c`). New card **[CLJW-RANGE-HEAPLONG]**
94-
(B3, `-range` rejects heap-Long bounds).
62+
outside contributors from the loop's conventions. Each `scripts/check_*.sh`
63+
header is its own SSOT.
64+
- **Shipped through v1.13.2.** On `staging` since: `slurp` drains `System/in`;
65+
two silent gate verdicts made honest; `clojure.walk/macroexpand-all`
66+
implemented; `when-not` now wraps its body in `(do …)` like clj.
67+
- **Active unit — bash e2e → cljw-native suites (Layer 5b).**
68+
`test/clj/run_suites.clj` runs `test/clj/suites/*_test.clj` by DISCOVERY
69+
(drop a file in, it runs) and is gated in SMOKE_CORE as `test_clj_suites`.
70+
2026-08-31: 18 scripts ported, **651 spawns removed**, suite tier 12 → **23
71+
suites / 195 tests / 1116 assertions**, e2e 410 → 395. What stays in bash is
72+
the CLI surface (exit code, stderr, argv) and anything needing the PROCESS
73+
(e.g. `CLJW_GC_TORTURE=1`, which is read at process start).
74+
- **Test layers 6/7/8 OPEN** (ADR-0186): golden (`test/golden/`, gated),
75+
properties (`src/testing/prop_*.zig`, `-Dprop-seed`/`-Dprop-iters`), mutation
76+
(`scripts/mutation/run.sh`, on demand, NEVER gated; `--oracle` defaults to
77+
`unit` and never builds the CLI — a rendering path needs `unit+golden`).
78+
First task **D-577**: rule out equivalence before writing a test for a
79+
survivor (`.dev/mutation_equivalent.jsonl`).
80+
- Deferred with a design memo: **[CLJW-DEF-NS]** (Capture-by-Var, multi-backend
81+
+ AOT — memory `20260825160952-2e68811c`).
9582

9683
## What was left unfinished (`.dev/debt.yaml` is the SSOT)
9784

9885
- **D-565** residuals (7)/(8) unreachable (upstream gitignored `private/`).
9986
- **Perf campaign (§9.2.S) — PAUSED** (D-520/D-386/D-005/006); **D-513** (1)
100-
`clojure.core.reducers` remaining. **D-548** (b) pmap wall-clock on 3-vCPU.
87+
`clojure.core.reducers`. **D-548** (b) pmap wall-clock on 3-vCPU.
88+
- Open cards: `[CLJW-JSON-REEXPORT]` (`20260831194114-196afd4a`, hypothesis
89+
unverified) · `[CLJW-ENTRYPOINT-FLAKE]` (`20260831192937-1e3c4ed0`) ·
90+
`[CLJW-NATIVE-HARNESS]` (`20260831201012-5386d9f9`).
10191

102-
## North star (ACTIVE, distal)
92+
## North star (ACTIVE, distal) + reading order
10393

10494
cljw's differentiator = **Wasm interop (gap II) × VM-perf fusion→JIT (gap
10595
III)**. zwasm JIT (ADR-0200) is the cljw default; remaining =
10696
components-through-the-JIT (zwasm-side, D-500). Distal — needs a user nod.
107-
NOTE ADR-0177: "edge execution" is an AIM owned by D-552, not a capability.
108-
109-
## Stopped — user requested
110-
111-
User instruction (2026-08-25): "commit and push current WIP in staging. and we
112-
continue later. make memories on all learnings this session, kg connect them,
113-
sync kanban, create remaining kanban tasks if any, and `workflow wrap`."
114-
Resume per the Resume-contract "First commit MUST be".
115-
116-
## Reading order (resume)
117-
118-
handover → `yq` the live `active:` list → ADR-0166 → ROADMAP §9.0. Memories:
119-
`verify_against_releasesafe_binary` / `smoke_first_batch_full_gate` /
120-
`external-publish-payload-guard`.
97+
ADR-0177: "edge execution" is an AIM owned by D-552, not a capability.
98+
Resume reading: handover → `yq` the live `active:` list → ADR-0166 → ROADMAP
99+
§9.0. Memories: `verify_against_releasesafe_binary` /
100+
`smoke_first_batch_full_gate` / `external-publish-payload-guard`.

0 commit comments

Comments
 (0)