#!/bin/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. bd (beads) installation and pin verification # 2. Issue tracker sync from the Dolt remote # 3. Optional cargo workspace warming # # Usage: # scripts/agent-bootstrap.sh [--skip-cargo-warm] # # The script is idempotent and safe to re-run. Nothing here writes to tracked # files, stages anything, or creates a commit. # # See also: `.claude/hooks/session-start.sh` (Claude Code wrapper), # `.cursor/environment.json` (Cursor cloud config) set -uo pipefail SKIP_CARGO_WARM=0 while [[ $# -gt 0 ]]; do case "$1" in --skip-cargo-warm) SKIP_CARGO_WARM=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' "$*"; } # ── 0. 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:-}'" \ "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 } # ── 1. 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 command -v bd >/dev/null 2>&1 && bd metrics off >/dev/null 2>&1 if [ "$pin_status" -eq 0 ]; then assert_bd_pin || pin_status=2 fi # ── 2. Sync the issue tracker from the Dolt remote ───────────────────────── if 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:}" ;; 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 # ── 3. Warm the build (optional) ─────────────────────────────────────────── if [ "$SKIP_CARGO_WARM" -eq 0 ] && command -v cargo >/dev/null 2>&1; then log "warming cargo build…" if cargo build --workspace --tests >/tmp/cargo-warm.log 2>&1; then log "workspace built" else log "WARNING: cargo build failed; see /tmp/cargo-warm.log" fi fi # ── 4. Environment notes ─────────────────────────────────────────────────── cat <<'NOTES' [agent-bootstrap] Environment notes (see context/cloud-agents.md): • Use the preinstalled cargo toolchain for ordinary work. `nix develop` also works, but only after scripts/nix-seed-flake-inputs.sh — GitHub tarballs are not served here, so the locked inputs are seeded over git. • `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. • `bd dolt push ` ignores its argument and pushes to origin. • Use `scripts/kb` for knowledge base operations, not Prolog MCP. NOTES exit "$pin_status"