Skip to content

[Chore] Adopt release-cut branch model (dev/main + tags) for the framework repo — not for managed projects #116

@atlas-apex

Description

@atlas-apex

Driver

External adopters are now using apexyard. Every commit on upstream/main is (potentially) pulled into their fork when they run /update. Under the current trunk-based model, main receives daily work-in-progress — half-finished skills, in-flight hook experiments, docs refactors mid-iteration. This was fine when the framework had one user; it's not fine now.

Fork users deserve upstream/main to mean "the state a reasonable adopter should live on." That's a release-branch promise, not a trunk promise.

Proposed model: "release-cut" (gitflow-lite)

Two long-lived branches, no release or hotfix branches:

dev   ────●──●──●──●──●──●──────●──●──────●──●──────  (daily work)
           \                    /          /
            \                  /          /
main  ──────●────────────────●──────────●──────────────  (released only, tagged)
            v1.1.0            v1.2.0    v1.2.1
  • dev — every feature/fix/chore PR targets here. All existing PR hooks + review gates apply unchanged.
  • main — only receives merges from dev, via release PRs. Every merge to main is tagged with a semver. Direct pushes blocked (already enforced by block-main-push.sh).
  • Release = devmain merge + git tag vX.Y.Z + git push --tags.

Explicitly NOT adopting from full git flow:

  • No release/* branches (no stabilisation window needed for a docs+hooks framework).
  • No hotfix/* branches (no multi-version support; forks always run latest).
  • No support/* branches.

See the rationale in the AgDR draft at the end of this ticket.

Scope

Framework only — NOT managed projects

This is the critical scoping call. apexyard-the-framework has downstream consumers (forks); it needs the release-branch promise. Managed projects under governance (per the registry) have no downstream consumers — they stay trunk-based with main as working. Contributors must not cargo-cult the dev/main pattern into project templates.

Changes required

Repo policy:

  1. Create dev branch from current main HEAD.
  2. Set dev as the default branch for PRs (GitHub repo setting + .github/PULL_REQUEST_TEMPLATE.md target).
  3. Branch protection on main:
    • Only merges from dev accepted (GitHub "restrict who can push" or a merge-gate check).
    • Required status checks: all existing + a "tag present" check on the merge commit.
  4. Branch protection on dev:
    • Same as today's main protection (required checks, review markers, CEO approval, design review).

Hooks:

  1. block-main-push.sh — expand to block direct commits/pushes to both main and dev. Today it blocks main; we need dev too.
  2. validate-pr-create.sh — default base is now dev; warn if a PR targets main but the author isn't on dev-HEAD (likely an accidental miss-target).
  3. check-upstream-drift.sh — no change (already tag-based; works identically).
  4. pre-push-gate.sh (from apexyard#111) — no change.

CI:

  1. Every workflow trigger that says on: push: [main] → add dev.
  2. Every workflow that says on: pull_request: [main] → switch to [dev] as primary; keep main for the release-PR case.

Skills:

  1. New /release skill:
    • Reads current dev HEAD, diffs against main.
    • Proposes a semver bump based on conventional commit types in the diff (breaking → major, feat → minor, fix/chore/docs → patch). Author can override.
    • Generates a changelog draft from commit messages (type+subject), grouped by type.
    • Creates the release PR: release/vX.Y.Z branch from dev, PR from there to main, title release: vX.Y.Z.
    • After the release PR merges: tags the merge commit, pushes the tag.
    • Optionally: opens a GitHub Release with the changelog body.
  2. /update skill — no change (already pulls upstream/main, which is now strictly release-tagged).

Docs:

  1. CLAUDE.md — new section clarifying the two-branch model, with the explicit "framework only, not managed projects" rule.
  2. docs/multi-project.md — reiterate that managed projects stay trunk-based.
  3. docs/getting-started.md — contributor flow updated (PRs target dev).
  4. docs/release-process.md (new) — how to cut a release, when, and the semver policy.
  5. AgDR under docs/agdr/ capturing the decision (draft below).

Migration:

  1. Retarget every open PR from main to dev before the cutover. Document the one-liner: gh pr edit N --base dev.
  2. Announce the change in the repo README + a pinned Discussions post.

Acceptance Criteria

  • dev branch exists, set as default.
  • main branch protection restricts merges to release-PRs from dev only.
  • block-main-push.sh blocks direct push/commit to both main and dev.
  • validate-pr-create.sh warns on misdirected base.
  • All CI workflows trigger on dev push + dev-targeting PRs.
  • /release skill exists and produces a valid release PR from dev HEAD.
  • /release skill tags the merged commit with semver and pushes the tag.
  • All open PRs at cutover retargeted to dev (zero PRs orphaned on main).
  • CLAUDE.md, multi-project.md, getting-started.md updated.
  • docs/release-process.md exists.
  • AgDR committed under docs/agdr/.
  • A first release cut through the new flow lands cleanly (dogfood).

Risks / Dependencies

  • Risk of contributor confusion. Short-term: PRs get opened against main by force of habit, get rejected, author is annoyed. Mitigation: clear banner on the repo, .github/PULL_REQUEST_TEMPLATE.md with a heading that names the target, migration-week grace period where hooks warn-only.
  • Risk of cargo-culting into managed projects. Mitigation: docs must be loud about framework-only. Add a SessionStart check (or at least a doc warning) if a managed-project registry entry has a dev branch.
  • Risk of stale release cadence. If devmain cadence slips, adopters get stale updates. Mitigation: target monthly cuts unless nothing material has landed.
  • Risk of the /release skill auto-tagging wrong version. Mitigation: always show the proposed bump for confirmation; never auto-tag without the author's explicit OK.
  • Depends on apexyard#109 (config location) only loosely — the release-branch change doesn't require any of the config work to land first, but hook updates in this ticket should share the config-reader pattern introduced by [Chore] Make ticket-prefix whitelist + schema project-configurable (not hardcoded) #109 where applicable.

Glossary

Term Definition
Release-cut model Two-branch strategy: daily work on dev, tagged releases on main. No explicit release or hotfix branches.
Trunk-based Single long-lived branch (usually main); features merge directly. What managed projects use.
Semver Semantic versioning: MAJOR.MINOR.PATCH where MAJOR = breaking, MINOR = backward-compatible feature, PATCH = backward-compatible fix.

AgDR draft (to commit as docs/agdr/AgDR-NNNN-release-branch-model.md during implementation)

---
id: AgDR-NNNN
timestamp: YYYY-MM-DDTHH:MM:SSZ
agent: claude
model: claude-opus-4-7
trigger: user-prompt
status: executed
---

# Adopt release-cut branch model (dev + main + tags) for apexyard

> In the context of external adopters forking apexyard and pulling from `upstream/main`,
> facing the problem that daily work-in-progress was polluting what downstream users see as "the framework",
> I decided to move daily work to a `dev` branch and reserve `main` for tagged releases (dev → main merge + semver tag),
> to achieve a predictable, adopter-safe upstream without the ceremony of full git flow,
> accepting that contributors need to learn a slightly different target branch and that release cadence must be curated.

## Context

- apexyard started as a single-user framework; `main` was both the working branch and the consumed surface.
- External adopters now fork the repo and sync via `/update` (which pulls `upstream/main`). Every commit on main reaches them immediately.
- Drift detection is already tag-based (apexyard v1.1.0), which means the infrastructure for release semantics exists; the policy does not.
- The model must stay lightweight — apexyard is docs + hooks + skills, not a compiled artefact needing stabilisation windows.

## Options Considered

| Option | Pros | Cons |
|--------|------|------|
| **A. Stay trunk-based (status quo)** | Zero change cost; one branch to reason about | Adopters pull WIP; no release promise |
| **B. Trunk + tags only (forks pin to tags)** | Minimal policy change | `/update` still pulls `main` HEAD unless we rewrite it to pull latest tag; adopters who don't pin still see WIP |
| **C. Release-cut (dev + main + tags) — CHOSEN** | Clean promise: main = released; light ceremony; compatible with existing tag-based drift | Contributors retarget PRs; release cadence must be curated; slight onboarding friction |
| **D. Full git flow (main + develop + release/* + hotfix/* + support/*)** | Handles multi-version maintenance, stabilisation, emergency patches | Heavy for a framework with one supported version and no compilation; most modern teams don't need it; overkill |

## Decision

Chosen: **Option C (release-cut)**, because it gives external adopters the release promise they need without the ceremony they don't. Full git flow's extra branch types (`release/*`, `hotfix/*`, `support/*`) solve problems apexyard doesn't have — there's no stabilisation window, no multi-version maintenance, and no compilation step that benefits from a soak period.

## Consequences

**Kept:**
- All existing hooks (block-main-push, validate-branch-name, validate-pr-create, merge-gate hooks) — they apply to `dev` PRs as-is.
- Tag-based drift detection — continues to work unchanged.
- `/update` skill — no behavioural change; semantic upgrade (pulls release-only content).

**Added:**
- `dev` branch as the daily-work trunk; default branch for PRs.
- Release-PR flow (`dev``main` + semver tag).
- `/release` skill to standardise the cut.
- Branch protection on `main` restricting merges to release-PRs only.

**Dropped:**
- Rolling-HEAD semantics on `main`. Adopters can no longer casually pull unreviewed WIP.

**Non-consequences (explicitly):**
- Managed projects under apexyard governance do NOT adopt this pattern. They stay trunk-based because they have no downstream consumers.
- No hotfix/support branches. If multi-version maintenance becomes a need, revisit.

## Artifacts

- Implementing ticket: this issue.
- Release skill: `.claude/skills/release/SKILL.md` (added in implementation).
- Release-process doc: `docs/release-process.md` (added in implementation).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High — material gap or user-impactingenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions