nsbx: add one-command dev onboarding (branch + worktree + isolated engram)
El SDK CI - dev / build-and-test (pull_request) Successful in 6m30s
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:
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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 "$@";;
|
||||
|
||||
Reference in New Issue
Block a user