agentleFS
Sign inSign up

bigpowers / rules

danielvm-git/bigpowers/.cursor/rules/release-branch.mdc

Make the merge/PR/keep/discard decision for a feature branch, verify coverage gates, create the PR with gh, and clean up the worktree. Use when a feature is done and ready to ship, or when user says \"release\", \"merge\", or \"open a PR\".

Cursor rule240 starsChanged 30 days ago
  • Commits and pushes
---
description: "Make the merge/PR/keep/discard decision for a feature branch, verify coverage gates, create the PR with gh, and clean up the worktree. Use when a feature is done and ready to ship, or when user says \"release\", \"merge\", or \"open a PR\"."
alwaysApply: false
---

<!-- story: e38s07 -->
<!-- story: e45s15 -->
<!-- story: e45s32 -->
<!-- story: e45s39 -->
<!-- story: e82s02 -->
<!-- story: e20s02 -->


# Release Branch

> **HARD GATE** — Do NOT merge or release if tests fail or if coverage gates are not met. If the branch is red, return to `develop-tdd` to fix regressions or add missing tests before proceeding.

Finalize a completed feature branch: verify coverage gates, integrate onto `main`, and clean up the worktree.

## Additional modes
- `--hotfix`: Cherry-pick to main + tag. Skip PR in solo.
- `--squash-state`: Squash `chore(state):` commits before merge.

## Integrate mode

Read `specs/state.yaml` key `workflow_mode` (`team-pr` | `solo-git`). Fall back to `profiles/solo-git.md`.

| Mode | When | Ship path |
|------|------|-----------|
| **solo-local** | `workflow_mode: solo-git` | Auto: `scripts/land-branch.sh` if present, else fallback (Step 5) |
| **team-pr** | `workflow_mode: team-pr` (default) | `gh pr create` → `gh pr merge --squash` |

If unsure, prefer **solo-local**. Also read `state.yaml` `vcs.kind`: Git follows the procedures below; Jujutsu uses workspaces/bookmarks and must not call Git-only landing scripts.

## Process

> **Timing:** `bash scripts/bp-timing.sh start release-branch` at invocation; `bash scripts/bp-timing.sh end release-branch` before handoff.

### 1. Final verification

```bash
<full test command> && <typecheck command> && <lint command>
git log main...HEAD --oneline | grep -vE "^[a-f0-9]+ (feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+$" && echo "❌ Non-conventional commits found" || echo "✅ Commits verified"
# Block AI agent attribution (P1)
git log main...HEAD --format="%B" | grep -qiE 'co[- ]authored[- ]by' && echo "❌ Co-authored-by footer found — blocked" || echo "✅ No AI attribution"
```

- [ ] All tests pass, no type errors, no lint violations, all commits follow Conventional Commits
- [ ] **NO `Co-authored-by` or `Co-Authored-By`** in any commit body — P1 rule (CONVENTIONS.md § Git Attribution). `land-branch.sh` blocks the merge if found.

### 2. Coverage check

- [ ] Overall coverage ≥ 80%; business logic coverage ≥ 95%

### 2a. Security gate

- [ ] `specs/security/REVIEW.md` exists and is fresh (matches current branch diff)
- [ ] No unresolved HIGH findings with confidence ≥ 8 (or all documented in `specs/security/EXCEPTIONS.md` with sign-off rationale)

If REVIEW.md is missing or stale → run `security-review` inline. Findings block the merge unless documented in EXCEPTIONS.md.

### 2b. Traceability gate

Run `gate-trace` before merge. FAIL blocks merge; CONCERNS requires explicit override in `specs/state.yaml` (`traceability_override: CONCERNS accepted, reason: <explanation>`). WAIVED if no matrix available.

> **Adversarial refute framing (e45s32):** The final pre-merge check is **refute, not rubber-stamp**. Before declaring ready, actively try to disprove traceability completeness — missing story tags, absent verify evidence, stale security review. Only proceed when refutation fails.

### 3. Diff review

