Rename minimal shell build to base

ober

4b04b621b3e62fd3b8f09bccd0c6269a85bf8953

diff --git a/.gitignore b/.gitignore
index 8550442..8fc1f5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,8 +13,8 @@
 /jsh-android.sha256
 /jsh-macos
 /jsh-macos.sha256
-/jsh-macos-minimal
-/jsh-macos-minimal.sha256
+/jsh-macos-base
+/jsh-macos-base.sha256
 /jsh-macos-full
 /jsh-macos-full.sha256
 /jsh-freebsd
diff --git a/Makefile b/Makefile
index d0fc697..69a5b57 100644
--- a/Makefile
+++ b/Makefile
@@ -64,9 +64,9 @@ PODMAN ?= podman
 .PHONY: ensure-jerboa-tools ensure-jsqlite stage-jsqlite ffi jerboa compile run clean test binary all jsh jsh-compile jsh-run jsh-binary jsh-jerbuild \
         musl test-binary test-mux test-mux-screen \
         gen-embed embed-crypto gen-certs verify-harden help check-platforms install-hooks \
-        jsh-macos jsh-macos-minimal jsh-macos-full macos \
-        jsh-freebsd jsh-freebsd-minimal jsh-freebsd-full \
-        jsh-android jsh-android-minimal jsh-android-full android \
+        jsh-macos jsh-macos-base jsh-macos-full macos \
+        jsh-freebsd jsh-freebsd-base jsh-freebsd-full \
+        jsh-android jsh-android-base jsh-android-full android \
         linux linux-amd64 linux-arm64 jsh-linux-amd64 jsh-linux-arm64 test-linux test-linux-amd64 \
         rust-musl-target-amd64 rust-musl-target-arm64 chez-cross-amd64 chez-cross-arm64 \
         rust-coreutils-linux-amd64 rust-coreutils-linux-arm64 jerboa-native-linux-amd64 jerboa-native-linux-arm64 \
@@ -322,7 +322,7 @@ jsh-freebsd: ensure-jerboa-tools ensure-jsqlite ffi-shim-symbols.list vendor-dep
 	@echo "=== Building jsh for FreeBSD with jerbuild ==="
 	JERBUILD="$(JERBUILD)" JSH_FEATURES="$(JSH_FEATURES)" $(JERBUILD) build --config .jerbuild.freebsd-amd64
 
-jsh-freebsd-minimal:
+jsh-freebsd-base:
 	@$(MAKE) jsh-freebsd JSH_FEATURES=none
 
 jsh-freebsd-full:
@@ -350,10 +350,10 @@ jsh-jerbuild: ensure-jerboa-tools ensure-jsqlite ffi-shim-symbols.list vendor-de
 
 jsh-macos: jsh-jerbuild
 
-jsh-macos-minimal:
+jsh-macos-base:
 	@$(MAKE) jsh-jerbuild JSH_FEATURES=none
-	@cp -f jsh-macos jsh-macos-minimal
-	@shasum -a 256 jsh-macos-minimal > jsh-macos-minimal.sha256
+	@cp -f jsh-macos jsh-macos-base
+	@shasum -a 256 jsh-macos-base > jsh-macos-base.sha256
 
 jsh-macos-full:
 	@$(MAKE) jsh-jerbuild JSH_FEATURES=all
@@ -619,7 +619,7 @@ jsh-android: ensure-jerboa-tools ffi-shim-symbols.list vendor-deps jerboa vendor
 	@echo "=== Building jsh on Android/Termux ==="
 	JSH_FEATURES="$(JSH_FEATURES)" bash build-jsh-android.sh
 
-jsh-android-minimal:
+jsh-android-base:
 	@$(MAKE) jsh-android JSH_FEATURES=none
 
 jsh-android-full:
@@ -865,13 +865,13 @@ help:
 	@echo "  make linux-amd64             Cross: linux/amd64 static ELF (Chez xpatch + musl-cross, no Podman)"
 	@echo "  make linux-arm64             Cross: linux/arm64 static ELF (Chez xpatch + musl-cross, no Podman)"
 	@echo "  make freebsd-amd64           freebsd/amd64 (native on a FreeBSD amd64 host)"
-	@echo "  make jsh-macos-minimal       macOS minimal build (core shell only)"
+	@echo "  make jsh-macos-base          macOS base build (core shell only)"
 	@echo "  make jsh-macos-full          macOS full build (all features)"
 	@echo "  make jsh-freebsd             FreeBSD binary via jerbuild"
-	@echo "  make jsh-freebsd-minimal     FreeBSD minimal build (core shell only)"
+	@echo "  make jsh-freebsd-base        FreeBSD base build (core shell only)"
 	@echo "  make jsh-freebsd-full        FreeBSD full build (all features)"
 	@echo "  make android                 Android/Termux (native on a Termux device)"
-	@echo "  make jsh-android-minimal     Android minimal build (core shell only)"
+	@echo "  make jsh-android-base        Android base build (core shell only)"
 	@echo "  make jsh-android-full        Android full build (all features)"
 	@echo "  make linux                   Alias for linux-amd64"
 	@echo "  make test-linux-amd64        (optional) smoke-run jsh-linux-amd64 under alpine via Podman"
@@ -988,7 +988,7 @@ clean:
 	find src -name "*.so" -delete 2>/dev/null || true
 	find src -name "*.wpo" -delete 2>/dev/null || true
 	find src \( -name "*.ta6le" -o -name "*.ta6fb" -o -name "*.tarm64osx" -o -name "*.tx86_64osx" \) -delete 2>/dev/null || true
-	rm -f libjsh-ffi.so libjsh-ffi.dylib libjsh-ffi-musl.so libembed-crypto.so libcoreutils.so libcoreutils.dylib jsh-all.so jsh.wpo jsh.boot jsh jsh-musl jsh-musl.sha256 jsh-freebsd jsh-freebsd.sha256 jsh-macos jsh-macos.sha256 jsh-macos-minimal jsh-macos-minimal.sha256 jsh-macos-full jsh-macos-full.sha256 ffi-shim-symbols.list
+	rm -f libjsh-ffi.so libjsh-ffi.dylib libjsh-ffi-musl.so libembed-crypto.so libcoreutils.so libcoreutils.dylib jsh-all.so jsh.wpo jsh.boot jsh jsh-musl jsh-musl.sha256 jsh-freebsd jsh-freebsd.sha256 jsh-macos jsh-macos.sha256 jsh-macos-base jsh-macos-base.sha256 jsh-macos-full jsh-macos-full.sha256 ffi-shim-symbols.list
 	rm -f jsh_program.h jsh_petite_boot.h jsh_scheme_boot.h jsh_jsh_boot.h
 	rm -f jsh-linux-amd64 jsh-linux-amd64-main.c jsh-linux-amd64.wp.so
 	rm -f jsh-linux-arm64 jsh-linux-arm64-main.c jsh-linux-arm64.wp.so
