Files
Jon Chery ab6af144b7 feat(P06): 3-tier versioning, branch hierarchy enforcement, ARCHITECTURE-PLAN synthesis
---ci---
phase: 6
milestone: v0.5
status: complete
decisions:
  - id: D-006
    decision: Research as intermediate work product
    rationale: Conclusions update .ci/ files; full research doc intentionally not preserved
    confidence: 0.90
  - id: D-007
    decision: Branch hierarchy enforcement: main > milestone > phase
    rationale: Prevents out-of-order merges and semantically wrong tags
    confidence: 0.92
  - id: D-008
    decision: 3-tier versioning: NFR/feature/schema-breaking
    rationale: Patch per phase (NFR/feature) or minor per phase (schema-breaking); milestone gets minor (feature) or major (schema-breaking)
    confidence: 0.95
requirements:
  covered: [VER-06, BRANCH-01, BRANCH-02, ARCH-01]
---/ci---

- Synthesize ARCHITECTURE-PLAN.md into .ci/ci/ARCHITECTURE.md (expanded 51→230 lines)
- Add D-006/D-007/D-008 to .ci/ci/PROJECT.md key decisions table
- Delete ARCHITECTURE-PLAN.md after synthesis
- Rewrite ship.md with 3-tier versioning model + branch hierarchy merge flows
- Rewrite branch-strategy.md with 3-tier versioning + branch hierarchy + version validation
- Add MilestoneType to config types
- Replace isNfrMilestone() with getMilestoneType() returning nfr|feature|schema-breaking
- Add validateMergeOrder(), mergeMilestoneBranch(), computeMilestoneTag() to GitBranch
- Add computeShipVersion(), validateVersionOrder(), resolveMergeTarget() to ship command
- Remove hardcoded v0.5. from error-recovery rollback
- Create .githooks/pre-push for semver ordering + branch hierarchy validation
- Add 15 new tests (370 total, all passing)
2026-05-29 17:18:10 +00:00

80 lines
3.4 KiB
Bash
Executable File

#!/bin/bash
# CI pre-push hook: enforce versioning and branching rules
# Install: git config core.hooksPath .githooks
zero="0000000000000000000000000000000000000000"
while read local_ref local_oid remote_ref remote_oid; do
if [ "$local_oid" = "$zero" ]; then
continue
fi
# Check pushed tags
if echo "$local_ref" | grep -qE "^refs/tags/"; then
tag_name=$(echo "$local_ref" | sed 's|^refs/tags/||')
# Validate semver format
if echo "$tag_name" | grep -qE "^v[0-9]+\.[0-9]+\.[0-9]+$"; then
tag_major=$(echo "$tag_name" | sed 's/v\([0-9]*\)\.[0-9]*\.[0-9]*/\1/')
tag_minor=$(echo "$tag_name" | sed 's/v[0-9]*\.\([0-9]*\)\.[0-9]*/\1/')
tag_patch=$(echo "$tag_name" | sed 's/v[0-9]*\.[0-9]*\.\([0-9]*\)/\1/')
# Check for semver ordering violations
for existing_tag in $(git tag -l "v${tag_major}.${tag_minor}.*" 2>/dev/null); do
if [ "$existing_tag" = "$tag_name" ]; then
continue
fi
existing_patch=$(echo "$existing_tag" | sed 's/v[0-9]*\.[0-9]*\.\([0-9]*\)/\1/')
if [ "$existing_patch" -ge "$tag_patch" ] && [ "$tag_patch" -le "$existing_patch" ]; then
echo "ERROR: Tag $tag_name is not greater than existing tag $existing_tag"
echo " Milestone tags must be the NEXT version (e.g., v0.6.0 after v0.5.1-5, NOT v0.5.0)"
exit 1
fi
done
# Check for milestone-tags-below-phase-tags
# If this is a .0 tag (milestone), verify no .N tags exist with higher patch
if [ "$tag_patch" = "0" ]; then
for existing_tag in $(git tag -l "v${tag_major}.${tag_minor}.*" 2>/dev/null); do
existing_patch=$(echo "$existing_tag" | sed 's/v[0-9]*\.[0-9]*\.\([0-9]*\)/\1/')
if [ "$existing_patch" -gt 0 ] && [ "$existing_patch" -gt "$tag_patch" ]; then
echo "ERROR: Milestone tag $tag_name is below existing phase tags (e.g., $existing_tag)"
echo " Feature milestone completion must be tagged as v${tag_major}.$(($tag_minor + 1)).0, not v${tag_major}.${tag_minor}.0"
exit 1
fi
done
fi
fi
fi
# Check branch merges: reject direct-to-main pushes if milestone branch exists
if echo "$local_ref" | grep -qE "^refs/heads/main$"; then
milestone_branches=$(git branch -r 2>/dev/null | grep 'milestone/v' | grep -v ':$' || true)
if [ -n "$milestone_branches" ]; then
# Allow if this is a merge commit from a milestone branch
merge_parents=$(git cat-file -p "$local_oid" 2>/dev/null | grep "^parent" | wc -l)
if [ "$merge_parents" -lt 2 ]; then
# Not a merge commit — check if there are active milestone branches
active_milestones=""
for mb in $milestone_branches; do
clean_name=$(echo "$mb" | sed 's|^[^/]*/||' | tr -d ' ')
merged=$(git branch -r --merged origin/main 2>/dev/null | grep "$clean_name" || true)
if [ -z "$merged" ]; then
active_milestones="$active_milestones $clean_name"
fi
done
if [ -n "$active_milestones" ]; then
echo "WARNING: Pushing directly to main while active milestone branches exist:"
for ms in $active_milestones; do
echo " - $ms"
done
echo " Phase branches should merge into the milestone branch first."
# Warning only — not blocking. The code-level enforcement in git-branch.ts
# is the hard gate; this hook is a safety net.
fi
fi
fi
fi
done
exit 0