Skip to content

Commit 35a0bb2

Browse files
committed
refactor: Option C — single-source versioning (devstack only)
1 parent ec90d8c commit 35a0bb2

4 files changed

Lines changed: 138 additions & 116 deletions

File tree

.githooks/commit-msg

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,5 @@
11
#!/bin/bash
2-
# Auto-increment patch version on every commit (unless [skip-version])
3-
# Increments VERSION across all stack repos and auto-commits with [skip-version]
4-
# Does NOT touch package.json versions (kept at original fork versions)
5-
6-
COMMIT_MSG_FILE="$1"
7-
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
8-
9-
# [skip-version] → no bump, just pass through
10-
if echo "$COMMIT_MSG" | grep -q '\[skip-version\]'; then
11-
exit 0
12-
fi
13-
14-
# Find devstack dir by searching upward from the git working tree
15-
DEVSTACK_DIR="$(git rev-parse --show-toplevel 2>/dev/null)"
16-
[ -z "$DEVSTACK_DIR" ] && DEVSTACK_DIR="$PWD"
17-
18-
[ ! -f "$DEVSTACK_DIR/VERSION" ] && exit 0
19-
20-
VERSION=$(cat "$DEVSTACK_DIR/VERSION" 2>/dev/null || echo "0.0.0-lpb")
21-
BASE=$(echo "$VERSION" | sed 's/^[0-9]*\.[0-9]*\.[0-9]*//')
22-
PATCH=$(echo "$VERSION" | sed 's/^[0-9]*\.[0-9]*\.\([0-9]*\).*/\1/')
23-
NEW_PATCH=$((PATCH + 1))
24-
NEW_VERSION="0.0.${NEW_PATCH}${BASE}"
25-
26-
# Update VERSION in all stack repos
27-
REPOS=(
28-
"$DEVSTACK_DIR/workspace/pi"
29-
"/home/lpb/.pi/agent/git/github.com/localpibox/lemonade-pi-plugin"
30-
"/home/lpb/workspace/localpibox/config"
31-
"/home/lpb/workspace/localpibox/lpb-memory"
32-
"/home/lpb/.pi/agent/git/github.com/localpibox/pi-subagents"
33-
)
34-
35-
echo ""
36-
echo "=== Auto-bump VERSION ==="
37-
echo " $VERSION$NEW_VERSION"
38-
39-
echo "$NEW_VERSION" > "$DEVSTACK_DIR/VERSION"
40-
41-
for repo in "${REPOS[@]}"; do
42-
[ -f "$repo/VERSION" ] && echo "$NEW_VERSION" > "$repo/VERSION"
43-
done
44-
45-
# Update lpb.stack.env
46-
if [ -f "$DEVSTACK_DIR/lpb.stack.env" ]; then
47-
sed -i "s/^LPB_PI_REF=.*/LPB_PI_REF=$NEW_VERSION/" "$DEVSTACK_DIR/lpb.stack.env"
48-
fi
49-
50-
# Auto-commit VERSION in each repo (with [skip-version] to avoid infinite loop)
51-
for repo in "${REPOS[@]}"; do
52-
repo_name=$(basename "$repo")
53-
[ -d "$repo" ] || continue
54-
[ -f "$repo/VERSION" ] || continue
55-
# Stage and commit if VERSION is the only staged change
56-
(cd "$repo" && git add VERSION 2>/dev/null)
57-
staged=$(cd "$repo" && git diff --cached --name-only 2>/dev/null)
58-
if [ "$staged" = "VERSION" ]; then
59-
(cd "$repo" && git commit -m "chore: bump VERSION $NEW_VERSION [skip-version]" --no-verify 2>/dev/null) && \
60-
echo "$repo_name: $NEW_VERSION" || \
61-
echo " ⚠️ $repo_name: version updated on disk, not committed"
62-
fi
63-
done
64-
65-
# Auto-commit VERSION in devstack
66-
(cd "$DEVSTACK_DIR" && git add VERSION lpb.stack.env 2>/dev/null)
67-
staged=$(cd "$DEVSTACK_DIR" && git diff --cached --name-only 2>/dev/null)
68-
if echo "$staged" | grep -q "VERSION"; then
69-
(cd "$DEVSTACK_DIR" && git commit -m "chore: bump VERSION $NEW_VERSION [skip-version]" --no-verify 2>/dev/null) && \
70-
echo " ✅ devstack: $NEW_VERSION"
71-
fi
72-
2+
# commit-msg hook — No-op
3+
# Version bumping is handled by CI (bump-version job after tests pass)
4+
# Git hooks do NOT touch VERSION files or cross-repo state.
735
exit 0