- [ ] All commits intentional, no secrets, CONVENTIONS.md compliance

### 4. Decision

Options: **Release (solo-local)** / **Open PR** / **Keep branch** / **Discard**

### 5. Integrate

Run `commit-message` first. Git solo-local uses `land-branch.sh`. Jujutsu team mode explicitly advances and pushes a bookmark; `-m` is mandatory for commit/describe operations:

```bash
# Git solo-local
bash scripts/land-branch.sh <task-slug> "feat(scope): description"
# Jujutsu team PR
jj describe -m "feat(scope): description"
jj bookmark set <task-slug> -r @
jj git push -b <task-slug>
```

Jujutsu solo-local landing is unsupported: run `bp_require_vcs_operation "$PWD" land-branch` and stop with its remediation instead of invoking the Git backend.

### 6. Create PR (team-pr only)

Create the pull request with a **literal provenance marker** in the body so agent-generated PRs are identifiable and do not rot silently:

```markdown
<!-- bigpowers-provenance: agent-generated -->
```

Place the marker on its own line immediately after the `## Summary` heading. Then merge via `gh`:

```bash
gh pr create --title "..." --body "$(cat <<'EOF'
## Summary
<!-- bigpowers-provenance: agent-generated -->

- ...

## Test plan
- [ ] ...

EOF
)"
gh pr merge --squash --delete-branch
```

`semantic-release` auto-detects the commit, bumps SemVer, tags the repo, generates release notes.

### 7a. Archive completed epic capsule

> **HARD GATE** — When all epic stories are done (all `done` in `execution-status.yaml`), archive the capsule:

```bash
mv specs/epics/eNN-slug specs/epics/archive/
```

### 7b. CI verification & agent lock release (e39s02)