diff --git a/asciinema.md b/asciinema.md
index 1b8e783..32c2c75 100644
--- a/asciinema.md
+++ b/asciinema.md
@@ -121,7 +121,7 @@ while jerboa-aware tools can reconstruct full session semantics.
 (recorder-resize! cols rows)   ; shorthand for "r" event
 ```
 
-#### Integration Points (minimal patches)
+#### Integration Points (base patches)
 
 1. **lineedit.ss**: After each raw byte read, `(when (*recording?*) (recorder-input! byte))`
 2. **executor.ss**: Before dispatch, emit "c" event. After completion, emit "x" event.
diff --git a/build-jerboa.ss b/build-jerboa.ss
index 860b912..fcb8150 100644
--- a/build-jerboa.ss
+++ b/build-jerboa.ss
@@ -1848,7 +1848,7 @@
   ;;     (hand-written file, not auto-generated — no patch needed here)
 
   ;; 14. Add --eval flag to sandbox builtin for restricted eval
-  ;;     Safe eval with a minimal binding allowlist
+  ;;     Safe eval with a base binding allowlist
   (patch-file! "src/jsh/main.sls"
     "            (fprintf (current-error-port)\n              \"  -c CMD   run command~n\")"
     (string-append
diff --git a/build-jsh-android.ss b/build-jsh-android.ss
index 4c16b94..26b4b6b 100644
--- a/build-jsh-android.ss
+++ b/build-jsh-android.ss
@@ -190,7 +190,7 @@
 
 ;; ========== Feature resolution ==========
 ;; Derive *enabled-features* from JSH_FEATURES env var.
-;;   ""/"none" → '()  (minimal build)
+;;   ""/"none" → '()  (base build)
 ;;   "all"     → all known optional features
 ;;   "foo,bar" → '(foo bar)
 
@@ -227,7 +227,7 @@
         'replace))))
 
 (printf "[1/7] Compiling jsh-generated.ss (~a, optimize-level 3)...~n"
-        (if (null? *enabled-features*) "minimal" "full"))
+        (if (null? *enabled-features*) "base" "full"))
 (with-warnings-suppressed
   (lambda ()
     (parameterize ([compile-imported-libraries #t]
diff --git a/build-jsh-cross.ss b/build-jsh-cross.ss
index dd55a1a..1acd9f6 100644
--- a/build-jsh-cross.ss
+++ b/build-jsh-cross.ss
@@ -56,7 +56,7 @@
 
 ;; Feature resolution — derive *enabled-features* from JSH_FEATURES env var.
 ;; JERBOA_FEATURES is accepted as a compatibility alias for CLI ergonomics.
-;;   ""/"none" → '()  (minimal build)
+;;   ""/"none" → '()  (base build)
 ;;   "all"     → all known optional features
 ;;   "foo,bar" → '(foo bar)
 (define cross-supported-all-features
@@ -621,10 +621,10 @@
 ;; jsh.ss carries a placeholder `(define *jsh-enabled-features* '())`.
 ;; jsh-generate.ss rewrites that line based on *enabled-features* so the
 ;; runtime `,features` command reports what was actually built. Without
-;; this step the cross-build would always report "minimal" regardless of
+;; this step the cross-build would always report "base" regardless of
 ;; JSH_FEATURES.
 (printf "==> [2/6] generate-jsh-program (features: ~a)~n"
-        (if (null? *enabled-features*) "minimal" "full"))
+        (if (null? *enabled-features*) "base" "full"))
 (load "features.def")
 (load "jsh-generate.ss")
 (generate-jsh-program *enabled-features*)
diff --git a/build-jsh-freebsd-cross.ss b/build-jsh-freebsd-cross.ss
index f960858..832554e 100644
--- a/build-jsh-freebsd-cross.ss
+++ b/build-jsh-freebsd-cross.ss
@@ -33,7 +33,7 @@
 (define ffi-shim     "ffi-shim.c")
 
 ;; Feature resolution — derive *enabled-features* from JSH_FEATURES env var.
-;;   ""/"none" → '()  (minimal build)
+;;   ""/"none" → '()  (base build)
 ;;   "all"     → all known optional features
 ;;   "foo,bar" → '(foo bar)
 (define *enabled-features*
@@ -128,7 +128,7 @@
 ;; jsh-generate.ss rewrites that line based on *enabled-features* so the
 ;; runtime `,features` command reports what was actually built.
 (printf "==> [2/6] generate-jsh-program (features: ~a)~n"
-        (if (null? *enabled-features*) "minimal" "full"))
+        (if (null? *enabled-features*) "base" "full"))
 (load "features.def")
 (load "jsh-generate.ss")
 (generate-jsh-program *enabled-features*)
diff --git a/build-jsh-freebsd.ss b/build-jsh-freebsd.ss
index faa03bc..be4b9ca 100644
--- a/build-jsh-freebsd.ss
+++ b/build-jsh-freebsd.ss
@@ -620,7 +620,7 @@
 
 ;; ========== Feature resolution ==========
 ;; Derive *enabled-features* from JSH_FEATURES env var.
-;;   ""/"none" → '()  (minimal build)
+;;   ""/"none" → '()  (base build)
 ;;   "all"     → all known optional features
 ;;   "foo,bar" → '(foo bar)
 
