This document describes the branching model, daily workflow, release process, and CI/CD policies for this repository.
This repository uses a simplified git-flow model with two long-lived branches and short-lived feature branches. All changes reach main through pull requests — direct pushes to protected branches are blocked by git hooks.
main ← develop ← feature/*
│ │
│ ├── docs/issue-42-add-readme
│ ├── feat/issue-50-new-skill
│ └── fix/issue-55-hook-crash
│
└── (releases tagged from main)
| Branch | Purpose | Protection |
|---|---|---|
main |
Stable, release-ready code | Protected — no direct push, PR required |
develop |
Integration branch for ongoing work | Protected — no direct push, PR required |
feature/* |
Short-lived branches for individual changes | None — deleted after merge |
-
Create a feature branch from
develop:git checkout develop git pull origin develop git checkout -b feat/issue-123-description
-
Branch naming convention:
<type>/issue-<number>-<short-description>Common types:
feat,fix,docs,refactor,test,chore
-
Push to your feature branch:
git push origin feat/issue-123-description
-
Open a pull request targeting
develop. -
Merge via squash merge after review. Note: CI does not run on develop-targeting PRs (see CI/CD Policy).
-
Request review if required by your team's policy.
-
Squash merge the PR once approved and CI is green.
- The feature branch is deleted (manually or via GitHub auto-delete).
- Use
/branch-cleanupto remove stale local branches.
-
Open a pull request from
developintomain. -
Review the changelog — use
/releaseto generate it automatically from commit history. -
Merge into
mainvia squash merge after CI passes. -
Tag the release on
main:git checkout main git pull origin main git tag -a v1.8.0 -m "v1.8.0" git push origin v1.8.0 -
Create a GitHub Release from the tag.
-
Recreate
developfrommainto synchronize histories.Automated path (preferred). The
post-release-develop-resetworkflow (.github/workflows/post-release-develop-reset.yml) runs on every push tomainand performs the reset server-side. No manual action required when the release PR is squash-merged through the normal flow.Manual path (fallback). When the workflow is disabled, failed, or you need to reset develop outside the release flow, run:
MAIN_SHA=$(gh api repos/$ORG/$PROJECT/git/ref/heads/main --jq .object.sha) # 1. Delete develop on the server. gh api -X DELETE repos/$ORG/$PROJECT/git/refs/heads/develop # 2. Recreate develop at main's HEAD via the REST API. Using gh api # instead of `git push origin develop` avoids the local pre-push hook # that blocks pushes to protected branches — branch protection is # still applied to the new ref by GitHub. gh api -X POST repos/$ORG/$PROJECT/git/refs \ -f ref=refs/heads/develop \ -f sha="$MAIN_SHA"
Prerequisites. The following repository settings are required for either path to succeed:
default_branch = main. GitHub refuses to delete whichever branch is set as the repository default, somainmust own that role.developremains the working/integration branch but is not the repository default.develop.allow_deletions = trueon branch protection.allow_force_pushescan remainfalse— recreation creates a fresh ref, it does not rewrite develop's history.Interaction with
delete_branch_on_merge. The repository settingdelete_branch_on_merge = truecauses GitHub to auto-delete the head branch of every merged PR, includingdevelopwhen a release PR merges into main. The automated workflow is idempotent: when it fires on the release push, develop is typically already gone and the workflow simply creates it fresh at main's SHA. The manual fallback behaves the same — if step 1's delete returns 422 ("Reference does not exist"), proceed to step 2 directly.Why recreate develop? Squash merging develop → main produces a single commit on
mainwith a different SHA than the original commits ondevelop. This causes the two branches to diverge in git history, making subsequent develop → main PRs show conflicts on already-merged content. Deleting and recreatingdevelopfrommainafter each release keeps the histories aligned.
| Event | CI Triggered? |
|---|---|
Pull request to main |
Yes — full validation (skills, hooks, shellcheck) |
Pull request to develop |
No — code review only |
| Push to any branch | No |
Tag push (v*) |
No (releases are created manually) |
Both workflows use path filters — they only trigger when relevant files change:
validate-skills.yml: triggers on changes toglobal/skills/**,project/.claude/skills/**,plugin/skills/**validate-hooks.yml: triggers on changes toglobal/hooks/**,tests/hooks/**
| Workflow | What It Validates | Path Filter |
|---|---|---|
validate-skills.yml |
SKILL.md frontmatter format, name, description length | **/skills/** |
validate-hooks.yml |
Hook script correctness (test suite + shellcheck) | global/hooks/**, tests/hooks/** |
Release PRs (develop → main):
- All triggered CI checks must pass before merging.
- Squash merge is the only allowed merge strategy.
- Never merge while any check is
queuedorin_progress. - CI failures must be investigated — do not dismiss failures as flaky or unrelated.
Feature PRs (feature → develop):
- CI does not run. Code review is the quality gate.
- Squash merge is the only allowed merge strategy.
| Setting | main |
develop |
|---|---|---|
| PR required | Yes | Yes |
| Required status checks | None (path-filtered CI runs when triggered) | None |
| Enforce admins | Yes | Yes |
| Force pushes | Blocked | Blocked |
| Branch deletion | Blocked | Blocked |
| Merge method | Squash only | Squash only |
Note: Required status checks are not enforced at the branch protection level because path-filtered CI workflows do not trigger on every PR. When a workflow doesn't trigger, its required check would remain pending indefinitely, blocking all merges. Instead, CI runs as advisory checks — they execute when path filters match and block merge only when they fail.
| Layer | Type | Scope |
|---|---|---|
| GitHub branch protection | Server-side | Blocks direct push to main and develop |
pre-push git hook |
Client-side | Blocks direct push to main and develop locally |
pr-target-guard.sh PreToolUse hook |
Client-side | Blocks gh pr create whose effective base is main/master unless --head develop or --head release/*. When --base is omitted, the hook resolves the repo's default branch via gh api repos/{owner}/{repo} --jq .default_branch and applies the same protection — main-default repos no longer bypass the policy by virtue of omitting --base (#616). |
validate-pr-target.yml workflow |
Server-side | Auto-closes PRs targeting main from non-develop, non-release/* branches |
| Squash merge only | Server-side | Only squash merge allowed in GitHub settings |
| Auto-delete branches | Server-side | Cleans up feature branches after PR merge |
Create a hotfix branch from main, fix the issue, and open a PR directly to main. After merging, merge main back into develop to keep branches in sync.
git checkout main
git pull origin main
git checkout -b fix/issue-99-critical-bug
# ... fix and push ...
# Open PR to main, merge, then sync:
git checkout develop
git merge main
git push origin developFix the issue on develop first, then update the release PR. Never bypass failing CI checks to merge a release. If the failure is in a test, fix the test or the code — do not increase timeouts or skip tests without understanding the root cause.