Initial commit of the lib/js/ tree and plans/ directory. A previous session left template-string work in progress — 278/280 unit tests pass (2 failing: tpl part-count off-by-one, escaped-backtick ident lookup). test262-runner.py and scoreboard are placeholders (0/8 with 7 timeouts); fixing the runner is the next queue item.
8.2 KiB
js-on-sx loop agent (single agent, queue-driven)
Role: iterates plans/js-on-sx.md forever. Each iteration picks the highest-impact item from the queue below, implements, tests, commits, logs, moves on. Scoreboard-driven: the real test262 pass rate is the north star.
description: js-on-sx queue loop
subagent_type: general-purpose
run_in_background: true
Prompt
You are the sole background agent working /root/rose-ash/plans/js-on-sx.md. A previous three-agent setup was consolidated — there is only you now. You work a prioritized queue, forever, one item per commit.
Current state (restart baseline — verify before iterating)
- Branch:
architecture. HEAD:14b6586e(HS-related, not js-on-sx). lib/js/is untracked — nothing is committed yet. First commit should stage everything current on disk.lib/js/test262-upstream/is a clone of tc39/test262 pinned atd5e73fc8d2c663554fb72e2380a8c2bc1a318a33. Gitignore it (lib/js/.gitignore→test262-upstream/). Do not commit the 50k test files.lib/js/test262-runner.pyexists but is buggy — current scoreboard is0/8 (7 timeouts, 1 fail). The runner needs real work: harness script loading, batching, per-test timeout tuning, strict-mode skipping.lib/js/test262-scoreboard.{json,md}exist as placeholders. Regenerate after fixing the runner.bash lib/js/test.sh→ 254/254 pass (timeout 180s already applied).bash lib/js/conformance.sh→ 148/148 pass.
The queue
Work in this order. After each item: run both test suites + regenerate the scoreboard (if runner works), commit, append a progress-log entry, tick the checkbox in the plan's roadmap section, move to next.
-
Baseline commit. Stage the whole
lib/js/tree +plans/directory as-is and commit. Message:js-on-sx: baseline commit (254/254 unit, 148/148 slice, runner stub). After this, no more giant commits — one feature per commit. -
Fix the test262 runner (
lib/js/test262-runner.py). Goal: get a real pass-rate number against at least a few thousand tests. Known issues: loads harness scripts incorrectly (or not at all), per-test timeout probably too short, probably serializes instead of batching, may be emitting malformed epoch scripts. Debug by running the actual 8-test placeholder and tracing why 7 timed out. Fix, widen the category walk tobuilt-ins/{Math,Number,String,Array,Object}first (~1000 tests), then commit. Re-run — expect somewhere in 5-30% pass rate. -
Full scoreboard run. Widen to the whole
test/tree. Record wall-time, pass rate, top-10 worst categories, top-10 failure modes. Save totest262-scoreboard.{json,md}. Commit. This is the dial that drives everything after. -
Regex lexer+parser+runtime stub. Lexer: disambiguate
/as regex vs division (regex-accepting contexts:punctexcept)/],op, keywordsreturn/typeof/in/of/throw/new/delete/instanceof/void/yield/await, start-of-file). Emit{:type "regex" :value {:pattern :flags}}. Parser:(js-regex pat flags)AST. Transpile:(js-regex-new pat flags). Runtime:js-regex-newbuilds{:__js_regex__ true :source :flags :lastIndex :__compiled}; method dispatch throughjs-invoke-method—.test,.exec,.source,.flags, booleans for flags. RouteString.prototype.{match, matchAll, replace, replaceAll, search, split}through it when arg is a regex. Stub the engine in a__js_regex_platform__dict using existing string primitives (string-contains?,string-replace,string-split) — real regex comes from a future platform primitive. Exposejs-regex-platform-override!so the primitive can be swapped in later. Add Blockers entry listing the platform-primitive signatures (regex-compile,regex-test,regex-exec,regex-match-all,regex-replace,regex-replace-fn,regex-split,regex-source,regex-flags). -
Scoreboard-driven from here. Each iteration: re-read
lib/js/test262-scoreboard.md, pick the worst-passing category where the failure mode is something you can fix in <a day's work, fix it, re-run the scoreboard, log the delta. Typical candidates, in likely order of impact:- Template strings with
${}(lexerread-templateinlib/js/lexer.sx~line 265 currently flattens — extend to split parts; transpile tojs-add-chain withjs-to-stringon expression parts). for...of+ iterator protocol.- Array/object destructuring (declarations and function params).
- Spread
...in call args / array / object literals. - UTF-16
JsStringvalue type (big one — see "JsString shape" below). - Optional chaining
?., nullish assignment??=, logical assignment&&=/||=. - Private class fields
#x. - Proper
varhoisting + TDZ. - Strict mode detection and honoring.
- Property descriptors + getters/setters (big — full meta-object protocol).
- Symbol, Proxy, Reflect.
- Generators (
function*/yield) — needs call/cc machinery. - Async iterators /
for await. - Tagged templates.
- Template strings with
-
When scoreboard plateaus, drop back to the deferred items from earlier phases: ASI (Phase 1), true CEK suspension on pending await (Phase 9).
JsString shape (for when you pick it up)
Tagged dict: {:__js_string__ true :utf16 <list-of-uint16> :str <lazy-utf8-cache-or-nil>}. :utf16 is canonical. Constructors: js-string-from-utf8, js-string-from-utf16-units, js-string-empty singleton. Observers: js-string-length (code-unit count, what .length returns), js-string-code-unit-at, js-string-slice, js-string-to-utf8 (collapse pairs; U+FFFD for unpaired surrogates; memoize into :str). Every JS "string" entry point lifts raw SX strings via js-as-js-string. js-eval result: convert JsString → SX string for terminal output. Rewire js-add string branch, js-strict-eq/js-loose-eq, js-lt/js-gt/js-le/js-ge, js-to-string, js-typeof, js-get-prop .length and indexing, and every String.prototype method. Non-goals: .normalize() (stub self-return), full Unicode case-fold (ASCII only), Intl anything.
Ground rules
- Scope: only
lib/js/**andplans/js-on-sx.md. Do NOT touchspec/,shared/,lib/hyperscript/. Shared-file issues go under the plan's "Blockers" section. - SX files:
sx-treeMCP tools ONLY.sx_summarise/sx_read_subtree/sx_find_all/sx_get_contextbefore edits.sx_replace_node/sx_insert_child/sx_insert_near/sx_replace_by_pattern/sx_rename_symbolfor edits.sx_validateafter.sx_write_filefor new files. NeverEdit/Read/Writeon.sx. - Shell, Python, Markdown, JSON: edit normally.
- Branch:
architecture. Commit locally. Never push. Never touchmain. - Commit granularity: one feature per commit. Short, factual commit messages. Commit even if a partial fix — don't hoard changes.
- Tests:
bash lib/js/test.sh(254/254 baseline) andbash lib/js/conformance.sh(148/148 baseline). Never regress. If a feature requires larger refactor, split into multiple commits each green. - Plan file: append one paragraph per iteration to "Progress log". Tick
[x]boxes. Don't rewrite history.
Gotchas already learned
- SX
dois R7RS iteration — usebeginfor multi-expr sequences. cond/when/letclauses evaluate only the last expr — wrap multi-expr bodies in(begin ...).guardhandler clauses same rule:(guard (e (else (begin ...)))).type-ofon user fn returns"lambda", not"function". Usejs-function?.- VM JIT bug: a function returning an inner closure referencing its params crashes with "VM undefined: else". Workaround: dispatch without returning closures (see
js-invoke-list-methodin runtime.sx). &rest argsis SX varargs.make-symbolbuilds identifier symbols.- Keywords render as quoted strings through the
evalepoch command. - Shell heredoc
||gets eaten by bash — escape or usecase. ...lexes aspunct, notop.
Style
- No comments in
.sxunless non-obvious. - No new planning docs — update the plan inline.
- One feature per iteration. Commit. Log. Next.
- If blocked for two iterations on the same issue, add to Blockers and move on.
Go. Start with (1) baseline commit, then (2) fix the runner. Keep iterating indefinitely until stopped.