@@ -656,7 +656,7 @@
 (generate-jsh-program *enabled-features*)
 
 (printf "~n[2/7] Compiling jsh-generated.ss (~a, optimize-level 3)...~n"
-        (if (null? *enabled-features*) "minimal" "full"))
+        (if (null? *enabled-features*) "base" "full"))
 (parameterize ([compile-imported-libraries #t]
                [optimize-level 3]
                [cp0-effort-limit 500]
diff --git a/build-jsh-macos.ss b/build-jsh-macos.ss
index c2f02df..f94a1e4 100644
--- a/build-jsh-macos.ss
+++ b/build-jsh-macos.ss
@@ -103,7 +103,7 @@
 
 ;; ========== Feature resolution ==========
 ;; Derive *enabled-features* from JSH_FEATURES env var.
-;;   ""/"none" → '()  (minimal build)
+;;   ""/"none" → '()  (base build)
 ;;   "all"     → all known optional features
 ;;   "foo,bar" → '(foo bar)
 
diff --git a/build-jsh-musl.ss b/build-jsh-musl.ss
index d8e955a..6473813 100644
--- a/build-jsh-musl.ss
+++ b/build-jsh-musl.ss
@@ -515,7 +515,7 @@
 
 ;; ========== Feature resolution ==========
 ;; Derive *enabled-features* from JSH_FEATURES env var.
-;;   ""/"none" → '()  (minimal build)
+;;   ""/"none" → '()  (base build)
 ;;   "all"     → all known optional features
 ;;   "foo,bar" → '(foo bar)
 
@@ -551,7 +551,7 @@
 (generate-jsh-program *enabled-features*)
 
 (printf "~n[2/7] Compiling jsh-generated.ss (~a, optimize-level 3)...~n"
-        (if (null? *enabled-features*) "minimal" "full"))
+        (if (null? *enabled-features*) "base" "full"))
 ;; compile-imported-libraries is needed so compile-program can resolve
 ;; imports from (jsh registry) etc. The .so files from step 1 already
 ;; exist, so Chez will use them rather than recompiling.
diff --git a/docs/capability-wrapper-followup.md b/docs/capability-wrapper-followup.md
index bac3bb2..d406419 100644
--- a/docs/capability-wrapper-followup.md
+++ b/docs/capability-wrapper-followup.md
@@ -19,7 +19,7 @@ Status updated: 2026-05-22 (Linux Landlock + cross-binary follow-up)
 | 10 | Tracefs                        | SAFE PARTIAL — Linux `strace` wrapper is invoked when available; Jerboa now provides normalized `fs_event` records, best-effort fd/cwd tracking, and read/write/exec suggestions; otherwise fail closed; native backends remain future work |
 | 11 | Audit output depth             | PARTIAL — process/policy/fs/net/shim/refusal/limit-install/limit-supervision/tracefs/workspace/sandbox-install events + argv redaction; **GAP**: exact process tree and redacted stdout/stderr summaries |
 | 12 | Phase/help text                | DONE — stale "(parse/explain only)" wording removed; `docs/meta-commands.md` documents policy status |
-| 13 | Full binary artifacts          | PARTIAL — macOS primary/minimal/full targets rebuild as distinct artifacts; Linux amd64 static cross artifact builds and passes policy/broad suites; musl Podman is blocked by qemu/rustc SIGSEGV; FreeBSD/Android rebuilds remain |
+| 13 | Full binary artifacts          | PARTIAL — macOS primary/base/full targets rebuild as distinct artifacts; Linux amd64 static cross artifact builds and passes policy/broad suites; musl Podman is blocked by qemu/rustc SIGSEGV; FreeBSD/Android rebuilds remain |
 | 14 | Tests that must be added       | DONE — policy binary suite has macOS/Linux coverage; unit, macOS broad, and Linux broad suites are clean |
 
 The detailed requirement lists in §§1–14 below preserve the original asks, with
@@ -91,7 +91,7 @@ resolution.
 
 `jsh-macos` was rebuilt locally in this pass with SHA-256
 `f7ab45ae5d32285d15e2cd5900fd2bd86d33af3b851c45ea9eea8e7e7114b9c5`.
-`jsh-macos-minimal` and `jsh-macos-full` were not executable in this working
+`jsh-macos-base` and `jsh-macos-full` were not executable in this working
 tree during the policy command smoke coverage.
 Optional recording, SSH, AWS, and cross-build feature blocks are feature-aware
 and skip when the tested binary reports those features disabled.
@@ -665,14 +665,14 @@ Required work:
 2. Update `docs/meta-commands.md` to document policy commands.
 3. Document which commands are enforcing, partially enforcing, or parse-only.
 4. Make `,help` feature-aware:
-   - minimal build should say sandbox/rl policy axes are unenforced
+   - base build should say sandbox/rl policy axes are unenforced
    - full build should say which features are available
 
 ## 13. Full Binary Artifacts Are Stale Or Inconsistent
 
 Observed:
 
-- `./jsh-macos` recognized the new policy commands but was a minimal build.
+- `./jsh-macos` recognized the new policy commands but was a base build.
 - `./jsh-macos-full` listed many features but did not recognize the new policy
   commands during review.
 
@@ -680,7 +680,7 @@ Required work:
 
 1. Rebuild all release artifacts after policy changes:
    - `jsh-macos` — rebuilt and committed
-   - `jsh-macos-minimal` — rebuilt locally via named-output target
+   - `jsh-macos-base` — rebuilt locally via named-output target
    - `jsh-macos-full` — rebuilt locally via named-output target
    - `jsh-linux-amd64` — rebuilt with the macOS-to-Linux static cross path and
      tested under Ubuntu amd64/podman
diff --git a/docs/musl-build.md b/docs/musl-build.md
index cb9835a..e710266 100644
--- a/docs/musl-build.md
+++ b/docs/musl-build.md
@@ -119,7 +119,7 @@ podman run -v $PWD:/work -w /work docker.io/library/alpine:latest ./jsh-linux-am
 # Ubuntu
 podman run -v $PWD:/work -w /work docker.io/library/ubuntu:latest ./jsh-linux-amd64 -c 'echo Works on Ubuntu'
 
-# FROM scratch (minimal container)
+# FROM scratch (base container)
 podman run -v $PWD:/work -w /work scratch ./jsh-linux-amd64 -c 'echo Works in scratch'
 ```
 
diff --git a/docs/performance-overview.md b/docs/performance-overview.md
index 9b6b664..822cbf4 100644
--- a/docs/performance-overview.md
+++ b/docs/performance-overview.md
@@ -6,7 +6,7 @@ Environment:
 
 - Host: macOS 15.5 arm64.
 - Benchmark: `~/mine/shellbench/shellbench -t 1 -w 0`.
-- Shells: `/bin/bash`, `jsh-macos-minimal` (`JSH_FEATURES=none`), `jsh-macos-full` (`JSH_FEATURES=all`).
+- Shells: `/bin/bash`, `jsh-macos-base` (`JSH_FEATURES=none`), `jsh-macos-full` (`JSH_FEATURES=all`).
 - Final raw output: `_tmp/shellbench/macos-final.txt`.
 - Focused Rust `cut` reruns after the fork fix:
   `_tmp/shellbench/repro-cut-pipeline-then-heredoc-final.txt`,
@@ -19,7 +19,7 @@ No changes were made in `~/mine/jerboa/`.
 
 ## Correctness fixes from this pass
 
-1. Linux/macOS minimal no longer registers coreutils weak stubs when the
+1. Linux/macOS base no longer registers coreutils weak stubs when the
    `coreutils` feature is disabled. That fixed the `command -v ls` result like
    `[jsh-linux-amd64-weak] jsh_ls` in `JSH_FEATURES=none` builds.
 
@@ -41,7 +41,7 @@ No changes were made in `~/mine/jerboa/`.
 5. Parameter expansion now uses the faster public Jerboa string APIs for common
    literal cases: `(std misc string)` for contains/prefix/suffix checks and
    `(std misc string-more)` for all-literal replacement. The macOS static boot
-   lists now include `std/misc/string-more`; without that, the minimal binary
+   lists now include `std/misc/string-more`; without that, the base binary
    built but failed at runtime when loading the new import.
 
 ## Final shellbench highlights
diff --git a/expander.ss b/expander.ss
index 2844d44..e2ce53c 100644
--- a/expander.ss
+++ b/expander.ss
@@ -1288,7 +1288,7 @@
 ;; one char. Returns the index of the separator slash, or #f if not found.
 (def (find-patsub-separator str pattern-start)
   (let ((len (string-length str)))
-    ;; First, skip one unit (char or quoted group) to consume the minimal pattern
+    ;; First, skip one unit (char or quoted group) to consume the base pattern
     (let ((past-first
            (cond
              ((>= pattern-start len) pattern-start)
diff --git a/feature-resolve.ss b/feature-resolve.ss
index 81264c6..44a637a 100644
--- a/feature-resolve.ss
+++ b/feature-resolve.ss
@@ -38,7 +38,7 @@
   "Display an interactive checkbox prompt on /dev/tty. Returns list of enabled feature symbols."
   (let* ([names (feature-names)]
          [n (length names)]
-         [selected (make-vector n #f)]  ;; all off by default = minimal
+         [selected (make-vector n #f)]  ;; all off by default = base
          [tty-in  (open-input-file "/dev/tty")]
          [tty-out (open-output-file "/dev/tty" '(no-fail no-create no-truncate))])
 
@@ -124,7 +124,7 @@
          all)]
       ;; CI override: none / empty
       [(and env (or (string=? env "none") (string=? env "")))
-       (printf "Features: minimal (core only)~n")
+       (printf "Features: base (core only)~n")
        '()]
       ;; CI override: comma-separated list
       [(and env (> (string-length env) 0))
@@ -140,12 +140,12 @@
       [(and (not env) (file-exists? "/dev/tty"))
        (let ([selected (interactive-feature-select)])
          (if (null? selected)
-           (printf "Features: minimal (core only)~n")
+           (printf "Features: base (core only)~n")
            (printf "Features: ~{~a ~}~n" selected))
          selected)]
-      ;; Batch mode, no env var, no TTY → minimal
+      ;; Batch mode, no env var, no TTY -> base
       [else
-       (printf "Features: minimal (core only, no TTY for selection)~n")
+       (printf "Features: base (core only, no TTY for selection)~n")
        '()])))
 
 ;; ─── Query helpers (used by build scripts after resolution) ──────────────────
diff --git a/features.conf.base b/features.conf.base
new file mode 100644
index 0000000..e2d27a2
--- /dev/null
+++ b/features.conf.base
@@ -0,0 +1,3 @@
+;; features.conf.base — Core shell only, no extras
+;; Copy to features.conf to use: cp features.conf.base features.conf
+(features)
diff --git a/features.conf.minimal b/features.conf.minimal
deleted file mode 100644
index 9146c9f..0000000
--- a/features.conf.minimal
+++ /dev/null
@@ -1,3 +0,0 @@
-;; features.conf.minimal — Core shell only, no extras
-;; Copy to features.conf to use: cp features.conf.minimal features.conf
-(features)
diff --git a/jerboa-src/lib/std/os/limits/sandbox.ss b/jerboa-src/lib/std/os/limits/sandbox.ss
index dd35cd7..e435bcb 100644
--- a/jerboa-src/lib/std/os/limits/sandbox.ss
+++ b/jerboa-src/lib/std/os/limits/sandbox.ss
@@ -24,7 +24,7 @@
 ;;;   net           'allow | 'deny | 'local-only | 'allowlist
 ;;;   net-allow     list of host:port for 'allowlist
 ;;;   net-connect-ports list of TCP destination ports allowed by Landlock
-;;;   syscalls      'unrestricted | 'safe | 'minimal (Linux seccomp hint)
+;;;   syscalls      'unrestricted | 'safe | 'compute-only (Linux seccomp hint)
 ;;;   ptrace?       whether the child may be ptraced (defaults #f)
 ;;;   no-new-privs? whether to set PR_SET_NO_NEW_PRIVS (Linux only)
 ;;;
@@ -557,7 +557,7 @@
   ;;   'safe (default) → default-allow blocklist of dangerous syscalls
   ;;                     (ptrace, kernel-module/kexec, bpf, perf, mount, …)
   ;;                     returning EPERM — robust for arbitrary programs.
-  ;;   'minimal      → strict compute-only allowlist (KILL on violation);
+  ;;   'compute-only → strict compute-only allowlist (KILL on violation);
   ;;                   may break programs needing syscalls outside that set.
   (let ([mode (sandbox-policy-get pol 'syscalls)])
     (cond
@@ -568,7 +568,7 @@
          (begin
            (seccomp-install!
             (case mode
-              [(minimal) compute-only-filter]
+              [(compute-only) compute-only-filter]
               [else      safe-blocklist]))
            'installed)
          (catch (e) 'degraded))])))
diff --git a/jerboa-src/src/jsh/mux-transport.ss b/jerboa-src/src/jsh/mux-transport.ss
index aeda844..8dd5a1d 100644
--- a/jerboa-src/src/jsh/mux-transport.ss
+++ b/jerboa-src/src/jsh/mux-transport.ss
@@ -681,7 +681,7 @@
 ;; Carries mux protocol inside WebSocket binary frames (RFC 6455).
 ;; Server->client frames are unmasked; client->server frames are masked.
 
-;; Minimal WebSocket binary frame encoder.
+;; Base WebSocket binary frame encoder.
 ;; Returns a bytevector containing the frame header + payload.
 (define (ws-encode-binary-frame payload masked?)
   (let* ([plen (bytevector-length payload)]
diff --git a/jerboa-src/src/jsh/worm.ss b/jerboa-src/src/jsh/worm.ss
index 5b20679..5db1869 100644
--- a/jerboa-src/src/jsh/worm.ss
+++ b/jerboa-src/src/jsh/worm.ss
@@ -1,7 +1,7 @@
 ;;; (jsh worm) -- jerboa-wormhole comma command integration.
 ;;;
 ;;; Registers ",worm" with the meta-command registry. The actual wormhole
-;;; implementation is loaded lazily from (wormhole cli), so minimal builds can
+;;; implementation is loaded lazily from (wormhole cli), so base builds can
 ;;; still compile and report a clean "not available" error.
 ;;; jerbuild-library: (jsh worm)
 
diff --git a/jerboa-src/vault-stage/chez/fuse/access.ss b/jerboa-src/vault-stage/chez/fuse/access.ss
index 40a9da9..8c6e9a4 100644
--- a/jerboa-src/vault-stage/chez/fuse/access.ss
+++ b/jerboa-src/vault-stage/chez/fuse/access.ss
@@ -114,7 +114,7 @@
 ;; to unauthorized processes. No error codes — just... nothing there.
 
 (define (stealth-deny-attr)
-  ;; Return a minimal directory attr for the root node.
+  ;; Return a base directory attr for the root node.
   ;; This makes the mountpoint itself appear to exist (it must, since it's
   ;; a mount point) but contain nothing.
   (let ([now (time-second (current-time))])
diff --git a/jsh-base.ss b/jsh-base.ss
new file mode 100644
index 0000000..f6b5f2c
--- /dev/null
+++ b/jsh-base.ss
@@ -0,0 +1,446 @@
+#!chezscheme
+;; jsh-base.ss — Core-only jsh program (no optional features)
+;;
+;; This is the baseline program for builds with zero features enabled.
+;; It provides: shell execution, ,help, ,features, ,room, ,out, ,use, ,<expr>
+;; No coreutils, mux, ssh, vault, aws, record, sandbox, cage, rl, profiler, proxy.
+
+(import (chezscheme) (jsh main) (except (jsh builtins) list-head)
+        (jsh registry) (jsh script)
+        (only (jsh executor) ast->command-text)
+        (only (jsh environment) *command-trace-hook* env-exported-alist)
+        (only (jsh parser) parse-complete-command)
+        (only (jsh ast) ast-pipeline? ast-pipeline-commands ast-pipeline-bang?
+              simple-command? simple-command-assignments simple-command-words
+              assignment? assignment-name assignment-value assignment-op)
+        (only (jsh embed) embed-encrypted? embed-unlocked?)
+        (only (jsh ffi) ffi-termios-restore ffi-termios-save ffi-set-raw-mode
+              ffi-fork-exec ffi-nanosleep-us ffi-byte-ready? ffi-read-byte
+              ffi-fdwrite ffi-terminal-columns ffi-terminal-rows
+              ffi-signal-flag-install ffi-signal-flag-check
+              ffi-dup ffi-dup2 ffi-close-fd ffi-open-raw ffi-getpid
+              ffi-self-exe-path ffi-process-alive ffi-read-pidfile
+              ffi-fork ffi-exit ffi-setsid ffi-ensure-std-fds
+              O_WRONLY O_CREAT O_TRUNC)
+        (only (jsh util) expand-tilde)
+        (only (jsh history) history-add-raw! history-entries history-clear!
+              history-entry-timestamp history-entry-cwd history-entry-command
+              *history-save-override*)
+        (only (compiler compile) jerboa-compile-top)
+        (only (reader reader) jerboa-read)
+        (only (std os path) path-strip-directory path-directory)
+        )
+
+;; ── Feature manifest (base = empty) ──
+(define *jsh-enabled-features* '())
+
+;; ── Session state ──
+
+(define *jsh-unlocked* #f)
+(define *jsh-privacy-mode* #f)
+
+(define (update-prompt-suffix!)
+  (*jsh-prompt-suffix*
+    (let* ([e (embed-encrypted?)]
+           [u *jsh-unlocked*]
+           [lock-part (cond [(and e u) "🔑"] [e "🔒"] [else ""])])
+      (if (string=? lock-part "") "" (string-append " " lock-part)))))
+
+(define (sync-history-enabled!)
+  (*jsh-history-enabled* (not *jsh-privacy-mode*)))
+
+;; ── Tier setup ──
+
+(*jsh-tier* "small")
+;; Force (jsh builtins) registration side effects
+(let () special-builtin? (void))
+
+;; ── String helpers ──
+
+(define (output-to-string proc)
+  (let ([p (open-output-string)])
+    (proc p)
+    (get-output-string p)))
+
+(define (string-trim-whitespace s)
+  (let* ([len (string-length s)]
+         [start (let lp ([i 0])
+                  (if (and (< i len) (char-whitespace? (string-ref s i)))
+                    (lp (+ i 1)) i))]
+         [end (let lp ([i len])
+                (if (and (> i start) (char-whitespace? (string-ref s (- i 1))))
+                  (lp (- i 1)) i))])
+    (substring s start end)))
+
+(define (string-prefix? prefix str)
+  (and (>= (string-length str) (string-length prefix))
+       (string=? (substring str 0 (string-length prefix)) prefix)))
+
+(define (simple-tokenize str)
+  (let ([n (string-length str)])
+    (let lp ([i 0] [tokens '()])
+      (cond
+        ((>= i n) (reverse tokens))
+        ((char-whitespace? (string-ref str i))
+         (lp (+ i 1) tokens))
+        ((char=? (string-ref str i) #\")
+         (let qloop ([j (+ i 1)] [chars '()])
+           (cond
+             ((>= j n)
+              (reverse (cons (list->string (reverse chars)) tokens)))
+             ((char=? (string-ref str j) #\")
+              (lp (+ j 1) (cons (list->string (reverse chars)) tokens)))
+             (else
+              (qloop (+ j 1) (cons (string-ref str j) chars))))))
+        ((char=? (string-ref str i) #\')
+         (let qloop ([j (+ i 1)] [chars '()])
+           (cond
+             ((>= j n)
+              (reverse (cons (list->string (reverse chars)) tokens)))
+             ((char=? (string-ref str j) #\')
+              (lp (+ j 1) (cons (list->string (reverse chars)) tokens)))
+             (else
+              (qloop (+ j 1) (cons (string-ref str j) chars))))))
+        (else
+         (let wloop ([j i])
+           (if (or (>= j n) (char-whitespace? (string-ref str j))
+                   (char=? (string-ref str j) #\") (char=? (string-ref str j) #\'))
+             (lp j (cons (substring str i j) tokens))
+             (wloop (+ j 1)))))))))
+
+(define (simple-join lst sep)
+  (if (null? lst) ""
+    (let loop ([rest (cdr lst)] [acc (car lst)])
+      (if (null? rest) acc
+        (loop (cdr rest) (string-append acc sep (car rest)))))))
+
+(define (meta-command-like? expr-str)
+  (let ([s (string-trim-whitespace expr-str)])
+    (and (> (string-length s) 0)
+         (let ([ch (string-ref s 0)])
+           (or (char-alphabetic? ch)
+               (char=? ch #\_))))))
+
+(define (unknown-meta-command expr-str)
+  (let* ([tokens (simple-tokenize expr-str)]
+         [name (if (null? tokens) expr-str (car tokens))])
+    (fprintf (current-error-port)
+      "jsh: unknown meta-command ',~a' (try ,help)~n"
+      name)
+    (cons "" 2)))
+
+;; ── Jerboa reader / eval ──
+
+(define (jerboa-read-all port)
+  (let lp ([forms '()])
+    (let ([datum (jerboa-read port)])
+      (if (eof-object? datum)
+        (reverse forms)
+        (lp (cons datum forms))))))
+
+(define (jerboa-read-all-from-string str)
+  (jerboa-read-all (open-input-string str)))
+
+(define *jerboa-env-ready* #f)
+
+(define (ensure-jerboa-env!)
+  (unless *jerboa-env-ready*
+    (let ([env (interaction-environment)])
+      (define (import-lib! lib)
+        (eval `(import ,lib) env))
+      (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 text glob)
+          (std os path)
+          (std format)
+          (std sort)
+          (std pregexp)))
+      (for-each
+        try-import-lib!
+        '((std misc lru-cache)
+          (std misc trie)))
+      (define-top-level-value '|jsh:run-cmd|    run-cmd    (interaction-environment))
+      (define-top-level-value '|jsh:run-script| run-script (interaction-environment))
+      (eval '(define run-cmd    |jsh:run-cmd|)    env)
+      (eval '(define run-script |jsh:run-script|) env)
+      ;; Gambit f64vector shims
+      (eval '(define (make-f64vector n . rest)
+               (if (null? rest) (make-flvector n)
+                 (make-flvector n (car rest)))) env)
+      (eval '(define f64vector-ref flvector-ref) env)
+      (eval '(define f64vector-set! flvector-set!) env)
+      ;; Gambit ##process-statistics shim
+      (eval `(define (,(string->symbol "##process-statistics"))
+               (let* ([v (make-flvector 20 0.0)]
+                      [stats (statistics)]
+                      [gc-real (sstats-gc-real stats)]
+                      [gc-real-secs (+ (time-second gc-real)
+                                       (/ (time-nanosecond gc-real) 1e9))]
+                      [heap (exact->inexact (current-memory-bytes))]
+                      [alloc (exact->inexact (bytes-allocated))])
+                 (flvector-set! v 0 (/ (cpu-time) 1000.0))
+                 (flvector-set! v 2 (/ (real-time) 1000.0))
+                 (flvector-set! v 5 gc-real-secs)
+                 (flvector-set! v 6 (exact->inexact (collections)))
+                 (flvector-set! v 7 heap)
+                 (flvector-set! v 15 alloc)
+                 (flvector-set! v 17 heap)
+                 v)) env)
+      ;; Gambit threading shims
+      (eval '(import (only (std misc thread)
+               make-thread thread-start! thread-join!
+               thread-yield! thread-sleep!
+               current-thread thread-name thread?
+               thread-send thread-receive)) env)
+      ;; Gambit SMP primitives
+      (eval `(define (,(string->symbol "##set-parallelism-level!") n) (void)) env)
+      (eval `(define (,(string->symbol "##startup-parallelism!")) (void)) env)
+      (eval `(define ,(string->symbol "##current-vm-processor-count")
+               (let ([count ,(let ([c-sysconf (foreign-procedure "sysconf" (int) long)])
+                               (let ([result (c-sysconf 84)])
+                                 (if (> result 0) result 1)))])
+                 (lambda () count))) env)
+      ;; Gambit I/O shims
+      (eval '(define (force-output . args)
+               (flush-output-port
+                 (if (null? args) (current-output-port) (car args)))) env)
+      (set! *jerboa-env-ready* #t)
+      (void))))
+
+(define (jerboa-eval-forms jerboa-forms)
+  (ensure-jerboa-env!)
+  (let ([env (interaction-environment)])
+    (let loop ([fs jerboa-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
+             (let ([chez-form (jerboa-compile-top form)])
+               (loop (cdr fs) (eval chez-form env))))))))))
+
+(define (format-result result)
+  (cond
+    [(eq? result (void)) ""]
+    [(or (pair? result) (vector? result))
+     (output-to-string (lambda (port) (pretty-print result port)))]
+    [else
+     (output-to-string (lambda (port) (write result port)))]))
+
+;; ,use file.ss — compile and load a Jerboa source file
+(define (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))))
+
+;; ── Meta-command handler ──
+
+(*meta-command-handler*
+  (lambda (expr-str)
+    (guard (exn
+             [#t (cons
+                   (output-to-string
+                     (lambda (port)
+                       (display "Jerboa error: " port)
+                       (display-condition exn port)))
+                   1)])
+      (cond
+        ;; Registry-based dispatch — packages self-register via meta-register!
+        [(let ([cmd (car (simple-tokenize expr-str))])
+           (and (not (string=? cmd "")) (meta-lookup cmd)))
+         => (lambda (handler)
+              (let* ([tokens (simple-tokenize expr-str)]
+                     [args (if (null? (cdr tokens)) ""
+                               (string-trim-whitespace
+                                 (substring expr-str (string-length (car tokens))
+                                            (string-length expr-str))))])
+                (handler args)))]
+        ;; ,help / ,h / ,?
+        [(or (string=? expr-str "help") (string=? expr-str "h") (string=? expr-str "?"))
+         (let ([lines
+                '("jsh meta-commands (base core):"
+                  "  ,help  ,h  ,?             show this help"
+                  "  ,features                  show enabled features"
+                  "  ,room                      GC & heap report"
+                  "  ,room #t                   verbose report with all object types"
+                  "  ,out [-o file] <cmd>       capture stdout+stderr to file"
+                  "  ,use <file.ss>             compile and load a Jerboa source file"
+                  "  ,<expr>                    evaluate a Jerboa Scheme expression")])
+           (for-each (lambda (l) (display l) (newline)) lines)
+           (cons "" 0))]
+        ;; ,features — show what's compiled in
+        [(string=? expr-str "features")
+         (if (null? *jsh-enabled-features*)
+           (display "jsh: base core (no optional features)\n")
+           (begin
+             (display "jsh enabled features:\n")
+             (for-each
+               (lambda (entry)
+                 (fprintf (current-output-port) "  ~a  ~a~n" (car entry) (cdr entry)))
+               *jsh-enabled-features*)))
+         (cons "" 0)]
+        ;; ,use file.ss
+        [(string-prefix? "use " expr-str)
+         (let ([result (handle-use-command
+                         (substring expr-str 4 (string-length expr-str)))])
+           (cons (format-result result) 0))]
+        ;; ,room — GC & heap report
+        [(or (string=? "room" expr-str) (string=? "room " expr-str))
+         (room)
+         (cons "" 0)]
+        ;; ,room #t — verbose
+        [(string-prefix? "room " expr-str)
+         (room #t)
+         (cons "" 0)]
+        ;; ,out — capture stdout+stderr to file
+        [(or (string=? expr-str "out")
+             (string-prefix? "out " expr-str))
+         (let ([rest (if (string=? expr-str "out") ""
+                         (string-trim-whitespace
+                           (substring expr-str 4 (string-length expr-str))))])
+           (cond
+             ((or (string=? rest "") (string-prefix? "--help" rest) (string=? rest "-h"))
+              (display "Usage: ,out [-o file] <command>\n")
+              (display "  Captures stdout and stderr from <command> at the fd level.\n")
+              (display "  Output is displayed after the command completes and saved to a file.\n")
+              (display "  -o file   save output to <file> (default: /tmp/jsh-out-<pid>.txt)\n")
+              (display "Example: ,out -o build.log make -j8\n")
+              (display "         ,out docker build .\n")
+              (cons "" 0))
+             (else
+              (let* ([tokens (simple-tokenize rest)]
+                     [out-file #f]
+                     [cmd-tokens '()])
+                ;; Parse -o file option
+                (let loop ([toks tokens])
+                  (cond
+                    ((null? toks) (set! cmd-tokens '()))
+                    ((and (string=? (car toks) "-o") (pair? (cdr toks)))
+                     (set! out-file (cadr toks))
+                     (set! cmd-tokens (cddr toks)))
+                    (else
+                     (set! cmd-tokens toks))))
+                (when (not out-file)
+                  (set! out-file (string-append "/tmp/jsh-out-"
+                                   (number->string (ffi-getpid)) ".txt")))
+                (if (null? cmd-tokens)
+                  (begin
+                    (display "jsh: ,out: no command specified\n")
+                    (cons "" 1))
+                  (let* ([cmd-str (let loop ([toks cmd-tokens] [acc ""])
+                                    (if (null? toks) acc
+                                      (loop (cdr toks)
+                                            (if (string=? acc "")
+                                              (car toks)
+                                              (string-append acc " " (car toks))))))]
+                         [file-fd (ffi-open-raw out-file
+                                    (bitwise-ior O_WRONLY O_CREAT O_TRUNC) #o666)])
+                    (if (< file-fd 0)
+                      (let ([reason
+                              (cond
+                                ((and (file-exists? out-file)
+                                      (file-directory? out-file))
+                                 "Is a directory")
+                                ((let ([dir (path-parent out-file)])
+                                   (and (> (string-length dir) 0)
+                                        (not (file-exists? dir))))
+                                 "No such file or directory")
+                                (else "Permission denied"))])
+                        (fprintf (current-error-port)
+                          "jsh: ,out: ~a: ~a~n" out-file reason)
+                        (cons "" 1))
+                      (let* ([saved-out-port (current-output-port)]
+                             [saved-err-port (current-error-port)]
+                             [_ (begin (flush-output-port saved-out-port)
+                                       (flush-output-port saved-err-port))]
+                             [saved-1 (ffi-dup 1)]
+                             [saved-2 (ffi-dup 2)])
+                        (ffi-dup2 file-fd 1)
+                        (ffi-dup2 file-fd 2)
+                        (ffi-close-fd file-fd)
+                        (let* ([cap-port (transcoded-port
+                                           (open-fd-output-port
+                                             (ffi-dup 1) (buffer-mode line))
+                                           (make-transcoder (utf-8-codec)))]
+                               [err-port (transcoded-port
+                                           (open-fd-output-port
+                                             (ffi-dup 2) (buffer-mode line))
+                                           (make-transcoder (utf-8-codec)))])
+                          (current-output-port cap-port)
+                          (current-error-port err-port)
+                          (let ([status (guard (e [#t (fprintf err-port
+                                                       "jsh: ~a~n"
+                                                       (if (message-condition? e)
+                                                         (condition-message e)
+                                                         e))
+                                                     1])
+                                          (run-cmd cmd-str))])
+                            (flush-output-port cap-port)
+                            (flush-output-port err-port)
+                            (close-port cap-port)
+                            (close-port err-port)
+                            ;; Restore original fds
+                            (ffi-dup2 saved-1 1)
+                            (ffi-dup2 saved-2 2)
+                            (ffi-close-fd saved-1)
+                            (ffi-close-fd saved-2)
+                            ;; Restore Scheme ports
+                            (current-output-port saved-out-port)
+                            (current-error-port saved-err-port)
+                            ;; Display captured output
+                            (guard (e [#t (void)])
+                              (let ([content (call-with-input-file out-file get-string-all)])
+                                (display content)))
+                            (fprintf (current-error-port)
+                              "~n[captured to ~a]~n" out-file)
+                            (cons "" (if (integer? status) status 1))))))))))))]
+        ;; Normal Jerboa eval (catch-all)
+        [else
+         (if (meta-command-like? expr-str)
+           (unknown-meta-command expr-str)
+           (let* ([jerboa-forms (jerboa-read-all-from-string expr-str)]
+                  [result (jerboa-eval-forms jerboa-forms)])
+             (cons (format-result result) 0)))]))))
+
+;; ── Main entry ──
+
+(define (get-real-args)
+  (let ([argc-str (getenv "JSH_ARGC")])
+    (if argc-str
+      (let ([argc (string->number argc-str)])
+        (let loop ([i 0] [acc '()])
+          (if (>= i argc)
+            (reverse acc)
+            (let ([val (getenv (format "JSH_ARG~a" i))])
+              (loop (+ i 1) (cons (or val "") acc))))))
+      (let ([cmdline (command-line)])
+        (if (pair? cmdline) (cdr cmdline) '())))))
+
+;; Inform main.ss whether this is an encrypted build
+(*jsh-embed-encrypted* (embed-encrypted?))
+
+;; Initialize prompt suffix
+(update-prompt-suffix!)
+
+(dynamic-wind
+  (lambda () (void))
+  (lambda () (apply main (get-real-args)))
+  (lambda () (void)))
diff --git a/jsh-generate.ss b/jsh-generate.ss
index fe51f70..a165553 100644
--- a/jsh-generate.ss
+++ b/jsh-generate.ss
@@ -4,7 +4,7 @@
 ;;; Reads jsh.ss, replaces the line beginning with
 ;;;   (define *jsh-enabled-features* ...
 ;;; with an expanded manifest of (name . description) pairs from features.def,
-;;; and writes jsh-generated.ss. Always uses jsh.ss as the source — minimal
+;;; and writes jsh-generated.ss. Always uses jsh.ss as the source — base
 ;;; vs. full builds differ only in the manifest contents (and which optional
 ;;; modules the platform build script chooses to compile).
 ;;;
@@ -152,5 +152,5 @@
 
     (close-output-port out)
     (printf "  jsh-generated.ss: ~a (features: ~a)~n"
-            (if (null? enabled-features) "minimal" "full")
+            (if (null? enabled-features) "base" "full")
             (length enabled-features))))
diff --git a/jsh-minimal.ss b/jsh-minimal.ss
deleted file mode 100644
index fef9d17..0000000
--- a/jsh-minimal.ss
+++ /dev/null
@@ -1,446 +0,0 @@
-#!chezscheme
-;; jsh-minimal.ss — Core-only jsh program (no optional features)
-;;
-;; This is the baseline program for builds with zero features enabled.
-;; It provides: shell execution, ,help, ,features, ,room, ,out, ,use, ,<expr>
-;; No coreutils, mux, ssh, vault, aws, record, sandbox, cage, rl, profiler, proxy.
-
-(import (chezscheme) (jsh main) (except (jsh builtins) list-head)
-        (jsh registry) (jsh script)
-        (only (jsh executor) ast->command-text)
-        (only (jsh environment) *command-trace-hook* env-exported-alist)
-        (only (jsh parser) parse-complete-command)
-        (only (jsh ast) ast-pipeline? ast-pipeline-commands ast-pipeline-bang?
-              simple-command? simple-command-assignments simple-command-words
-              assignment? assignment-name assignment-value assignment-op)
-        (only (jsh embed) embed-encrypted? embed-unlocked?)
-        (only (jsh ffi) ffi-termios-restore ffi-termios-save ffi-set-raw-mode
-              ffi-fork-exec ffi-nanosleep-us ffi-byte-ready? ffi-read-byte
-              ffi-fdwrite ffi-terminal-columns ffi-terminal-rows
-              ffi-signal-flag-install ffi-signal-flag-check
-              ffi-dup ffi-dup2 ffi-close-fd ffi-open-raw ffi-getpid
-              ffi-self-exe-path ffi-process-alive ffi-read-pidfile
-              ffi-fork ffi-exit ffi-setsid ffi-ensure-std-fds
-              O_WRONLY O_CREAT O_TRUNC)
-        (only (jsh util) expand-tilde)
-        (only (jsh history) history-add-raw! history-entries history-clear!
-              history-entry-timestamp history-entry-cwd history-entry-command
-              *history-save-override*)
-        (only (compiler compile) jerboa-compile-top)
-        (only (reader reader) jerboa-read)
-        (only (std os path) path-strip-directory path-directory)
-        )
-
-;; ── Feature manifest (minimal = empty) ──
-(define *jsh-enabled-features* '())
-
-;; ── Session state ──
-
-(define *jsh-unlocked* #f)
-(define *jsh-privacy-mode* #f)
-
-(define (update-prompt-suffix!)
-  (*jsh-prompt-suffix*
-    (let* ([e (embed-encrypted?)]
-           [u *jsh-unlocked*]
-           [lock-part (cond [(and e u) "🔑"] [e "🔒"] [else ""])])
-      (if (string=? lock-part "") "" (string-append " " lock-part)))))
-
-(define (sync-history-enabled!)
-  (*jsh-history-enabled* (not *jsh-privacy-mode*)))
-
-;; ── Tier setup ──
-
-(*jsh-tier* "small")
-;; Force (jsh builtins) registration side effects
-(let () special-builtin? (void))
-
-;; ── String helpers ──
-
-(define (output-to-string proc)
-  (let ([p (open-output-string)])
-    (proc p)
-    (get-output-string p)))
-
-(define (string-trim-whitespace s)
-  (let* ([len (string-length s)]
-         [start (let lp ([i 0])
-                  (if (and (< i len) (char-whitespace? (string-ref s i)))
-                    (lp (+ i 1)) i))]
-         [end (let lp ([i len])
-                (if (and (> i start) (char-whitespace? (string-ref s (- i 1))))
-                  (lp (- i 1)) i))])
-    (substring s start end)))
-
-(define (string-prefix? prefix str)
-  (and (>= (string-length str) (string-length prefix))
-       (string=? (substring str 0 (string-length prefix)) prefix)))
-
-(define (simple-tokenize str)
-  (let ([n (string-length str)])
-    (let lp ([i 0] [tokens '()])
-      (cond
-        ((>= i n) (reverse tokens))
-        ((char-whitespace? (string-ref str i))
-         (lp (+ i 1) tokens))
-        ((char=? (string-ref str i) #\")
-         (let qloop ([j (+ i 1)] [chars '()])
-           (cond
-             ((>= j n)
-              (reverse (cons (list->string (reverse chars)) tokens)))
-             ((char=? (string-ref str j) #\")
-              (lp (+ j 1) (cons (list->string (reverse chars)) tokens)))
-             (else
-              (qloop (+ j 1) (cons (string-ref str j) chars))))))
-        ((char=? (string-ref str i) #\')
-         (let qloop ([j (+ i 1)] [chars '()])
-           (cond
-             ((>= j n)
-              (reverse (cons (list->string (reverse chars)) tokens)))
-             ((char=? (string-ref str j) #\')
-              (lp (+ j 1) (cons (list->string (reverse chars)) tokens)))
-             (else
-              (qloop (+ j 1) (cons (string-ref str j) chars))))))
-        (else
-         (let wloop ([j i])
-           (if (or (>= j n) (char-whitespace? (string-ref str j))
-                   (char=? (string-ref str j) #\") (char=? (string-ref str j) #\'))
-             (lp j (cons (substring str i j) tokens))
-             (wloop (+ j 1)))))))))
-
-(define (simple-join lst sep)
-  (if (null? lst) ""
-    (let loop ([rest (cdr lst)] [acc (car lst)])
-      (if (null? rest) acc
-        (loop (cdr rest) (string-append acc sep (car rest)))))))
-
-(define (meta-command-like? expr-str)
-  (let ([s (string-trim-whitespace expr-str)])
-    (and (> (string-length s) 0)
-         (let ([ch (string-ref s 0)])
-           (or (char-alphabetic? ch)
-               (char=? ch #\_))))))
-