#!/usr/bin/env bash # regenerate-soul-amalgam.sh — regenerate dist/soul.c from the current .el # sources, working around three real elc/elb toolchain gotchas found and # root-caused during the 2026-08-15 local-build audit. See AGENTS.md's # "Build / regenerate dist/soul.c" section for the full explanation of each. # # Requires: `elc` (the El compiler, macOS arm64 binary at # foundation/el/lang/dist/platform/elc-darwin-arm64) on $PATH as `elc`. # # Usage: # tools/regenerate-soul-amalgam.sh # # After it succeeds: # tools/soulc-stamp.sh --write # tools/build-soul-from-dist.sh dist/neuron set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # soul.el's own `import "../foundation/el/elp/src/elp.el"` assumes neuron and # foundation are siblings. True for a normal checkout; FALSE for a # `git worktree add .worktrees/` checkout (nested one level deeper) — # exactly the layout this audit was run from. Try both before giving up. FOUNDATION="$(cd "$ROOT/../foundation" 2>/dev/null && pwd || true)" if [ -z "$FOUNDATION" ]; then FOUNDATION="$(cd "$ROOT/../../foundation" 2>/dev/null && pwd || true)" fi ELP_SRC="${ELP_SRC:-$FOUNDATION/el/elp/src}" RUNTIME="$ROOT/vendor/el-runtime/v1.0.0-20260501" FLAT="$(mktemp -t flat-soul).el" OUT_C="$(mktemp -t flat-soul-out).c" command -v elc >/dev/null 2>&1 || { echo "regenerate-soul-amalgam: 'elc' not found on \$PATH." >&2 echo " export PATH=\"\$(dirname /elc-darwin-arm64):\$PATH\" (symlinked as 'elc')" >&2 exit 2 } [ -d "$ELP_SRC" ] || { echo "regenerate-soul-amalgam: elp.el source dir not found at $ELP_SRC" >&2 echo " set ELP_SRC=/path/to/foundation/el/elp/src if 'foundation' isn't a sibling of this repo" >&2 exit 2 } [ -f "$RUNTIME/el_runtime.h" ] || { echo "regenerate-soul-amalgam: pinned runtime missing at $RUNTIME" >&2 exit 2 } # Gotcha #1: stale committed .elh headers silently truncate the build (elc/elb # prefer an existing .elh over recompiling its source, with no warning). echo "[regen] deleting all *.elh in repo root and dist/ (stale-cache gotcha)" find "$ROOT" -maxdepth 2 -iname "*.elh" -delete > "$FLAT" BUF_N=0 emit_buffer() { # Gotcha #3: elc silently drops the 1-2 top-level fn defs immediately after # any multi-line leading comment block / file-boundary transition when # compiling a flat concatenated file. Two throwaway functions per boundary # absorb the drop; stripped back out of the .c below. BUF_N=$((BUF_N+1)); printf 'fn __amalgam_buf_%d__() -> Int { return 0 }\n' "$BUF_N" >> "$FLAT" BUF_N=$((BUF_N+1)); printf 'fn __amalgam_buf_%d__() -> Int { return 0 }\n' "$BUF_N" >> "$FLAT" echo "" >> "$FLAT" } add_file() { emit_buffer grep -v '^import ' "$1" >> "$FLAT" echo "" >> "$FLAT" } emit_buffer # elp.el's own documented dependency order (see its header comment). for f in language-profile.el vocabulary.el morphology.el \ morphology-es.el morphology-fr.el morphology-de.el morphology-ru.el \ morphology-ja.el morphology-fi.el morphology-ar.el morphology-hi.el \ morphology-sw.el morphology-la.el morphology-he.el morphology-grc.el \ morphology-ang.el morphology-sa.el morphology-got.el morphology-non.el \ morphology-enm.el morphology-pi.el morphology-fro.el morphology-goh.el \ morphology-sga.el morphology-txb.el morphology-peo.el morphology-akk.el \ morphology-uga.el morphology-egy.el morphology-sux.el morphology-gez.el \ morphology-cop.el grammar.el realizer.el semantics.el elp.el; do add_file "$ELP_SRC/$f" done # elb's own reported topological order for this repo's 13 soul modules. cd "$ROOT" for f in persist.el memory.el safety.el stewardship.el imprint.el \ awareness.el chat.el studio.el elp-input.el neuron-api.el sessions.el \ routes.el soul.el; do add_file "$f" done echo "[regen] flat source: $FLAT ($(wc -l < "$FLAT" | tr -d ' ') lines)" # Gotcha #2: elb cannot produce this repo's single-TU dist/soul.c (it does # per-module separate compilation, which fails on this codebase's # cross-module implicit-declaration style). Use plain elc on the flat file. echo "[regen] compiling with elc against the pinned runtime ($RUNTIME)" CPATH="$RUNTIME" C_INCLUDE_PATH="$RUNTIME" elc "$FLAT" > "$OUT_C" echo "[regen] stripping the priming buffer functions back out" python3 - "$OUT_C" "$ROOT/dist/soul.c" << 'PYEOF' import re, sys src, dst = sys.argv[1], sys.argv[2] with open(src) as f: content = f.read() content = re.sub(r'el_val_t __amalgam_buf_\d+__\(void\);\n', '', content) content = re.sub(r'el_val_t __amalgam_buf_\d+__\(void\) \{\n(?:\s*return 0;\n)+\}\n\n', '', content) with open(dst, 'w') as f: f.write(content) PYEOF echo "[regen] OK -> dist/soul.c ($(wc -c < "$ROOT/dist/soul.c" | tr -d ' ') bytes)" echo "[regen] next: tools/soulc-stamp.sh --write && tools/build-soul-from-dist.sh dist/neuron" rm -f "$FLAT" "$OUT_C"