Fix extras targets and binary use loading

ober

d4d9bb0a9a22a2f1df434ef2c71e653e8402e0b8

diff --git a/.gitignore b/.gitignore
index b269989..b9fc3d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,8 +16,10 @@
 /jsh-macos
 /jsh-macos.sha256
 /ffi-shim-symbols.list
+/.jsh-extras-embed.conf
 
 # Generated source/staging
+/jsh-generated.ss
 /jsh-src/
 /_jerbuild-stage/
 /src/
@@ -28,6 +30,7 @@
 # Vendored external checkouts and local caches
 /_vendor/
 /vendor/
+/rust-coreutils/target/
 /.jerboa/bin/
 /.jcode/
 /.claude/
diff --git a/Makefile b/Makefile
index 30a21ef..1093668 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,8 @@ JERBOA_VERSION ?= v0.2.0
 JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin
 JERBOA_SHELL_EXTRAS_URL ?= https://git.sr.ht/~lisp/jerboa-shell-extras
 JERBOA_SHELL_EXTRAS_DIR ?= $(CURDIR)/_vendor/jerboa-shell-extras
+JSH_EXTRAS_EMBED_CONF ?= $(CURDIR)/.jsh-extras-embed.conf
+JSH_EXTRAS_DEFAULT_EMBED ?= $(JERBOA_SHELL_EXTRAS_DIR)/embed
 JERBUILD ?= $(shell if [ -x ./jerbuild ]; then echo ./jerbuild; \
 	elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ]; then echo "$(JERBOA_TOOL_DIR)/jerbuild"; \
 	elif command -v jerbuild >/dev/null 2>&1; then command -v jerbuild; \
@@ -31,6 +33,9 @@ JERBOA_NATIVE_HOST_LIB ?= $(JERBOA_NATIVE_RS_DIR)/target/release/libjerboa_nativ
 JSH_NATIVE_NO_SQLITE_FEATURES ?= tls crypto
 JERBOA_NATIVE_HOST_FEATURES ?= $(JSH_NATIVE_NO_SQLITE_FEATURES)
 JSH_CROSS_FEATURES ?= coreutils
+# jsh evaluates Jerboa source at runtime via ,use. Jerbuild's deterministic
+# FASL metadata strip removes import information needed by that runtime path.
+JSH_JERBUILD_DETERMINISTIC_IDS ?= 0
 JERBOA_SSH_REPO ?= $(VENDOR)/jerboa-ssh
 JSQLITE_REPO ?= $(VENDOR)/jsqlite
 JSQLITE_URL ?= https://git.sr.ht/~lisp/jsqlite
@@ -142,11 +147,75 @@ ensure-shell-extras:
 	fi
 	@test -f "$(JERBOA_SHELL_EXTRAS_DIR)/Makefile"
 
