home / skills / constellos / claude-code / branch-orchestration

This skill automates intelligent git branch naming, linking to issues, and lifecycle management to simplify branch workflows.

npx playbooks add skill constellos/claude-code --skill branch-orchestration

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.5 KB
---
name: Branch Orchestration
description: Use this skill when the user wants to "create branch", "rename branch", "smart branch name", "link branch to issue", or manage branch lifecycle. Provides intelligent branch naming with automatic issue linking following the pattern {issueNum}-{workType}/{kebab-name}.
version: 0.1.0
---

# 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:

```bash
# 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:

```bash
# 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:

```bash
# 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:

```bash
# 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:

```typescript
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

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

Generates formatted branch name.

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

### parseBranchName

```typescript
parseBranchName(branchName: string): ParsedBranchName
```

Parses branch name into components.

**Example:**
```typescript
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

```typescript
validateBranchName(branchName: string): BranchValidation
```

Validates branch name against conventions.

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

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

### extractIssueNumber

```typescript
extractIssueNumber(branchName: string): number | null
```

Extracts issue number from branch name.

**Example:**
```typescript
extractIssueNumber('42-feature/add-dark-mode')
// Returns: 42

extractIssueNumber('main')
// Returns: null
```

## Examples

### Example 1: Create Branch for Issue

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

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

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

Overview

This skill provides intelligent Git branch management with automated naming, issue linking, and lifecycle operations. It enforces the convention {issueNum}-{workType}/{kebab-name}, detects work types from context or labels, and automates creation, renaming, remote sync, and cleanup. Use it to keep branches consistent and traceable to issues.

How this skill works

The skill generates branch names using generateBranchName(issueNumber, workType, title) and converts titles to kebab-case with length limits. It can detect workType from issue labels or text via detectWorkType, validate names with validateBranchName, and extract issue IDs with extractIssueNumber. It also links branches to GitHub issues by updating issue bodies and a local state file (.claude/logs/branch-issues.json), and handles remote renames and deletions to keep refs in sync.

When to use it

  • Create a branch from an issue with a consistent, traceable name
  • Rename an existing branch to follow project conventions and sync remote
  • Automatically link a branch to its GitHub issue for easy tracking
  • Run CI validations to enforce branch naming rules
  • Bulk-clean merged or stale branches while respecting linked issues

Best practices

  • Always use the {issueNum}-{workType}/{kebab-name} pattern for new branches
  • Generate branch names from issue metadata when possible (title + labels)
  • Validate branch names in CI using validateBranchName before allowing merges
  • Keep .claude/logs/branch-issues.json updated when linking branches to issues
  • Push new branches with git push -u origin <branch> and remove old remotes when renaming
  • Regularly prune merged and stale branches, preferring to check issue state first

Example use cases

  • Create branch 42-feature/add-dark-mode from issue #42, push and add a comment linking it to the issue
  • Rename a local branch to 123-fix/safari-auth-bug, push the new branch and delete the old remote ref
  • Run validateBranchName in CI to block branches that don't match the convention
  • Bulk-delete merged branches and only remove those with closed linked issues
  • Switch to the branch that corresponds to a given issue number using a simple lookup

FAQ

What happens if a branch has no issue number?

You can still generate a branch name by omitting the issueNum portion, but best practice is to create branches from issue numbers to ensure traceability.

How does work type detection decide between feature and fix?

detectWorkType uses issue title and labels (like bug, docs) to infer the work type; label signals usually take precedence over free-text heuristics.