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)
This commit is contained in:
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user