Branch Orchestration

SKILL.md

Branch Orchestration

Intelligent Git branch management with automated naming, issue linking, and lifecycle operations.

Purpose

Branch Orchestration provides systematic branch naming and management following the convention {issueNum}-{workType}/{kebab-name}. Automatically links branches to GitHub issues, detects work types, and manages branch lifecycle including creation, renaming, remote sync, and cleanup.

When to Use

  • Creating branches with smart naming from issue context
  • Renaming branches to follow project conventions
  • Linking branches to GitHub issues
  • Cleaning up merged or stale branches
  • When hooks don't provide enough control over naming

Core Capabilities

Branch Naming Convention

Format: {issueNumber}-{workType}/{kebab-case-title}

Examples:

  • 42-feature/add-dark-mode
  • 123-fix/safari-auth-bug
  • 7-docs/update-readme
  • 99-refactor/simplify-api

Branch Creation

Create branches with intelligent naming:

# Manual creation with naming utilities
ISSUE_NUM=42
WORK_TYPE="feature"
TITLE="Add dark mode support"

# Generate branch name
BRANCH_NAME=$(generateBranchName $ISSUE_NUM "$WORK_TYPE" "$TITLE")
# Result: "42-feature/add-dark-mode-support"

# Create and checkout branch
git checkout -b "$BRANCH_NAME"

# Push to remote with tracking
git push -u origin "$BRANCH_NAME"

Utilities:

  • generateBranchName(issueNumber, workType, title) - Generate formatted name
  • toKebabCase(title) - Convert title to kebab-case (max 40 chars)
  • validateBranchName(branchName) - Check naming conventions

Branch Renaming

Rename branches with remote sync:

# Get current branch
OLD_BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Generate new name
NEW_BRANCH="42-feature/add-dark-mode"

# Rename local branch
git branch -m "$OLD_BRANCH" "$NEW_BRANCH"

# Check if old branch exists on remote
if git ls-remote --heads origin "$OLD_BRANCH" | grep -q "$OLD_BRANCH"; then
  # Push new branch and delete old remote
  git push -u origin "$NEW_BRANCH"
  git push origin --delete "$OLD_BRANCH"
else
  # Just set upstream for new branch
  git push -u origin "$NEW_BRANCH"
fi