-extras: ensure-shell-extras
-	@$(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" extras \
+configure: ensure-shell-extras
+	@$(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" configure \
 		JERBOA_SHELL_DIR="$(CURDIR)" \
-		JSH_EMBED="$(JERBOA_SHELL_EXTRAS_DIR)/embed" \
 		EXTRAS_OUTPUT="$(CURDIR)/jsh-extras"
+	@tools/select-extras-embed.sh \
+		--features "$(JERBOA_SHELL_EXTRAS_DIR)/features.conf" \
+		--state "$(JSH_EXTRAS_EMBED_CONF)" \
+		--default "$(JSH_EXTRAS_DEFAULT_EMBED)" \
+		--prompt >/dev/null
+
+features: configure
+
+features-all: ensure-shell-extras
+	@$(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" features-all \
+		JERBOA_SHELL_DIR="$(CURDIR)" \
+		EXTRAS_OUTPUT="$(CURDIR)/jsh-extras"
+	@tools/select-extras-embed.sh \
+		--features "$(JERBOA_SHELL_EXTRAS_DIR)/features.conf" \
+		--state "$(JSH_EXTRAS_EMBED_CONF)" \
+		--default "$(JSH_EXTRAS_DEFAULT_EMBED)" \
+		--prompt >/dev/null
+
+features-minimal: ensure-shell-extras
+	@$(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" features-minimal \
+		JERBOA_SHELL_DIR="$(CURDIR)" \
+		EXTRAS_OUTPUT="$(CURDIR)/jsh-extras"
+	@tools/select-extras-embed.sh \
+		--features "$(JERBOA_SHELL_EXTRAS_DIR)/features.conf" \
+		--state "$(JSH_EXTRAS_EMBED_CONF)" \
+		--default "$(JSH_EXTRAS_DEFAULT_EMBED)" \
+		--no-prompt >/dev/null
+
+show-features: ensure-shell-extras
+	@$(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" show-features \
+		JERBOA_SHELL_DIR="$(CURDIR)" \
+		EXTRAS_OUTPUT="$(CURDIR)/jsh-extras"
+	@tools/select-extras-embed.sh \
+		--features "$(JERBOA_SHELL_EXTRAS_DIR)/features.conf" \
+		--state "$(JSH_EXTRAS_EMBED_CONF)" \
+		--default "$(JSH_EXTRAS_DEFAULT_EMBED)" \
+		--show \
+		--no-prompt
+
+extras: configure
+	@embed_path="$$(tools/select-extras-embed.sh \
+		--features "$(JERBOA_SHELL_EXTRAS_DIR)/features.conf" \
+		--state "$(JSH_EXTRAS_EMBED_CONF)" \
+		--default "$(JSH_EXTRAS_DEFAULT_EMBED)" \
+		--no-prompt)"; \
+	mkdir -p "$$embed_path"; \
+	echo "=== Fetching dependencies for the selected jerboa-shell extras ==="; \
+	$(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" vendor-deps \
+		JERBOA_SHELL_DIR="$(CURDIR)" \
+		JSH_EMBED="$$embed_path" \
+		EXTRAS_OUTPUT="$(CURDIR)/jsh-extras"; \
+	echo "=== Building jerboa-shell with selected extras ==="; \
+	$(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" binary \
+		JERBOA_SHELL_DIR="$(CURDIR)" \
+		JSH_EMBED="$$embed_path" \
+		EXTRAS_OUTPUT="$(CURDIR)/jsh-extras"; \
+	case "$$(uname -s)" in \
+	  Darwin) extras_bin="jsh-macos" ;; \
+	  Linux) if [ -n "$$TERMUX_VERSION" ] || [ -d /data/data/com.termux ]; then extras_bin="jsh-android"; else extras_bin="jsh-linux-$$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')"; fi ;; \
+	  FreeBSD) extras_bin="jsh-freebsd-$$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')" ;; \
+	  *) echo "Unsupported platform: $$(uname -s)" >&2; exit 1 ;; \
+	esac; \
+	cp -f "$(JERBOA_SHELL_EXTRAS_DIR)/$$extras_bin" "$(CURDIR)/jsh-extras"; \
+	echo "=== Extras shell ready: $(CURDIR)/jsh-extras ==="; \
+	echo "=== JSH_EMBED: $$embed_path ==="
 
 ensure-jsqlite:
 	@if [ ! -f "$(JSQLITE_DIR)/jsqlite/api.ss" ]; then \
@@ -185,7 +254,7 @@ jerboa: ensure-jerboa-tools
 jsh-macos: ensure-jerboa-tools ffi-shim-symbols.list jerboa
 	@case "$(UNAME_S)" in Darwin) ;; *) echo "ERROR: jsh-macos must be built on macOS" >&2; exit 1 ;; esac
 	@echo "=== Building jsh-macos ==="
-	JERBUILD="$(JERBUILD)" $(JERBUILD) build --config .jerbuild
+	JERBUILD="$(JERBUILD)" JERBOA_BINARY_DETERMINISTIC_IDS="$(JSH_JERBUILD_DETERMINISTIC_IDS)" $(JERBUILD) build --config .jerbuild
 	@shasum -a 256 jsh-macos > jsh-macos.sha256
 
 macos: jsh-macos
