OCaml: StringBuffer of Buffer.t in sx_types.ml; 5 primitives in
sx_primitives.ml (make-string-buffer, string-buffer?, string-buffer-append!,
string-buffer->string, string-buffer-length); inspect case added.
JS: SxStringBuffer with array+join backend; _string_buffer marker for
typeOf dispatch and dict? exclusion (also excludes _vector from dict?).
spec/primitives.sx: 5 define-primitive entries.
17/17 tests pass on both OCaml and JS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
10 new tests: state field transitions (ready/suspended/dead), yield from
nested helper function, initial resume arg ignored by ready coroutine,
mutable closure state via dict-set!, complex yield values (list/dict),
round-robin scheduling, factory creates independent coroutines, resuming
non-coroutine raises error.
27/27 pass on both OCaml and JS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All CEK primitives (cek-step-loop/cek-resume/make-cek-state/cek-suspended?/
cek-io-request/cek-terminal?/cek-value) were already registered in sx-browser.js.
Root cause of test failure: (import (sx coroutines)) creates an io-suspended
state when the library isn't pre-loaded; overridden cekRun throws on suspension.
Fix: pre-load spec/signals.sx + spec/coroutines.sx before test files run.
17/17 coroutine tests pass in JS. 1965/2500 total (+25 vs 1940 baseline),
zero new failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No native SxCoroutine type needed. dict-based coroutine identity +
cek-step-loop/cek-resume/perform/make-cek-state primitives already in
run_tests.ml fully implement the coroutine contract. 284/284 pass
(coroutines+vectors+numeric-tower+dynamic-wind), zero regressions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
spec/coroutines.sx: define-library with make-coroutine, coroutine-resume,
coroutine-yield, coroutine?, coroutine-alive?. Built on existing perform/
cek-step-loop/cek-resume suspension machinery.
spec/tests/test-coroutines.sx: 17 tests — multi-yield, final return,
arg passthrough, alive? predicate, nested coroutines, recursive iteration,
independent coroutine interleaving.
Key: coroutine body must use (define loop (fn…)) not named let — named let
transpiles to cek_call→cek_run which rejects IO suspension. All 17/17 pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sx_types.ml: CallccContinuation gains winders depth int field
- sx_runtime.ml: make_callcc_continuation(captured, winders_len),
callcc_continuation_winders_len accessor; get_val maps after-thunk,
winders-len, body-result to cf_f/cf_extra/cf_name
- sx_ref.ml: step_limit/step_count restored; make_wind_after_frame and
make_wind_return_frame now store their args in the CekFrame fields
- transpiler.sx: after-thunk→cf_f, winders-len→cf_extra,
body-result→cf_name for future bootstrap runs
- 8 new dynamic-wind tests pass (OCaml), 235/235 no regressions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add integer?/float?/exact?/inexact? predicates (Number.isInteger check).
Add truncate/remainder/modulo/random-int/exact->inexact/inexact->exact/parse-number.
inexact->exact uses Math.round (rounds to nearest, matching OCaml).
Fix sx_server.ml epoch/blob/io-response protocol to accept Integer as
well as Number — parser now produces Integer for whole-number literals.
JS: 60 new passing tests (1880→1940). OCaml: 4874/394 baseline unchanged.
Note: 6 tests fail in JS due to platform limitation (JS cannot distinguish
float 2.0 from integer 2).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix PRIMITIVES["index-of"] for arrays: return NIL when not found (matching
OCaml semantics) so bind-lambda-params correctly detects absent &rest params.
Previously String(array).indexOf() returned -1, which passed number? check
and mis-fired the &rest branch, leaving non-&rest params unbound.
- Declare var _lastErrorKont_ and var hostError in IIFE scope (strict mode fix)
- Add PRIMITIVES["host-error"], ["try-catch"], ["without-io-hook"]
- Add env["test-allowed?"] stub in run_tests.js
- Add spec/tests/test-vectors.sx: 42 tests for all vector primitives
- Rebuild sx-browser.js: 1847 standard / 2362 full tests pass (up from 5)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 10 vector primitives now have :as type annotations on every parameter,
:returns types, and :doc strings. make-vector gains optional fill annotation;
vector uses :rest for its variadic args; vector-ref/set! document bounds error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
vector-ref and vector-set! now raise Eval_error on out-of-bounds index instead of
an OCaml array exception. vector-copy accepts optional start and end parameters for
slicing (R7RS §6.8). spec/primitives.sx doc updated to reflect slice params.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Threads declaration kind ("var"/"let"/"const") through js-transpile-var →
js-vardecl-forms so the transpiler knows which kind is being declared.
Infrastructure for full TDZ enforcement: js-tdz-check can wrap let/const
reads to raise TypeError before initialization.
Updates plans/js-on-sx.md: ticks [x] for TDZ, marks regex blocker RESOLVED,
adds progress log entry for 2026-04-25.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add recognizer for expect(await error("HS")).toBe("MSG") pattern in
generate-sx-tests.py, plus eval-hs-error SX helper in the generated
test file. All 18 runtimeErrors tests now generate real test cases
instead of SKIP stubs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plans + briefings for four new language loops, each with a delcc/JIT
showcase that the runtime already supports natively:
- common-lisp — conditions + restarts on delimited continuations
- apl — rank-polymorphic primitives + 6 operators on the JIT
- ruby — fibers as delcc, blocks/yield as escape continuations
- tcl — uplevel/upvar via first-class env chain, the Dodekalogue
Launcher scripts now spawn 12 windows (was 8).
Tracks the path from 1277/1496 (85.4%) to 100%. Records each blocker's
fix sketch, files in scope, and order of attack. Cluster #31 spec'd in
detail for the next focused sit-down.
Previous version ran all 7 claude sessions in the main working tree on
branch 'architecture'. That would race on git operations and cross-
contaminate commits between languages even though their file scopes
don't overlap. Now each session runs in /root/rose-ash-loops/<lang> on
branch loops/<lang>, created from the current architecture HEAD.
sx-loops-down.sh gains --clean to remove the worktrees; loops/<lang>
branches stay unless explicitly deleted.
Also: second Enter keystroke after the /loop command, since Claude's
input box sometimes interprets the first newline as a soft break.
sx-loops-up.sh spawns a tmux session 'sx-loops' with 7 windows (lua,
prolog, forth, erlang, haskell, js, hs). Each window runs 'claude'
and then /loop against its briefing at plans/agent-briefings/<x>-loop.md.
Optional arg is the interval (e.g. 15m); omit for model-self-paced.
Each loop does ONE iteration per fire: pick the first unchecked [ ] item,
implement, test, commit, tick, log — then stop. Commits push to
origin/loops/<lang> (safe; not main).
sx-loops-down.sh sends /exit to each window and kills the session.
Attach with: tmux a -t sx-loops
- scripts/loop-guard.sh — atomic claim with 30-min staleness overtake,
appends NDJSON event to .loop-logs/<lang>.ndjson. Exit 0 = go ahead,
exit 1 = another run is live, skip.
- scripts/loop-release.sh — clear lock, log release with exit status.
Intended for 7 per-language /schedule routines firing every 15 minutes.
Lock detects overlap so tight cadences are safe; stale lock (>30 min)
overtaken automatically if an agent dies mid-run.
sx-tree MCP file ops broken this session (Yojson Type_error "Expected
string, got null" on every file-based call — sx_read_subtree,
sx_find_all, sx_replace_by_pattern, sx_summarise, sx_pretty_print, even
sx_load_check on existing files works but summarise fails). Can't edit
integration.sx to add before:init/after:init dispatch. Additionally 4
of the 6 tests fundamentally require stricter parser error-rejection
(add - to currently parses to (set! nil ...); on click blargh end
accepts blargh as symbol expression) — out of single-cluster budget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cluster 19 was implemented in 4be90bf2 but the plan/scoreboard rows
still marked it pending. Sync the plan state: mark done, add log entry,
bump merged total 1264 → 1277.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five new guest-language plans mirroring the js-on-sx / hs-loop pattern, each
with a phased roadmap (Progress log + Blockers), a self-contained agent
briefing for respawning a long-lived loop, and a shared restore-all.sh that
snapshots state across all seven language loops.
Briefings bake in the lessons from today's stall debugging: never call
sx_build (600s watchdog), only touch lib/<lang>/** + own plan file, commit
every feature, update Progress log on each commit, route shared-file
issues to Blockers rather than fixing them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds regex-compile/test/exec/match-all/replace/replace-fn/split/source/flags.
Opaque dict handle {:__regex__ true :id :source :flags}; compiled Re.re
cached in a primitives-local table. Replacement supports $&, $1-$9, $$.
Flags: i (CASELESS), m (MULTILINE), s (DOTALL). g is a runtime flag handled
in replace. u (unicode) skipped for now.
Unblocks js-on-sx's regex-platform-override! hook — the JS RegExp shim can
now delegate to real regex instead of the substring stub.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements cluster 19 — pick command extensions for hs-upstream-pick suite
(11/24 → 24/24, +13):
- Parser:
- pick items/item EXPR to EXPR supports `start` and `end` keywords
- pick match / pick matches accept `| <flag>` syntax after regex
- pick item N without `to` still works (single-item slice)
- Runtime:
- hs-pick-items / hs-pick-first / hs-pick-last now handle strings
(not just lists) via slice
- hs-pick-items resolves `start`/`end` sentinel strings and negative
indices (len + N) at runtime
- hs-pick-matches added (wraps regex-find-all, each match as a list)
- hs-pick-regex-pattern handles (list pat flags) form; `i` flag
transforms pattern to case-insensitive by replacing alpha chars with
[aA] character classes (Re.Pcre has no inline-flag support)
- Generator:
- extract_hs_expr now decodes JS string escape sequences (\" -> ",
\\ -> \) instead of stripping all backslashes, then re-escapes for
SX. Preserves regex escapes (\d, \s), CSS escapes, and lambda `\`
syntax for String.raw template literals while still producing
correct output for regular JS strings.
Smoke (0-195): 170/195 unchanged (no regressions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shared formatter in sx_types.ml. Small integer-valued floats still print
as plain ints; floats outside safe-int range (|n| >= 1e16) now print as
%.17g (full precision) instead of silently wrapping to negative or 0.
Non-integer values keep %g 6-digit behavior — no existing SX tests regress.
Unblocks Number.MAX_VALUE / Math.pow(2,N) style tests in js-on-sx where
iterative float loops were collapsing to 0 at ~2^63.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Number.prototype.constructor === Number etc. Four dict-set! lines add
the backlink after each constructor dict is defined.
new String().constructor === String now returns true. Array literals
don't yet link to Array.prototype so [].constructor === Array is still
false — that would need a boxing refactor.
Unit 521/522, slice 148/148 unchanged.
Number 76/100 → 77/100 (+1). String variance-heavy under CPU load.
Wire up the `ask` and `answer` commands end-to-end:
- tokenizer.sx: register `ask` and `answer` as hs-keywords.
- parser.sx: cmd-kw? gains both; parse-cmd dispatches to new
parse-ask-cmd (emits `(ask MSG)`) and parse-answer-cmd, which
reads `answer MSG [with YES or NO]`. The with/or pair reads
yes/no via parse-atom — parse-expr would collapse
`"Yes" or "No"` into `(or "Yes" "No")` before match-kw "or"
could fire. The no-`with` form emits `(answer-alert MSG)`.
- compiler.sx: three new cond branches (ask, answer, answer-alert)
compile to a let that binds __hs-a, sets `the-result` and `it`,
and returns the value — so `then put it into ...` works.
- runtime.sx: hs-ask / hs-answer / hs-answer-alert call
window.prompt / confirm / alert via host-call + host-global.
- tests/hs-run-filtered.js: test-name-keyed globalThis.{alert,
confirm,prompt}; __currentHsTestName is updated before each
test. Host-set! for innerHTML/textContent now coerces JS
null → "null" (browser behaviour) so `prompt → null` →
`put it into #out` renders literal text "null", which the
fourth test depends on.
Suite hs-upstream-askAnswer: 1/5 -> 5/5.
Smoke 0-195: 166/195 -> 170/195.
Three parser additions so scripts like `(on click (log me) (trigger foo))`
parse into a single feature with both commands in its body:
1. parse-feat: new cond branch for `paren-open` — advance, recurse
parse-feat, consume `paren-close`. Allows a feature like `(on click
...)` to be grouped in parens.
2. parse-cmd: two new cond branches — on `paren-close` return nil (so
cl-collect terminates at an outer group close), and on `paren-open`
advance / recurse / close. Allows single parenthesized commands like
`(log me)`.
3. cl-collect: previously only recursed when the next token was a
recognised command keyword (`cmd-kw?`), so after `(log me)` the
sibling `(trigger foo)` would end the feature body and re-surface as
a top-level feature. Extended the recursion predicate to also fire
when the next token is `paren-open`.
Suite hs-upstream-core/parser: 9/14 -> 10/14.
Smoke 0-195: 165/195 -> 166/195.
SX's (str 1e-7) gives "1e-07" but JS spec is "1e-7" — no padding, no
leading zeros in the exponent (sign stays). We stepped through:
mant "e" expraw → mant "e" (sign (strip-zeros body))
Added four small helpers: js-normalize-num-str, js-split-sign,
js-strip-leading-zeros, js-strip-zeros-loop. All pure string walkers.
Unit 521/522, slice 148/148 unchanged.
String 40 → 42, Number 75 → 76 (+3 total).
Fixes S9.8.1_A9_T1, fromCharCode/S9.7_A3.1_T1..T2 family.
Tests expected Function.prototype.isPrototypeOf(Number/String/…) ===
true because every built-in ctor inherits from Function.prototype.
Our model doesn't link Number.__proto__ anywhere, so the default
Object.isPrototypeOf walked an empty chain and returned false.
Fix: post-definition dict-set! adds an explicit isPrototypeOf override
on js-function-global.prototype that returns (js-function? x) — which
accepts lambdas, functions, components, and __callable__ dicts. Good
enough to satisfy the spec for every case that isn't a bespoke proto
chain.
Unit 521/522, slice 148/148 unchanged.
Wide scoreboard: 156/300 → 159/300 (+3, Number/S15.7.3_A7 and the
three S15.5.3_A2 / S15.6.3_A2 / S15.9.3_A2 twins).