|
| 1 | +#!/bin/bash |
| 2 | +# pre-commit hook — Validate & sync all stack repos before commit |
| 3 | +# |
| 4 | +# Validates: |
| 5 | +# 1. All VERSION files in sync |
| 6 | +# 2. lpb.stack.env LPB_PI_REF matches VERSION |
| 7 | +# 3. settings.json pins match VERSION |
| 8 | +# 4. All repos clean (except VERSION/env) |
| 9 | +# 5. Auto-add VERSION + env changes to staging |
| 10 | +# |
| 11 | +# Exit non-zero to abort commit. |
| 12 | + |
| 13 | +# Find devstack dir by searching upward |
| 14 | +DEVSTACK_DIR="" |
| 15 | +check_dir="$PWD" |
| 16 | +while [ "$check_dir" != "/" ]; do |
| 17 | + [ -f "$check_dir/VERSION" ] && [ -f "$check_dir/lpb.stack.env" ] && DEVSTACK_DIR="$check_dir" && break |
| 18 | + check_dir=$(dirname "$check_dir") |
| 19 | +done |
| 20 | +[ -z "$DEVSTACK_DIR" ] && DEVSTACK_DIR="$PWD" |
| 21 | + |
| 22 | +ERRORS=0 |
| 23 | +CURRENT_VERSION=$(cat "$DEVSTACK_DIR/VERSION" 2>/dev/null || echo "NOT FOUND") |
| 24 | + |
| 25 | +# Repo definitions |
| 26 | +declare -a REPOS=( |
| 27 | + "$DEVSTACK_DIR/workspace/pi" |
| 28 | + "/home/lpb/.pi/agent/git/github.com/localpibox/lemonade-pi-plugin" |
| 29 | + "/home/lpb/workspace/localpibox/config" |
| 30 | + "/home/lpb/workspace/localpibox/lpb-memory" |
| 31 | + "/home/lpb/.pi/agent/git/github.com/localpibox/pi-subagents" |
| 32 | +) |
| 33 | + |
| 34 | +SETTINGS_JSON="/home/lpb/.pi/agent/settings.json" |
| 35 | + |
| 36 | +info() { echo " ✅ $*"; } |
| 37 | +warn() { echo " ⚠️ $*"; } |
| 38 | +error() { echo " ❌ $*"; ERRORS=$((ERRORS + 1)); } |
| 39 | + |
| 40 | +# 1. Validate VERSION files |
| 41 | +echo "=== Stack Version Sync ===" |
| 42 | +for repo in "${REPOS[@]}"; do |
| 43 | + repo_name=$(basename "$repo") |
| 44 | + if [ ! -f "$repo/VERSION" ]; then |
| 45 | + error "$repo_name: missing VERSION file" |
| 46 | + continue |
| 47 | + fi |
| 48 | + repo_version=$(cat "$repo/VERSION") |
| 49 | + if [ "$repo_version" != "$CURRENT_VERSION" ]; then |
| 50 | + error "$repo_name: VERSION=$repo_version (expected $CURRENT_VERSION)" |
| 51 | + else |
| 52 | + info "$repo_name: $repo_version" |
| 53 | + fi |
| 54 | +done |
| 55 | + |
| 56 | +# 2. Validate lpb.stack.env |
| 57 | +echo "" |
| 58 | +echo "=== Environment Sync ===" |
| 59 | +if [ -f "$DEVSTACK_DIR/lpb.stack.env" ]; then |
| 60 | + PI_REF=$(grep '^LPB_PI_REF=' "$DEVSTACK_DIR/lpb.stack.env" | cut -d= -f2) |
| 61 | + if [ "$PI_REF" != "$CURRENT_VERSION" ]; then |
| 62 | + error "lpb.stack.env: LPB_PI_REF=$PI_REF (expected $CURRENT_VERSION)" |
| 63 | + sed -i "s/^LPB_PI_REF=.*/LPB_PI_REF=$CURRENT_VERSION/" "$DEVSTACK_DIR/lpb.stack.env" |
| 64 | + echo " 🔧 Fixed LPB_PI_REF to $CURRENT_VERSION" |
| 65 | + else |
| 66 | + info "lpb.stack.env: LPB_PI_REF=$PI_REF" |
| 67 | + fi |
| 68 | +fi |
| 69 | + |
| 70 | +# 3. Validate settings.json pins |
| 71 | +echo "" |
| 72 | +echo "=== Extension Pins ===" |
| 73 | +if [ -f "$SETTINGS_JSON" ]; then |
| 74 | + for repo in lemonade-pi-plugin lpb-memory pi-subagents; do |
| 75 | + if python3 -c " |
| 76 | +import json, sys |
| 77 | +with open('$SETTINGS_JSON') as f: |
| 78 | + d = json.load(f) |
| 79 | +pins = d.get('packages', []) |
| 80 | +for p in pins: |
| 81 | + if isinstance(p, dict) and '$repo' in p.get('extension', ''): |
| 82 | + ver = p.get('version', '') |
| 83 | + if ver and ver != '$CURRENT_VERSION': |
| 84 | + print(f'MISMATCH: {ver}') |
| 85 | + sys.exit(1) |
| 86 | + elif not ver: |
| 87 | + print('NO-PIN') |
| 88 | + sys.exit(1) |
| 89 | +sys.exit(0) |
| 90 | +" 2>/dev/null; then |
| 91 | + info "$repo: pinned correctly" |
| 92 | + else |
| 93 | + warn "$repo: pin verification skipped (format may differ)" |
| 94 | + fi |
| 95 | + done |
| 96 | +else |
| 97 | + warn "settings.json not found at $SETTINGS_JSON" |
| 98 | +fi |
| 99 | + |
| 100 | +# 4. Check repo cleanliness |
| 101 | +echo "" |
| 102 | +echo "=== Repo Cleanliness ===" |
| 103 | +for repo in "${REPOS[@]}"; do |
| 104 | + repo_name=$(basename "$repo") |
| 105 | + # Skip if repo doesn't exist |
| 106 | + [ -d "$repo" ] || continue |
| 107 | + dirty=$(cd "$repo" && git status --porcelain --short 2>/dev/null | grep -v VERSION | grep -v '.git/hooks' | wc -l) |
| 108 | + if [ "$dirty" -gt 0 ]; then |
| 109 | + error "$repo_name: $dirty uncommitted change(s) (excluding VERSION)" |
| 110 | + else |
| 111 | + info "$repo_name: clean" |
| 112 | + fi |
| 113 | +done |
| 114 | + |
| 115 | +# Check devstack |
| 116 | +if [ -d "$DEVSTACK_DIR" ]; then |
| 117 | + devstack_dirty=$(cd "$DEVSTACK_DIR" && git status --porcelain --short 2>/dev/null | grep -vE 'VERSION|lpb\.stack\.env|lpb\.conf\.env|\.githooks/' | wc -l) |
| 118 | + if [ "$devstack_dirty" -gt 0 ]; then |
| 119 | + error "devstack: $devstack_dirty uncommitted change(s)" |
| 120 | + else |
| 121 | + info "devstack: clean" |
| 122 | + fi |
| 123 | +fi |
| 124 | + |
| 125 | +# 5. Auto-add VERSION + env changes |
| 126 | +if [ $ERRORS -eq 0 ] && [ -n "$CURRENT_VERSION" ] && [ "$CURRENT_VERSION" != "NOT FOUND" ]; then |
| 127 | + echo "" |
| 128 | + echo "=== Auto-stage VERSION changes ===" |
| 129 | + cd "$DEVSTACK_DIR" |
| 130 | + git add VERSION lpb.stack.env 2>/dev/null |
| 131 | + for repo in "${REPOS[@]}"; do |
| 132 | + if [ -f "$repo/VERSION" ]; then |
| 133 | + (cd "$repo" && git add VERSION 2>/dev/null) |
| 134 | + fi |
| 135 | + done |
| 136 | + info "VERSION files + lpb.stack.env staged for commit" |
| 137 | +fi |
| 138 | + |
| 139 | +# Result |
| 140 | +echo "" |
| 141 | +if [ $ERRORS -gt 0 ]; then |
| 142 | + echo "❌ Pre-commit FAILED ($ERRORS error(s)) — commit aborted" |
| 143 | + echo " Fix issues above and try again, or use --no-verify to skip" |
| 144 | + exit 1 |
| 145 | +else |
| 146 | + echo "✅ All checks passed" |
| 147 | + exit 0 |
| 148 | +fi |
0 commit comments