Cleanup temporary state files after blueprint completion #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ───────────────────────────────────────────────────────────────── | |
| # 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 |