Files
improvise/scripts/agent-bootstrap.sh
T
Edward Langley 303a0388fb chore: stuff
2026-09-27 10:27:12 -07:00

324 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
# Stack-agnostic agent bootstrap for this repository.
#
# This script prepares any coding agent (Claude Code, Cursor, etc.) to work
# on this repo. It handles:
# 1. Nix flake input seeding (so `nix develop` works in cloud environments)
# 2. bd (beads) installation and pin verification
# 3. Issue tracker sync from the Dolt remote
# 4. Optional cargo workspace warming (inside nix develop)
#
# Usage:
# scripts/agent-bootstrap.sh [--skip-cargo-warm] [--skip-nix-seed]
#
# The script is idempotent and safe to re-run. Nothing here writes to tracked
# files, stages anything, or creates a commit.
#
# IMPORTANT: This repo requires `nix develop` (or direnv) for builds/tests.
# Bare system cargo that fails edition2024 is a misconfigured session.
#
# See also: `.claude/hooks/session-start.sh` (Claude Code wrapper),
# `.cursor/environment.json` (Cursor cloud config)
set -uo pipefail
SKIP_CARGO_WARM=0
SKIP_NIX_SEED=0
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-cargo-warm) SKIP_CARGO_WARM=1; shift ;;
--skip-nix-seed) SKIP_NIX_SEED=1; shift ;;
*) shift ;;
esac
done
ROOT="${AGENT_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}}"
cd "$ROOT" || exit 0
log() { printf '[agent-bootstrap] %s\n' "$*"; }
nix_status=0
ensure_nix_daemon() {
command -v nix >/dev/null 2>&1 || return 1
# Single-user Nix does not need a daemon; multi-user/cloud installs do.
# If the store already answers, leave the environment alone.
if nix --extra-experimental-features 'nix-command' store ping >/dev/null 2>&1; then
return 0
fi
if ! command -v nix-daemon >/dev/null 2>&1; then
log "ERROR: Nix is installed, but the store is unavailable and nix-daemon is not on PATH"
return 1
fi
log "starting nix-daemon for bootstrap…"
nix-daemon >/tmp/nix-daemon.log 2>&1 &
# The daemon is started asynchronously; wait briefly for its socket before
# invoking the seed/build steps that depend on it.
for _ in {1..50}; do
if nix --extra-experimental-features 'nix-command' store ping --store daemon >/dev/null 2>&1; then
return 0
fi
sleep 0.1
done
log "ERROR: nix-daemon did not become ready; see /tmp/nix-daemon.log"
return 1
}
# Start the daemon during cloud-agent installation, before any required Nix
# operation. This is harmless for single-user Nix because nix-store --ping
# succeeds without starting a daemon.
if [ "$SKIP_NIX_SEED" -eq 0 ] || [ "$SKIP_CARGO_WARM" -eq 0 ]; then
if ! ensure_nix_daemon; then
nix_status=1
fi
fi
# ── 0. Seed nix flake inputs (required for cloud environments) ─────────────
# GitHub tarball endpoints are blocked in cloud containers. This seeds the
# flake inputs over git so `nix develop` can build the dev shell.
if [ "$SKIP_NIX_SEED" -eq 0 ]; then
if [ "$nix_status" -ne 0 ]; then
log "ERROR: cannot seed flake inputs because Nix is unavailable"
elif ! command -v nix >/dev/null 2>&1; then
log "ERROR: nix not available; required flake input seeding cannot run"
nix_status=1
else
# Set SSL cert for proxy environments
if [ -z "${NIX_SSL_CERT_FILE:-}" ] && [ -r /root/.ccr/ca-bundle.crt ]; then
export NIX_SSL_CERT_FILE=/root/.ccr/ca-bundle.crt
fi
if [ ! -f "$ROOT/scripts/nix-seed-flake-inputs.sh" ]; then
log "ERROR: required scripts/nix-seed-flake-inputs.sh is missing"
nix_status=1
else
log "seeding nix flake inputs (GitHub tarballs blocked in cloud)…"
if "$ROOT/scripts/nix-seed-flake-inputs.sh" >/tmp/nix-seed.log 2>&1; then
log "flake inputs seeded"
else
log "ERROR: nix-seed-flake-inputs.sh failed; see /tmp/nix-seed.log"
log " 'nix develop' is not validated for this session."
nix_status=1
fi
fi
fi
fi
# ── 1. The bd on PATH must be the bd flake.nix pins ────────────────────────
beads_pin() {
sed -n 's|.*beads\.url[[:space:]]*=[[:space:]]*"github:[^/]*/[^/]*/v\{0,1\}\([^"]*\)".*|\1|p' \
flake.nix 2>/dev/null | head -1
}
bd_running_version() {
bd version 2>/dev/null | sed -n 's/^bd version \([0-9][0-9.]*\).*/\1/p' | head -1
}
bd_pin_banner() {
{
printf '\n[agent-bootstrap] ===== BEADS VERSION SKEW =====\n'
printf '[agent-bootstrap] %s\n' "$@"
printf '[agent-bootstrap] =============================\n\n'
} >&2
}
assert_bd_pin() {
command -v bd >/dev/null 2>&1 || return 0
pinned=$(beads_pin)
running=$(bd_running_version)
case "$pinned" in
[0-9]*.[0-9]*.[0-9]*) ;;
*)
bd_pin_banner "cannot read the beads pin out of flake.nix" \
"read: '${pinned:-<nothing>}'" \
"The pin moved to a ref this hook cannot parse (a branch or a" \
"commit, perhaps). Teach beads_pin() to read it, or the check" \
"silently protects nothing."
return 1
;;
esac
if [ -z "$running" ]; then
bd_pin_banner "cannot read the version of the bd on PATH" \
"bd: $(command -v bd)" \
"'bd version' did not answer in the expected shape. Until it does," \
"nothing here can tell whether that build matches the pin."
return 1
fi
[ "$running" = "$pinned" ] && return 0
bd_pin_banner "the bd on PATH is NOT the bd this repo pins" \
"pinned (flake.nix): $pinned" \
"running: $running ($(command -v bd))" \
"" \
"Reading is safe. Do NOT run a mutating bd command — create, update," \
"close, dep, dolt push. Version skew between builds empties the" \
"dependency graph in .beads/issues.jsonl and reports success." \
"" \
"Fix now: nix develop (in this directory), then re-run the" \
" command; or 'direnv allow' here." \
"Fix for good: worktrees are created fresh per dispatch, so allowing" \
" them one at a time never catches the next one. Put the" \
" repo root under [whitelist] prefix in" \
" ~/.config/direnv/direnv.toml."
return 1
}
pin_status=0
assert_bd_pin || pin_status=2
# Install a verified database at .beads/embeddeddolt.
install_tracker() {
staged=$1
live=.beads/embeddeddolt
incoming=$live.incoming
previous=$live.previous
rm -rf "$incoming" "$previous" || return 1
cp -rf "$staged" "$incoming" || { rm -rf "$incoming"; return 1; }
if [ -d "$live" ] && ! mv -f "$live" "$previous"; then
rm -rf "$incoming"
return 1
fi
if mv -f "$incoming" "$live"; then
rm -rf "$previous"
return 0
fi
if [ -d "$previous" ] && ! mv -f "$previous" "$live"; then
log "ERROR: tracker left at $previous — move it back to $live by hand"
fi
rm -rf "$incoming"
return 1
}
# ── 2. bd on PATH ──────────────────────────────────────────────────────────
export PATH="$PATH:/root/go/bin:$HOME/go/bin"
BD_INSTALLER_REF=f45b249ce6b40ba62aecc03949e6371e8f7c79d8 # tag v1.3.0
BD_INSTALLER_URL="https://raw.githubusercontent.com/steveyegge/beads/$BD_INSTALLER_REF/scripts/install.sh"
if ! command -v bd >/dev/null 2>&1; then
log "installing bd (beads v1.3.0)…"
if ! curl -fsSL "$BD_INSTALLER_URL" -o /tmp/bd-install.sh 2>/tmp/bd-install.log; then
log "WARNING: could not download the bd installer; see /tmp/bd-install.log."
elif bash /tmp/bd-install.sh >>/tmp/bd-install.log 2>&1; then
log "bd installed"
else
log "WARNING: bd install failed; see /tmp/bd-install.log (the script that"
log " ran is at /tmp/bd-install.sh). Issue tracking unavailable."
fi
fi
# Export PATH for the calling environment if AGENT_ENV_FILE is set
if [ -n "${AGENT_ENV_FILE:-${CLAUDE_ENV_FILE:-}}" ]; then
env_file="${AGENT_ENV_FILE:-${CLAUDE_ENV_FILE}}"
echo 'export PATH="$PATH:/root/go/bin:$HOME/go/bin"' >> "$env_file"
fi
if [ "$pin_status" -eq 0 ]; then
assert_bd_pin || pin_status=2
fi
# Do not run any tracker/config mutation through a bd binary whose version does
# not match the repository pin.
if [ "$pin_status" -eq 0 ] && command -v bd >/dev/null 2>&1; then
bd metrics off >/dev/null 2>&1 || true
fi
# ── 3. Sync the issue tracker from the Dolt remote ─────────────────────────
if [ "$pin_status" -eq 0 ] && command -v bd >/dev/null 2>&1; then
DB_NAME="$(jq -r '.dolt_database // "improvise"' .beads/metadata.json 2>/dev/null || echo improvise)"
ORIGIN="$(git remote get-url origin 2>/dev/null || true)"
case "$ORIGIN" in
git@github.com:*)
ORIGIN="https://github.com/${ORIGIN#git@github.com:}"
git remote add gh "$ORIGIN"
;;
esac
ORIGIN="${ORIGIN%.git}"
if [ -n "$ORIGIN" ] && TMP=$(mktemp -d) && [ -d "$TMP" ]; then
log "syncing issue tracker from $ORIGIN …"
STAGE="$TMP/$DB_NAME"
mkdir -p "$STAGE"
sync_ok=0
if (
cd "$STAGE" && git init -q . &&
BD_NON_INTERACTIVE=1 bd init --non-interactive --skip-agents --skip-hooks -q \
--remote "git+$ORIGIN"
) >/tmp/bd-sync.log 2>&1 && [ -d "$STAGE/.beads/embeddeddolt" ]; then
listing="$( (cd "$STAGE" && bd list --all 2>/dev/null) || true )"
case "$listing" in
"" | "No issues found."*) ;;
*) sync_ok=1 ;;
esac
fi
if [ "$sync_ok" -eq 1 ] && install_tracker "$STAGE/.beads/embeddeddolt"; then
log "issue tracker synced — 'bd ready' now reflects the remote, not issues.jsonl"
elif [ -d .beads/embeddeddolt ]; then
log "WARNING: tracker sync failed (see /tmp/bd-sync.log); kept the previous database."
log " It may be out of date — verify any bead against the code."
else
log "WARNING: tracker sync failed; see /tmp/bd-sync.log."
log " .beads/issues.jsonl is a STALE export — verify any bead against the code."
fi
rm -rf "$TMP"
elif [ -n "$ORIGIN" ]; then
log "WARNING: could not create a temporary directory; skipped the tracker sync."
log " .beads/issues.jsonl is a STALE export — verify any bead against the code."
fi
fi
# ── 4. Warm the build inside nix develop (optional) ────────────────────────
# Uses nix develop to ensure the correct toolchain (rust from flake, wasm target, etc.)
if [ "$SKIP_CARGO_WARM" -eq 0 ]; then
if [ "$nix_status" -ne 0 ]; then
log "ERROR: skipping required cargo warm because Nix bootstrap already failed"
elif ! command -v nix >/dev/null 2>&1; then
log "ERROR: nix not available; required cargo warm cannot run"
nix_status=1
else
log "warming cargo build inside nix develop…"
NIX_FLAGS=(--extra-experimental-features 'nix-command flakes')
if nix "${NIX_FLAGS[@]}" develop --command cargo build --workspace --tests >/tmp/cargo-warm.log 2>&1; then
log "workspace built (nix develop toolchain)"
else
log "ERROR: cargo build failed; see /tmp/cargo-warm.log"
log " Required nix develop validation failed."
nix_status=1
fi
fi
fi
# ── 5. Environment notes ───────────────────────────────────────────────────
cat <<'NOTES'
[agent-bootstrap] Environment notes (see context/cloud-agents.md):
• This repo REQUIRES `nix develop` (or direnv) for builds and tests.
Bare system cargo that fails edition2024 is a misconfigured session.
• The bootstrap seeded flake inputs over git (GitHub tarballs are blocked
in cloud). If nix develop still fails, check /tmp/nix-seed.log.
• `bd dolt push` is blocked outbound (403). Bead status changes stay local.
• Never run a bare `bd init` in this repo: it rewrites AGENTS.md, CLAUDE.md
and .claude/settings.json, and commits. Use --skip-agents --skip-hooks.
• Use `scripts/kb` for knowledge base operations, not Prolog MCP.
• Run commands inside `nix develop` or prefix with `nix develop --command`.
NOTES
exit_status="$nix_status"
if [ "$pin_status" -ne 0 ]; then
exit_status="$pin_status"
fi
exit "$exit_status"