#!/usr/bin/env bash # sandbox — the Neuron STACK sandbox: assemble a WHOLE stack of repos into ONE # combined worktree workspace, wired so they build/run TOGETHER, on an isolated # clean base — so an agent (or Will) can work on the full stack at once instead of # one repo at a time. # # It is the multi-repo generalisation of `nsbx` (this same directory): where # `nsbx dev` stands up ONE repo's worktree + an isolated engram, `sandbox` stands # up EVERY constituent repo of a named stack as sibling git worktrees under a # single workspace, mirroring their on-disk relative layout so the cross-repo # `../foundation/el` imports resolve to the SANDBOX copy — never the live tree. # # sandbox el-stack # elc compiler + EL language + framework + tooling (+ consumers) # sandbox neuron-stack # runtime/soul + engram + app/UI (the full product) # sandbox list # list assembled stack workspaces # sandbox status # inspect one # sandbox build # run the workspace's combined build.sh # sandbox down # tear down: remove every worktree, drop the workspace # # RAILS (always): # * worktrees fork off each repo's COMMITTED HEAD -> a clean, reproducible base # (the dirty state of the live checkout is deliberately NOT carried in). # * the workspace lives at a PERSISTENT path (never /tmp — ablated on compaction). # * NEVER touches the live soul/engram (:7770 / :8742). It only creates git # worktrees + a build script; bringing up an isolated engram is delegated, # opt-in, to `nsbx` (which already guards the live store & ports). # * idempotent & safe: refuses to clobber an existing workspace; teardown removes # worktrees through their origin repo and prunes — branches are kept by default. # * own-the-core: pure bash + git worktree. No new dependencies. set -uo pipefail # ---------------------------------------------------------------- constants ---- # Root that holds all the peer repos (neuron, foundation/el, products/*, ...). DEV_ROOT="${NEURON_DEV_ROOT:-$HOME/Development/neuron-technologies}" # Where assembled stack workspaces live (persistent; sibling to el-worktrees/). STACK_ROOT="${NSBX_STACK_ROOT:-$DEV_ROOT/stack-worktrees}" EL_REPO_REL="foundation/el" LIVE_ENGRAM_PORT=8742 # live engram — sandbox must never bind it LIVE_SOUL_PORT=7770 # live soul — sandbox must never bind it # nsbx (single-repo isolated-engram tool) lives next to this script. NSBX="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/nsbx" C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'; C_CYN=$'\033[36m'; C_DIM=$'\033[2m'; C_BLD=$'\033[1m'; C_0=$'\033[0m' # ---------------------------------------------------------------- helpers ------ die(){ printf '%serror:%s %s\n' "$C_RED" "$C_0" "$*" >&2; exit 1; } log(){ printf '%s==>%s %s\n' "$C_BLD" "$C_0" "$*" >&2; } info(){ printf ' %s\n' "$*" >&2; } ok(){ printf ' %s%s%s\n' "$C_GRN" "$*" "$C_0" >&2; } warn(){ printf ' %s%s%s\n' "$C_YEL" "$*" "$C_0" >&2; } need(){ command -v "$1" >/dev/null 2>&1 || die "missing dependency: $1"; } # ---------------------------------------------------------------- profiles ----- # profile_repos : emit one line per constituent repo: # | | # The relpath is preserved INSIDE the workspace, so all cross-repo `../foundation/el` # references resolve to the sandbox copy automatically (mirrored-layout wiring). profile_repos(){ case "$1" in el-stack) # The compiler+language+framework+tooling are all ONE repo (foundation/el). # Its downstream SDK consumers (forge, dharma) + the language-faculty POC come # along so a change to elc can be proven end-to-end across the kit. cat <<'EOF' foundation/el | required | elc + elb compiler, el_runtime, engram source, el-ui framework, elp/ql/ide/epm tooling engram-language | optional | language-faculty reference POC (Python) — ported into el/elp foundation/forge | optional | downstream SDK consumer — `make build` (imprint forge CLI) foundation/dharma | optional | downstream SDK consumer — CGI provenance registry EOF ;; neuron-stack) # The full product: substrate (el) + soul + UI. Engram is NOT a separate repo # (its source lives in foundation/el/engram/src/server.el). cat <<'EOF' foundation/el | required | substrate: elc + el_runtime + engram source + elp NLG the soul imports neuron | required | the soul (:7770) + engram build; soul.el imports ../foundation/el/elp/src/elp.el products/NeuronUI | required | the app/UI (Kotlin/Compose desktop client; bundles the soul binary) products/web | optional | marketing site + interactive soul-demo EOF ;; *) return 1;; esac } is_profile(){ profile_repos "$1" >/dev/null 2>&1; } ws_dir(){ printf '%s/%s-%s' "$STACK_ROOT" "$1" "$2"; } # /- ws_branch(){ printf 'sandbox/%s-%s' "$1" "$2"; } # branch name used in each repo manifest(){ printf '%s/.stack-manifest.json' "$1"; } # /.stack-manifest.json # ================================================================ up =========== cmd_up(){ local profile="$1"; shift local name="" branch="" base_override="" minimal=0 [ $# -gt 0 ] && [ "${1#-}" = "$1" ] && { name="$1"; shift; } || die "usage: sandbox $profile [--minimal] [--branch B] [--base REF]" while [ $# -gt 0 ]; do case "$1" in --minimal) minimal=1; shift;; --branch) branch="$2"; shift 2;; --base) base_override="$2"; shift 2;; *) die "unknown flag: $1";; esac; done need git is_profile "$profile" || die "unknown profile: $profile (try: el-stack | neuron-stack)" local ws; ws="$(ws_dir "$profile" "$name")" [ -n "$branch" ] || branch="$(ws_branch "$profile" "$name")" # -------- pre-flight (fail before creating anything) -------- case "$ws" in /tmp/*|/private/tmp/*|/var/tmp/*) die "refusing workspace under a temp dir ($ws) — temp dirs are ablated on compaction; set NSBX_STACK_ROOT to a persistent path";; esac [ -e "$ws" ] && die "workspace already exists: $ws (sandbox down $profile $name first)" # resolve + validate every repo, and pick a base sha per repo, BEFORE touching disk local -a rels roles bases origins wts local line rel role_extra role req origin base wt while IFS= read -r line; do [ -z "${line// }" ] && continue rel="$(printf '%s' "$line" | cut -d'|' -f1 | xargs)" req="$(printf '%s' "$line" | cut -d'|' -f2 | xargs)" role="$(printf '%s' "$line" | cut -d'|' -f3- | sed 's/^ *//')" [ "$minimal" -eq 1 ] && [ "$req" = "optional" ] && continue origin="$DEV_ROOT/$rel" git -C "$origin" rev-parse --git-dir >/dev/null 2>&1 || { [ "$req" = "required" ] && die "required repo missing or not a git repo: $origin" warn "skipping optional repo (missing): $rel"; continue; } if [ -n "$base_override" ]; then base="$base_override"; else base="$(git -C "$origin" rev-parse HEAD)"; fi wt="$ws/$rel" [ -e "$wt" ] && die "target worktree path already exists: $wt" rels+=("$rel"); roles+=("$role"); origins+=("$origin"); bases+=("$base"); wts+=("$wt") done < <(profile_repos "$profile") [ "${#rels[@]}" -gt 0 ] || die "no repos resolved for profile $profile" log "assembling '$profile' workspace '$name'" info "workspace: $ws" info "branch: $branch (created in each repo, off its committed HEAD)" mkdir -p "$ws" # -------- create a worktree per repo (mirrored relpath layout) -------- local i n="${#rels[@]}" SB_DONE_WTS=(); SB_DONE_ORIGINS=() for ((i=0; i&1)" \ || { _rollback; die "git worktree add failed for $rel (existing branch $branch):"$'\n'" $gerr"; } else gerr="$(git -C "$origin" worktree add -b "$branch" "$wt" "$base" 2>&1)" \ || { _rollback; die "git worktree add -b $branch failed for $rel (base $base):"$'\n'" $gerr"; } fi SB_DONE_WTS+=("$wt"); SB_DONE_ORIGINS+=("$origin") ok "worktree: $rel -> ${wt#$ws/} (branch $branch @ ${base:0:9})" done local el_ws="$ws/$EL_REPO_REL" _write_env "$ws" "$profile" "$name" "$branch" "$el_ws" _write_manifest "$ws" "$profile" "$name" "$branch" _write_build "$ws" "$profile" "$el_ws" _write_readme "$ws" "$profile" "$name" "$branch" "$el_ws" # -------- summary -------- echo >&2 printf '%s STACK WORKSPACE READY — %s / %s%s\n' "$C_BLD" "$profile" "$name" "$C_0" >&2 printf ' %-11s %s\n' "workspace" "$ws" >&2 printf ' %-11s %s\n' "branch" "$branch (in each repo)" >&2 printf ' %-11s %s\n' "repos" "$n worktrees, mirrored layout" >&2 echo >&2 info "get in: cd $ws && source .stack-env" info "build all: sandbox build $profile $name # (or: cd $ws && ./build.sh)" if [ "$profile" = "neuron-stack" ]; then info "isolated engram (opt-in, via nsbx):" info " nsbx create $profile-$name --source $el_ws && nsbx up $profile-$name" fi info "tear down: sandbox down $profile $name # removes all worktrees; branches kept" } # _rollback : remove any worktrees already created this run (globals set by cmd_up) SB_DONE_WTS=(); SB_DONE_ORIGINS=() _rollback(){ local j [ "${#SB_DONE_WTS[@]}" -gt 0 ] && warn "rolling back ${#SB_DONE_WTS[@]} partial worktree(s)" for ((j=${#SB_DONE_WTS[@]}-1; j>=0; j--)); do git -C "${SB_DONE_ORIGINS[$j]}" worktree remove --force "${SB_DONE_WTS[$j]}" 2>/dev/null || rm -rf "${SB_DONE_WTS[$j]}" git -C "${SB_DONE_ORIGINS[$j]}" worktree prune 2>/dev/null || true done } # ---------------------------------------------------------------- writers ------ _write_env(){ local ws="$1" profile="$2" name="$3" branch="$4" el_ws="$5" local elc_dir="$el_ws/lang/dist/platform" cat > "$ws/.stack-env" <> "$ws/.stack-env" </dev/null || source .stack-env\n' > "$ws/.envrc" } _write_manifest(){ local ws="$1" profile="$2" name="$3" branch="$4" # emit worktree records from git's own worktree list, filtered to this workspace python3 - "$ws" "$profile" "$name" "$branch" "$DEV_ROOT" <<'PY' import json, os, subprocess, sys ws, profile, name, branch, dev = sys.argv[1:6] repos = [] for rel in sorted(os.listdir(ws)) if False else []: pass # discover worktrees by walking one level of relpaths we created def git(root, *a): return subprocess.run(["git","-C",root,*a], capture_output=True, text=True).stdout.strip() for dirpath, dirnames, filenames in os.walk(ws): if ".git" in filenames or ".git" in dirnames: rel = os.path.relpath(dirpath, ws) toplevel = git(dirpath, "rev-parse", "--show-toplevel") common = git(dirpath, "rev-parse", "--git-common-dir") origin = os.path.realpath(os.path.join(common, "..")) head = git(dirpath, "rev-parse", "HEAD") repos.append({"rel": rel, "worktree": dirpath, "origin": origin, "branch": branch, "head": head}) dirnames[:] = [] # don't descend into a repo repos.sort(key=lambda r: r["rel"]) json.dump({"profile": profile, "name": name, "branch": branch, "workspace": ws, "repos": repos}, open(os.path.join(ws, ".stack-manifest.json"), "w"), indent=2) PY } _write_build(){ local ws="$1" profile="$2" el_ws="$3" cat > "$ws/build.sh" <<'BUILD' #!/usr/bin/env bash # build.sh — build the assembled stack together, in dependency order. # Generated by `sandbox`. Run from the workspace root (it sources .stack-env). set -uo pipefail cd "$(dirname "$0")"; source ./.stack-env say(){ printf '\033[1m==>\033[0m %s\n' "$*"; } ok(){ printf ' \033[32m%s\033[0m\n' "$*"; } bad(){ printf ' \033[31m%s\033[0m\n' "$*"; } # locate an elc that runs on THIS machine (darwin-arm64 / linux-amd64), from the sandbox el find_elc(){ local d="$EL_REPO/lang/dist/platform" case "$(uname -s)-$(uname -m)" in Darwin-arm64) echo "$d/elc-darwin-arm64";; Linux-x86_64) echo "$d/elc-linux-amd64";; *) echo "$d/elc";; esac } ELC="$(find_elc)"; [ -x "$ELC" ] || ELC="$EL_REPO/lang/dist/platform/elc" say "elc: $ELC" [ -x "$ELC" ] && ok "$("$ELC" 2>&1 | head -1 || echo present)" || { bad "elc not executable"; exit 1; } # canonical runtime C to link (CI-published release copy; ~8 copies exist in-tree) RT="$EL_REPO/lang/releases/v1.0.0-20260501" [ -f "$RT/el_runtime.c" ] || RT="$EL_REPO/lang/el-compiler/runtime" [ -f "$RT/el_runtime.c" ] && ok "el_runtime: $RT/el_runtime.c" || bad "no el_runtime.c found under $EL_REPO/lang" BUILD if [ "$profile" = "el-stack" ]; then cat >> "$ws/build.sh" <<'BUILD' # ---- EL STACK: prove elc + the el stuff (incl. the el-ui framework) build together ---- say "el-ui framework present: $EL_REPO/ui" [ -d "$EL_REPO/ui" ] && ok "framework dir present ($(ls "$EL_REPO/ui" | tr '\n' ' '))" || bad "no ui/ dir" # end-to-end compiler proof: elc compiles a real, substantial stack source to C, # then cc links it against the runtime -> a working native binary. B="$(mktemp -d)" say "elc end-to-end: compile engram/src/server.el and link a native binary" if "$ELC" "$EL_REPO/engram/src/server.el" > "$B/x.c" 2>"$B/elc.err"; then ok "elc -> C ($(wc -c <"$B/x.c" | tr -d ' ') bytes)" if cc -std=c11 -O2 -w -I "$RT" -o "$B/x" "$B/x.c" "$RT/el_runtime.c" -lcurl -lpthread -lm 2>"$B/cc.err"; then ok "cc link ok -> native binary $(ls -lh "$B/x" | awk '{print $5}') (elc + runtime build together)" else bad "cc link failed:"; grep -i 'error:' "$B/cc.err" | sort -u | head | sed 's/^/ /' fi else bad "elc compile failed:"; sed 's/^/ /' "$B/elc.err" | head fi # optional downstream consumer: forge builds on the SDK (make build) — proves the # freshly-assembled el SDK still compiles a real downstream repo. FORGE="$STACK_ROOT_WS/foundation/forge" if [ -f "$FORGE/Makefile" ]; then say "downstream consumer: foundation/forge (make build)" ( cd "$FORGE" && EL_REPO="$EL_REPO" PATH="$EL_REPO/lang/dist/platform:$PATH" make build ) \ && ok "forge built against the sandbox SDK" || bad "forge build failed (see above)" fi say "el-stack build complete" BUILD else cat >> "$ws/build.sh" <<'BUILD' # ---- 1) engram (elc engram/src/server.el -> cc engram.c el_runtime.c), from the sandbox el ---- say "build engram from $EL_REPO/engram/src/server.el" B="$(mktemp -d)" if "$ELC" "$EL_REPO/engram/src/server.el" > "$B/engram.c" 2>"$B/elc.err"; then ok "elc -> engram.c ($(wc -c <"$B/engram.c" | tr -d ' ') bytes)" if cc -std=c11 -O2 -w -I "$RT" -o "$B/engram" \ "$B/engram.c" "$RT/el_runtime.c" -lcurl -lpthread -lm 2>"$B/cc.err"; then ok "engram binary built: $(ls -lh "$B/engram" | awk '{print $5}')" else bad "engram cc link failed:"; grep -i 'error:' "$B/cc.err" | sort -u | head | sed 's/^/ /' fi else bad "engram elc transpile failed:"; sed 's/^/ /' "$B/elc.err" fi # ---- 2) soul (imports ../foundation/el/elp/src/elp.el — resolves to SANDBOX el) ---- say "soul present + cross-repo import resolves inside the sandbox" [ -f "$NEURON_REPO/soul.el" ] && ok "neuron/soul.el present" || bad "no soul.el" if [ -f "$EL_REPO/elp/src/elp.el" ]; then ok "../foundation/el/elp/src/elp.el resolves -> $EL_REPO/elp/src/elp.el (sandbox copy)" else bad "elp NLG source missing under sandbox el" fi # soul is a heavy single-TU compile; prove elc parses it rather than a full link if "$ELC" "$NEURON_REPO/soul.el" > "$B/soul.c" 2>"$B/soul.err"; then ok "elc compiled soul.el -> $(wc -c <"$B/soul.c" | tr -d ' ') bytes of C (cross-repo imports resolved)" else bad "soul.el elc compile failed:"; sed 's/^/ /' "$B/soul.err" | head fi # ---- 3) UI (present + buildable; gradle/JDK21 is heavy so we don't run it here) ---- say "app/UI present + buildable" if [ -f "$NEURONUI_REPO/build.sh" ] || [ -f "$NEURONUI_REPO/gradlew" ]; then ok "NeuronUI build entry present (./build.sh / ./gradlew — needs JDK21; run: cd $NEURONUI_REPO && ./gradlew run)" else bad "no NeuronUI build entry" fi say "neuron-stack build complete (engram compiled, soul compiled, UI present & buildable)" BUILD fi chmod +x "$ws/build.sh" } _write_readme(){ local ws="$1" profile="$2" name="$3" branch="$4" el_ws="$5" cat > "$ws/README.md" </dev/null || rm -rf "$wt" git -C "$origin" worktree prune 2>/dev/null || true ok "removed worktree: ${wt#$ws/}" fi if [ "$del_branch" -eq 1 ]; then git -C "$origin" branch -D "$branch" 2>/dev/null && ok "deleted branch $branch in ${origin#$DEV_ROOT/}" || true fi done # drop the (now worktree-free) workspace tree rm -rf "$ws" ok "workspace removed: $ws" [ "$del_branch" -eq 1 ] || info "branch '$branch' kept in each repo (use --delete-branch to drop)" ok "down '$profile/$name' complete (live untouched)" } # ================================================================ build ======== cmd_build(){ local profile="$1" name="$2"; local ws; ws="$(ws_dir "$profile" "$name")" [ -x "$ws/build.sh" ] || die "no build.sh in $ws (is it assembled? sandbox $profile $name)" exec "$ws/build.sh" } # ================================================================ list/status == cmd_list(){ [ -d "$STACK_ROOT" ] || { info "no stack workspaces (root $STACK_ROOT absent)"; return 0; } local mf found=0 for mf in "$STACK_ROOT"/*/.stack-manifest.json; do [ -f "$mf" ] || continue; found=1 python3 -c "import json;d=json.load(open('$mf'));print(' %-22s %-8s %2d repos branch=%s'%(d['profile']+'/'+d['name'],'',len(d['repos']),d['branch']))" 2>/dev/null done [ "$found" -eq 1 ] || info "no assembled stack workspaces under $STACK_ROOT" } cmd_status(){ local profile="$1" name="$2"; local ws; ws="$(ws_dir "$profile" "$name")" local mf; mf="$(manifest "$ws")"; [ -f "$mf" ] || die "no such workspace: $ws" log "stack '$profile/$name'"; info "workspace: $ws" python3 - "$mf" <<'PY' import json,sys,subprocess d=json.load(open(sys.argv[1])) print(f" branch: {d['branch']}") for r in d['repos']: st=subprocess.run(["git","-C",r["worktree"],"status","--porcelain"],capture_output=True,text=True).stdout n=len([l for l in st.splitlines() if l.strip()]) print(f" {r['rel']:<20} {r['head'][:9]} {'clean' if n==0 else str(n)+' changed'}") PY } # ================================================================ usage/main === usage(){ cat >&2 <${C_0} [--minimal] elc + EL language + el-ui framework + tooling (+ SDK consumers) ${C_CYN}sandbox neuron-stack ${C_0} [--minimal] runtime/soul + engram + app/UI (the full product) ${C_CYN}sandbox build ${C_0} build the assembled stack together (runs its build.sh) ${C_CYN}sandbox status ${C_0} inspect one workspace ${C_CYN}sandbox list${C_0} list assembled workspaces ${C_CYN}sandbox down ${C_0} [--delete-branch] tear down (remove worktrees; branch kept) Flags: --minimal only the required repos --branch B branch name --base REF fork point Env: NSBX_STACK_ROOT (workspace root, default \$DEV_ROOT/stack-worktrees) NEURON_DEV_ROOT Each constituent repo becomes a git worktree at its natural relpath inside the workspace, so cross-repo ../foundation/el imports resolve to the SANDBOX el. The live soul/engram (:$LIVE_SOUL_PORT / :$LIVE_ENGRAM_PORT) are never touched; isolated-engram bring-up is delegated to nsbx. EOF } main(){ local cmd="${1:-}"; shift || true case "$cmd" in el-stack|neuron-stack) cmd_up "$cmd" "$@";; up) [ $# -ge 1 ] || die "usage: sandbox up "; local p="$1"; shift; cmd_up "$p" "$@";; down) [ $# -ge 2 ] || die "usage: sandbox down "; cmd_down "$@";; build) [ $# -ge 2 ] || die "usage: sandbox build "; cmd_build "$@";; status) [ $# -ge 2 ] || die "usage: sandbox status "; cmd_status "$@";; list|ls) cmd_list "$@";; ""|-h|--help|help) usage;; *) die "unknown command: $cmd (try: sandbox help)";; esac } main "$@"