Utilities:

  • parseBranchName(branchName) - Extract components (issue#, work type, title)
  • extractIssueNumber(branchName) - Get issue number from branch name

Branch Linking

Link branches to GitHub issues:

# Add branch reference to issue body
ISSUE_NUM=42
BRANCH_NAME="42-feature/add-dark-mode"

CURRENT_BODY=$(gh issue view $ISSUE_NUM --json body -q .body)
UPDATED_BODY="$CURRENT_BODY

---

**Branch:** \`$BRANCH_NAME\`"

echo "$UPDATED_BODY" | gh issue edit $ISSUE_NUM --body-file -

# Save to state file
STATE_FILE=".claude/logs/branch-issues.json"
jq --arg branch "$BRANCH_NAME" --arg issue "$ISSUE_NUM" \
  '.[$branch] = {issueNumber: ($issue | tonumber)}' \
  "$STATE_FILE" > tmp.json && mv tmp.json "$STATE_FILE"

State File:

  • .claude/logs/branch-issues.json - Maps branch names to issue numbers

Branch Cleanup

Clean up merged or stale branches:

# Delete merged local branches
git branch --merged main | grep -v "^\*\|main\|master" | xargs -n 1 git branch -d

# Delete merged remote branches
gh pr list --state merged --json headRefName -q '.[].headRefName' | \
  xargs -I {} git push origin --delete {}

# Delete stale branches (no commits in 30 days)
git for-each-ref --sort=-committerdate refs/heads/ \
  --format='%(refname:short) %(committerdate:relative)' | \
  awk '$2 ~ /months/ && $3 >= 1 {print $1}' | \
  xargs -n 1 git branch -D

Work Type Detection

Automatically detect work type from context:

import { detectWorkType } from '../shared/hooks/utils/work-type-detector.js';

// From prompt
const workType = detectWorkType('fix the authentication bug');
// Returns: 'fix'

// From issue labels
const workType = detectWorkType(
  'Update authentication system',
  ['bug', 'priority:high']
);
// Returns: 'fix' (detected from 'bug' label)

Work Types:

  • feature - New functionality
  • fix - Bug fixes
  • chore - Maintenance tasks
  • docs - Documentation
  • refactor - Code improvements

Integration with Hooks

This skill complements the automatic hooks:

Hook Automatic Behavior When to Use Skill
create-issue-on-prompt Renames branch after issue creation Rename before issue creation, custom naming
add-github-context Discovers issue from branch name Manual branch-issue linking

State Files:

  • .claude/logs/branch-issues.json - Branch → Issue mapping

Naming Utilities

generateBranchName

generateBranchName(issueNumber: number, workType: WorkType, title: string): string

Generates formatted branch name.

Example:

generateBranchName(42, 'feature', 'Add Dark Mode')
// Returns: "42-feature/add-dark-mode"

parseBranchName

parseBranchName(branchName: string): ParsedBranchName

Parses branch name into components.

Example:

parseBranchName('42-feature/add-dark-mode')
// Returns: { issueNumber: 42, workType: 'feature', title: 'add-dark-mode' }

parseBranchName('123-fix-auth-bug')
// Returns: { issueNumber: 123, title: 'fix-auth-bug' }

validateBranchName

validateBranchName(branchName: string): BranchValidation

Validates branch name against conventions.

Example:

validateBranchName('42-feature/add-dark-mode')
// Returns: { valid: true }

validateBranchName('invalid name with spaces')
// Returns: { valid: false, reason: 'Branch name cannot contain spaces' }

extractIssueNumber

extractIssueNumber(branchName: string): number | null

Extracts issue number from branch name.

Example:

extractIssueNumber('42-feature/add-dark-mode')
// Returns: 42

extractIssueNumber('main')
// Returns: null

Examples

Example 1: Create Branch for Issue

# Get issue details
ISSUE_NUM=42
ISSUE_DATA=$(gh issue view $ISSUE_NUM --json title,labels)
TITLE=$(echo "$ISSUE_DATA" | jq -r '.title')
LABELS=$(echo "$ISSUE_DATA" | jq -r '.labels[].name' | tr '\n' ',')

# Detect work type
WORK_TYPE=$(detectWorkType "$TITLE" "$LABELS")
# Returns: 'feature' or 'fix' or 'chore', etc.

# Generate branch name
BRANCH_NAME=$(generateBranchName $ISSUE_NUM "$WORK_TYPE" "$TITLE")
# Returns: "42-feature/add-dark-mode-support"

# Create and checkout
git checkout -b "$BRANCH_NAME"
git push -u origin "$BRANCH_NAME"

# Link to issue
echo "Branch \`$BRANCH_NAME\` created" | gh issue comment $ISSUE_NUM --body-file -

Example 2: Rename Branch to Convention

# Current branch doesn't follow convention
CURRENT=$(git rev-parse --abbrev-ref HEAD)
# e.g., "claude-agile-narwhal-x7h3k"

# Get linked issue (if exists)
ISSUE_NUM=$(parseIssueFromBranch "$CURRENT")

if [ -z "$ISSUE_NUM" ]; then
  echo "No issue linked to this branch"
  exit 1
fi

# Get issue details
ISSUE_DATA=$(gh issue view $ISSUE_NUM --json title,labels)
TITLE=$(echo "$ISSUE_DATA" | jq -r '.title')
LABELS=$(echo "$ISSUE_DATA" | jq -r '.labels[].name' | tr '\n' ',')

# Generate new name
WORK_TYPE=$(detectWorkType "$TITLE" "$LABELS")
NEW_BRANCH=$(generateBranchName $ISSUE_NUM "$WORK_TYPE" "$TITLE")

# Rename with remote sync
git branch -m "$CURRENT" "$NEW_BRANCH"
git push -u origin "$NEW_BRANCH"
git push origin --delete "$CURRENT" 2>/dev/null || true

Example 3: Validate Branch Names in CI

# Get current branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Skip validation for protected branches
if [[ "$BRANCH" =~ ^(main|master|develop)$ ]]; then
  exit 0
fi

# Validate format
if ! validateBranchName "$BRANCH"; then
  echo "❌ Branch name '$BRANCH' doesn't follow convention: {issueNum}-{workType}/{kebab-name}"
  echo "Examples: 42-feature/add-dark-mode, 123-fix/safari-bug"
  exit 1
fi

# Check if issue exists
ISSUE_NUM=$(extractIssueNumber "$BRANCH")
if [ -n "$ISSUE_NUM" ]; then
  if ! gh issue view $ISSUE_NUM &>/dev/null; then
    echo "⚠️  Issue #$ISSUE_NUM referenced in branch name doesn't exist"
  fi
fi

Example 4: Bulk Branch Cleanup

# Get all merged branches
MERGED=$(git branch --merged main | grep -v "^\*\|main\|master")

for branch in $MERGED; do
  # Parse branch name
  PARSED=$(parseBranchName "$branch")
  ISSUE_NUM=$(echo "$PARSED" | jq -r '.issueNumber // empty')

  # Check if issue is closed
  if [ -n "$ISSUE_NUM" ]; then
    STATE=$(gh issue view $ISSUE_NUM --json state -q .state 2>/dev/null || echo "")
    if [ "$STATE" = "CLOSED" ]; then
      echo "Deleting merged branch: $branch (issue #$ISSUE_NUM closed)"
      git branch -d "$branch"
      git push origin --delete "$branch" 2>/dev/null || true
    fi
  else
    echo "Deleting merged branch: $branch (no linked issue)"
    git branch -d "$branch"
  fi
done

Best Practices

  1. Follow naming convention - Always use {issueNum}-{workType}/{kebab-name}
  2. Link to issues - Create branches from issue numbers when possible
  3. Validate before push - Use validateBranchName() to check format
  4. Clean up regularly - Delete merged and stale branches
  5. Update state files - Keep .claude/logs/branch-issues.json synced
  6. Use work type detection - Automatically categorize with detectWorkType()
  7. Sync with remote - Always use -u flag when pushing new branches

Common Patterns

Pattern: Create Branch from Current Issue

# Auto-detect issue from current context
ISSUE_NUM=$(cat .claude/logs/branch-issues.json | jq -r '.[] | .issueNumber' | head -1)

if [ -n "$ISSUE_NUM" ]; then
  ISSUE_DATA=$(gh issue view $ISSUE_NUM --json title,labels)
  TITLE=$(echo "$ISSUE_DATA" | jq -r '.title')
  WORK_TYPE=$(detectWorkType "$TITLE")
  BRANCH_NAME=$(generateBranchName $ISSUE_NUM "$WORK_TYPE" "$TITLE")

  git checkout -b "$BRANCH_NAME"
  git push -u origin "$BRANCH_NAME"
fi

Pattern: Switch Branch by Issue Number

# Switch to branch for issue #42
ISSUE_NUM=42
BRANCH=$(git branch -a | grep -E "^[ *]*$ISSUE_NUM-" | sed 's/^[ *]*//' | head -1)

if [ -n "$BRANCH" ]; then
  git checkout "$BRANCH"
else
  echo "No branch found for issue #$ISSUE_NUM"
fi

Pattern: List Branches by Work Type

# List all feature branches
git branch | grep -E "feature/" | sed 's/^[ *]*//'

# List all fix branches
git branch | grep -E "fix/" | sed 's/^[ *]*//'
Weekly Installs
0
GitHub Stars
5
First Seen
Jan 1, 1970