feat: align agent stacks (Claude, Cursor) on shared bootstrap
Unifies coding-agent setup across Claude Code and Cursor so both stacks get: - Pin-matched bd from flake.nix - Fresh Dolt beads tracker sync (not stale JSONL export) - Same skills via symlinks to .agents/skills/ Key changes: 1. scripts/agent-bootstrap.sh: stack-agnostic bootstrap that handles bd installation, tracker sync, and optional cargo warming. Works for any agent stack (Claude, Cursor, or manual). 2. .claude/hooks/session-start.sh: now a thin wrapper that calls the shared bootstrap for cloud sessions, keeping only the local pin-check inline. 3. .cursor/environment.json: configures Cursor cloud agents to run the bootstrap via 'install'. 4. .cursor/skills/: symlinks to .agents/skills/* so Cursor agents discover the same skills Claude has. 5. AGENTS.md: adds Multi-Stack Agent Support section; clarifies that scripts/kb is the KB interface (Prolog MCP is not required and not available in all stacks). 6. context/cloud-agents.md: updated for multi-stack support; added KB section emphasizing scripts/kb over Prolog MCP. The canonical skill source remains .agents/skills/. Both .claude/skills/ and .cursor/skills/ are symlinks to it, so there is one truth that both stacks read. Co-authored-by: Ed Langley <fiddlerwoaroof@users.noreply.github.com>
This commit is contained in:
Executable
+222
@@ -0,0 +1,222 @@
|
||||
#!/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:-<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
|
||||
}
|
||||
|
||||
# ── 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 <remote>` ignores its argument and pushes to origin.
|
||||
• Use `scripts/kb` for knowledge base operations, not Prolog MCP.
|
||||
NOTES
|
||||
|
||||
exit "$pin_status"
|
||||
Reference in New Issue
Block a user