nsbx: add one-command dev onboarding (branch + worktree + isolated engram)
El SDK CI - dev / build-and-test (pull_request) Successful in 6m30s

Add 'nsbx dev <name>' / 'nsbx dev-down <name>' plus a Makefile so a newcomer
goes from clone to coding on an isolated cloned engram in one command. The
worktree is created on a real named branch at a persistent path (never /tmp,
guarded), and the whole worktree is pinned to the clone via an emitted .nsbx-env
so live :8742 / ~/.neuron/engram is unreachable by accident. Optimizes the El
edit->build->run loop so provisional work is built in El against a throwaway
clone instead of prototyped in Python and re-ported. Additive over the proven
primitives; no live cutover.
This commit is contained in:
bigmerge
2026-08-14 17:24:06 -05:00
parent 112bb2540f
commit 6660becfdb
3 changed files with 295 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# nsbx — Neuron dev-environment Makefile
# ---------------------------------------------------------------------------
# Thin, documented wrappers over the `nsbx` primitive so a newcomer never has to
# memorise the elc/cc build incantation or the sandbox lifecycle. Every target is
# a one-liner over `nsbx`; nothing here reimplements engram logic.
#
# make dev NAME=tim # new branch + worktree + isolated engram, one shot
# make status NAME=tim # inspect it (omit NAME to list all sandboxes)
# make run NAME=tim # poke its API (API=/api/stats by default)
# make test NAME=tim # run the safety rails as checks (nsbx validate)
# make build NAME=tim # compile the worktree's changes into the engram
# make destroy NAME=tim # tear it all down (branch kept)
#
# The isolated engram is ALWAYS a clone of the live store on a NON-default port;
# prod (:8742 / :7770) is untouchable from here.
# ---------------------------------------------------------------------------
# locate nsbx next to this Makefile, regardless of where make is run from
NSBX := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))nsbx
SBX := dev-$(NAME)
API ?= /api/stats
.DEFAULT_GOAL := help
.PHONY: help dev build run test status list destroy bt
help: ## Show this help
@echo "nsbx dev-environment — one-command isolated Neuron dev setup"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) \
| awk 'BEGIN{FS=":.*?## "}{printf " make %-22s %s\n", $$1, $$2}'
@echo ""
@echo " Variables: NAME=<dev name> (required for most) API=<path> (run)"
@echo " BASE=<git ref> WT=<worktree dir> PORT=<n> ARGS=<extra nsbx flags>"
dev: ## Create branch + worktree + isolated engram (NAME=x [BASE=ref PORT=n WT=dir ARGS=...])
@test -n "$(NAME)" || { echo "usage: make dev NAME=<name>"; exit 2; }
$(NSBX) dev $(NAME) $(if $(BASE),--base $(BASE)) $(if $(PORT),--port $(PORT)) $(if $(WT),--worktree $(WT)) $(ARGS)
build: ## Rebuild the engram from the dev worktree's own source (NAME=x)
@test -n "$(NAME)" || { echo "usage: make build NAME=<name>"; exit 2; }
@wt=$$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.neuron/sandboxes/$(SBX)/dev.json')))['worktree'])" 2>/dev/null); \
test -n "$$wt" || { echo "no dev.json for $(SBX) — run 'make dev NAME=$(NAME)' first"; exit 2; }; \
$(NSBX) build $(SBX) --source "$$wt"
bt: build run ## Fast El loop: rebuild the engram from the worktree, then poke it (NAME=x)
run: ## Poke the isolated engram's API (NAME=x [API=/api/stats])
@test -n "$(NAME)" || { echo "usage: make run NAME=<name> [API=/path]"; exit 2; }
$(NSBX) run $(SBX) api $(API)
test: ## Run the safety rails as checks: zero-loss, reboot, RSS, parity, keystones (NAME=x)
@test -n "$(NAME)" || { echo "usage: make test NAME=<name>"; exit 2; }
$(NSBX) validate $(SBX)
status: ## Show one sandbox's status (NAME=x), or list all if NAME is unset
@if [ -n "$(NAME)" ]; then $(NSBX) status $(SBX); else $(NSBX) list; fi
list: ## List all sandboxes
$(NSBX) list
destroy: ## Tear down engram + worktree (NAME=x [ARGS=--delete-branch])
@test -n "$(NAME)" || { echo "usage: make destroy NAME=<name>"; exit 2; }
$(NSBX) dev-down $(NAME) $(ARGS)
+58
View File
@@ -31,6 +31,64 @@ nsbx destroy <name> # cheap teardown; live untouched
That is the whole loop. Sane defaults: stock prod binary, auto-allocated port
(`8900+`, never `8742`/`7770`), snapshot of the live store.
## One-command dev onboarding — `nsbx dev` (start here)
Going from a clone to *coding on the mind* is a single command. It creates a git
**branch**, a persistent git **worktree**, and an **isolated engram** (a clone of the
live store on a non-default port) — and wires the whole worktree to that clone so you
**cannot hit live `:8742` by accident**.
```bash
make dev NAME=tim # branch wt/tim + worktree + isolated engram, in one shot
cd ~/Development/neuron-technologies/el-worktrees/tim
source .nsbx-env # every ENGRAM_* var now points at YOUR clone
# edit El in the worktree, then the fast loop:
make build NAME=tim # compile your El change into the isolated engram
make run NAME=tim # poke it (API=/api/stats by default)
make test NAME=tim # run the safety rails as checks
make destroy NAME=tim # tear it all down (branch kept; live untouched)
```
**Why this exists:** so provisional/experimental work is built **directly in El against a
throwaway cloned engram** — not prototyped in Python and re-ported later. The El
edit → `make build``make run` loop is the path of least resistance; that double-work is
what stranded the translation faculty for weeks.
### What `nsbx dev <name>` does, in order
1. **branch**`git worktree add -b <prefix><name>` (default prefix `dev/`; a *real named
branch*, never detached HEAD).
2. **worktree** — at a **persistent** path (default `…/el-worktrees/<name>`, override
`NSBX_DEV_WT_ROOT`). It **refuses** `/tmp` — temp dirs are ablated on compaction, which
is the exact "worktree in /tmp + no branch = lost work" failure this designs out.
3. **isolated engram**`nsbx create` under the hood: clone of the live store + WAL +
config, booted on an auto-allocated port (`8900+`, never `:8742`/`:7770`). Stock prod
binary by default (instant); `--build` compiles the worktree's own runtime instead.
4. **env pin** — writes `.nsbx-env` (+ `.envrc` for direnv) into the worktree exporting
`ENGRAM_URL / ENGRAM_PORT / ENGRAM_DATA_DIR / ENGRAM_API_KEY / NEURON_ENGRAM_URL …` — all
pointing at the clone. Nothing references live.
```
nsbx dev <name> [--base REF] [--worktree DIR] [--port N] [--prefix P] [--build] [--no-engram] [--repo R]
nsbx dev-down <name> [--delete-branch] [--repo R] # destroy engram + remove worktree
```
### Makefile targets
| target | does |
|--------|------|
| `make dev NAME=x` | branch + worktree + isolated engram (one shot) |
| `make bt NAME=x` | fast El loop: `build` then `run` |
| `make build NAME=x` | recompile the engram from the worktree's El source |
| `make run NAME=x` | poke the isolated engram (`API=/api/stats`) |
| `make test NAME=x` | rails as checks (`nsbx validate`) |
| `make status [NAME=x]` | inspect one, or `list` all |
| `make destroy NAME=x` | tear down (add `ARGS=--delete-branch` to drop the branch) |
> Note: if a bare `dev` branch already exists in the repo, git can't create `dev/*` names —
> pass `--prefix wt/` (or delete the stray `dev` branch). The tool surfaces git's exact error.
## The code-change dev loop (first-class)
Run *your changed runtime*, not just the stock binary, against a snapshot:
+173
View File
@@ -623,10 +623,181 @@ cmd_status(){
[ -f "$(sdir "$name")/validate.json" ] && { echo "--- last validation ---"; python3 -m json.tool "$(sdir "$name")/validate.json"; }
}
# ================================================================ dev ==========
# ONE-COMMAND isolated dev environment. Everything a newcomer (Tim, any agent)
# needs to go from clone -> coding on an isolated running mind, in a single shot:
# 1) a git BRANCH (dev/<name>, or --prefix)
# 2) a git WORKTREE for it, at a visible path they can open + edit
# 3) an ISOLATED engram bound to a NON-default port, on a clone of the live store
# (stock prod binary by default — instant + safe; --build to compile the
# worktree's own runtime instead). Prod :$LIVE_BIND_PORT/:$SOUL_PORT is untouchable.
#
# This is additive sugar over the proven primitives (git worktree + cmd_create).
# It never binds a forbidden port and never touches ~/.neuron/engram (the live store)
# except the same READ-only snapshot cmd_create already performs.
#
# nsbx dev <name> [--repo R] [--base REF] [--worktree DIR] [--port N]
# [--prefix P] [--build] [--no-engram]
cmd_dev(){
local name="" repo="$EL_REPO" base="" wt="" port="" prefix="dev/" build=0 no_engram=0
[ $# -gt 0 ] && [ "${1#-}" = "$1" ] && { name="$1"; shift; } || die "usage: nsbx dev <name> [flags]"
while [ $# -gt 0 ]; do case "$1" in
--repo) repo="$2"; shift 2;;
--base) base="$2"; shift 2;;
--worktree|--wt) wt="$2"; shift 2;;
--port) port="$2"; shift 2;;
--prefix) prefix="$2"; shift 2;;
--build) build=1; shift;;
--no-engram) no_engram=1; shift;;
*) die "unknown flag: $1";;
esac; done
need git
git -C "$repo" rev-parse --git-dir >/dev/null 2>&1 || die "not a git repo: $repo"
local branch="${prefix}${name}"
local sbx="dev-${name}"
# default worktree path: a PERSISTENT, git-managed dir — NEVER /tmp (which is
# ablated on compaction). Default root = <repo-grandparent>/el-worktrees, i.e.
# ~/Development/neuron-technologies/el-worktrees/<name>. Override with NSBX_DEV_WT_ROOT.
local wt_root="${NSBX_DEV_WT_ROOT:-$(cd "$(dirname "$(dirname "$repo")")" && pwd -P)/el-worktrees}"
[ -n "$wt" ] || wt="${wt_root}/${name}"
case "$wt" in /tmp/*|/private/tmp/*|/var/tmp/*)
die "refusing worktree under a temp dir ($wt) — temp dirs are ablated on compaction; set NSBX_DEV_WT_ROOT to a persistent path";;
esac
# default base: whatever the repo's working checkout is on now
[ -n "$base" ] || base="$(git -C "$repo" rev-parse --abbrev-ref HEAD 2>/dev/null)"
# pre-flight (fail before creating anything)
[ "$no_engram" -eq 1 ] || ! mexists "$sbx" || die "engram sandbox '$sbx' already exists (nsbx dev-down $name first)"
[ -e "$wt" ] && die "worktree path already exists: $wt"
if [ -n "$port" ]; then
{ [ "$port" = "$LIVE_BIND_PORT" ] || [ "$port" = "$SOUL_PORT" ]; } && die "refusing forbidden port $port (live)"
fi
log "dev env '$name' (branch=$branch worktree=$wt base=$base)"
# ---- 1+2) branch + worktree in one shot ----
local gerr
if git -C "$repo" show-ref --verify --quiet "refs/heads/$branch"; then
info "branch $branch exists — checking it out into a new worktree"
gerr="$(git -C "$repo" worktree add "$wt" "$branch" 2>&1)" \
|| die "git worktree add failed for existing branch $branch:"$'\n'" $gerr"
else
gerr="$(git -C "$repo" worktree add -b "$branch" "$wt" "$base" 2>&1)" \
|| die "git worktree add -b $branch (base $base) failed:"$'\n'" $gerr"$'\n'" (a bare 'dev' branch blocks 'dev/*' names — try --prefix, e.g. nsbx dev $name --prefix wt/)"
fi
ok "worktree ready: $wt (branch $branch)"
# ---- 3) isolated engram ----
local eport="(none)"
if [ "$no_engram" -eq 1 ]; then
warn "--no-engram: skipped standing up an engram"
else
if [ "$build" -eq 1 ]; then
log "isolated engram: building the worktree's own runtime"
cmd_create "$sbx" ${port:+--port "$port"} --source "$wt" || die "engram create (--build) failed"
else
log "isolated engram: stock prod binary on a clone of the live store"
cmd_create "$sbx" ${port:+--port "$port"} || die "engram create failed"
fi
eport="$(mget "$sbx" "['port']")"
# record the dev linkage next to the sandbox so dev-down can clean up
python3 - "$(sdir "$sbx")/dev.json" "$name" "$branch" "$wt" "$repo" "$eport" <<'PY'
import json,sys
p,name,branch,wt,repo,port=sys.argv[1:7]
json.dump({"name":name,"branch":branch,"worktree":wt,"repo":repo,"port":int(port)},
open(p,'w'),indent=2)
PY
# ---- pin the WHOLE worktree to the CLONE ----
# Every var any El tooling in this worktree might read for an engram target now
# points at the isolated clone. Sourcing .nsbx-env makes hitting live :$LIVE_BIND_PORT
# or ~/.neuron/engram by accident structurally impossible from this shell.
local edata ekey eurl ebin
edata="$(sdir "$sbx")/data"; ekey="sbx-$sbx"; eurl="http://127.0.0.1:$eport"; ebin="$(sdir "$sbx")/bin/engram"
cat > "$wt/.nsbx-env" <<ENV
# nsbx dev env for '$name' — SOURCE this to pin THIS shell to the isolated clone.
# The live mind (:$LIVE_BIND_PORT engram / :$SOUL_PORT soul / $LIVE_DATA_DIR) is deliberately
# NOT referenced here. Regenerated by 'nsbx dev'. -> source .nsbx-env
export NSBX_NAME="$sbx"
export ENGRAM_URL="$eurl"
export ENGRAM_BIND=":$eport"
export ENGRAM_PORT="$eport"
export ENGRAM_HOST="127.0.0.1"
export ENGRAM_DATA_DIR="$edata"
export ENGRAM_API_KEY="$ekey"
export NEURON_ENGRAM_URL="$eurl"
export NEURON_ENGRAM_KEY="$ekey"
# nsbx run compatibility (same names 'nsbx run' exports)
export SBX_NAME="$sbx" SBX_PORT="$eport" SBX_URL="$eurl" SBX_KEY="$ekey" SBX_DATA="$edata" SBX_BIN="$ebin"
ENV
# direnv users get it automatically on cd; everyone else runs 'source .nsbx-env'
[ -e "$wt/.envrc" ] || printf 'source_env .nsbx-env 2>/dev/null || source .nsbx-env\n' > "$wt/.envrc"
ok "wrote $wt/.nsbx-env (pins this worktree to the clone)"
fi
# ---- summary ----
echo >&2
printf '%s DEV ENV READY — %s%s\n' "$C_BLD" "$name" "$C_0" >&2
printf ' %-10s %s\n' "worktree" "$wt" >&2
printf ' %-10s %s\n' "branch" "$branch" >&2
if [ "$no_engram" -ne 1 ]; then
printf ' %-10s %s\n' "engram" "http://127.0.0.1:$eport (isolated clone; prod :$LIVE_BIND_PORT untouchable)" >&2
printf ' %-10s %s\n' "sandbox" "$sbx" >&2
echo >&2
printf '%s env exported into %s/.nsbx-env (source it -> pinned to the clone):%s\n' "$C_DIM" "$wt" "$C_0" >&2
grep '^export' "$wt/.nsbx-env" | sed 's/^/ /' >&2
fi
echo >&2
info "code in: cd $wt && source .nsbx-env # now every engram var points at the clone"
[ "$no_engram" -ne 1 ] && info "poke it: nsbx run $sbx api /api/stats (or: make run NAME=$name)"
[ "$no_engram" -ne 1 ] && info "edit->test: make build NAME=$name && make run NAME=$name # El change -> clone, seconds"
info "tear down: nsbx dev-down $name (destroys engram + removes worktree; branch kept)"
}
# nsbx dev-down <name> [--delete-branch] [--repo R]
# Teardown counterpart: destroy the isolated engram, remove the git worktree,
# and (optionally) delete the branch. Live prod is never touched.
cmd_dev_down(){
local name="" repo="$EL_REPO" del_branch=0
[ $# -gt 0 ] && [ "${1#-}" = "$1" ] && { name="$1"; shift; } || die "usage: nsbx dev-down <name> [--delete-branch]"
while [ $# -gt 0 ]; do case "$1" in
--repo) repo="$2"; shift 2;;
--delete-branch) del_branch=1; shift;;
*) die "unknown flag: $1";;
esac; done
local sbx="dev-${name}" wt="" branch="dev/${name}"
# recover worktree/branch/repo from the dev linkage if present
if mexists "$sbx" && [ -f "$(sdir "$sbx")/dev.json" ]; then
local dj; dj="$(sdir "$sbx")/dev.json"
wt="$(python3 -c "import json;print(json.load(open('$dj'))['worktree'])" 2>/dev/null)"
branch="$(python3 -c "import json;print(json.load(open('$dj'))['branch'])" 2>/dev/null)"
repo="$(python3 -c "import json;print(json.load(open('$dj'))['repo'])" 2>/dev/null)"
fi
# 1) engram
if mexists "$sbx"; then cmd_destroy "$sbx"; else info "no engram sandbox '$sbx'"; fi
# 2) worktree
if [ -n "$wt" ] && [ -d "$wt" ]; then
log "removing git worktree $wt"
git -C "$repo" worktree remove --force "$wt" 2>/dev/null || rm -rf "$wt"
git -C "$repo" worktree prune 2>/dev/null || true
ok "worktree removed"
else info "no worktree to remove"; fi
# 3) branch (opt-in)
if [ "$del_branch" -eq 1 ]; then
git -C "$repo" branch -D "$branch" 2>/dev/null && ok "deleted branch $branch" || warn "could not delete branch $branch"
else info "branch $branch kept (use --delete-branch to remove)"; fi
ok "dev-down '$name' complete (live untouched)"
}
usage(){ cat >&2 <<EOF
${C_BLD}nsbx${C_0} — Neuron Sandbox: experiments + code changes against the REAL engram
runtime on an isolated snapshot of the live mind, with a gated promote-to-prod path.
nsbx dev <name> [--base REF] [--worktree DIR] ONE command onboarding: new branch (dev/<name>) + git
[--port N] [--prefix P] [--build] worktree (persistent, never /tmp) + isolated engram on a
[--no-engram] [--repo R] non-default port. clone -> coding on the mind in one shot.
nsbx dev-down <name> [--delete-branch] [--repo R] teardown: destroy the engram + remove the worktree
(branch kept unless --delete-branch). live untouched.
nsbx up [name] [flags…] one command: your private, isolated copy of the mind
(creates on first run, starts thereafter; prod untouchable)
nsbx create [name] [--port N] [--source DIR | --branch REF [--repo R] | --binary PATH]
@@ -647,6 +818,8 @@ EOF
main(){
local cmd="${1:-}"; shift || true
case "$cmd" in
dev) cmd_dev "$@";;
dev-down) cmd_dev_down "$@";;
up) cmd_up "$@";;
create) cmd_create "$@";;
build) cmd_build "$@";;