#!/usr/bin/env bash # Verify brain installation integrity # Checks: .brain-version, sha256 from manifest.json, settings.json validity, hooks syntax # # Usage: verify.sh --target= [--strict-secrets] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "$SCRIPT_DIR/lib/common.sh" # Determine brain root: env BRAIN_ROOT or script's parent BRAIN_ROOT="${BRAIN_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}" target="" strict_secrets=0 while [ $# -gt 0 ]; do case "$1" in --target=*) target="${1#--target=}" ;; --strict-secrets) strict_secrets=1 ;; *) log_error "Unknown: $1"; exit 1 ;; esac shift done [ -n "$target" ] || { log_error "--target required"; exit 1; } # Check 1: .brain-version exists bv="$target/.brain-version" [ -f "$bv" ] || { log_error ".brain-version not found in $target"; exit 1; } # Parse version + sha version=$(head -1 "$bv") sha_line=$(grep '^sha:' "$bv" || echo "") [ -n "$version" ] || { log_error ".brain-version: missing version line"; exit 1; } log_info "Found .brain-version: $version" # Check 2: manifest.json exists in brain root manifest="$BRAIN_ROOT/manifest.json" [ -f "$manifest" ] || { log_error "manifest.json missing in BRAIN_ROOT=$BRAIN_ROOT"; exit 1; } # Bug 3 fix: manifest.files now has three sub-maps with target-relative paths: # project-mode — paths as they appear in consumer project target # user-mode — paths as they appear in ~/.claude/ target # brain-internal — brain-repo paths (for self-verify when target == brain root) # # We pick the map based on detected target type: # - target is the brain root itself (contains project-files/ AND manifest.json) → brain-internal # - target is ~/.claude or has hooks/ or settings.json → user-mode # - everything else (project-mode default) manifest_section="" if [ -d "$target/project-files" ] && [ -f "$target/manifest.json" ]; then manifest_section="brain-internal" elif [ "$target" = "$HOME/.claude" ] || [ -d "$target/hooks" ] || [ -f "$target/settings.json" ]; then manifest_section="user-mode" else manifest_section="project-mode" fi log_info "Manifest section: $manifest_section" # Check 3: sha256 per file in chosen section files_count=$(jq -r --arg s "$manifest_section" '(.files[$s] // {}) | length' "$manifest") if [ "$files_count" -gt 0 ]; then log_info "Verifying $files_count file hashes..." while IFS= read -r entry; do rel_path=$(echo "$entry" | jq -r '.key') expected_sha=$(echo "$entry" | jq -r '.value') actual_path="$target/$rel_path" if [ ! -f "$actual_path" ]; then log_error "Missing: $rel_path" exit 2 fi if command -v sha256sum >/dev/null 2>&1; then actual_sha=$(sha256sum "$actual_path" | cut -d' ' -f1) else actual_sha=$(certutil -hashfile "$actual_path" SHA256 | sed -n '2p' | tr -d ' \r') fi if [ "$actual_sha" != "$expected_sha" ]; then log_error "SHA mismatch: $rel_path (expected $expected_sha, got $actual_sha)" exit 2 fi done < <(jq -c --arg s "$manifest_section" '(.files[$s] // {}) | to_entries[]' "$manifest") fi # Check 4: strict secrets — no unresolved <<*>> placeholders if [ "$strict_secrets" -eq 1 ]; then if grep -rE '<<[A-Z_]+>>' "$target" 2>/dev/null | grep -v '.brain-backup' | head -1; then log_error "Unresolved placeholders found" exit 7 fi fi log_info "Verification passed" exit 0