claude.yml
1 name: Claude Assistant 2 3 on: 4 issue_comment: 5 types: [created] 6 pull_request_review_comment: 7 types: [created] 8 issues: 9 types: [assigned, labeled, opened] 10 pull_request_review: 11 types: [submitted] 12 13 jobs: 14 # ================================================================ 15 # Job 1: Lightweight triage for ALL new issues (including external users) 16 # Posts welcome comment + adds triage labels. No Claude code execution. 17 # ================================================================ 18 issue-triage: 19 if: | 20 github.event_name == 'issues' && 21 github.event.action == 'opened' && 22 !contains(toJSON(github.event.issue.labels), 'claude') 23 runs-on: ubuntu-latest 24 permissions: 25 issues: write 26 contents: read 27 steps: 28 - name: Generate GitHub App Token 29 id: app-token 30 uses: actions/create-github-app-token@v1 31 with: 32 app-id: ${{ secrets.CLAUDE_APP_ID }} 33 private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }} 34 owner: ${{ github.repository_owner }} 35 36 - name: Triage and acknowledge issue 37 uses: actions/github-script@v7 38 with: 39 github-token: ${{ steps.app-token.outputs.token }} 40 script: | 41 const issue = context.payload.issue; 42 const title = (issue.title || '').toLowerCase(); 43 const body = (issue.body || '').toLowerCase(); 44 const content = title + ' ' + body; 45 const isOwner = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes( 46 issue.author_association 47 ); 48 49 // --- Label triage --- 50 const labels = []; 51 52 // Kind labels 53 if (content.includes('feature request') || content.includes('[feature')) { 54 labels.push('enhancement'); 55 } else if (content.includes('bug') || content.includes('error') || content.includes('crash') || content.includes('traceback')) { 56 labels.push('bug'); 57 } else if (content.includes('question') || content.includes('how do i') || content.includes('how to')) { 58 labels.push('question'); 59 } 60 61 // Area labels 62 if (content.includes('security') || content.includes('cve') || content.includes('vulnerability')) { 63 labels.push('security'); 64 } 65 if (content.includes('performance') || content.includes('slow') || content.includes('memory leak')) { 66 labels.push('performance'); 67 } 68 if (content.includes('documentation') || content.includes('docs')) { 69 labels.push('documentation'); 70 } 71 if (content.includes('typescript') || content.includes('javascript') || content.includes('npm')) { 72 labels.push('javascript'); 73 } 74 75 // Apply labels 76 if (labels.length > 0) { 77 await github.rest.issues.addLabels({ 78 issue_number: issue.number, 79 owner: context.repo.owner, 80 repo: context.repo.repo, 81 labels 82 }); 83 } 84 85 // --- Acknowledgment comment (external users only) --- 86 if (!isOwner) { 87 await github.rest.issues.createComment({ 88 issue_number: issue.number, 89 owner: context.repo.owner, 90 repo: context.repo.repo, 91 body: [ 92 `👋 Thanks for opening this issue, @${issue.user.login}!`, 93 '', 94 'A maintainer will review this shortly. In the meantime:', 95 '- Make sure you\'ve included steps to reproduce (for bugs)', 96 '- Check [existing issues](https://github.com/MervinPraison/PraisonAI/issues) for duplicates', 97 '- Review the [documentation](https://docs.praison.ai) for related guides', 98 '', 99 '_A maintainer can trigger deeper analysis by commenting `@claude` on this issue._' 100 ].join('\n') 101 }); 102 } 103 104 // --- For owner: add 'claude' label to trigger the code-fix job --- 105 if (isOwner) { 106 await github.rest.issues.addLabels({ 107 issue_number: issue.number, 108 owner: context.repo.owner, 109 repo: context.repo.repo, 110 labels: ['claude'] 111 }); 112 } 113 114 # ================================================================ 115 # Job 2: Full Claude code-fix (owner/collaborator only) 116 # Triggers on: labeled 'claude', @claude comments, assignments 117 # ================================================================ 118 claude-response: 119 # Allow: human users, github-actions[bot] (auto-comments/labels) 120 # Block: dependabot, cursor, renovate, other bots 121 # Skip 'opened' events — triage job handles those above 122 if: | 123 github.event.action != 'opened' && 124 (github.event.action != 'labeled' || github.event.label.name == 'claude') && 125 (github.event_name != 'issue_comment' || contains(github.event.comment.body, '@claude')) && 126 ( 127 !contains(github.actor, '[bot]') || 128 github.actor == 'github-actions[bot]' || 129 github.actor == 'praisonai-triage-agent[bot]' 130 ) && 131 github.actor != 'dependabot[bot]' && 132 github.actor != 'cursor[bot]' && 133 github.actor != 'renovate[bot]' 134 runs-on: ubuntu-latest 135 permissions: 136 contents: write 137 pull-requests: write 138 issues: write 139 actions: read 140 id-token: write 141 steps: 142 - name: Generate GitHub App Token 143 id: app-token 144 uses: actions/create-github-app-token@v1 145 with: 146 app-id: ${{ secrets.CLAUDE_APP_ID }} 147 private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }} 148 owner: ${{ github.repository_owner }} 149 150 - name: Check for Fork PR 151 id: check_fork 152 uses: actions/github-script@v7 153 with: 154 script: | 155 const isPR = context.eventName === 'pull_request' || (context.payload.issue && context.payload.issue.pull_request); 156 if (isPR) { 157 const prNumber = context.payload.pull_request ? context.payload.pull_request.number : context.payload.issue.number; 158 const pr = await github.rest.pulls.get({ 159 owner: context.repo.owner, 160 repo: context.repo.repo, 161 pull_number: prNumber 162 }); 163 164 core.setOutput('pr_branch', pr.data.head.ref); 165 166 if (pr.data.head.repo && pr.data.head.repo.full_name !== context.repo.full_name) { 167 core.setOutput('is_fork', 'true'); 168 core.setOutput('clone_url', pr.data.head.repo.clone_url); 169 core.setOutput('branch', pr.data.head.ref); 170 return; 171 } 172 } 173 core.setOutput('is_fork', 'false'); 174 175 - name: Checkout repository 176 uses: actions/checkout@v4 177 with: 178 persist-credentials: false 179 fetch-depth: 0 180 token: ${{ steps.app-token.outputs.token }} 181 182 - name: Fetch PR branch and Setup Remote 183 if: github.event.issue.pull_request 184 run: | 185 # Fetch PR head from base repo and put it into local branch 186 git fetch origin pull/${{ github.event.issue.number }}/head:${{ steps.check_fork.outputs.pr_branch }} 187 188 # If it's a fork, make origin point to local to trick claude-code-action's `git fetch origin <branch>` 189 if [ "${{ steps.check_fork.outputs.is_fork }}" == "true" ]; then 190 git remote set-url origin file://$(pwd) 191 fi 192 193 - uses: anthropics/claude-code-action@beta 194 env: 195 GH_TOKEN: ${{ steps.app-token.outputs.token }} 196 with: 197 allowed_bots: 'praisonai-triage-agent[bot]' 198 claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 199 github_token: ${{ steps.app-token.outputs.token }} 200 trigger_phrase: "@claude" 201 label_trigger: "claude" 202 203 direct_prompt: | 204 ${{ steps.check_fork.outputs.is_fork == 'true' && 'CRITICAL: THIS IS A PULL REQUEST FROM A FORK. YOU DO NOT HAVE PUSH PERMISSIONS TO THIS REPOSITORY. ONLY READ FILES, VALIDATE CODE, AND PROVIDE YOUR COMMENTS/FEEDBACK DIRECTLY IN THIS PR VIA COMMENTS. DO NOT ATTEMPT TO PUSH OR CREATE PULL REQUESTS.\n\n' || 'NOTE: The branch is under MervinPraison/PraisonAI (not a fork). You are able to make modifications and push directly to this branch.\n\n' }} 205 You are working on the PraisonAI SDK. Follow AGENTS.md strictly. 206 207 STEP 0 — SETUP GIT IDENTITY & AUTH (GLOBAL — required for all repos): 208 git config --global user.name "MervinPraison" 209 git config --global user.email "454862+MervinPraison@users.noreply.github.com" 210 gh auth setup-git 211 212 213 STEP 1 — READ GUIDELINES: 214 Read AGENTS.md to understand the architecture rules. 215 SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify or touch praisonai-rust or praisonai-ts packages. 216 217 STEP 2 — ARCHITECTURE VALIDATION & ROUTING (MANDATORY before writing code): 218 Before implementing anything, answer these questions: 219 - CORE vs WRAPPER vs TOOLS vs DOCS vs PLUGINS ROUTING: 220 1. Core SDK (praisonaiagents/): Only core protocols, base classes, decorators. No heavy implementations. 221 2. Wrapper (praisonai/): CLI, heavy implementations, optional dependencies. 222 3. Tools (PraisonAI-Tools): Capabilities Agents actively call during execution (e.g. SurrealDB tool, Slack tool). 223 4. Documentation (PraisonAIDocs): Documentation pages, guides, API references. 224 5. Plugins (PraisonAI-Plugins): Framework lifecycle extensions replacing systemic behavior (e.g. tracing, logging, metrics, hooks, guardrails). 225 For items routed to an EXTERNAL repository (Tools, Docs, Plugins), follow STEP 3-ALT below. 226 - Does it duplicate existing functionality? Check if Agent already supports this via existing params (reflection, planning, tools, hooks, memory). 227 - Does it inherit from Agent properly? New agent types MUST inherit Agent, not wrap it with composition. 228 - Does it add new dependencies? Only optional deps allowed, must be lazy-imported. 229 - Will agent.py grow larger? If the change adds >50 lines to agent.py, find a way to extract instead. 230 - Is there a name collision with existing exports in __init__.py? 231 If ANY of these conceptual checks fail (excluding routing), add a comment to the issue explaining why and close it. Do NOT create a PR. 232 233 STEP 3 — IMPLEMENT (for changes in THIS repo — PraisonAI): 234 - Create a fix branch: git checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) 235 - Implement a minimal, focused fix 236 - Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper 237 - Keep changes small and backward-compatible 238 - Commit: git add -A && git commit -m "fix: <description> (fixes #$ISSUE_NUMBER)" 239 - Push: git push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) 240 CRITICAL: After pushing, you MUST immediately run: gh pr create --title "fix: <title>" --body "Fixes #$ISSUE_NUMBER" --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) 241 Do NOT stop after pushing. Do NOT say "manually create a PR". Always create the PR automatically. 242 243 STEP 3-ALT — IMPLEMENT IN EXTERNAL REPO (for PraisonAI-Tools, PraisonAIDocs, PraisonAI-Plugins): 244 When work must happen in a different repository, follow these steps EXACTLY. Do NOT attempt to use `cd`, as directory state is not preserved. Use `git -C` for all commands. 245 a) Clone the repository: 246 gh repo clone MervinPraison/<REPO_NAME> /tmp/<REPO_NAME> 247 b) Copy GitHub authentication from the main repository so push works seamlessly: 248 git -C /tmp/<REPO_NAME> config http."https://github.com/".extraheader "$(git config --get http."https://github.com/".extraheader)" 249 c) Create a feature branch (NEVER commit to main): 250 git -C /tmp/<REPO_NAME> checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) 251 d) Make your changes using absolute paths (e.g. edit /tmp/<REPO_NAME>/docs.json) 252 e) Commit: 253 git -C /tmp/<REPO_NAME> add -A 254 git -C /tmp/<REPO_NAME> commit -m "feat: <description> (fixes #$ISSUE_NUMBER)" 255 f) Push the branch: 256 git -C /tmp/<REPO_NAME> push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) 257 g) Create PR in the EXTERNAL repo: 258 gh pr create -R MervinPraison/<REPO_NAME> --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) --title "feat: <title>" --body "Fixes MervinPraison/PraisonAI#$ISSUE_NUMBER 259 260 <body>" 261 262 STEP 4 — TEST: 263 - For SDK changes: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30 264 - For docs changes: verify files exist and are valid markdown/MDX 265 - Ensure no regressions 266 267 STEP 5 — CREATE PR: 268 - Commit with descriptive message, push, and create PR using `gh pr create` 269 - For external repos, use `gh pr create -R MervinPraison/<REPO_NAME>` 270 CRITICAL: You MUST create the PR automatically using `gh pr create`. Do NOT just provide a link or say "manual push required". 271 NOTE: If you worked on an external repo, the wrapper action will report "No commits" for the main repo at the end. This is expected, do not worry about it. 272 allowed_tools: | 273 Bash(git:*) 274 Bash(python:*) 275 Bash(pip:*) 276 Bash(conda:*) 277 Bash(pytest:*) 278 Bash(gh:*) 279 Bash(python -m pytest:*) 280 Bash(python -m pip:*) 281 Bash(poetry:*) 282 View 283 GlobTool 284 GrepTool 285 BatchTool 286 Edit 287 Replace 288 mcp__github__get_issue 289 mcp__github__get_issue_comments 290 mcp__github__update_issue 291 timeout_minutes: 30