Use Jerboa artifacts without gherkin

ober

336a1d230f8ae0797cc771e7aa04b427f014a3da

diff --git a/.gitignore b/.gitignore
index 1a35b1a..c474387 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,10 +11,14 @@ lib/**/*.wpo
 
 # Build symlinks / local artifacts
 libjsh-ffi.so
+/.jerboa/
 
 # Build outputs (binaries, objects, boot files, generated headers)
 jemacs
 jemacs-qt
+main.so
+main.wpo
+jemacs-all.so
 *.o
 *.boot
 *_boot.h
diff --git a/Dockerfile b/Dockerfile
index 26e093b..f8cecd6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
 # Intermediate Podman image for jemacs-qt static builds.
 # Bakes static Qt6 + QScintilla from source, plus all Chez Scheme
-# dependencies (jerboa, gherkin, jsh, chez-pcre2, chez-scintilla,
+# dependencies (jerboa, jsh, chez-pcre2, chez-scintilla,
 # chez-qt) so that subsequent jemacs-qt builds only compile jemacs
 # itself (~5-10 min instead of ~30 min).
 #
@@ -259,7 +259,6 @@ ENV SCHEME=/opt/chez/bin/scheme
 
 # Copy dependency sources from build contexts
 COPY --from=jerboa-src . /deps/jerboa
-COPY --from=gherkin-src . /deps/gherkin
 COPY --from=jsh-src . /deps/jsh
 COPY --from=pcre2-src . /deps/chez-pcre2
 COPY --from=sci-src . /deps/chez-scintilla
@@ -274,7 +273,7 @@ COPY vendor/qt_chez_shim.c vendor/qt_shim.h /deps/chez-qt/
 # need to compile jemacs-specific modules.
 COPY support/static-precompile-deps.ss /tmp/static-precompile-deps.ss
 RUN /opt/chez/bin/scheme \
-      --libdirs /deps/jerboa/lib:/deps/gherkin:/deps/jsh/src:/deps/chez-pcre2:/deps/chez-scintilla/src:/deps/chez-qt \
+      --libdirs /deps/jerboa/lib:/deps/jsh/src:/deps/jsh/vendor/jerboa-coreutils/lib:/deps/jsh/vendor/jerboa-awk/lib:/deps/jsh/vendor/jerboa-sed/lib:/deps/chez-pcre2:/deps/chez-scintilla/src:/deps/chez-qt \
       --compile-imported-libraries \
       --script /tmp/static-precompile-deps.ss
 
diff --git a/Makefile b/Makefile
index cc3f782..5993e33 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,19 @@
 VENDOR    ?= $(CURDIR)/vendor
 
+JERBOA_VERSION ?= v0.2.0
+JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin
+JERBUILD_BIN ?= $(shell if [ -x ./jerbuild ] && [ -x ./jerboa ]; then \
+	printf '%s\n' "$(CURDIR)/jerbuild"; \
+elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \
+	printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; \
+elif [ -x ../jerboa/dist/jerbuild ] && [ -x ../jerboa/dist/jerboa ]; then \
+	printf '%s\n' "$(abspath ../jerboa/dist/jerbuild)"; \
+elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then \
+	command -v jerbuild; \
+else \
+	printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; \
+fi)
+
 JERBOA_REPO        ?= https://git.sr.ht/~lisp/jerboa
 JERBOA_SHELL_REPO  ?= https://git.sr.ht/~lisp/jerboa-shell
 JERBOA_PCRE2_REPO    ?= https://git.sr.ht/~lisp/jerboa-pcre2
@@ -43,7 +57,6 @@ JERBOA_SSH  ?= $(JSH_VENDOR)/jerboa-ssh/src
 JERBOA_SQLITE ?= $(JSH_VENDOR)/jerboa-sqlite/src
 JERBOA_CRYPTO ?= $(JSH_VENDOR)/jerboa-crypto/src
 JERBOA_FUSE ?= $(JSH_VENDOR)/jerboa-fuse/lib
-GHERKIN   ?= $(VENDOR)/gherkin-runtime
 JAWS_ROOT ?= $(VENDOR)/jerboa-aws
 JAWS      ?= $(JAWS_ROOT)/lib
 JERBOA_SSL_ROOT ?= $(VENDOR)/jerboa-ssl
@@ -52,13 +65,15 @@ JERBOA_HTTPS_ROOT ?= $(VENDOR)/jerboa-https
 CHTTPS    ?= $(JERBOA_HTTPS_ROOT)/src
 CQT       ?= $(VENDOR)/jerboa-qt
 JERBOA_PCRE2_ROOT ?= $(VENDOR)/jerboa-pcre2
+JERBOA_PCRE2_LIBDIR ?= $(JERBOA_PCRE2_ROOT)/lib
 JERBOA_SCINTILLA_ROOT ?= $(VENDOR)/jerboa-scintilla
+JERBOA_SCINTILLA_LIBDIR ?= $(JERBOA_SCINTILLA_ROOT)/lib
 AWS_LIBDIRS :=
 ifeq ($(JEMACS_ENABLE_AWS),1)
   AWS_LIBDIRS := :$(JAWS):$(CSSL):$(CHTTPS)
 endif
-LIBDIRS   = --libdirs lib:$(JERBOA)/lib:$(JSH):$(COREUTILS):$(JSH_AWK):$(JSH_SED):$(JERBOA_SSH):$(JERBOA_SQLITE):$(JERBOA_CRYPTO):$(JERBOA_FUSE):$(GHERKIN):$(JERBOA_PCRE2_ROOT):$(JERBOA_SCINTILLA_ROOT)/src:$(CQT)$(AWS_LIBDIRS)
-JERBUILD  = $(SCHEME) --libdirs $(JERBOA)/lib --script $(JERBOA)/jerbuild.ss
+LIBDIRS   = --libdirs lib:$(JERBOA)/lib:$(JSH):$(COREUTILS):$(JSH_AWK):$(JSH_SED):$(JERBOA_SSH):$(JERBOA_SQLITE):$(JERBOA_CRYPTO):$(JERBOA_FUSE):$(JERBOA_PCRE2_LIBDIR):$(JERBOA_SCINTILLA_LIBDIR):$(CQT)$(AWS_LIBDIRS)
+JERBUILD  ?= $(JERBUILD_BIN) transpile
 
 # --- Platform detection -------------------------------------------------------
 UNAME_S := $(shell uname -s)
@@ -89,17 +104,22 @@ else
 endif
 NATIVE_LIB := $(JERBOA)/lib/libjerboa_native.$(SHLIB_EXT)
 SSL_SHIM := $(JERBOA_SSL_ROOT)/jerboa_ssl_shim.$(SHLIB_EXT)
+SCINTILLA_SHIM := ./jerboa_scintilla_shim.$(SHLIB_EXT)
 PRELOAD_ENV := $(PRELOAD_VAR)=./libqt_shim.$(SHLIB_EXT):./jerboa_qt_shim.$(SHLIB_EXT):$(NATIVE_LIB):./libjsh-ffi.$(SHLIB_EXT):./libjsh_coreutils_stub.$(SHLIB_EXT):$(SSL_SHIM)
+CORE_PRELOAD_ENV := $(PRELOAD_VAR)=$(NATIVE_LIB):$(SCINTILLA_SHIM):./libjsh-ffi.$(SHLIB_EXT):./libjsh_coreutils_stub.$(SHLIB_EXT):$(SSL_SHIM)
 # -----------------------------------------------------------------------------
 