> **HARD GATE** — Do NOT declare success until CI completes. **Three-independent-facts** (e45s15): commit landed, workflow green, registry visible — see [REFERENCE.md](REFERENCE.md#three-independent-facts-release).

```bash
bash scripts/wait-for-ci.sh --timeout 600 --interval 30
```

- [ ] CI passes; `release.ci_verified: true` in state.yaml
- On failure: `handoff.next_skill = fix-bug`

### 8. Clean up & return

Git: prune worktree, delete branch, return to main. Jujutsu: `jj workspace forget <workspace>` only after integration; do not delete its bookmark implicitly. Cycle-time: see [REFERENCE.md](REFERENCE.md#cycle-time).

Report: "Branch released."

## Verify

→ verify: `command -v gh >/dev/null 2>&1 && test -f specs/state.yaml && test -d skills/verify-work`

---

# Release Branch — Reference

## Navigation

| Lines | Section |
|-------|---------|
| 1 | Title |
| 3–22 | Navigation |
| 23–31 | PR body template (team-pr mode) |
| 32–35 | Summary |
| 36–41 | Verify |
| 42–47 | specs/ artifacts |
| 48–58 | Worktree cleanup details |
| 59–81 | Cycle-time recording |
| 82–100 | Why not story_start minus story_end? |
| 101–121 | CI verification |
| 122–127 | Solo-local fallback detail |
| 128–134 | Handoff |
| 135–159 | Reference block 1 |

# Release Branch — Reference

## PR body template (team-pr mode)

```bash
PR_TITLE="<type>(<scope>): <description>"
echo "$PR_TITLE" | grep -vE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+$" && echo "❌ ERROR: PR Title must follow Conventional Commits"

gh pr create \
  --title "$PR_TITLE" \
  --body "$(cat <<'EOF'
## Summary
- [What this PR does]
- [Key decisions made]

## Verify
- [ ] All tests pass
- [ ] Coverage gates met (≥80% overall, ≥95% business logic)
- [ ] CONVENTIONS.md compliance verified
- [ ] PR Title follows Conventional Commits (for automated release)

## specs/ artifacts
- [List any specs/ files produced or updated]
EOF
)"
```

## Worktree cleanup details

```bash
# From the main repo root
git worktree prune
git worktree remove ../<branch-name> 2>/dev/null || true
git branch -d <branch-name>
```

If `git worktree remove` fails due to uncommitted changes, ask: "There are uncommitted changes in the worktree. Force remove? (y/n)". If yes: `git worktree remove -f ../<branch-name>`.

## Cycle-time recording

After landing the branch, record delivery metrics using the git-derived,
additive script (replaces hand-arithmetic):

```bash
bash scripts/record-cycle-time.sh append \
  --story <story_id> --bcps <bcps> \
  --range "$(git merge-base main HEAD)..HEAD" \
  --file specs/metrics/cycle-times.yaml
```

This appends a row to the cycle-times ledger with two separated metrics:

- **effort_hours** — ADDITIVE. Idle-stripped estimated effort from git commit
  history (git-hours model: 120-min session threshold, 120-min first-commit pad).
  Sums exactly to whole-repo effort. NO hand-arithmetic, NO wall-clock includes.
- **lead_time_minutes** — calendar latency from first commit to merge.
  Median-aggregated across stories; NEVER summed.

The script also runs an additivity self-check: Σ(story effort) == whole-repo effort
within rounding tolerance.

### Why not story_start minus story_end?

The previous hand-arithmetic approach (survey-context writes `story_start`,
release-branch writes `story_end`, agent hand-computes `cycle_minutes`) was
retired because:

1. It was **agent-self-reported** — trivially fabricated or mis-subtracted.
2. Wall-clock included **overnight/weekend/UAT gaps** — calendar latency,
   not coding effort.
3. The `bcp_per_hour` metric was **computationally meaningless** (velocity
   derived from a latency measurement).

The new approach derives effort from commit history (objective, reproducible)
and lead time from first commit → merge (honest calendar latency). See
`docs/references/bcp.md` for BCP sizing context and
`scripts/record-cycle-time.sh` for the full algorithm.

---

## CI verification

The CI polling logic has been extracted to `scripts/wait-for-ci.sh`.
See the script's `--help` for usage. Step 7b of the main SKILL.md invokes it directly:

```bash
bash scripts/wait-for-ci.sh --timeout 600 --interval 30
```

**Exit codes:**
- **0** — all workflows green. Set `release.ci_verified: true` in state.yaml.
- **1** — at least one workflow failed. Prints failure URLs. Set `handoff.next_skill = fix-bug`.
- **2** — timeout. CI did not complete. Retry or investigate.
- **0 with warning** — `gh` CLI not available, git-only fallback confirmed push landed but CI status unverified.

The script handles: auto-discovery of all workflows for the current
branch/commit, polling until completion, success/failure/timeout exit codes,
and git-only fallback when `gh` CLI is unavailable.

---

## Solo-local fallback detail

The fallback sequence (Path B above) handles the "remote has moved" case with `git pull --rebase`. Use when `scripts/land-branch.sh` is absent.

**Acceptance:** When fallback runs, main is updated, feature branch is deleted locally, and output states `"used fallback merge (land-branch.sh not found)"`.

## Handoff

Gate: READY -> next: survey-context
Writes: state.yaml handoff.next_skill = survey-context

---

## Reference block 1

```bash
# Fallback: manual squash-merge when land-branch.sh is absent
FEATURE_BRANCH=<task-slug>
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo main)

# Ensure we're on the feature branch
if [ "$(git branch --show-current)" != "$FEATURE_BRANCH" ]; then
  git checkout "$FEATURE_BRANCH"
fi

# Checkout default branch and update
git checkout "$DEFAULT_BRANCH"
git pull --rebase origin "$DEFAULT_BRANCH" 2>/dev/null || git pull origin "$DEFAULT_BRANCH"

# Squash-merge the feature branch
git merge --no-ff "$FEATURE_BRANCH" -m "<conventional-commit-message>"

# Push
git push origin "$DEFAULT_BRANCH"

# Clean up local feature branch
git branch -d "$FEATURE_BRANCH"
```

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.