@@ -207,17 +276,6 @@ jsh-macos-minimal:
 	@echo "=== jsh-macos-minimal is a compatibility alias; building with JSH_FEATURES=none ==="
 	@$(MAKE) jsh-macos-base
 
-features-all:
-	@echo "=== features-all compatibility target: use JSH_FEATURES=all make <target> ==="
-
-configure features: features-all
-
-features-minimal:
-	@echo "=== features-minimal compatibility target: use JSH_FEATURES=none make <target> ==="
-
-show-features:
-	@echo "JSH_FEATURES=$${JSH_FEATURES:-$${JERBOA_FEATURES:-all/default by target}}"
-
 compile: ensure-jerboa-tools stage-jsqlite jerboa gen-embed
 	@echo "=== Compiling .sls -> .so ==="
 	$(JERBUILD) exec --libdirs "$(LIBDIRS_JSH)" build-all.ss
@@ -716,7 +774,9 @@ install-hooks:
 	@if [ ! -d .git ]; then echo "not a git checkout -- cannot install hooks" >&2; exit 1; fi
 	@mkdir -p .git/hooks
 	@ln -sf ../../tools/pre-commit .git/hooks/pre-commit
+	@ln -sf ../../tools/pre-push .git/hooks/pre-push
 	@echo "Installed pre-commit hook -> tools/pre-commit"
+	@echo "Installed pre-push hook -> tools/pre-push"
 
 learn-codex-shim:
 	@echo "ERROR: tools/jsh-learn-policy is not present in this repo; use obersh for learn-codex-shim." >&2
@@ -728,7 +788,10 @@ run: ensure-jerboa-tools $(FFI_LIB) jerboa
 security:
 	@REPO_ROOT="$(CURDIR)" sh tools/security-check.sh
 
-test: ensure-jerboa-tools $(FFI_LIB) jerboa
+test-make-extras:
+	@bash test/test-make-extras.sh
+
+test: ensure-jerboa-tools $(FFI_LIB) jerboa test-make-extras
 	@echo "=== Running unit tests ==="
 	$(JSH_FFI_ENV) $(JERBUILD) exec --libdirs "$(LIBDIRS)" test/test-jsh.ss
 
diff --git a/main.ss b/main.ss
index 5654859..147d5c8 100644
--- a/main.ss
+++ b/main.ss
@@ -26,6 +26,7 @@
         :jsh/startup
         :jsh/arithmetic
         :jsh/ffi
+        (only (std os platform) platform-cpu-count)
         (only (compat gambit) tty-mode-set!))
 
 ;;; --- Session state parameters ---
@@ -373,6 +374,209 @@
              (tty-mode-set! (current-input-port) #t #t #f #f 0))
            (loop (+ cmd-num 1))))))))
 
