diff --git a/tools/neuron-sandbox/STACK.md b/tools/neuron-sandbox/STACK.md new file mode 100644 index 0000000..0c7f140 --- /dev/null +++ b/tools/neuron-sandbox/STACK.md @@ -0,0 +1,120 @@ +# sandbox — the Neuron STACK sandbox + +**Work on a whole stack at once, not one repo at a time.** `sandbox` assembles every +constituent repo of a named stack into **one combined worktree workspace**, wired so +they build and run **together**, on an isolated clean base — then tears it all down +cleanly. The live soul/engram (`:7770` / `:8742`) are never touched. + +It is the multi-repo sibling of [`nsbx`](./README.md): where `nsbx dev` stands up +**one** repo's worktree + an isolated engram, `sandbox` stands up **every** repo of a +stack as sibling git worktrees under a single workspace. + +```bash +export PATH="$PWD:$PATH" # or symlink `sandbox` onto your PATH + +sandbox neuron-stack tim # el + neuron soul + NeuronUI, assembled together +cd ~/Development/neuron-technologies/stack-worktrees/neuron-stack-tim +source .stack-env # EL_REPO + PATH now point at the SANDBOX el +./build.sh # engram compiles, soul compiles, UI present & buildable +sandbox down neuron-stack tim # remove every worktree; live untouched +``` + +## The two profiles + +### `el-stack` — the whole EL kit +The compiler + language + framework + tooling are **all one repo** (`foundation/el`: +`lang/` = elc/elb + runtime, `engram/src/server.el`, `elp/` = NLG, `ui/` = the **el-ui +framework**, plus `ql`, `ide`, `epm`, `arbor`, `tools`). Its downstream SDK consumers +come along so a change to `elc` can be proven end-to-end across the kit. + +| repo | required | role | +|------|----------|------| +| `foundation/el` | ✓ | elc + elb compiler, el_runtime, engram source, **el-ui framework**, elp/ql/ide/epm tooling | +| `engram-language` | | language-faculty reference POC (Python) — being ported into `el/elp` | +| `foundation/forge` | | downstream SDK consumer — `make build` | +| `foundation/dharma` | | downstream SDK consumer — CGI provenance registry | + +`build.sh` proves it: `elc` compiles a real stack source and `cc` links it against the +runtime into a native binary (elc + runtime build together), and — if present — `forge` +builds against the freshly-assembled SDK. + +### `neuron-stack` — the full product +Substrate + soul + UI. **Engram is not a separate repo** — its source lives inside +`foundation/el`. + +| repo | required | role | +|------|----------|------| +| `foundation/el` | ✓ | substrate: elc + el_runtime + engram source + the `elp` NLG the soul imports | +| `neuron` | ✓ | the soul (`:7770`) + engram build; `soul.el` imports `../foundation/el/elp/src/elp.el` | +| `products/NeuronUI` | ✓ | the app/UI (Kotlin/Compose desktop client; bundles the soul binary) | +| `products/web` | | marketing site + interactive soul-demo | + +`build.sh` proves it: **engram** builds (`elc engram/src/server.el` → `cc … el_runtime.c` +→ native binary), the **soul** compiles with its cross-repo `../foundation/el` import +resolving to the *sandbox* el, and the **UI** is present with its build entry. + +## Why it works — mirrored-layout wiring + +The repos reference each other by **relative sibling paths** (e.g. the soul imports +`../foundation/el/elp/src/elp.el`). So `sandbox` lays every worktree out at its **natural +relative path** inside the workspace: + +``` +stack-worktrees/neuron-stack-tim/ +├── foundation/el/ ← worktree of foundation/el (the sandbox el) +├── neuron/ ← worktree of neuron +└── products/NeuronUI/ ← worktree of products/NeuronUI +``` + +From `neuron/`, `../foundation/el` resolves to `…/neuron-stack-tim/foundation/el` — the +**sandbox** copy, never the live tree. No symlinks, no path rewriting: the layout *is* +the wiring. `.stack-env` additionally pins `EL_REPO` and prepends the sandbox `elc`/`elb` +to `PATH`. + +## Commands + +| command | does | +|---------|------| +| `sandbox el-stack [--minimal]` | assemble the EL kit (`--minimal` = required repos only) | +| `sandbox neuron-stack [--minimal]` | assemble the full product | +| `sandbox build ` | run the workspace's combined `build.sh` | +| `sandbox status ` | per-repo head + clean/dirty | +| `sandbox list` | list assembled workspaces | +| `sandbox down [--delete-branch]` | remove every worktree + drop the workspace (branch kept unless `--delete-branch`) | + +Flags: `--minimal` (required repos only), `--branch B` (branch name; default +`sandbox/-`), `--base REF` (fork point; default each repo's committed +HEAD). + +## Rails (always) + +- **Clean base** — worktrees fork off each repo's **committed HEAD**; the dirty state of + the live checkout is deliberately *not* carried in. +- **Persistent** — the workspace lives under `NSBX_STACK_ROOT` (default + `~/Development/neuron-technologies/stack-worktrees`), **never `/tmp`** (ablated on + compaction). +- **Never touches live** — `sandbox` only does `git worktree` + offline `cc`. It never + binds `:8742`/`:7770`, never `launchctl`, never `pkill`. Bringing up an **isolated + engram** is delegated, opt-in, to `nsbx` (which guards the live store and refuses the + live ports). +- **Idempotent & safe** — refuses to clobber an existing workspace; a failed assembly + rolls back its partial worktrees; teardown removes worktrees through their origin repo + and prunes. +- **Own-the-core** — pure bash + `git worktree`. No new dependencies. + +## Env knobs + +`NSBX_STACK_ROOT` (workspace root), `NEURON_DEV_ROOT` (the dir holding all the peer +repos, default `~/Development/neuron-technologies`). + +## Isolated engram for `neuron-stack` (opt-in) + +`sandbox` gets the code building together; to run the soul against an **isolated** engram +(never live), delegate to `nsbx` from inside the workspace: + +```bash +source .stack-env +nsbx create $STACK_NAME --source "$EL_REPO" # clone live store onto a non-default port +nsbx up $STACK_NAME +nsbx status $STACK_NAME # prints the isolated engram URL +``` diff --git a/tools/neuron-sandbox/sandbox b/tools/neuron-sandbox/sandbox new file mode 100755 index 0000000..dd9dfce --- /dev/null +++ b/tools/neuron-sandbox/sandbox @@ -0,0 +1,505 @@ +#!/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 "$@"