.githooks/pre-commit

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
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

@@ -51,12 +50,13 @@ info() { echo " ✅ $*"; }
5150
warn() { echo " ⚠️ $*"; }
5251
error() { echo "$*"; ERRORS=$((ERRORS + 1)); }
5352

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
53+
# 1. Validate VERSION file format
54+
echo "=== Stack Version ==="
55+
if echo "$CURRENT_VERSION" | grep -qE '^0\.[0-9]+\.[0-9]+-lpb$'; then
56+
info "VERSION=$CURRENT_VERSION (valid)"
57+
else
58+
error "VERSION='$CURRENT_VERSION' does not match 0.x.y-lpb format"
59+
fi
6060

6161
# 2. Validate lpb.stack.env
6262
echo ""
@@ -65,8 +65,7 @@ if [ -f "$DEVSTACK_DIR/lpb.stack.env" ]; then
6565
PI_REF=$(grep '^LPB_PI_REF=' "$DEVSTACK_DIR/lpb.stack.env" | cut -d= -f2)
6666
if [ "$PI_REF" != "$CURRENT_VERSION" ]; then
6767
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"
68+
echo " 🔧 Hint: CI bumps VERSION + updates LPB_PI_REF automatically"
7069
else
7170
info "LPB_PI_REF=$PI_REF"
7271
fi
@@ -104,7 +103,7 @@ fi
104103

105104
# 4. Check repo cleanliness
106105
echo ""
107-
echo "=== Unstaged Changes ==="
106+
echo "=== Clean State ==="
108107
check_unstaged() {
109108
local repo_path="$1"
110109
local repo_name="$2"
@@ -120,19 +119,6 @@ done
120119

121120
check_unstaged "$DEVSTACK_DIR" "devstack"
122121

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
128-
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)
132-
done
133-
info "VERSION files staged (auto-commit by commit-msg hook)"
134-
fi
135-
136122
# Result
137123
echo ""
138124
if [ $ERRORS -gt 0 ]; then

.github/workflows/build-and-publish.yml

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
# Extensions update at runtime via pi update --extensions
66
#
77
# Triggers:
8+
# - Push to dev (code changes, NOT VERSION bumps)
89
# - Push to main (Dockerfile, support/, lpb.stack.env, lpb.conf.env)
910
# - Weekly cron (Monday 3am UTC) — keep image fresh
1011
# - Manual dispatch
1112
#
12-
# Images built:
13-
# ghcr.io/localpibox/devstack:cli — Base dev environment + Pi CLI
14-
# ghcr.io/localpibox/devstack:web — Extends cli + VSCodium server
13+
# Version model (Option C):
14+
# - Single source: devstack/VERSION
15+
# - CI bumps version after tests pass, pushes commit + tags
16+
# - All 6 repos share the same tag (0.0.x-lpb)
17+
# - Docker images tagged: :{version}-cli, :{version}-web
1518
#
1619
# Actions: all latest major versions (Node.js 24 native)
1720
# actions/checkout@v6 · docker/build-push-action@v7
@@ -27,19 +30,17 @@ on:
2730
paths:
2831
- 'Dockerfile'
2932
- 'support/**'
30-
- 'lpb.stack.env'
31-
- 'lpb.conf.env'
32-
- '.github/workflows/*.yml'
3333
- 'scripts/**'
34+
- '.github/workflows/*.yml'
35+
# NOTE: VERSION + lpb.stack.env changes intentionally excluded
36+
# to avoid re-triggering on auto-bump commits
3437
pull_request:
3538
branches: [main]
3639
paths:
3740
- 'Dockerfile'
3841
- 'support/**'
39-
- 'lpb.stack.env'
40-
- 'lpb.conf.env'
41-
- '.github/workflows/*.yml'
4242
- 'scripts/**'
43+
- '.github/workflows/*.yml'
4344
schedule:
4445
- cron: '0 3 * * 1'
4546
workflow_dispatch:
@@ -61,6 +62,9 @@ env:
6162
IMAGE_NAME: ghcr.io/localpibox/devstack
6263

