Skip to content

Cleanup temporary state files after blueprint completion #1

Cleanup temporary state files after blueprint completion

Cleanup temporary state files after blueprint completion #1

# ─────────────────────────────────────────────────────────────────
# Create Branch on Issue Workflow
# ─────────────────────────────────────────────────────────────────
# Automatically creates feature branches when issues are labeled
# with both 'claude-code' and 'status:ready'.
#
# Features:
# - Smart branch naming: {type}/issue-{number}-{slug}
# - Base branch detection (dev → main fallback)
# - Configurable via labels (base:staging, etc.)
# - Idempotency (skip if branch exists)
# - Project board status update (→ In Progress)
# - Helpful comment with checkout instructions
#
# Author: Alireza Rezvani
# Date: 2025-11-06
# ─────────────────────────────────────────────────────────────────
name: Create Branch on Issue
on:
issues:
types:
- labeled
permissions:
contents: write
issues: write
pull-requests: read
jobs:
# ─────────────────────────────────────────────────────────────────
# Check Trigger Conditions
# ─────────────────────────────────────────────────────────────────
check-labels:
name: Check Required Labels
runs-on: ubuntu-latest
outputs:
should-create-branch: ${{ steps.check.outputs.should-create-branch }}
has-claude-code: ${{ steps.check.outputs.has-claude-code }}
has-status-ready: ${{ steps.check.outputs.has-status-ready }}
steps:
- name: Check for required labels
id: check
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |
const issue = context.payload.issue;
const labels = issue.labels.map(l => l.name);
console.log(`🏷️ Issue #${issue.number} labels:`, labels);
const hasClaudeCode = labels.includes('claude-code');
const hasStatusReady = labels.includes('status:ready');
console.log(` claude-code: ${hasClaudeCode ? '✅' : '❌'}`);
console.log(` status:ready: ${hasStatusReady ? '✅' : '❌'}`);
const shouldCreate = hasClaudeCode && hasStatusReady;
if (!shouldCreate) {
console.log('⏭️ Skipping branch creation - missing required labels');
console.log(' Required: claude-code AND status:ready');
} else {
console.log('✅ All required labels present - will create branch');
}
core.setOutput('should-create-branch', shouldCreate);
core.setOutput('has-claude-code', hasClaudeCode);
core.setOutput('has-status-ready', hasStatusReady);
# ─────────────────────────────────────────────────────────────────
# Create Branch
# ─────────────────────────────────────────────────────────────────
create-branch:
name: Create Feature Branch
runs-on: ubuntu-latest
needs: check-labels
if: needs.check-labels.outputs.should-create-branch == 'true'
outputs:
branch-name: ${{ steps.branch.outputs.branch-name }}
base-branch: ${{ steps.branch.outputs.base-branch }}
branch-created: ${{ steps.branch.outputs.branch-created }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine branch details
id: branch
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |
const issue = context.payload.issue;
const labels = issue.labels.map(l => l.name);
console.log(`📝 Processing issue #${issue.number}: ${issue.title}`);
// Determine branch type from labels
let branchType = 'feature'; // default
if (labels.includes('type:fix')) {
branchType = 'fix';
} else if (labels.includes('type:hotfix')) {
branchType = 'hotfix';
} else if (labels.includes('type:refactor')) {
branchType = 'refactor';
} else if (labels.includes('type:test')) {
branchType = 'test';
} else if (labels.includes('type:docs')) {
branchType = 'docs';
}
console.log(` Branch type: ${branchType}`);
// Create slug from issue title
const slug = issue.title
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '') // Remove special chars
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Collapse multiple hyphens
.substring(0, 50) // Max 50 chars
.replace(/-+$/, ''); // Remove trailing hyphens
console.log(` Slug: ${slug}`);
// Build branch name
const branchName = `${branchType}/issue-${issue.number}-${slug}`;
console.log(` Branch name: ${branchName}`);
// Determine base branch
let baseBranch = 'dev'; // default
// Check for custom base branch label (e.g., base:staging)
const baseBranchLabel = labels.find(l => l.startsWith('base:'));
if (baseBranchLabel) {
baseBranch = baseBranchLabel.split(':')[1];
console.log(` Custom base branch from label: ${baseBranch}`);
} else {
// Check if dev branch exists
try {
await github.rest.repos.getBranch({
owner: context.repo.owner,
repo: context.repo.repo,
branch: 'dev'
});
console.log(` Base branch: dev (exists)`);
} catch (error) {
// dev doesn't exist, use main
baseBranch = 'main';
console.log(` Base branch: main (dev not found, using fallback)`);
}
}
core.setOutput('branch-name', branchName);
core.setOutput('base-branch', baseBranch);
core.setOutput('branch-type', branchType);
core.setOutput('slug', slug);
return { branchName, baseBranch, branchType, slug };
- name: Check if branch already exists
id: check-existing
run: |
BRANCH_NAME="${{ steps.branch.outputs.branch-name }}"
echo "🔍 Checking if branch already exists: $BRANCH_NAME"
# Check remote branches
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
echo "⏭️ Branch already exists: $BRANCH_NAME"
echo "branch-exists=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "✅ Branch does not exist - will create"
echo "branch-exists=false" >> $GITHUB_OUTPUT
- name: Create branch from base
if: steps.check-existing.outputs.branch-exists != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
BRANCH_NAME="${{ steps.branch.outputs.branch-name }}"
BASE_BRANCH="${{ steps.branch.outputs.base-branch }}"
echo "🌿 Creating branch: $BRANCH_NAME"
echo " From base: $BASE_BRANCH"
# Get base branch SHA
BASE_SHA=$(git rev-parse "origin/$BASE_BRANCH")
echo " Base SHA: $BASE_SHA"
# Create branch via API
gh api repos/${{ github.repository }}/git/refs \
-f ref="refs/heads/$BRANCH_NAME" \
-f sha="$BASE_SHA"
echo "✅ Branch created successfully"
echo "branch-created=true" >> $GITHUB_OUTPUT
- name: Set output for existing branch
if: steps.check-existing.outputs.branch-exists == 'true'
run: |
echo "branch-created=false" >> $GITHUB_OUTPUT
# ─────────────────────────────────────────────────────────────────
# Comment on Issue
# ─────────────────────────────────────────────────────────────────
comment-on-issue:
name: Add Comment with Instructions
runs-on: ubuntu-latest
needs:
- check-labels
- create-branch
if: always() && needs.check-labels.outputs.should-create-branch == 'true'
steps:
- name: Add helpful comment
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |
const issue = context.payload.issue;
const branchName = '${{ needs.create-branch.outputs.branch-name }}';
const baseBranch = '${{ needs.create-branch.outputs.base-branch }}';
const branchCreated = '${{ needs.create-branch.outputs.branch-created }}' === 'true';
let comment = '';
if (branchCreated) {
comment = `## 🌿 Branch Created!
I've automatically created a feature branch for this issue.
### 📋 Branch Details
- **Branch:** \`${branchName}\`
- **Base:** \`${baseBranch}\`
- **Issue:** #${issue.number}
### 🚀 Get Started
**Option 1: Clone and checkout**
\`\`\`bash
git fetch origin
git checkout ${branchName}
\`\`\`
**Option 2: Create local branch tracking remote**
\`\`\`bash
git fetch origin
git checkout -b ${branchName} origin/${branchName}
\`\`\`
**Option 3: GitHub CLI**
\`\`\`bash
gh pr create --base ${baseBranch} --head ${branchName}
\`\`\`
### 📝 Next Steps
1. Make your changes on this branch
2. Commit following conventional commit format
3. Push to origin: \`git push origin ${branchName}\`
4. Create a PR to \`${baseBranch}\` branch
5. Link this issue in your PR description: \`Closes #${issue.number}\`
### 💡 Tips
- Use conventional commit messages: \`feat:\`, \`fix:\`, \`docs:\`, etc.
- Run quality checks before pushing: \`npm run lint && npm test\`
- PR will auto-update this issue's status when created
---
🤖 _This branch was created automatically by the workflow_`;
} else {
comment = `## ℹ️ Branch Already Exists
A branch for this issue already exists:
### 📋 Branch Details
- **Branch:** \`${branchName}\`
- **Base:** \`${baseBranch}\`
### 🚀 Checkout Instructions
\`\`\`bash
git fetch origin
git checkout ${branchName}
\`\`\`
If you need to recreate this branch, delete it first:
\`\`\`bash
git push origin --delete ${branchName}
\`\`\`
Then remove the \`status:ready\` label and add it again to trigger branch creation.
---
🤖 _This is an automated message_`;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: comment
});
console.log(`✅ Comment added to issue #${issue.number}`);
# ─────────────────────────────────────────────────────────────────
# Update Project Board Status
# ─────────────────────────────────────────────────────────────────
update-project-status:
name: Update Project Board
runs-on: ubuntu-latest
needs:
- check-labels
- create-branch
if: |
always() &&
needs.check-labels.outputs.should-create-branch == 'true' &&
needs.create-branch.outputs.branch-created == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update issue status to In Progress
uses: ./.github/actions/project-sync
with:
project-url: ${{ secrets.PROJECT_URL }}
issue-number: ${{ github.event.issue.number }}
status-field: 'Status'
status-value: 'In Progress'
github-token: ${{ github.token }}
- name: Log status update
run: |
echo "✅ Updated issue #${{ github.event.issue.number }} status to 'In Progress'"
# ─────────────────────────────────────────────────────────────────
# Generate Summary
# ─────────────────────────────────────────────────────────────────
summary:
name: Workflow Summary
runs-on: ubuntu-latest
needs:
- check-labels
- create-branch
- comment-on-issue
- update-project-status
if: always()
steps:
- name: Generate summary
run: |
echo "# 🌿 Create Branch Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Check label requirements
if [[ "${{ needs.check-labels.outputs.should-create-branch }}" != "true" ]]; then
echo "## ⏭️ Branch Creation Skipped" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Reason:** Issue does not have both required labels" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Label | Present |" >> $GITHUB_STEP_SUMMARY
echo "|-------|---------|" >> $GITHUB_STEP_SUMMARY
echo "| \`claude-code\` | ${{ needs.check-labels.outputs.has-claude-code == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
echo "| \`status:ready\` | ${{ needs.check-labels.outputs.has-status-ready == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**To trigger branch creation:**" >> $GITHUB_STEP_SUMMARY
echo "Add both \`claude-code\` and \`status:ready\` labels to the issue" >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Branch creation results
echo "## ✅ Branch Creation Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Detail | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Issue | #${{ github.event.issue.number }} |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ needs.create-branch.outputs.branch-name }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Base Branch | \`${{ needs.create-branch.outputs.base-branch }}\` |" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.create-branch.outputs.branch-created }}" == "true" ]]; then
echo "| Status | ✅ Created |" >> $GITHUB_STEP_SUMMARY
else
echo "| Status | ⏭️ Already Exists |" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Project board update
if [[ "${{ needs.update-project-status.result }}" == "success" ]]; then
echo "## 📊 Project Board" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Issue status updated to **In Progress**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "_Branch creation completed at $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" >> $GITHUB_STEP_SUMMARY