52584df34e
Bug 1: merge-mcp.sh fails on Cyrillic brain repo paths (jq fopen UTF-8 issue)
→ use --argjson with $(cat ...) instead of --slurpfile
Bug 2: install.sh stripped only trailing .template, missed middle .template.
→ strip both '.template.' middle and '.template' trailing patterns
Bug 3: manifest.json used brain-repo paths but verify.sh checked target paths
→ restructure manifest.files into {project-mode, user-mode, brain-internal} maps
→ verify.sh now picks map based on detected target mode
Bug 4: make_backup_dir created empty backup dir, no file preservation
→ rsync/cp -r target tree to backup before any modifications
Bug 5: brain's project-files/README.md overwrote consumer's README.md
→ install.sh now skips brain-internal READMEs in project/user copy loops
Phase 9 self-test re-run on c:/tmp/test-consumer-fix: install + verify both PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
3.4 KiB
Bash
99 lines
3.4 KiB
Bash
#!/usr/bin/env bash
|
|
# Verify brain installation integrity
|
|
# Checks: .brain-version, sha256 from manifest.json, settings.json validity, hooks syntax
|
|
#
|
|
# Usage: verify.sh --target=<path> [--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
|