-export JERBOA_SCINTILLA_LIB := $(JERBOA_SCINTILLA_ROOT)
+export JERBOA_SCINTILLA_LIB := $(CURDIR)
 export JERBOA_PCRE2_LIB := $(JERBOA_PCRE2_ROOT)
 export JSH_FFI_LIB := $(JSH_ROOT)
 export JERBOA_SSL_DIR := $(JERBOA_SSL_ROOT)
 export JERBOA_QT_LIB := .
 export JERBOA_QT_SHIM_DIR := .
 ifeq ($(UNAME_S),Darwin)
-  export CHEZ_DIR := $(shell ls -d /opt/homebrew/lib/csv*/tarm64osx 2>/dev/null | head -1)
+  HOMEBREW_CHEZ_DIR := $(shell ls -d /opt/homebrew/lib/csv*/tarm64osx 2>/dev/null | head -1)
+  ifneq ($(HOMEBREW_CHEZ_DIR),)
+    export CHEZ_DIR ?= $(HOMEBREW_CHEZ_DIR)
+  endif
 endif
 SCHEME_PREFIX = $(abspath $(dir $(SCHEME))/..)
 SCHEME_MT = $(shell if [ -x "$(SCHEME)" ]; then printf '(display (machine-type))\n' | "$(SCHEME)" -q 2>/dev/null; fi)
@@ -107,7 +127,7 @@ SCHEME_CHEZ_DIR = $(shell if [ -n "$(SCHEME_MT)" ]; then ls -d "$(SCHEME_PREFIX)
 CHEZ_DIR ?= $(SCHEME_CHEZ_DIR)
 export CHEZ_DIR
 
-.PHONY: all vendor-deps deps build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \
+.PHONY: all vendor-deps deps ensure-jerboa-tools scintilla-vendor-deps scintilla-vendor-clean build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \
         test-org-duration test-org-element test-org-fold test-org-footnote \
         test-org-lint test-org-num test-org-property test-org-src test-org-tempo \
         test-vtscreen test-debug-repl test-qt test-qt-e2e build-qt binary-qt \
@@ -127,6 +147,8 @@ export CHEZ_DIR
 all:
 	@echo "Available targets:"
 	@echo "  vendor-deps    Clone required chez-* and jerboa-* deps into vendor/"
+	@echo "  ensure-jerboa-tools Ensure jerboa/jerbuild are available"
+	@echo "  scintilla-vendor-deps Fetch/build Scintilla, Lexilla, and termbox archives"
 	@echo "  build          Translate src/*.ss → lib/*.sls (incremental)"
 	@echo "  rebuild        Force full retranslation"
 	@echo "  run            Build and run TUI editor"
@@ -183,14 +205,33 @@ vendor-deps:
 deps: vendor-deps
 	$(MAKE) -C $(JSH_ROOT) vendor-deps
 	$(MAKE) -C $(JERBOA) build
-	$(MAKE) -C $(JERBOA_PCRE2_ROOT) SCHEME=$(SCHEME)
+	$(MAKE) -C $(JERBOA_PCRE2_ROOT) SCHEME=$(SCHEME) JERBOA_HOME=$(JERBOA) JERBUILD=$(JERBUILD_BIN)
+	$(MAKE) -C $(JERBOA_SCINTILLA_ROOT) JERBOA_HOME=$(JERBOA) JERBUILD=$(JERBUILD_BIN) transpile
+
+ensure-jerboa-tools:
+	@if [ -x ./jerbuild ] && [ -x ./jerboa ]; then \
+	  echo "=== Using project-local ./jerbuild ==="; \
+	elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \
+	  echo "=== Using downloaded Jerboa toolchain: $(JERBOA_TOOL_DIR) ==="; \
+	elif [ -x ../jerboa/dist/jerbuild ] && [ -x ../jerboa/dist/jerboa ]; then \
+	  echo "=== Using sibling Jerboa build: ../jerboa/dist/jerbuild ==="; \
+	elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then \
+	  echo "=== Using Jerboa toolchain from PATH: $$(command -v jerbuild) ==="; \
+	else \
+	  echo "=== Fetching Jerboa $(JERBOA_VERSION) release tools into $(JERBOA_TOOL_DIR) ==="; \
+	  sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \
+	fi
+	@"$(JERBUILD_BIN)" --jerboa-home >/dev/null || { \
+	  echo "ERROR: Jerboa toolchain is unavailable; set JERBUILD_BIN=/path/to/jerbuild or JERBOA_VERSION=<tag>"; \
+	  exit 1; \
+	}
 
 # Generate lib/jerboa-emacs/*.sls from src/jerboa-emacs/*.ss (incremental)
-build: deps
+build: ensure-jerboa-tools deps
 	$(JERBUILD) src/ lib/
 
 # Force regenerate all
-rebuild: deps
+rebuild: ensure-jerboa-tools deps
 	$(JERBUILD) src/ lib/ --force
 
 check-generated:
@@ -293,14 +334,14 @@ jerboa_qt_shim.$(SHLIB_EXT): vendor/jerboa_qt_shim.c vendor/qt_shim.h
 libjsh-ffi.$(SHLIB_EXT): $(JSH_ROOT)/ffi-shim.c
 	gcc $(SHLIB_FLAGS) -O2 -o libjsh-ffi.$(SHLIB_EXT) $(JSH_ROOT)/ffi-shim.c -Wall
 
-libjsh_coreutils_stub.$(SHLIB_EXT): vendor/libjsh_coreutils_stub.a
-	gcc $(SHLIB_FLAGS) -o libjsh_coreutils_stub.$(SHLIB_EXT) -Wl,--whole-archive vendor/libjsh_coreutils_stub.a -Wl,--no-whole-archive
+libjsh_coreutils_stub.$(SHLIB_EXT): support/jsh_coreutils_stubs.c
+	gcc $(SHLIB_FLAGS) -O2 -o libjsh_coreutils_stub.$(SHLIB_EXT) support/jsh_coreutils_stubs.c -Wall
 
 $(NATIVE_LIB):
 	$(MAKE) -C $(JERBOA) native
 
 $(SSL_SHIM): $(JERBOA_SSL_ROOT)/jerboa_ssl_shim.c
-	$(MAKE) -C $(JERBOA_SSL_ROOT)
+	$(MAKE) -C $(JERBOA_SSL_ROOT) JERBOA_HOME=$(JERBOA) SCHEME=$(SCHEME) JERBUILD=$(JERBUILD_BIN)
 
 # macOS native binary — compile all modules + link jemacs-qt binary
 macos: build libqt_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) repl_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT)
@@ -486,8 +527,8 @@ test-qt-e2e:
 test-emacs:
 	$(SCHEME) $(LIBDIRS) --program tests/test-emacs.ss
 
