feat: add prepare phase to autopilot workflow #3
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
| name: Org Agent Autopilot | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| issue_number: | ||
| description: 'Issue number to work on' | ||
| required: true | ||
| type: number | ||
| action: | ||
| description: 'Label action (labeled or unlabeled)' | ||
| required: true | ||
| type: string | ||
| label_name: | ||
| description: 'Label that triggered this workflow' | ||
| required: true | ||
| type: string | ||
| project_id: | ||
| description: 'GitHub Projects board ID' | ||
| required: false | ||
| type: string | ||
| default: '' | ||
| status_field_id: | ||
| description: 'Status field ID in project' | ||
| required: false | ||
| type: string | ||
| default: '' | ||
| status_planning_id: | ||
| description: 'Status option ID for "Planning"' | ||
| required: false | ||
| type: string | ||
| default: '' | ||
| status_in_progress_id: | ||
| description: 'Status option ID for "In Progress"' | ||
| required: false | ||
| type: string | ||
| default: '' | ||
| status_review_id: | ||
| description: 'Status option ID for "Review"' | ||
| required: false | ||
| type: string | ||
| default: '' | ||
| secrets: | ||
| CLAUDE_CODE_OAUTH_TOKEN: | ||
| description: 'Claude Code OAuth token' | ||
| required: true | ||
| GH_TOKEN: | ||
| description: 'GitHub token for API access' | ||
| required: true | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| issues: write | ||
| actions: read | ||
| id-token: write | ||
| concurrency: | ||
| group: autopilot-${{ github.repository }}-${{ inputs.issue_number }} | ||
| cancel-in-progress: false # Don't cancel ongoing work | ||
| jobs: | ||
| # Check if issue is ready for implementation | ||
| check-readiness: | ||
| if: inputs.action == 'labeled' && inputs.label_name == 'agent: claude' | ||
| runs-on: arc-happyvertical | ||
| timeout-minutes: 5 | ||
| outputs: | ||
| ready: ${{ steps.check.outputs.ready }} | ||
| missing: ${{ steps.check.outputs.missing }} | ||
| steps: | ||
| - name: Check Definition of Ready | ||
| id: check | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| run: | | ||
| echo "Checking Definition of Ready for issue #${{ inputs.issue_number }}..." | ||
| ISSUE=$(gh issue view ${{ inputs.issue_number }} --json labels,body,title,state) | ||
| STATE=$(echo "$ISSUE" | jq -r '.state') | ||
| if [ "$STATE" != "OPEN" ]; then | ||
| echo "ready=false" >> $GITHUB_OUTPUT | ||
| echo "missing=Issue is closed" >> $GITHUB_OUTPUT | ||
| exit 0 | ||
| fi | ||
| LABELS=$(echo "$ISSUE" | jq -r '.labels[].name' 2>/dev/null || echo "") | ||
| HAS_TYPE=$(echo "$LABELS" | grep -c "^type:" || true) | ||
| HAS_PRIORITY=$(echo "$LABELS" | grep -c "^priority:" || true) | ||
| HAS_SIZE=$(echo "$LABELS" | grep -c "^size:" || true) | ||
| BODY_LENGTH=$(echo "$ISSUE" | jq '.body | length // 0') | ||
| MISSING="" | ||
| if [ "$HAS_TYPE" -eq 0 ]; then | ||
| MISSING="${MISSING}type label, " | ||
| fi | ||
| if [ "$HAS_PRIORITY" -eq 0 ]; then | ||
| MISSING="${MISSING}priority label, " | ||
| fi | ||
| if [ "$HAS_SIZE" -eq 0 ]; then | ||
| MISSING="${MISSING}size label, " | ||
| fi | ||
| if [ "$BODY_LENGTH" -lt 50 ]; then | ||
| MISSING="${MISSING}detailed description, " | ||
| fi | ||
| if [ -n "$MISSING" ]; then | ||
| echo "ready=false" >> $GITHUB_OUTPUT | ||
| echo "missing=${MISSING%, }" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "ready=true" >> $GITHUB_OUTPUT | ||
| echo "missing=" >> $GITHUB_OUTPUT | ||
| fi | ||
| # PREPARE: If not ready, Claude will prepare the issue | ||
| prepare: | ||
| needs: check-readiness | ||
| if: needs.check-readiness.outputs.ready == 'false' && needs.check-readiness.outputs.missing != 'Issue is closed' | ||
| runs-on: arc-happyvertical | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 1 | ||
| - name: Post Preparation Comment | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| run: | | ||
| gh issue comment ${{ inputs.issue_number }} --body "## Preparing Issue for Implementation | ||
| This issue needs some preparation before I can implement it. | ||
| **Missing**: ${{ needs.check-readiness.outputs.missing }} | ||
| I'll analyze the issue and: | ||
| - Add appropriate labels (type, priority, size) | ||
| - Clarify requirements if needed | ||
| - Create an implementation plan | ||
| --- | ||
| *Automated preparation by Claude*" | ||
| - name: Run Claude Preparation | ||
| uses: anthropics/claude-code-action@v1 | ||
| with: | ||
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | ||
| timeout_minutes: 12 | ||
| prompt: | | ||
| REPO: ${{ github.repository }} | ||
| ISSUE_NUMBER: ${{ inputs.issue_number }} | ||
| MISSING: ${{ needs.check-readiness.outputs.missing }} | ||
| This issue has the `agent: claude` label but is not ready for implementation. | ||
| Your job is to PREPARE it so it can be implemented. | ||
| ## Step 1: Read and Understand | ||
| ```bash | ||
| gh issue view ${{ inputs.issue_number }} | ||
| ``` | ||
| Read the issue thoroughly. Also read CLAUDE.md for repository context. | ||
| ## Step 2: Add Missing Labels | ||
| Based on the issue content, apply appropriate labels: | ||
| **Type** (choose one): | ||
| - `type: bug` - Something broken | ||
| - `type: feature` - New functionality | ||
| - `type: docs` - Documentation | ||
| - `type: maintenance` - Refactoring, cleanup | ||
| - `type: research` - Investigation needed | ||
| **Priority** (choose one): | ||
| - `priority: critical` - Urgent, blocking | ||
| - `priority: high` - Important | ||
| - `priority: medium` - Normal (default) | ||
| - `priority: low` - Nice to have | ||
| **Size** (choose one based on complexity): | ||
| - `size: xs` - < 2 hours, trivial | ||
| - `size: s` - 2-4 hours, small | ||
| - `size: m` - ~1 day, moderate | ||
| - `size: l` - 2-3 days, significant | ||
| - `size: xl` - > 3 days, large | ||
| Apply labels: | ||
| ```bash | ||
| gh issue edit ${{ inputs.issue_number }} --add-label "type: X,priority: Y,size: Z" | ||
| ``` | ||
| ## Step 3: Clarify and Plan | ||
| If the issue description is vague or missing details, post a comment with: | ||
| 1. Your understanding of what needs to be done | ||
| 2. Any clarifying questions | ||
| 3. A brief implementation plan | ||
| ## Step 4: Completion | ||
| Post a summary comment: | ||
| ``` | ||
| ## Issue Prepared | ||
| **Labels Applied**: [list labels] | ||
| **Implementation Plan**: [brief plan] | ||
| This issue is now ready for implementation. The `agent: claude` label will trigger | ||
| the implementation phase automatically. | ||
| If you'd like me to proceed with implementation now, just comment "@claude implement this". | ||
| Otherwise, I'll wait for manual review. | ||
| ``` | ||
| DO NOT start implementation yet - just prepare the issue. | ||
| allowed_tools: | | ||
| Bash(gh issue:*),Bash(gh search:*),Read,Grep,Glob | ||
| - name: Update Project Board to Planning | ||
| if: inputs.project_id != '' && inputs.status_planning_id != '' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| run: | | ||
| ISSUE_NODE_ID=$(gh api graphql -f query=' | ||
| query($owner: String!, $repo: String!, $number: Int!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| issue(number: $number) { | ||
| id | ||
| projectItems(first: 10) { | ||
| nodes { | ||
| id | ||
| project { id } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ' -f owner="${{ github.repository_owner }}" -f repo="${{ github.event.repository.name }}" -F number=${{ inputs.issue_number }} --jq '.data.repository.issue') | ||
| ITEM_ID=$(echo "$ISSUE_NODE_ID" | jq -r ".projectItems.nodes[] | select(.project.id == \"${{ inputs.project_id }}\") | .id" 2>/dev/null || echo "") | ||
| if [ -n "$ITEM_ID" ]; then | ||
| gh api graphql -f query=' | ||
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { | ||
| updateProjectV2ItemFieldValue(input: { | ||
| projectId: $projectId | ||
| itemId: $itemId | ||
| fieldId: $fieldId | ||
| value: { singleSelectOptionId: $optionId } | ||
| }) { | ||
| projectV2Item { id } | ||
| } | ||
| } | ||
| ' -f projectId="${{ inputs.project_id }}" -f itemId="$ITEM_ID" -f fieldId="${{ inputs.status_field_id }}" -f optionId="${{ inputs.status_planning_id }}" || true | ||
| fi | ||
| # IMPLEMENT: If ready, Claude implements the issue | ||
| implement: | ||
| needs: check-readiness | ||
| if: needs.check-readiness.outputs.ready == 'true' | ||
| runs-on: arc-happyvertical | ||
| timeout-minutes: 60 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| token: ${{ secrets.GH_TOKEN }} | ||
| - name: Configure Git | ||
| run: | | ||
| git config user.name "claude[bot]" | ||
| git config user.email "claude[bot]@users.noreply.github.com" | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '24' | ||
| - name: Setup pnpm | ||
| uses: pnpm/action-setup@v4 | ||
| with: | ||
| version: 9 | ||
| - name: Install dependencies | ||
| run: | | ||
| if [ -f "pnpm-lock.yaml" ]; then | ||
| pnpm install --frozen-lockfile | ||
| elif [ -f "package-lock.json" ]; then | ||
| npm ci | ||
| elif [ -f "package.json" ]; then | ||
| pnpm install || npm install | ||
| fi | ||
| continue-on-error: true | ||
| - name: Post Start Comment | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| run: | | ||
| gh issue comment ${{ inputs.issue_number }} --body "## Implementation Started | ||
| This issue is ready and I'm beginning implementation. | ||
| **Status**: In Progress | ||
| **Started**: $(date -u +"%Y-%m-%dT%H:%M:%SZ") | ||
| I'll post updates as I progress. | ||
| --- | ||
| *Automated implementation by Claude*" | ||
| - name: Update Project Board to In Progress | ||
| if: inputs.project_id != '' && inputs.status_in_progress_id != '' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| run: | | ||
| ISSUE_NODE_ID=$(gh api graphql -f query=' | ||
| query($owner: String!, $repo: String!, $number: Int!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| issue(number: $number) { | ||
| id | ||
| projectItems(first: 10) { | ||
| nodes { | ||
| id | ||
| project { id } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ' -f owner="${{ github.repository_owner }}" -f repo="${{ github.event.repository.name }}" -F number=${{ inputs.issue_number }} --jq '.data.repository.issue') | ||
| ITEM_ID=$(echo "$ISSUE_NODE_ID" | jq -r ".projectItems.nodes[] | select(.project.id == \"${{ inputs.project_id }}\") | .id" 2>/dev/null || echo "") | ||
| if [ -n "$ITEM_ID" ]; then | ||
| gh api graphql -f query=' | ||
| mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { | ||
| updateProjectV2ItemFieldValue(input: { | ||
| projectId: $projectId | ||
| itemId: $itemId | ||
| fieldId: $fieldId | ||
| value: { singleSelectOptionId: $optionId } | ||
| }) { | ||
| projectV2Item { id } | ||
| } | ||
| } | ||
| ' -f projectId="${{ inputs.project_id }}" -f itemId="$ITEM_ID" -f fieldId="${{ inputs.status_field_id }}" -f optionId="${{ inputs.status_in_progress_id }}" || true | ||
| fi | ||
| - name: Run Claude Implementation | ||
| id: implement | ||
| uses: anthropics/claude-code-action@v1 | ||
| with: | ||
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | ||
| timeout_minutes: 55 | ||
| track_progress: true | ||
| prompt: | | ||
| REPO: ${{ github.repository }} | ||
| ISSUE_NUMBER: ${{ inputs.issue_number }} | ||
| You are implementing issue #${{ inputs.issue_number }} autonomously. | ||
| ## Workflow | ||
| 1. **Read the issue**: `gh issue view ${{ inputs.issue_number }}` | ||
| 2. **Read CLAUDE.md** for repository guidelines | ||
| 3. **Create branch**: `git checkout -b feat/issue-${{ inputs.issue_number }}-[description]` | ||
| 4. **Implement** following existing patterns | ||
| 5. **Write/update tests** | ||
| 6. **Quality checks**: `pnpm typecheck && pnpm lint && pnpm test && pnpm build` | ||
| 7. **Commit**: `git commit -m "feat(scope): description\n\nCloses #${{ inputs.issue_number }}"` | ||
| 8. **Push**: `git push -u origin [branch]` | ||
| 9. **Create PR**: `gh pr create --title "..." --body "..."` | ||
| 10. **Post completion** comment on the issue | ||
| If blocked, post a comment explaining why. | ||
| allowed_tools: | | ||
| Bash(git:*),Bash(gh pr:*),Bash(gh issue:*),Bash(pnpm:*),Bash(npm:*),Bash(npx:*),Bash(node:*),Bash(bun:*),Read,Write,Edit,Glob,Grep,Bash(ls:*),Bash(cat:*),Bash(mkdir:*),Bash(cp:*),Bash(mv:*),Bash(rm:*) | ||
| - name: Post Failure Comment | ||
| if: failure() | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
| run: | | ||
| gh issue comment ${{ inputs.issue_number }} --body "## Implementation Failed | ||
| I encountered an error while implementing this issue. | ||
| [View workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | ||
| The \`agent: claude\` label remains - remove it if you want to stop automated attempts. | ||
| --- | ||
| *Automated implementation by Claude*" | ||