+(def (output-to-string proc)
+  (let ([p (open-output-string)])
+    (proc p)
+    (get-output-string p)))
+
+(def (jerboa-preprocess-brackets str)
+  (let* ([len (string-length str)]
+         [out (open-output-string)])
+    (let loop ([i 0] [in-string #f] [in-line-comment #f])
+      (if (>= i len)
+        (get-output-string out)
+        (let ([ch (string-ref str i)])
+          (cond
+            [in-line-comment
+             (write-char ch out)
+             (loop (+ i 1) in-string (not (char=? ch #\newline)))]
+            [in-string
+             (write-char ch out)
+             (cond
+               [(char=? ch #\\)
+                (when (< (+ i 1) len)
+                  (write-char (string-ref str (+ i 1)) out))
+                (loop (+ i 2) in-string #f)]
+               [(char=? ch #\") (loop (+ i 1) #f #f)]
+               [else (loop (+ i 1) in-string #f)])]
+            [(and (char=? ch #\#)
+                  (< (+ i 1) len)
+                  (char=? (string-ref str (+ i 1)) #\\))
+             (write-char ch out)
+             (write-char (string-ref str (+ i 1)) out)
+             (if (>= (+ i 2) len)
+               (loop (+ i 2) #f #f)
+               (let ([nc (string-ref str (+ i 2))])
+                 (if (not (char-alphabetic? nc))
+                   (begin (write-char nc out) (loop (+ i 3) #f #f))
+                   (let char-loop ([j (+ i 2)])
+                     (if (>= j len)
+                       (loop j #f #f)
+                       (let ([ac (string-ref str j)])
+                         (if (or (char-whitespace? ac)
+                                 (memv ac '(#\( #\) #\[ #\] #\; #\")))
+                           (loop j #f #f)
+                           (begin (write-char ac out) (char-loop (+ j 1))))))))))]
+            [(and (char=? ch #\#)
+                  (< (+ i 1) len)
+                  (char=? (string-ref str (+ i 1)) #\|))
+             (write-char ch out)
+             (write-char (string-ref str (+ i 1)) out)
+             (let block-loop ([j (+ i 2)] [depth 1])
+               (if (or (>= j len) (= depth 0))
+                 (loop j #f #f)
+                 (let ([bc (string-ref str j)])
+                   (cond
+                     [(and (char=? bc #\|)
+                           (< (+ j 1) len)
+                           (char=? (string-ref str (+ j 1)) #\#))
+                      (write-char bc out)
+                      (write-char (string-ref str (+ j 1)) out)
+                      (block-loop (+ j 2) (- depth 1))]
+                     [(and (char=? bc #\#)
+                           (< (+ j 1) len)
+                           (char=? (string-ref str (+ j 1)) #\|))
+                      (write-char bc out)
+                      (write-char (string-ref str (+ j 1)) out)
+                      (block-loop (+ j 2) (+ depth 1))]
+                     [else
+                      (write-char bc out)
+                      (block-loop (+ j 1) depth)]))))]
+            [(char=? ch #\;)
+             (write-char ch out)
+             (loop (+ i 1) #f #t)]
+            [(char=? ch #\")
+             (write-char ch out)
+             (loop (+ i 1) #t #f)]
+            [(and (char=? ch #\[)
+                  (< (+ i 1) len)
+                  (char=? (string-ref str (+ i 1)) #\]))
+             (display "'()" out)
+             (loop (+ i 2) #f #f)]
+            [(char=? ch #\[) (write-char #\( out) (loop (+ i 1) #f #f)]
+            [(char=? ch #\]) (write-char #\) out) (loop (+ i 1) #f #f)]
+            [else (write-char ch out) (loop (+ i 1) #f #f)]))))))
+
+(def (jerboa-read-all-from-string str)
+  (let* ([processed (jerboa-preprocess-brackets str)]
+         [p (open-input-string (string-append "#!chezscheme\n" processed))])
+    (let loop ([forms '()])
+      (let ([form (read p)])
+        (if (eof-object? form)
+          (reverse forms)
+          (loop (cons form forms)))))))
+
+(def (jerboa-read-all port)
+  (let ([raw (let ([p (open-output-string)])
+               (let loop ()
+                 (let ([ch (read-char port)])
+                   (unless (eof-object? ch)
+                     (write-char ch p)
+                     (loop))))
+               (get-output-string p))])
+    (jerboa-read-all-from-string raw)))
+
+(def (jerboa-body->expr body)
+  (cond
+    [(null? body) '(void)]
+    [(null? (cdr body)) (car body)]
+    [else `(begin ,@body)]))
+
+(def (jerboa-lower-top form)
+  (cond
+    [(and (pair? form) (eq? (car form) 'def) (pair? (cdr form)))
+     (let ([target (cadr form)]
+           [body (cddr form)])
+       (cond
+         [(symbol? target) `(define ,target ,(jerboa-body->expr body))]
+         [(pair? target) `(define ,target ,@body)]
+         [else form]))]
+    [else form]))
+
+(def *jerboa-use-env-ready* #f)
+
+(def (ensure-jerboa-use-env!)
+  (unless *jerboa-use-env-ready*
+    (let ([ienv (interaction-environment)])
+      (define (import-lib! lib)
+        (eval `(import ,lib) ienv))
+      (define (try-import-lib! lib)
+        (guard (e (#t #f))
+          (import-lib! lib)
+          #t))
+      (for-each
+        import-lib!
+        '((jerboa runtime)
+          (std error)
+          (std misc string)
+          (std misc list)
+          (std os path)
+          (std os platform)
+          (std format)
+          (std sort)
+          (std pregexp)
+          (std misc thread)))
+      (for-each
+        try-import-lib!
+        '((std text glob)
+          (std misc lru-cache)
+          (std misc trie)))
+      (eval '(define (force-output . args)
+               (flush-output-port
+                 (if (null? args) (current-output-port) (car args)))) ienv)
+      (eval '(define cpu-count platform-cpu-count) ienv)
+      (eval '(define processor-count platform-cpu-count) ienv)
+      (eval `(define ,(string->symbol "##current-vm-processor-count")
+               (let ([count ,(platform-cpu-count)])
+                 (lambda () count))) ienv)
+      (eval `(define (,(string->symbol "##set-parallelism-level!") n) (void)) ienv)
+      (eval `(define (,(string->symbol "##startup-parallelism!")) (void)) ienv)
+      (set! *jerboa-use-env-ready* #t))))
+
+(def (jerboa-eval-forms forms)
+  (ensure-jerboa-use-env!)
+  (let ([ienv (interaction-environment)])
+    (let loop ([fs forms] [last (void)])
+      (if (null? fs)
+        last
+        (let ([form (car fs)])
+          (cond
+            [(and (pair? form) (eq? (car form) 'export))
+             (loop (cdr fs) last)]
+            [(and (pair? form) (eq? (car form) 'import))
+             (loop (cdr fs) last)]
+            [else
+             (loop (cdr fs) (eval (jerboa-lower-top form) ienv))]))))))
+
+(def (format-jerboa-result result)
+  (cond
+    [(eq? result (void)) ""]
+    [else
+     (output-to-string (lambda (port) (write result port)))]))
+
+(def (handle-use-command path-str)
+  (let ([path (string-trim-whitespace path-str)])
+    (unless (file-exists? path)
+      (error 'use (string-append "file not found: " path)))
+    (let* ([port (open-input-file path)]
+           [forms (jerboa-read-all port)])
+      (close-input-port port)
+      (let ([result (jerboa-eval-forms forms)])
+        (fprintf (current-error-port) "loaded: ~a (~a forms)~n" path (length forms))
+        result))))
+
+(def (execute-comma-input input env)
+  (let ([expr (string-trim-whitespace (substring input 1 (string-length input)))])
+    (cond
+      [(string-prefix? "use " expr)
+       (let ([result (handle-use-command (substring expr 4 (string-length expr)))])
+         (let ([text (format-jerboa-result result)])
+           (unless (string=? text "") (displayln text)))
+         0)]
+      [else
+       (fprintf (current-error-port) "jsh: unknown meta-command: ,~a~n" expr)
+       1])))
+
 (def (execute-input input env)
   ;; Parse and execute a line of input
   (with-catch
@@ -393,7 +597,10 @@
                        (string-prefix? "bad substitution: unclosed" msg)))
             2 1)))))
    (lambda ()
-     (let ([cmd (with-catch
+     (if (and (> (string-length input) 0)
+              (char=? (string-ref input 0) #\,))
+       (execute-comma-input input env)
+       (let ([cmd (with-catch
                  (lambda (e)
                    (fprintf (current-error-port) "jsh: syntax error: ~a~n"
                             (exception-message e))
@@ -405,7 +612,7 @@
        (cond
          ((eq? cmd 'error) 2)
          ((not cmd) 0)
-         (else (execute-command cmd env)))))))
+         (else (execute-command cmd env))))))))
 
 ;;; --- Trap processing ---
 
diff --git a/support/stage-for-jerbuild.sh b/support/stage-for-jerbuild.sh
index e88c074..71d0c2d 100755
--- a/support/stage-for-jerbuild.sh
+++ b/support/stage-for-jerbuild.sh
@@ -4,6 +4,9 @@
 set -eu
 
 ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+FUSE_SSL_PREFIX="$(brew --prefix openssl@3 2>/dev/null || echo /opt/homebrew/opt/openssl@3)"
+FUSE_SSL_INC=""
+[ -d "${FUSE_SSL_PREFIX}/include" ] && FUSE_SSL_INC="-I${FUSE_SSL_PREFIX}/include"
 
 echo "==> stage-for-jerbuild: cleaning stale compiled artifacts"
 find "${ROOT}/src" -type f \( -name '*.so' -o -name '*.wpo' \
diff --git a/test/test-binary.sh b/test/test-binary.sh
index e427929..d2cee5c 100755
--- a/test/test-binary.sh
+++ b/test/test-binary.sh
@@ -48,6 +48,22 @@ check "subshell" "inner" -c "(echo inner)"
 check_rc "false status" 1 -c "false"
 check_rc "syntax error status" 2 -c "if"
 
+use_tmp="$(mktemp -d "${TMPDIR:-/tmp}/jsh-use-test.XXXXXX")"
+cat > "$use_tmp/use-smoke.ss" <<'EOF'
+(import (jerboa prelude))
+(def (jsh-use-smoke x y) (+ x y))
+(displayln "jsh-use-smoke=" (jsh-use-smoke 2 3))
+EOF
+got="$(printf ',use %s/use-smoke.ss\n' "$use_tmp" | "$BINARY" 2>&1)"
+rc=$?
+if [ "$rc" -eq 0 ] && printf '%s\n' "$got" | grep -Fq "jsh-use-smoke=5"; then
+    pass=$((pass + 1))
+else
+    fail=$((fail + 1))
+    printf 'FAIL: ,use loads Jerboa script\n  rc=%s\n  got=%s\n' "$rc" "$got"
+fi
+rm -rf "$use_tmp"
+
 rm -f /tmp/jsh-test.out /tmp/jsh-test.err
 echo "Binary tests ($BINARY): $pass passed, $fail failed"
 [ "$fail" -eq 0 ]
diff --git a/test/test-make-extras.sh b/test/test-make-extras.sh
new file mode 100755
index 0000000..9e3c5d5
--- /dev/null
+++ b/test/test-make-extras.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+# Regression coverage for make extras wiring and JSH_EMBED selection.
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT"
+
+fail() {
+    echo "FAIL: $*" >&2
+    exit 1
+}
+
+target_exists() {
+    local target="$1"
+    awk -v t="$target" '
+        /^[A-Za-z0-9_.-]+[[:space:]]*:/ {
+            name = $1
+            sub(/:.*/, "", name)
+            if (name == t) found = 1
+        }
+        END { exit found ? 0 : 1 }
+    ' Makefile
+}
+
+for target in extras configure features features-all features-minimal show-features build binary test-binary; do
+    target_exists "$target" || fail "missing Makefile target: $target"
+done
+
+if grep -Fq 'JSH_EMBED="$(JERBOA_SHELL_EXTRAS_DIR)/embed"' Makefile; then
+    fail "make extras must not force JSH_EMBED to the extras repo embed directory"
+fi
+
+tmp="$(mktemp -d "${TMPDIR:-/tmp}/jsh-extras-test.XXXXXX")"
+trap 'rm -rf "$tmp"' EXIT
+
+cat > "$tmp/features-with-embed.conf" <<'EOF'
+(features
+  coreutils
+  embed
+)
+EOF
+
+chosen="$tmp/custom embed"
+got="$(JSH_EMBED="$chosen" tools/select-extras-embed.sh \
+    --features "$tmp/features-with-embed.conf" \
+    --state "$tmp/embed-state.conf" \
+    --default "$tmp/default" \
+    --no-prompt)"
+[ "$got" = "$chosen" ] || fail "JSH_EMBED env override not preserved"
+grep -Fqx "JSH_EMBED=$chosen" "$tmp/embed-state.conf" || fail "JSH_EMBED state not written"
+
+unset JSH_EMBED
+got="$(tools/select-extras-embed.sh \
+    --features "$tmp/features-with-embed.conf" \
+    --state "$tmp/embed-state.conf" \
+    --default "$tmp/default" \
+    --no-prompt)"
+[ "$got" = "$chosen" ] || fail "saved JSH_EMBED state not reused"
+
+cat > "$tmp/features-without-embed.conf" <<'EOF'
+(features
+  coreutils
+)
+EOF
+
+got="$(tools/select-extras-embed.sh \
+    --features "$tmp/features-without-embed.conf" \
+    --state "$tmp/no-state.conf" \
+    --default "$tmp/default" \
+    --no-prompt)"
+[ "$got" = "$tmp/default" ] || fail "non-embed selection should use default embed dir"
+
+show="$(tools/select-extras-embed.sh \
+    --features "$tmp/features-with-embed.conf" \
+    --state "$tmp/embed-state.conf" \
+    --default "$tmp/default" \
+    --show \
+    --no-prompt)"
+case "$show" in
+    *"Embed feature: enabled"*"$chosen"*) ;;
+    *) fail "--show did not report enabled embed path" ;;
+esac
+
+echo "PASS: make extras wiring"
diff --git a/tools/pre-push b/tools/pre-push
new file mode 100755
index 0000000..1791f45
--- /dev/null
+++ b/tools/pre-push
@@ -0,0 +1,18 @@
+#!/bin/sh
+# Pre-push hook: prove the native build and binary smoke tests before push.
+
+set -e
+
+REPO_ROOT="$(git rev-parse --show-toplevel)"
+cd "$REPO_ROOT"
+
+echo "=== pre-push: Makefile/extras regression tests ==="
+make test-make-extras
+
+echo "=== pre-push: native binary build ==="
+make binary
+
+echo "=== pre-push: binary functional tests ==="
+make test-binary
+
+echo "=== pre-push: all checks passed ==="
diff --git a/tools/select-extras-embed.sh b/tools/select-extras-embed.sh
new file mode 100755
index 0000000..00c73d7
--- /dev/null
+++ b/tools/select-extras-embed.sh
@@ -0,0 +1,171 @@
+#!/usr/bin/env bash
+# Resolve the JSH_EMBED source directory for make extras.
+
+set -euo pipefail
+
+FEATURES_CONF=""
+STATE_FILE=".jsh-extras-embed.conf"
+DEFAULT_EMBED="${HOME}/.embed"
+PROMPT=0
+SHOW=0
+NO_PROMPT=0
+
+usage() {
+    cat <<EOF
+Usage: tools/select-extras-embed.sh --features PATH [options]
+
+Options:
+  --state PATH    Persist selected JSH_EMBED path in PATH
+  --default PATH  Default embed path when none is selected yet
+  --prompt        Prompt for path when the embed feature is selected
+  --no-prompt     Never prompt; print saved/default path
+  --show          Print current embed selection details
+EOF
+}
+
+while [ "$#" -gt 0 ]; do
+    case "$1" in
+        --features)
+            shift
+            [ "$#" -gt 0 ] || { echo "ERROR: --features needs a path" >&2; exit 2; }
+            FEATURES_CONF="$1"
+            ;;
+        --state)
+            shift
+            [ "$#" -gt 0 ] || { echo "ERROR: --state needs a path" >&2; exit 2; }
+            STATE_FILE="$1"
+            ;;
+        --default)
+            shift
+            [ "$#" -gt 0 ] || { echo "ERROR: --default needs a path" >&2; exit 2; }
+            DEFAULT_EMBED="$1"
+            ;;
+        --prompt)
+            PROMPT=1
+            ;;
+        --no-prompt)
+            NO_PROMPT=1
+            ;;
+        --show)
+            SHOW=1
+            ;;
+        -h|--help)
+            usage
+            exit 0
+            ;;
+        *)
+            echo "ERROR: unknown argument: $1" >&2
+            usage >&2
+            exit 2
+            ;;
+    esac
+    shift
+done
+
+[ -n "$FEATURES_CONF" ] || { echo "ERROR: --features is required" >&2; exit 2; }
+
+expand_path() {
+    case "$1" in
+        "~") printf '%s\n' "$HOME" ;;
+        "~/"*) printf '%s/%s\n' "$HOME" "${1#~/}" ;;
+        *) printf '%s\n' "$1" ;;
+    esac
+}
+
+config_tokens() {
+    [ -f "$FEATURES_CONF" ] || return 0
+    awk '
+        {
+            raw = $0
+            sub(/;.*/, "", raw)
+            if (raw ~ /^[[:space:]]*\(features/) {
+                in_features = 1
+                sub(/^[[:space:]]*\(features/, "", raw)
+            }
+            if (in_features) {
+                done = (raw ~ /\)/)
+                gsub(/[()]/, " ", raw)
+                n = split(raw, parts, /[[:space:]]+/)
+                for (i = 1; i <= n; i++) {
+                    if (parts[i] != "") print parts[i]
+                }
+                if (done) in_features = 0
+            }
+        }
+    ' "$FEATURES_CONF"
+}
+
+embed_selected() {
+    local token saw=0
+    while IFS= read -r token; do
+        saw=1
+        case "$token" in
+            embed|all|full) return 0 ;;
+            none|minimal|core) return 1 ;;
+        esac
+    done < <(config_tokens)
+    [ "$saw" -eq 0 ] && return 1
+    return 1
+}
+
+read_state() {
+    [ -f "$STATE_FILE" ] || return 1
+    awk -F= '$1 == "JSH_EMBED" { sub(/^JSH_EMBED=/, ""); print; found = 1; exit } END { exit found ? 0 : 1 }' "$STATE_FILE"
+}
+
+write_state() {
+    local path="$1" dir tmp
+    dir="$(dirname "$STATE_FILE")"
+    mkdir -p "$dir"
+    tmp="${STATE_FILE}.tmp"
+    {
+        echo "# Generated by tools/select-extras-embed.sh"
+        printf 'JSH_EMBED=%s\n' "$path"
+    } > "$tmp"
+    mv "$tmp" "$STATE_FILE"
+}
+
+choose_path() {
+    local current input
+    if [ -n "${JSH_EMBED:-}" ]; then
+        current="$(expand_path "$JSH_EMBED")"
+        write_state "$current"
+        printf '%s\n' "$current"
+        return
+    fi
+
+    current="$(read_state || true)"
+    if [ -z "$current" ]; then
+        current="$DEFAULT_EMBED"
+    fi
+    current="$(expand_path "$current")"
+
+    if [ "$PROMPT" -eq 1 ] && [ "$NO_PROMPT" -eq 0 ] && embed_selected && [ -r /dev/tty ] && [ -w /dev/tty ]; then
+        {
+            echo
+            echo "Embed feature selected."
+            printf 'JSH_EMBED source directory [%s]: ' "$current"
+        } > /dev/tty
+        IFS= read -r input < /dev/tty || input=""
+        if [ -n "$input" ]; then
+            current="$(expand_path "$input")"
+        fi
+    fi
+
+    write_state "$current"
+    printf '%s\n' "$current"
+}
+
+path="$(choose_path)"
+
+if [ "$SHOW" -eq 1 ]; then
+    if embed_selected; then
+        printf 'Embed feature: enabled\n'
+    else
+        printf 'Embed feature: disabled\n'
+    fi
+    printf 'JSH_EMBED: %s\n' "$path"
+    printf 'Embed config: %s\n' "$STATE_FILE"
+else
+    printf '%s\n' "$path"
+fi