6364
jobs:
65+
# ──────────────────────────────────────────────────────
66+
# Phase 1: Test
67+
# ──────────────────────────────────────────────────────
6468
test-lpb:
6569
name: Run lpb.py unit tests
6670
runs-on: ubuntu-latest
@@ -73,11 +77,77 @@ jobs:
7377
python3 scripts/test_lpb.py
7478
python3 scripts/test_localpibox.py
7579
80+
# ──────────────────────────────────────────────────────
81+
# Phase 2: Bump version + create tags
82+
# ──────────────────────────────────────────────────────
83+
bump-version:
84+
name: Bump version & create tags
85+
runs-on: ubuntu-latest
86+
needs: [test-lpb]
87+
if: ${{ github.event_name != 'pull_request' }}
88+
outputs:
89+
version: ${{ steps.bump.outputs.version }}
90+
steps:
91+
- name: Checkout (with write access)
92+
uses: actions/checkout@v6
93+
with:
94+
fetch-depth: 0
95+
persist-credentials: true
96+
97+
- name: Bump patch version
98+
id: bump
99+
run: |
100+
set -e
101+
VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0-lpb")
102+
PATCH=$(echo "$VERSION" | sed 's/^[0-9]*\.[0-9]*\.\([0-9]*\).*/\1/')
103+
NEW_PATCH=$((PATCH + 1))
104+
NEW_VERSION="0.0.${NEW_PATCH}-lpb"
105+
echo "Bumping $VERSION → $NEW_VERSION"
106+
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
107+
108+
# Update devstack
109+
echo "$NEW_VERSION" > VERSION
110+
sed -i "s/^LPB_PI_REF=.*/LPB_PI_REF=$NEW_VERSION/" lpb.stack.env
111+
112+
# Commit and push
113+
git config user.name "ci-localpibox"
114+
git config user.email "ci@localpibox.dev"
115+
git add VERSION lpb.stack.env
116+
git commit -m "chore: bump VERSION $NEW_VERSION [skip-version]"
117+
git push origin dev
118+
119+
- name: Create tags on all repos
120+
env:
121+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
122+
VERSION: ${{ steps.bump.outputs.version }}
123+
run: |
124+
set -e
125+
REPOS=(
126+
"localpibox/pi"
127+
"localpibox/pi-subagents"
128+
"localpibox/lemonade-pi-plugin"
129+
"localpibox/config"
130+
"localpibox/lpb-memory"
131+
)
132+
for repo in "${REPOS[@]}"; do
133+
echo "Tagging $repo@$VERSION"
134+
gh api repos/$repo/git/refs/tags \
135+
--method POST \
136+
-f ref="refs/tags/$VERSION" \
137+
-f sha="$(git rev-parse origin/lpb-dev 2>/dev/null || git rev-parse origin/dev 2>/dev/null || git rev-parse HEAD)" \
138+
--jq '.message' 2>/dev/null || true
139+
done
140+
echo "✅ Tags created on all 5 repos"
141+
142+
# ──────────────────────────────────────────────────────
143+
# Phase 3: Build & publish images
144+
# ──────────────────────────────────────────────────────
76145
build-cli:
77146
name: Build & publish cli image
78147
runs-on: ubuntu-latest
79148
timeout-minutes: 45
80-
needs: [test-lpb]
149+
needs: [bump-version]
150+
if: ${{ github.event_name != 'pull_request' }}
81151
steps:
82152
- name: Checkout
83153
uses: actions/checkout@v6
@@ -111,11 +181,8 @@ jobs:
111181
MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
112182
echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
113183
sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
114-
# Read stack version from config repo
115-
cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
116-
stack_ver=$(curl -sf "https://raw.githubusercontent.com/$cfg_repo/$LPB_CONFIG_REF/VERSION" 2>/dev/null || echo "unknown")
117184
echo "sha=${sha:-unknown}" >> "$GITHUB_OUTPUT"
118-
echo "stack_version=${stack_ver}" >> "$GITHUB_OUTPUT"
185+
echo "stack_version=${LPB_PI_REF}" >> "$GITHUB_OUTPUT"
119186
120187
- name: Build & push cli
121188
uses: docker/build-push-action@v7
@@ -142,7 +209,7 @@ jobs:
142209
${{ env.IMAGE_NAME }}:dev-cli
143210
${{ env.IMAGE_NAME }}:main-cli
144211
${{ env.IMAGE_NAME }}:${{ github.sha }}-cli
145-
${{ env.IMAGE_NAME }}:${{ steps.config.outputs.stack_version }}-cli
212+
${{ env.IMAGE_NAME }}:${{ needs.bump-version.outputs.version }}-cli
146213
${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && format('{0}:latest', env.IMAGE_NAME) || '' }}
147214
${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' && format('{0}:dev', env.IMAGE_NAME) || '' }}
148215
${{ github.event.inputs.publish_latest && format('{0}:latest', env.IMAGE_NAME) || '' }}
@@ -154,7 +221,8 @@ jobs:
154221
name: Build & publish web image
155222
runs-on: ubuntu-latest
156223
timeout-minutes: 45
157-
needs: [build-cli]
224+
needs: [build-cli, bump-version]
225+
if: ${{ github.event_name != 'pull_request' }}
158226
steps:
159227
- name: Checkout
160228
uses: actions/checkout@v6
@@ -188,11 +256,8 @@ jobs:
188256
MAX_TOKENS=$(grep -E '^LPB_MAX_TOKENS_CONTEXT_RATIO=' lpb.conf.env | cut -d= -f2- | tr -d '"' 2>/dev/null || true)
189257
echo "MAX_TOKENS=${MAX_TOKENS:-0.06}" >> "$GITHUB_OUTPUT"
190258
sha=$(git ls-remote "$LPB_PI_FORK" "refs/heads/$LPB_PI_REF" | awk '{print $1}')
191-
# Read stack version from config repo
192-
cfg_repo=$(echo "$LPB_CONFIG_FORK" | sed -E 's#https://github.com/([^/]+)/([^./]+)\.git#\1/\2#')
193-
stack_ver=$(curl -sf "https://raw.githubusercontent.com/$cfg_repo/$LPB_CONFIG_REF/VERSION" 2>/dev/null || echo "unknown")
194259
echo "sha=${sha:-unknown}" >> "$GITHUB_OUTPUT"
195-
echo "stack_version=${stack_ver}" >> "$GITHUB_OUTPUT"
260+
echo "stack_version=${LPB_PI_REF}" >> "$GITHUB_OUTPUT"
196261
197262
- name: Build & push web
198263
uses: docker/build-push-action@v7
@@ -219,6 +284,7 @@ jobs:
219284
${{ env.IMAGE_NAME }}:dev-web
220285
${{ env.IMAGE_NAME }}:main-web
221286
${{ env.IMAGE_NAME }}:${{ github.sha }}-web
287+
${{ env.IMAGE_NAME }}:${{ needs.bump-version.outputs.version }}-web
222288
${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && format('{0}:latest-web', env.IMAGE_NAME) || '' }}
223289
${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' && format('{0}:dev-web', env.IMAGE_NAME) || '' }}
224290
${{ github.event.inputs.publish_latest && format('{0}:latest-web', env.IMAGE_NAME) || '' }}

0 commit comments

Comments
 (0)