#!/usr/bin/env bash # bootstrap.sh — stand up (or repair) an ECHO vault deterministically. # # Idempotent and additive: every write is probe-before-write and NEVER overwrites an # existing file. The marker (_agent/echo-vault.md) is written LAST, so the vault is # only flagged "set up" once every piece is in place. Safe to re-run any time — that # is also the "repair" path (it fills in only what is missing). # # All scaffold is resolved relative to THIS script's location, so it works regardless # of the caller's CWD (fixes the old `@scaffold/...` relative-path assumption). # # Usage: # bootstrap.sh [--dry-run] # # Env: ECHO_BASE, ECHO_KEY (passed through to echo.sh), ECHO_TODAY (YYYY-MM-DD for {{DATE}}). set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" SCAFFOLD="$SKILL_DIR/scaffold" ECHO="$SCRIPT_DIR/echo.sh" TODAY="${ECHO_TODAY:-$(date +%Y-%m-%d)}" DRY=0 [ "${1:-}" = "--dry-run" ] || [ "${1:-}" = "-n" ] && DRY=1 [ -d "$SCAFFOLD" ] || { echo "bootstrap: scaffold not found at $SCAFFOLD" >&2; exit 1; } [ -x "$ECHO" ] || chmod +x "$ECHO" 2>/dev/null || true say() { echo "bootstrap: $*"; } exists() { ECHO_VERIFY=0 "$ECHO" get "$1" >/dev/null 2>&1; } # 0 = present(200), nonzero = absent/404 # seed VAULT_PATH from LOCAL_FILE (with {{DATE}} substitution), only if absent seed() { local vpath="$1" local_file="$2" if exists "$vpath"; then say "skip (exists) $vpath"; return 0; fi if [ "$DRY" = "1" ]; then say "would seed $vpath <- ${local_file#$SKILL_DIR/}"; return 0; fi sed "s/{{DATE}}/$TODAY/g" "$local_file" | ECHO_VERIFY=1 "$ECHO" put "$vpath" - >/dev/null say "seeded $vpath" } # write a one-line leaf README only if absent leaf_readme() { local dir="$1" name="${1##*/}" local vpath="$dir/README.md" if exists "$vpath"; then return 0; fi if [ "$DRY" = "1" ]; then say "would readme $vpath"; return 0; fi printf '# %s\n\nMemory vault folder. See the echo-memory plugin for conventions.\n' "$name" \ | ECHO_VERIFY=0 "$ECHO" put "$vpath" - >/dev/null say "readme $vpath" } # ---- Pre-flight: is the vault already bootstrapped? -------------------------- if exists "_agent/echo-vault.md"; then ver="$("$ECHO" get _agent/echo-vault.md 2>/dev/null | sed -n 's/^schema_version:[[:space:]]*//p' | head -1)" say "marker present (schema_version=${ver:-unknown}). Running repair pass (fills only missing files)." CUR_SCHEMA=2 if [ -n "$ver" ] && [ "$ver" -lt "$CUR_SCHEMA" ] 2>/dev/null; then say "NOTE: schema_version $ver < $CUR_SCHEMA — run migrate.sh before relying on the vault." fi fi # ---- 1. Folder tree (leaf READMEs guarantee non-empty dirs) ------------------ LEAVES=( inbox/captures inbox/imports inbox/processing-log journal/daily journal/weekly journal/monthly journal/quarterly journal/annual journal/templates projects/active projects/incubating projects/on-hold projects/archived areas/business areas/personal areas/learning areas/systems resources/concepts resources/references resources/people resources/companies resources/meetings decisions/by-date _agent/context _agent/memory/working _agent/memory/episodic _agent/memory/semantic _agent/sessions _agent/health _agent/templates _agent/heartbeat _agent/skills/active _agent/skills/archived _agent/locks ) for d in "${LEAVES[@]}"; do leaf_readme "$d"; done # ---- 2. Templates (mirror scaffold/templates/ 1:1 into the vault) ------------ if [ -d "$SCAFFOLD/templates" ]; then while IFS= read -r f; do rel="${f#$SCAFFOLD/templates/}" seed "$rel" "$f" done < <(find "$SCAFFOLD/templates" -type f -name '*.md') fi # ---- 3. Anchor seeds (only if absent — never fabricate facts) ---------------- seed "_agent/memory/semantic/operator-preferences.md" "$SCAFFOLD/anchors/operator-preferences.seed.md" seed "_agent/context/current-context.md" "$SCAFFOLD/anchors/current-context.seed.md" seed "inbox/captures/inbox.md" "$SCAFFOLD/anchors/inbox.seed.md" # ---- 4. Vault README (human signpost) ---------------------------------------- seed "README.md" "$SCAFFOLD/README.vault.md" # ---- 5. Marker (write LAST) -------------------------------------------------- seed "_agent/echo-vault.md" "$SCAFFOLD/echo-vault.md" say "done (${DRY:+DRY-RUN }$TODAY)." say "Next: create today's daily note + a bootstrap session log + heartbeat (see SKILL.md First-run trace)."