11#! /bin/bash
2- # pre-commit hook — Validate & sync all stack repos before commit
2+ # pre-commit hook — Validate devstack state before commit
33#
44# Validates:
5- # 1. All VERSION files in sync
5+ # 1. VERSION file format (devstack only)
66# 2. lpb.stack.env LPB_PI_REF matches VERSION
77# 3. settings.json pins match VERSION
88# 4. All repos clean (except VERSION/env)
9- # 5. Auto-stage + auto-commit VERSION across all repos
109#
1110# Exit non-zero to abort commit.
1211
@@ -20,6 +19,12 @@ while [ "$check_dir" != "/" ]; do
2019done
2120[ -z " $DEVSTACK_DIR " ] && DEVSTACK_DIR=" $PWD "
2221
22+ # If we can't find devstack, do a local-only validation
23+ NOT_IN_DEVSTACK=false
24+ if [ ! -f " $DEVSTACK_DIR /VERSION" ] || [ ! -f " $DEVSTACK_DIR /lpb.stack.env" ]; then
25+ NOT_IN_DEVSTACK=true
26+ fi
27+
2328ERRORS=0
2429CURRENT_VERSION=$( cat " $DEVSTACK_DIR /VERSION" 2> /dev/null || echo " NOT FOUND" )
2530
@@ -31,42 +36,32 @@ declare -a REPOS=(
3136 " /home/lpb/.pi/agent/git/github.com/localpibox/pi-subagents"
3237)
3338
34- check_repo_version () {
35- local repo_path=" $1 "
36- local repo_name=" $2 "
37- [ -d " $repo_path " ] || return 0
38- [ -f " $repo_path /VERSION" ] || return 0
39- local repo_version
40- repo_version=$( cat " $repo_path /VERSION" )
41- if [ " $repo_version " != " $CURRENT_VERSION " ]; then
42- error " $repo_name : VERSION=$repo_version (expected $CURRENT_VERSION )"
43- else
44- info " $repo_name : $repo_version "
45- fi
46- }
47-
4839SETTINGS_JSON=" /home/lpb/.pi/agent/settings.json"
4940
5041info () { echo " ✅ $* " ; }
5142warn () { echo " ⚠️ $* " ; }
5243error () { echo " ❌ $* " ; ERRORS=$(( ERRORS + 1 )) ; }
5344
54- # 1. Validate VERSION files
55- echo " === Stack Version Sync ==="
56- for repo in " ${REPOS[@]} " ; do
57- repo_name=$( basename " $repo " )
58- check_repo_version " $repo " " $repo_name "
59- done
60-
61- # 2. Validate lpb.stack.env
45+ # 1. Validate VERSION file format (devstack only)
46+ echo " === Stack Version ==="
47+ if [ " $NOT_IN_DEVSTACK " = true ]; then
48+ info " (running outside devstack — skipping version check)"
49+ else
50+ if echo " $CURRENT_VERSION " | grep -qE ' ^0\.[0-9]+\.[0-9]+-lpb$' ; then
51+ info " VERSION=$CURRENT_VERSION (valid)"
52+ else
53+ error " VERSION='$CURRENT_VERSION ' does not match 0.x.y-lpb format"
54+ fi
55+ fi
6256echo " "
6357echo " === Environment Sync ==="
64- if [ -f " $DEVSTACK_DIR /lpb.stack.env" ]; then
58+ if [ " $NOT_IN_DEVSTACK " = true ]; then
59+ info " (skipped — not in devstack)"
60+ elif [ -f " $DEVSTACK_DIR /lpb.stack.env" ]; then
6561 PI_REF=$( grep ' ^LPB_PI_REF=' " $DEVSTACK_DIR /lpb.stack.env" | cut -d= -f2)
6662 if [ " $PI_REF " != " $CURRENT_VERSION " ]; then
6763 error " lpb.stack.env: LPB_PI_REF=$PI_REF (expected $CURRENT_VERSION )"
68- sed -i " s/^LPB_PI_REF=.*/LPB_PI_REF=$CURRENT_VERSION /" " $DEVSTACK_DIR /lpb.stack.env"
69- echo " 🔧 Fixed LPB_PI_REF to $CURRENT_VERSION "
64+ echo " 🔧 Hint: CI bumps VERSION + updates LPB_PI_REF automatically"
7065 else
7166 info " LPB_PI_REF=$PI_REF "
7267 fi
7570# 3. Validate settings.json pins
7671echo " "
7772echo " === Extension Pins ==="
78- if [ -f " $SETTINGS_JSON " ]; then
73+ if [ " $NOT_IN_DEVSTACK " = true ]; then
74+ info " (skipped — not in devstack)"
75+ elif [ -f " $SETTINGS_JSON " ]; then
7976 for repo in lemonade-pi-plugin lpb-memory pi-subagents; do
8077 if python3 -c "
8178import json, sys
104101
105102# 4. Check repo cleanliness
106103echo " "
107- echo " === Unstaged Changes ==="
108- check_unstaged () {
109- local repo_path=" $1 "
110- local repo_name=" $2 "
111- [ -d " $repo_path " ] || return 0
112- local dirty
113- dirty=$( cd " $repo_path " && git diff --name-only 2> /dev/null | grep -vE ' ^$' | grep -vE ' VERSION|lpb\.stack\.env|lpb\.conf\.env|\.githooks/' | grep -c . || true)
114- [ " $dirty " -gt 0 ] && error " $repo_name : $dirty unstaged change(s)" || info " $repo_name : clean"
115- }
116-
117- for repo in " ${REPOS[@]} " ; do
118- check_unstaged " $repo " " $( basename " $repo " ) "
119- done
120-
121- check_unstaged " $DEVSTACK_DIR " " devstack"
104+ echo " === Clean State ==="
105+ if [ " $NOT_IN_DEVSTACK " = true ]; then
106+ info " (skipped — not in devstack)"
107+ else
108+ check_unstaged () {
109+ local repo_path=" $1 "
110+ local repo_name=" $2 "
111+ [ -d " $repo_path " ] || return 0
112+ local dirty
113+ dirty=$( cd " $repo_path " && git diff --name-only 2> /dev/null | grep -vE ' ^$' | grep -vE ' VERSION|lpb\.stack\.env|lpb\.conf\.env|\.githooks/' | grep -c . || true)
114+ [ " $dirty " -gt 0 ] && error " $repo_name : $dirty unstaged change(s)" || info " $repo_name : clean"
115+ }
122116
123- # 5. Auto-stage VERSION + env changes (commit-msg auto-commits after)
124- if [ $ERRORS -eq 0 ] && [ " $CURRENT_VERSION " != " NOT FOUND" ]; then
125- echo " "
126- echo " === Auto-stage VERSION ==="
127- cd " $DEVSTACK_DIR " && git add VERSION lpb.stack.env 2> /dev/null
128117 for repo in " ${REPOS[@]} " ; do
129- repo_name=$( basename " $repo " )
130- [ -d " $repo " ] || continue
131- [ -f " $repo /VERSION" ] && (cd " $repo " && git add VERSION 2> /dev/null)
118+ check_unstaged " $repo " " $( basename " $repo " ) "
132119 done
133- info " VERSION files staged (auto-commit by commit-msg hook)"
120+
121+ check_unstaged " $DEVSTACK_DIR " " devstack"
134122fi
135123
136124# Result
0 commit comments