-test-functional: build libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM)
-	$(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --program tests/test-functional.ss
+test-functional: build jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM)
+	$(CORE_PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --program tests/test-functional.ss
 
 test-pty:
 	$(SCHEME) $(LIBDIRS) --script tests/test-pty.ss
@@ -502,10 +543,61 @@ binary-qt: build
 # binaries, must live under the vendored jerboa-scintilla checkout.
 SCI_DIR ?= $(JERBOA_SCINTILLA_ROOT)
 SCINTILLA_VENDOR ?= $(SCI_DIR)/vendor
+SCINTILLA_VER ?= 558
+LEXILLA_VER ?= 546
+SCINTILLA_URL ?= https://scintilla.org/scintilla$(SCINTILLA_VER).tgz
+LEXILLA_URL ?= https://scintilla.org/lexilla$(LEXILLA_VER).tgz
+SCINTILLA_TB_URL ?= https://github.com/masahino/scintilla-termbox
+TERMBOX_URL ?= https://github.com/masahino/termbox_next
+SCI_VENDOR_SCINTILLA_DIR := $(SCINTILLA_VENDOR)/scintilla
+SCI_VENDOR_LEXILLA_DIR := $(SCINTILLA_VENDOR)/lexilla
+SCI_VENDOR_TB_DIR := $(SCI_VENDOR_SCINTILLA_DIR)/termbox
+SCI_VENDOR_TERMBOX_DIR := $(SCI_VENDOR_TB_DIR)/termbox_next
+SCI_VENDOR_SCINTILLA_A := $(SCI_VENDOR_SCINTILLA_DIR)/bin/scintilla.a
+SCI_VENDOR_LEXILLA_A := $(SCI_VENDOR_LEXILLA_DIR)/bin/liblexilla.a
+SCI_VENDOR_TERMBOX_A := $(SCI_VENDOR_TERMBOX_DIR)/bin/termbox.a
+SCI_VENDOR_SCINTILLA_H := $(SCI_VENDOR_SCINTILLA_DIR)/include/Scintilla.h
+SCI_VENDOR_LEXILLA_H := $(SCI_VENDOR_LEXILLA_DIR)/include/Lexilla.h
+SCI_VENDOR_TB_H := $(SCI_VENDOR_TB_DIR)/ScintillaTermbox.h
+SCI_VENDOR_TERMBOX_H := $(SCI_VENDOR_TERMBOX_DIR)/src/termbox.h
+
+ifeq ($(UNAME_S),Darwin)
+  SCINTILLA_ARCHIVES = -Wl,-force_load,$(SCI_VENDOR_SCINTILLA_A) -Wl,-force_load,$(SCI_VENDOR_LEXILLA_A) -Wl,-force_load,$(SCI_VENDOR_TERMBOX_A)
+  SCINTILLA_STDLIBS = -lc++ -lpthread
+else
+  SCINTILLA_ARCHIVES = -Wl,--whole-archive $(SCI_VENDOR_SCINTILLA_A) $(SCI_VENDOR_LEXILLA_A) $(SCI_VENDOR_TERMBOX_A) -Wl,--no-whole-archive
+  SCINTILLA_STDLIBS = -lstdc++ -lpthread
+endif
+
+scintilla-vendor-deps: $(SCI_VENDOR_SCINTILLA_A) $(SCI_VENDOR_LEXILLA_A) $(SCI_VENDOR_TERMBOX_A)
+
+$(SCI_VENDOR_SCINTILLA_H):
+	mkdir -p $(SCINTILLA_VENDOR)
+	curl -fsSL $(SCINTILLA_URL) | tar xzf - -C $(SCINTILLA_VENDOR)
 
-jerboa_scintilla_shim.$(SHLIB_EXT): $(SCI_DIR)/jerboa_scintilla_shim.c
-	@test -d $(SCINTILLA_VENDOR) || \
-	  { echo "ERROR: SCINTILLA_VENDOR='$(SCINTILLA_VENDOR)' not found. Populate it from the jerboa-scintilla dependency or set SCINTILLA_VENDOR."; exit 1; }
+$(SCI_VENDOR_LEXILLA_H):
+	mkdir -p $(SCINTILLA_VENDOR)
+	curl -fsSL $(LEXILLA_URL) | tar xzf - -C $(SCINTILLA_VENDOR)
+
+$(SCI_VENDOR_TB_H): $(SCI_VENDOR_SCINTILLA_H)
+	git clone --depth 1 $(SCINTILLA_TB_URL) $(SCI_VENDOR_TB_DIR)
+
+$(SCI_VENDOR_TERMBOX_H): $(SCI_VENDOR_TB_H)
+	git clone --depth 1 $(TERMBOX_URL) $(SCI_VENDOR_TERMBOX_DIR)
+
+$(SCI_VENDOR_TERMBOX_A): $(SCI_VENDOR_TERMBOX_H)
+	$(MAKE) -C $(SCI_VENDOR_TERMBOX_DIR) FLAGS+="-fPIC"
+
+$(SCI_VENDOR_SCINTILLA_A): $(SCI_VENDOR_SCINTILLA_H) $(SCI_VENDOR_TB_H) $(SCI_VENDOR_TERMBOX_A)
+	$(MAKE) -C $(SCI_VENDOR_TB_DIR) EXTRA_FLAGS="-fPIC"
+
+$(SCI_VENDOR_LEXILLA_A): $(SCI_VENDOR_LEXILLA_H) $(SCI_VENDOR_SCINTILLA_H)
+	$(MAKE) -C $(SCI_VENDOR_LEXILLA_DIR)/src
+
+scintilla-vendor-clean:
+	rm -rf $(SCINTILLA_VENDOR)
+
+jerboa_scintilla_shim.$(SHLIB_EXT): $(SCI_DIR)/jerboa_scintilla_shim.c $(SCI_VENDOR_SCINTILLA_A) $(SCI_VENDOR_LEXILLA_A) $(SCI_VENDOR_TERMBOX_A)
 	gcc $(SHLIB_FLAGS) -O2 \
 	  -I$(SCINTILLA_VENDOR)/scintilla/include \
 	  -I$(SCINTILLA_VENDOR)/scintilla/src \
@@ -514,25 +606,26 @@ jerboa_scintilla_shim.$(SHLIB_EXT): $(SCI_DIR)/jerboa_scintilla_shim.c
 	  -I$(SCINTILLA_VENDOR)/lexilla/include \
 	  -o jerboa_scintilla_shim.$(SHLIB_EXT) \
 	  $(SCI_DIR)/jerboa_scintilla_shim.c \
-	  -Wl,--whole-archive \
-	  $(SCINTILLA_VENDOR)/scintilla/bin/scintilla.a \
-	  $(SCINTILLA_VENDOR)/lexilla/bin/liblexilla.a \
-	  $(SCINTILLA_VENDOR)/scintilla/termbox/termbox_next/bin/termbox.a \
-	  -Wl,--no-whole-archive \
-	  -lstdc++ -lpthread -Wall
+	  $(SCINTILLA_ARCHIVES) \
+	  $(SCINTILLA_STDLIBS) -Wall
 
 # pcre2 shim — copied from jerboa-pcre2 (already built there)
 PCRE2_DIR ?= $(JERBOA_PCRE2_ROOT)
 
-pcre2_shim.$(SHLIB_EXT): $(PCRE2_DIR)/pcre2_shim.$(SHLIB_EXT)
+jerboa_pcre2_shim.$(SHLIB_EXT): $(PCRE2_DIR)/jerboa_pcre2_shim.$(SHLIB_EXT)
+	cp $< $@
+
+pcre2_shim.$(SHLIB_EXT): jerboa_pcre2_shim.$(SHLIB_EXT)
 	cp $< $@
 
 # TUI binary: embeds all Scheme code, links dynamically against system libs.
 # Shims must be in the same directory as the binary (jemacs-main.c sets JERBOA_SCINTILLA_LIB
 # and JERBOA_PCRE2_LIB to dirname(binary) when not already set in environment).
-binary: build vterm_shim.$(SHLIB_EXT) jerboa_scintilla_shim.$(SHLIB_EXT) pcre2_shim.$(SHLIB_EXT)
+binary: build vterm_shim.$(SHLIB_EXT) jerboa_scintilla_shim.$(SHLIB_EXT) jerboa_pcre2_shim.$(SHLIB_EXT)
 	find vendor/jerboa-shell -name '*.wpo' -delete 2>/dev/null; true
-	$(SCHEME) $(LIBDIRS) --script build-binary.ss
+	JERBOA_PCRE2_DIR=$(JERBOA_PCRE2_LIBDIR) JERBOA_PCRE2_ROOT=$(JERBOA_PCRE2_ROOT) \
+	  JERBOA_SCINTILLA_DIR=$(JERBOA_SCINTILLA_LIBDIR) SCI_VENDOR_DIR=$(SCINTILLA_VENDOR) \
+	  $(SCHEME) $(LIBDIRS) --script build-binary.ss
 
 # =============================================================================
 # Static binary builds (Podman-based, Alpine musl)
@@ -548,7 +641,6 @@ PODMAN_RUN_FLAGS ?=
 
 # Dependency source directories (vendored clones)
 JERBOA_SRC   ?= $(JERBOA)
-GHERKIN_SRC  ?= $(CURDIR)/vendor/gherkin-runtime
 JSH_SRC      ?= $(JSH_ROOT)
 JSH_VENDOR_SRC ?= $(JSH_SRC)/vendor
 PCRE2_SRC    ?= $(JERBOA_PCRE2_ROOT)
@@ -596,7 +688,6 @@ podman-deps: check-podman
 	  --build-arg ARCH=$(ARCH) \
 	  --build-arg CHEZ_COMMIT=$(CHEZ_COMMIT) \
 	  --build-context jerboa-src=$(JERBOA_SRC) \
-	  --build-context gherkin-src=$(GHERKIN_SRC) \
 	  --build-context jsh-src=$(JSH_SRC) \
 	  --build-context pcre2-src=$(PCRE2_SRC) \
 	  --build-context sci-src=$(SCI_SRC) \
@@ -721,7 +812,6 @@ build-jemacs-qt-static: check-root
 	CHEZ_DIR=$(CHEZ_MUSL_DIR) \
 	JERBOA_DIR=/deps/jerboa/lib \
 	JSH_DIR=/deps/jsh/src \
-	GHERKIN_DIR=/src/vendor/gherkin-runtime \
 	JERBOA_PCRE2_DIR=/deps/jerboa-pcre2 \
 	JERBOA_SCINTILLA_DIR=/deps/jerboa-scintilla/src \
 	JERBOA_QT_DIR=/deps/jerboa-qt \
@@ -735,7 +825,7 @@ build-jemacs-qt-static: check-root
 	TREE_SITTER_QUERIES_OBJ=/tmp/jemacs-build/treesitter_queries.o \
 	PKG_CONFIG_PATH=/opt/qt6-static/lib/pkgconfig \
 	/opt/chez/bin/scheme \
-	  --libdirs lib:/deps/jerboa/lib:/deps/jsh/src:/deps/jsh/vendor/jerboa-coreutils/lib:/deps/jsh/vendor/jerboa-awk/lib:/deps/jsh/vendor/jerboa-sed/lib:/deps/jsh/vendor/jerboa-ssh/src:/deps/jsh/vendor/jerboa-sqlite/src:/deps/jsh/vendor/jerboa-crypto/src:/deps/jsh/vendor/jerboa-fuse/lib:/src/vendor/gherkin-runtime:/deps/jerboa-pcre2:/deps/jerboa-scintilla/src:/deps/jerboa-qt$(AWS_STATIC_LIBDIRS) \
+	  --libdirs lib:/deps/jerboa/lib:/deps/jsh/src:/deps/jsh/vendor/jerboa-coreutils/lib:/deps/jsh/vendor/jerboa-awk/lib:/deps/jsh/vendor/jerboa-sed/lib:/deps/jsh/vendor/jerboa-ssh/src:/deps/jsh/vendor/jerboa-sqlite/src:/deps/jsh/vendor/jerboa-crypto/src:/deps/jsh/vendor/jerboa-fuse/lib:/deps/jerboa-pcre2:/deps/jerboa-scintilla/src:/deps/jerboa-qt$(AWS_STATIC_LIBDIRS) \
 	  --script build-binary-qt.ss
 
 linux-static-qt-podman: ensure-podman-deps
@@ -850,13 +940,12 @@ build-jemacs-tui-static: check-root
 	CHEZ_DIR=$(CHEZ_MUSL_DIR) \
 	JERBOA_DIR=/deps/jerboa/lib \
 	JSH_DIR=/deps/jsh/src \
-	GHERKIN_DIR=/src/vendor/gherkin-runtime \
 	JERBOA_PCRE2_DIR=/deps/jerboa-pcre2 \
 	JERBOA_SCINTILLA_DIR=/deps/jerboa-scintilla/src \
 	SCI_VENDOR_DIR=/deps/sci-vendor \
 	JSH_COREUTILS_LIB=/deps/jsh/libjsh_coreutils.a \
 	/opt/chez/bin/scheme \
-	  --libdirs lib:/deps/jerboa/lib:/deps/jsh/src:/deps/jsh/vendor/jerboa-coreutils/lib:/deps/jsh/vendor/jerboa-awk/lib:/deps/jsh/vendor/jerboa-sed/lib:/deps/jsh/vendor/jerboa-ssh/src:/deps/jsh/vendor/jerboa-sqlite/src:/deps/jsh/vendor/jerboa-crypto/src:/deps/jsh/vendor/jerboa-fuse/lib:/src/vendor/gherkin-runtime:/deps/jerboa-pcre2:/deps/jerboa-scintilla/src \
+	  --libdirs lib:/deps/jerboa/lib:/deps/jsh/src:/deps/jsh/vendor/jerboa-coreutils/lib:/deps/jsh/vendor/jerboa-awk/lib:/deps/jsh/vendor/jerboa-sed/lib:/deps/jsh/vendor/jerboa-ssh/src:/deps/jsh/vendor/jerboa-sqlite/src:/deps/jsh/vendor/jerboa-crypto/src:/deps/jsh/vendor/jerboa-fuse/lib:/deps/jerboa-pcre2:/deps/jerboa-scintilla/src \
 	  --script build-binary.ss
 
 linux-static-tui-podman: ensure-podman-deps
@@ -929,8 +1018,6 @@ linux-tui-local: vendor-deps
 	find $(MUSL_SCINTILLA) -name '*.so' -delete 2>/dev/null; true
 	find $(MUSL_PCRE2) -name '*.so' -delete 2>/dev/null; true
 	find $(MUSL_JSH_ROOT) -name '*.wpo' -delete 2>/dev/null; true
-	find vendor/gherkin-runtime -name '*.so' -delete 2>/dev/null; true
-	find vendor/gherkin-runtime -name '*.wpo' -delete 2>/dev/null; true
 	find vendor/jerboa-shell -name '*.so' -delete 2>/dev/null; true
 	find vendor/jerboa-shell -name '*.wpo' -delete 2>/dev/null; true
 	@echo "Recompiling jerboa core + prelude..."
@@ -979,12 +1066,11 @@ linux-tui-local: vendor-deps
 	CHEZ_DIR=$(MUSL_CHEZ_DIR) \
 	JERBOA_DIR=$(MUSL_JERBOA)/lib \
 	JSH_DIR=$(MUSL_JSH) \
-	GHERKIN_DIR=$(GHERKIN) \
 	JERBOA_PCRE2_DIR=$(MUSL_PCRE2) \
 	JERBOA_SCINTILLA_DIR=$(MUSL_SCINTILLA)/src \
 	SCI_VENDOR_DIR=$(MUSL_SCI_VENDOR) \
 	$(MUSL_SCHEME) \
-	  --libdirs lib:$(MUSL_JERBOA)/lib:$(MUSL_JSH):$(COREUTILS):$(JSH_AWK):$(JSH_SED):$(JERBOA_SSH):$(JERBOA_SQLITE):$(JERBOA_CRYPTO):$(JERBOA_FUSE):$(GHERKIN):$(MUSL_PCRE2):$(MUSL_SCINTILLA)/src \
+	  --libdirs lib:$(MUSL_JERBOA)/lib:$(MUSL_JSH):$(COREUTILS):$(JSH_AWK):$(JSH_SED):$(JERBOA_SSH):$(JERBOA_SQLITE):$(JERBOA_CRYPTO):$(JERBOA_FUSE):$(MUSL_PCRE2):$(MUSL_SCINTILLA)/src \
 	  --script build-binary.ss
 	@echo ""
 	@echo "=== jemacs static TUI binary built ==="
@@ -1024,8 +1110,6 @@ linux-qt-local: vendor-deps
 	find $(MUSL_SCINTILLA) -name '*.so' -delete 2>/dev/null; true
 	find $(MUSL_PCRE2) -name '*.so' -delete 2>/dev/null; true
 	find $(MUSL_JSH_ROOT) -name '*.wpo' -delete 2>/dev/null; true
-	find vendor/gherkin-runtime -name '*.so' -delete 2>/dev/null; true
-	find vendor/gherkin-runtime -name '*.wpo' -delete 2>/dev/null; true
 	find vendor/jerboa-shell -name '*.so' -delete 2>/dev/null; true
 	find vendor/jerboa-shell -name '*.wpo' -delete 2>/dev/null; true
 	find vendor/jerboa-ssl -name '*.so' -delete 2>/dev/null; true
@@ -1117,7 +1201,6 @@ linux-qt-local: vendor-deps
 	CHEZ_DIR=$(MUSL_CHEZ_DIR) \
 	JERBOA_DIR=$(MUSL_JERBOA)/lib \
 	JSH_DIR=$(MUSL_JSH) \
-	GHERKIN_DIR=$(GHERKIN) \
 	JERBOA_PCRE2_DIR=$(MUSL_PCRE2) \
 	JERBOA_SCINTILLA_DIR=$(MUSL_SCINTILLA)/src \
 	JERBOA_QT_DIR=/tmp/jemacs-build/jerboa-qt \
@@ -1131,7 +1214,7 @@ linux-qt-local: vendor-deps
 	TREE_SITTER_QUERIES_OBJ=/tmp/jemacs-build/treesitter_queries.o \
 	PKG_CONFIG_PATH=/opt/qt6-static/lib/pkgconfig \
 	$(MUSL_SCHEME) \
-	  --libdirs lib:$(MUSL_JERBOA)/lib:$(MUSL_JSH):$(COREUTILS):$(JSH_AWK):$(JSH_SED):$(JERBOA_SSH):$(JERBOA_SQLITE):$(JERBOA_CRYPTO):$(JERBOA_FUSE):$(GHERKIN):$(MUSL_PCRE2):$(MUSL_SCINTILLA)/src:/tmp/jemacs-build/jerboa-qt$(AWS_LOCAL_STATIC_LIBDIRS) \
+	  --libdirs lib:$(MUSL_JERBOA)/lib:$(MUSL_JSH):$(COREUTILS):$(JSH_AWK):$(JSH_SED):$(JERBOA_SSH):$(JERBOA_SQLITE):$(JERBOA_CRYPTO):$(JERBOA_FUSE):$(MUSL_PCRE2):$(MUSL_SCINTILLA)/src:/tmp/jemacs-build/jerboa-qt$(AWS_LOCAL_STATIC_LIBDIRS) \
 	  --script build-binary-qt.ss
 	@echo ""
 	@echo "=== jemacs-qt static binary built ==="
diff --git a/README.md b/README.md
index 1a707f5..37072f2 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,6 @@ The TUI backend runs in a terminal using Scintilla's text model. The Qt backend 
 | Chez Scheme ≥ 9.6 | Scheme runtime | system |
 | jerboa | Gerbil-compat stdlib | `vendor/jerboa` |
 | jerboa-shell (jsh) | Shell interpreter | `vendor/jerboa-shell` |
-| gherkin runtime | Scheme dialect runtime | `vendor/gherkin-runtime` |
 | jerboa-scintilla | Scintilla editor FFI | `vendor/jerboa-scintilla` |
 | jerboa-pcre2 | PCRE2 regex FFI | `vendor/jerboa-pcre2` |
 
@@ -37,6 +36,13 @@ The TUI backend runs in a terminal using Scintilla's text model. The Qt backend 
 
 All commands run from the project root.
 
+### Jerboa build tools
+
+`make build` and `make binary` use project-local `./jerbuild` first, then
+`.jerboa/bin`, then `../jerboa/dist`, then `PATH`, and download the matching
+Jerboa release artifact if none are available. The editor still vendors and
+builds its native Scheme/FFI dependencies under `vendor/`.
+
 ### Vendor dependencies
 
 ```bash
@@ -193,7 +199,6 @@ The Gerbil form `(with-output-to-string "" thunk)` is not valid in Chez.
 | `SCHEME` | `vendor/jerboa/.chez/bin/scheme` | Chez Scheme executable |
 | `JERBOA` | `vendor/jerboa` | jerboa checkout |
 | `JSH` | `vendor/jerboa-shell/src` | jsh source path |
-| `GHERKIN` | `vendor/gherkin-runtime` | gherkin runtime path |
 
 Override on the command line: `make SCHEME=/usr/local/bin/chez build`
 
diff --git a/build-binary-qt.ss b/build-binary-qt.ss
index 94db182..612c353 100644
--- a/build-binary-qt.ss
+++ b/build-binary-qt.ss
@@ -102,12 +102,10 @@
 (define jerboa-dir
   (or (getenv "JERBOA_DIR")
       (repo-path "vendor/jerboa/lib")))
-(define gherkin-dir
-  (or (getenv "GHERKIN_DIR")
-      (repo-path "vendor/gherkin-runtime")))
 (define jsh-dir
   (or (getenv "JSH_DIR")
       (repo-path "vendor/jerboa-shell/src")))
+(define jsh-root (path-parent jsh-dir))
 (define pcre2-dir
   (or (getenv "JERBOA_PCRE2_DIR")
       (repo-path "vendor/jerboa-pcre2")))
@@ -166,7 +164,6 @@
 
 (printf "Chez dir:      ~a~n" chez-dir)
 (printf "Jerboa dir:    ~a~n" jerboa-dir)
-(printf "Gherkin dir:   ~a~n" gherkin-dir)
 (printf "jsh dir:       ~a~n" jsh-dir)
 (printf "pcre2 dir:     ~a~n" pcre2-dir)
 (printf "Sci dir:       ~a~n" sci-dir)
@@ -230,12 +227,15 @@
                 (format "~a/jerboa/~a.so" jerboa-dir (symbol-path tail))))
            ((jsh)
             (format "~a/jsh/~a.so" jsh-dir (symbol-path tail)))
+           ((jerboa-awk)
+            (format "~a/vendor/jerboa-awk/lib/jerboa-awk/~a.so" jsh-root (symbol-path tail)))
+           ((sed)
+            (format "~a/vendor/jerboa-sed/lib/sed/~a.so" jsh-root (symbol-path tail)))
            ((compat)
             (if (and (pair? tail) (eq? (car tail) 'gambit))
                 (format "~a/compat/gambit.so" jsh-dir)
-                (format "~a/compat/~a.so" gherkin-dir (symbol-path tail))))
-           ((runtime)
-            (format "~a/runtime/~a.so" gherkin-dir (symbol-path tail)))
+                #f))
+           ((runtime) #f)
            ((jerboa-pcre2)
             (format "~a/jerboa-pcre2/~a.so" pcre2-dir (symbol-path tail)))
            ((jerboa-scintilla)
diff --git a/build-binary.ss b/build-binary.ss
index e88f44e..63088b2 100644
--- a/build-binary.ss
+++ b/build-binary.ss
@@ -82,24 +82,52 @@
 (define jerboa-dir
   (or (getenv "JERBOA_DIR")
       (repo-path "vendor/jerboa/lib")))
-(define gherkin-dir
-  (or (getenv "GHERKIN_DIR")
-      (repo-path "vendor/gherkin-runtime")))
 (define jsh-dir
   (or (getenv "JSH_DIR")
       (repo-path "vendor/jerboa-shell/src")))
+(define jsh-root (path-parent jsh-dir))
 (define pcre2-dir
   (or (getenv "JERBOA_PCRE2_DIR")
+      (repo-path "vendor/jerboa-pcre2/lib")))
+(define pcre2-root
+  (or (getenv "JERBOA_PCRE2_ROOT")
       (repo-path "vendor/jerboa-pcre2")))
 (define sci-dir
   (or (getenv "JERBOA_SCINTILLA_DIR")
-      (repo-path "vendor/jerboa-scintilla/src")))
+      (repo-path "vendor/jerboa-scintilla/lib")))
 ;; Scintilla vendor dir: contains scintilla.a, liblexilla.a, termbox.a and the
 ;; C header files needed to compile jerboa_scintilla_shim.c.
 (define sci-vendor-dir
   (or (getenv "SCI_VENDOR_DIR")
       (repo-path "vendor/jerboa-scintilla/vendor")))
 
+(define (first-existing-file paths)
+  (let loop ((rest paths))
+    (cond
+      ((null? rest) #f)
+      ((file-exists? (car rest)) (car rest))
+      (else (loop (cdr rest))))))
+
+(define (require-existing-file label paths)
+  (or (first-existing-file paths)
+      (begin
+        (printf "Error: ~a not found. Tried:~n" label)
+        (for-each (lambda (path) (printf "  ~a~n" path)) paths)
+        (exit 1))))
+
+(define libcoreutils-c
+  (require-existing-file
+    "libcoreutils.c"
+    (list (format "~a/libcoreutils.c" jsh-root)
+          (format "~a/vendor/jerboa-coreutils/support/libcoreutils.c" jsh-root)
+          (format "~a/patches/libcoreutils.c" jsh-root))))
+
+(define machine-name (symbol->string (machine-type)))
+(define darwin?
+  (let ((n (string-length machine-name)))
+    (and (>= n 3)
+         (string=? (substring machine-name (- n 3) n) "osx"))))
+
 ;; Static build detection
 (define jemacs-static?
   (let ((v (getenv "JEMACS_STATIC")))
@@ -122,9 +150,9 @@
 
 (printf "Chez dir:      ~a~n" chez-dir)
 (printf "Jerboa dir:    ~a~n" jerboa-dir)
-(printf "Gherkin dir:   ~a~n" gherkin-dir)
 (printf "jsh dir:       ~a~n" jsh-dir)
 (printf "pcre2 dir:     ~a~n" pcre2-dir)
+(printf "pcre2 root:    ~a~n" pcre2-root)
 (printf "Sci dir:       ~a~n" sci-dir)
 (when jemacs-static?
   (printf "Sci vendor:    ~a~n" sci-vendor-dir)
@@ -179,12 +207,15 @@
                 (format "~a/jerboa/~a.so" jerboa-dir (symbol-path tail))))
            ((jsh)
             (format "~a/jsh/~a.so" jsh-dir (symbol-path tail)))
+           ((jerboa-awk)
+            (format "~a/vendor/jerboa-awk/lib/jerboa-awk/~a.so" jsh-root (symbol-path tail)))
+           ((sed)
+            (format "~a/vendor/jerboa-sed/lib/sed/~a.so" jsh-root (symbol-path tail)))
            ((compat)
             (if (and (pair? tail) (eq? (car tail) 'gambit))
                 (format "~a/compat/gambit.so" jsh-dir)
-                (format "~a/compat/~a.so" gherkin-dir (symbol-path tail))))
-           ((runtime)
-            (format "~a/runtime/~a.so" gherkin-dir (symbol-path tail)))
+                #f))
+           ((runtime) #f)
            ((jerboa-pcre2)
             (format "~a/jerboa-pcre2/~a.so" pcre2-dir (symbol-path tail)))
            ((jerboa-scintilla)
@@ -235,8 +266,8 @@
 
 ;; pcre2 shim
 (let* ((pcre2-cflags (shell-output "pkg-config --cflags libpcre2-8" "-I/usr/include"))
-       (cmd (format "gcc -c -O2 -o jemacs-pcre2-shim.o ~a/pcre2_shim.c ~a -Wall 2>&1"
-                    pcre2-dir pcre2-cflags)))
+       (cmd (format "gcc -c -O2 -o jemacs-pcre2-shim.o ~a/jerboa_pcre2_shim.c ~a -Wall 2>&1"
+                    pcre2-root pcre2-cflags)))
   (unless (= 0 (system cmd))
     (display "Error: pcre2 shim compilation failed\n")
     (exit 1)))
@@ -244,17 +275,15 @@
 ;; Static-only C sources
 (when jemacs-static?
   ;; jsh FFI shim
-  (let* ((jsh-root (path-parent jsh-dir))
-         (cmd (format "gcc -c -O2 -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
+  (let* ((cmd (format "gcc -c -O2 -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
                       jsh-root)))
     (unless (= 0 (system cmd))
       (display "Error: jsh ffi-shim.c compilation failed\n")
       (exit 1)))
 
   ;; libcoreutils.c (terminal/raw-mode symbols)
-  (let* ((jsh-root (path-parent jsh-dir))
-         (cmd (format "gcc -c -O2 -o jemacs-libcoreutils.o ~a/libcoreutils.c -Wall 2>&1"
-                      jsh-root)))
+  (let* ((cmd (format "gcc -c -O2 -o jemacs-libcoreutils.o ~a -Wall 2>&1"
+                      libcoreutils-c)))
     (unless (= 0 (system cmd))
       (display "Error: libcoreutils.c compilation failed\n")
       (exit 1)))
@@ -268,8 +297,7 @@
   ;; embed-crypto. Older jsh trees had embed-crypto.c; newer ones use
   ;; libjerboa_native. If neither is available, compile explicit failing stubs
   ;; so non-embed editor features still link and encrypted embed calls fail.
-  (let* ((jsh-root (path-parent jsh-dir))
-         (embed-src (format "~a/embed-crypto.c" jsh-root))
+  (let* ((embed-src (format "~a/embed-crypto.c" jsh-root))
          (fallback-src "support/jsh_embed_crypto_stubs.c")
          (src (if (file-exists? embed-src) embed-src fallback-src))
          (cmd (format "gcc -c -O2 -o jemacs-embed-crypto.o ~a -I~a -Wall 2>&1"
@@ -331,7 +359,7 @@
 
   ;; Generate FFI symbol registration table
   ;; Scans Scheme sources for foreign-procedure calls, generates tui_static_symbols.c
-  (let* ((ffi-path (format "~a/jerboa-pcre2/ffi.ss" pcre2-dir))
+  (let* ((ffi-path (format "~a/src/jerboa-pcre2/ffi.ss" pcre2-root))
          (include-dir chez-dir)
          (gen-cmd
            (format
@@ -389,23 +417,23 @@ echo OK"
       (display "Error: repl_shim.c (dynamic) compilation failed\n")
       (exit 1)))
   ;; jsh FFI shim (ffi_file_type, ffi_*, etc.)
-  (let* ((jsh-root (path-parent jsh-dir))
-         (cmd (format "gcc -c -O2 -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
+  (let* ((cmd (format "gcc -c -O2 -o jemacs-jsh-ffi.o ~a/ffi-shim.c -Wall 2>&1"
                       jsh-root)))
     (unless (= 0 (system cmd))
       (display "Error: jsh ffi-shim.c (dynamic) compilation failed\n")
       (exit 1)))
   ;; libcoreutils (coreutils_raw_mode_*, coreutils_terminal_*, etc.)
-  (let* ((jsh-root (path-parent jsh-dir))
-         (cmd (format "gcc -c -O2 -o jemacs-libcoreutils.o ~a/libcoreutils.c -Wall 2>&1"
-                      jsh-root)))
+  (let* ((cmd (format "gcc -c -O2 -o jemacs-libcoreutils.o ~a -Wall 2>&1"
+                      libcoreutils-c)))
     (unless (= 0 (system cmd))
       (display "Error: libcoreutils.c (dynamic) compilation failed\n")
       (exit 1)))
   ;; embed-crypto (embed_* symbols)
-  (let* ((jsh-root (path-parent jsh-dir))
-         (cmd (format "gcc -c -O2 -o jemacs-embed-crypto.o ~a/embed-crypto.c -I~a -Wall 2>&1"
-                      jsh-root jsh-root)))
+  (let* ((embed-src (format "~a/embed-crypto.c" jsh-root))
+         (fallback-src "support/jsh_embed_crypto_stubs.c")
+         (src (if (file-exists? embed-src) embed-src fallback-src))
+         (cmd (format "gcc -c -O2 -o jemacs-embed-crypto.o ~a -I~a -Wall 2>&1"
+                      src jsh-root)))
     (unless (= 0 (system cmd))
       (display "Error: embed-crypto.c (dynamic) compilation failed\n")
       (exit 1)))
@@ -496,6 +524,7 @@ tui_static_symbols.o \
   ;; Chez's foreign-procedure finds them via dlopen(NULL)/dlopen("").
   (let* ((pcre2-libs   (shell-output "pkg-config --libs libpcre2-8" "-lpcre2-8"))
          (ncurses-libs (shell-output "pkg-config --libs ncurses" "-lncurses"))
+         (iconv-libs   (if darwin? "-liconv" ""))
          (cmd (format "gcc -rdynamic -o jemacs \
 jemacs-main.o jemacs-pcre2-shim.o jemacs-repl-shim.o \
 jemacs-jsh-ffi.o jemacs-libcoreutils.o jemacs-embed-crypto.o \
@@ -503,8 +532,8 @@ jemacs-ssh-agent-stub.o jemacs-jsh-coreutils-stubs.o \
 jemacs-tls-stubs.o \
 -L~a -lkernel -llz4 -lz \
 ~a ~a \
--lm -ldl -lpthread 2>&1"
-                      chez-dir pcre2-libs ncurses-libs)))
+-lm -ldl -lpthread ~a 2>&1"
+                      chez-dir pcre2-libs ncurses-libs iconv-libs)))
     (printf "  ~a~n" cmd)
     (unless (= 0 (system cmd))
       (display "Error: Dynamic link failed\n")
diff --git a/jemacs-main.c b/jemacs-main.c
index 9708d96..3d5e314 100644
--- a/jemacs-main.c
+++ b/jemacs-main.c
@@ -30,23 +30,70 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
 #include <unistd.h>
 #include <libgen.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif
 #include "scheme.h"
 #include "jemacs_program.h"      /* jemacs_program_data[], jemacs_program_size */
 #include "jemacs_petite_boot.h"  /* petite_boot_data[], petite_boot_size */
 #include "jemacs_scheme_boot.h"  /* scheme_boot_data[], scheme_boot_size */
 #include "jemacs_jemacs_boot.h"  /* jemacs_boot_data[], jemacs_boot_size */
 
+static int write_all(int fd, const unsigned char *data, unsigned int size) {
+    const unsigned char *cursor = data;
+    size_t remaining = size;
+
+    while (remaining > 0) {
+        ssize_t written = write(fd, cursor, remaining);
+        if (written < 0) {
+            if (errno == EINTR)
+                continue;
+            return -1;
+        }
+        if (written == 0)
+            return -1;
+        cursor += written;
+        remaining -= (size_t)written;
+    }
+
+    return 0;
+}
+
 int main(int argc, char *argv[]) {
     /* Resolve real executable path */
     char exe_buf[4096];
     char exe_dir[4096];
+#ifdef __APPLE__
+    uint32_t exe_len = sizeof(exe_buf);
+    int have_exe = (_NSGetExecutablePath(exe_buf, &exe_len) == 0);
+    if (have_exe) {
+        char real_buf[4096];
+        char *resolved = realpath(exe_buf, real_buf);
+        if (resolved) {
+            strncpy(exe_buf, resolved, sizeof(exe_buf) - 1);
+            exe_buf[sizeof(exe_buf) - 1] = '\0';
+        }
+    }
+#elif defined(__FreeBSD__)
+    ssize_t len = readlink("/proc/curproc/file", exe_buf, sizeof(exe_buf) - 1);
+    int have_exe = (len > 0);
+    if (have_exe) {
+        exe_buf[len] = '\0';
+    }
+#else
     ssize_t len = readlink("/proc/self/exe", exe_buf, sizeof(exe_buf) - 1);
-    if (len > 0) {
+    int have_exe = (len > 0);
+    if (have_exe) {
         exe_buf[len] = '\0';
+    }
+#endif
+    if (have_exe) {
         setenv("JEMACS_EXE", exe_buf, 1);
         /* dirname() may modify its argument, use a copy */
         strncpy(exe_dir, exe_buf, sizeof(exe_dir) - 1);
@@ -59,19 +106,36 @@ int main(int argc, char *argv[]) {
             setenv("JERBOA_PCRE2_LIB", dir, 0);
     }
 
-    /* Create memfd for embedded program .so */
+    /* Create a file for the embedded program .so. Linux can avoid touching
+     * disk via memfd; other hosts use a private temporary file. */
+    char prog_path[4096];
+#ifdef __linux__
     int fd = memfd_create("jemacs-program", MFD_CLOEXEC);
     if (fd < 0) {
         perror("memfd_create");
         return 1;
     }
-    if (write(fd, jemacs_program_data, jemacs_program_size) != (ssize_t)jemacs_program_size) {
-        perror("write memfd");
+#else
+    const char *tmpdir = getenv("TMPDIR");
+    if (!tmpdir || !tmpdir[0]) tmpdir = "/tmp";
+    snprintf(prog_path, sizeof(prog_path), "%s/jemacs-program-XXXXXX", tmpdir);
+    int fd = mkstemp(prog_path);
+    if (fd < 0) {
+        perror("mkstemp");
+        return 1;
+    }
+#endif
+    if (write_all(fd, jemacs_program_data, jemacs_program_size) != 0) {
+        perror("write embedded program");
         close(fd);
+#ifndef __linux__
+        unlink(prog_path);
+#endif
         return 1;
     }
-    char prog_path[64];
+#ifdef __linux__
     snprintf(prog_path, sizeof(prog_path), "/proc/self/fd/%d", fd);
+#endif
 
     /* Initialize Chez Scheme */
     Sscheme_init(NULL);
@@ -106,6 +170,9 @@ int main(int argc, char *argv[]) {
     int status = Sscheme_script(prog_path, argc, (const char **)argv);
 
     close(fd);
+#ifndef __linux__
+    unlink(prog_path);
+#endif
     Sscheme_deinit();
     return status;
 }
diff --git a/lib/jerboa-emacs/qt/commands-parity5.sls b/lib/jerboa-emacs/qt/commands-parity5.sls
index e496750..2be3f3c 100644
--- a/lib/jerboa-emacs/qt/commands-parity5.sls
+++ b/lib/jerboa-emacs/qt/commands-parity5.sls
@@ -637,8 +637,7 @@
          (echo-message! (app-state-echo app) "Frame raised")))
   (def (cmd-rotate-window app)
        (execute-command! app 'other-window))
-  (def (cmd-run-scheme app)
-       (execute-command! app 'run-chez))
+  (def (cmd-run-scheme app) (execute-command! app 'run-chez))
   (def (cmd-shell-here app) (execute-command! app 'shell))
   (def (cmd-whitespace-report app)
        (let* ([fr (app-state-frame app)]
diff --git a/lib/jerboa-emacs/qt/commands-shell.sls b/lib/jerboa-emacs/qt/commands-shell.sls
index 4fe510b..f6d38cb 100644
--- a/lib/jerboa-emacs/qt/commands-shell.sls
+++ b/lib/jerboa-emacs/qt/commands-shell.sls
@@ -2039,9 +2039,7 @@
                                                            #\#))]
                                                    [(memq
                                                       lang
-                                                      '(scheme
-                                                         lisp
-                                                        ))
+                                                      '(scheme lisp))
                                                     (or (string-prefix?
                                                           "(def "
                                                           trimmed)
diff --git a/src/jerboa-emacs/core.ss b/src/jerboa-emacs/core.ss
index 68c8752..cc5c16d 100644
--- a/src/jerboa-emacs/core.ss
+++ b/src/jerboa-emacs/core.ss
@@ -1991,9 +1991,6 @@
       (lambda ()
         (jemacs-log! "ensure-eval!: Chez Scheme eval environment ready")))))
 
-;; Backward compatibility alias
-(def ensure-eval! ensure-eval!)
-
 (def (eval-expression-string str)
   "In-process eval: read+eval an expression string, capture output.
    Returns (values result-string error?).
diff --git a/support/ensure-jerboa.sh b/support/ensure-jerboa.sh
new file mode 100755
index 0000000..e96aed7
--- /dev/null
+++ b/support/ensure-jerboa.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+# Bootstrap a project-local Jerboa toolchain from SourceHut release artifacts.
+#
+# Usage:
+#   support/ensure-jerboa.sh v0.2.0 .jerboa/bin
+#
+# Override the artifact location with:
+#   JERBOA_RELEASE_BASE=https://example.org/releases/v0.2.0
+# or the SourceHut repo with:
+#   JERBOA_RELEASE_REPO=~lisp/jerboa
+# For testing or unusual hosts, override target detection with:
+#   JERBOA_RELEASE_TARGET=macos-arm64
+
+set -eu
+
+usage() {
+    echo "Usage: $0 VERSION [BINDIR]" >&2
+}
+
+[ "${1:-}" ] || { usage; exit 2; }
+
+version=$1
+bindir=${2:-.jerboa/bin}
+repo=${JERBOA_RELEASE_REPO:-~lisp/jerboa}
+origin=${JERBOA_RELEASE_ORIGIN:-https://git.sr.ht}
+
+if [ "${JERBOA_RELEASE_TARGET:-}" ]; then
+    target=$JERBOA_RELEASE_TARGET
+else
+    os=$(uname -s)
+    arch=$(uname -m)
+    case "$os-$arch" in
+        Darwin-arm64) target=macos-arm64 ;;
+        Linux-x86_64|Linux-amd64) target=linux-amd64 ;;
+        Linux-aarch64|Linux-arm64) target=linux-arm64 ;;
+        FreeBSD-amd64|FreeBSD-x86_64) target=freebsd-amd64 ;;
+        *)
+            echo "ERROR: unsupported platform for Jerboa release artifacts: $os $arch" >&2
+            echo "       set JERBUILD=/path/to/jerbuild or build Jerboa locally for this host" >&2
+            exit 1
+            ;;
+    esac
+fi
+
+case "$target" in
+    macos-arm64|linux-amd64|linux-arm64|freebsd-amd64) ;;
+    *)
+        echo "ERROR: unsupported release target: $target" >&2
+        exit 1
+        ;;
+esac
+
+file="jerboa-${version}-${target}.tar.gz"
+base=${JERBOA_RELEASE_BASE:-${origin}/${repo}/refs/download/${version}}
+url="${base%/}/${file}"
+sum_url="${url}.sha256"
+
+download() {
+    src=$1
+    dst=$2
+    if command -v curl >/dev/null 2>&1; then
+        curl -fsSL "$src" -o "$dst"
+    elif command -v fetch >/dev/null 2>&1; then
+        fetch -q -o "$dst" "$src"
+    elif command -v wget >/dev/null 2>&1; then
+        wget -q -O "$dst" "$src"
+    else
+        echo "ERROR: need curl, fetch, or wget to download Jerboa" >&2
+        exit 1
+    fi
+}
+
+sha256_file() {
+    path=$1
+    if command -v sha256sum >/dev/null 2>&1; then
+        sha256sum "$path" | awk '{print $1}'
+    elif command -v shasum >/dev/null 2>&1; then
+        shasum -a 256 "$path" | awk '{print $1}'
+    elif command -v sha256 >/dev/null 2>&1; then
+        sha256 -q "$path"
+    else
+        echo "ERROR: need sha256sum, shasum, or sha256" >&2
+        exit 1
+    fi
+}
+
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/jerboa-bootstrap.XXXXXX")
+trap 'rm -rf "$tmp"' EXIT HUP INT TERM
+
+archive="$tmp/$file"
+sum_file="$tmp/$file.sha256"
+
+echo "fetching $url"
+download "$url" "$archive"
+download "$sum_url" "$sum_file"
+
+expected=$(awk '{print $1; exit}' "$sum_file")
+actual=$(sha256_file "$archive")
+if [ "$expected" != "$actual" ]; then
+    echo "ERROR: checksum mismatch for $file" >&2
+    echo "expected: $expected" >&2
+    echo "actual:   $actual" >&2
+    exit 1
+fi
+
+mkdir -p "$tmp/extract"
+tar -xzf "$archive" -C "$tmp/extract"
+root="$tmp/extract/jerboa-${version}-${target}"
+[ -x "$root/bin/jerboa" ] || {
+    echo "ERROR: archive did not contain bin/jerboa" >&2
+    exit 1
+}
+
+mkdir -p "$bindir"
+cp "$root/bin/jerboa" "$bindir/jerboa"
+chmod 0755 "$bindir/jerboa"
+for link in jmcp jlsp jerbuild jpkg; do
+    ln -sf jerboa "$bindir/$link"
+done
+
+echo "installed $bindir/{jerboa,jmcp,jlsp,jerbuild,jpkg}"
diff --git a/support/static-precompile-deps.ss b/support/static-precompile-deps.ss
index 7ab4a47..cd9fb1b 100644
--- a/support/static-precompile-deps.ss
+++ b/support/static-precompile-deps.ss
@@ -14,12 +14,6 @@
   (std misc thread)
   (std os path)
   (std os signal)
-  (compat types)
-  (compat gambit-compat)
-  (runtime util)
-  (runtime table)
-  (runtime c3)
-  (runtime mop)
   (compat gambit)
   (jsh ffi)
   (jsh static-compat)
diff --git a/vendor/jerboa-https b/vendor/jerboa-https
index 7f3c000..3bc7a40 160000
--- a/vendor/jerboa-https
+++ b/vendor/jerboa-https
@@ -1 +1 @@
-Subproject commit 7f3c000355c97167dd3428a025168f9ba5a26a5c
+Subproject commit 3bc7a40adcade47a776501ca9447cd501d63ad09
diff --git a/vendor/jerboa-scintilla b/vendor/jerboa-scintilla
index 4642655..96745c5 160000
--- a/vendor/jerboa-scintilla
+++ b/vendor/jerboa-scintilla
@@ -1 +1 @@
-Subproject commit 4642655e00ddefbd4c4bf40d033d6e0587d9f085
+Subproject commit 96745c5ea69c95f1c4fa2ce80f43e9cdf59a55a6