name: Issue & PR Automation on: issues: types: [opened] pull_request: types: [opened] issue_comment: types: [created] pull_request_review_comment: types: [created] permissions: contents: read issues: write pull-requests: write models: read id-token: write jobs: validate-issue: if: github.event_name == 'issues' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 - name: Save issue body to file env: ISSUE_BODY: ${{ github.event.issue.body }} run: printf '%s' "${ISSUE_BODY:-}" > issue_body.txt - name: Validate issue with AI id: validate uses: actions/ai-inference@a380166897b5408b8fb7dddd148142794cb5624a # v2.0.6 with: prompt-file: .github/prompts/issue-validation.prompt.yml input: | issue_title: ${{ github.event.issue.title }} issue_labels: ${{ join(github.event.issue.labels.*.name, ', ') }} file_input: | issue_body: ./issue_body.txt max-tokens: 1024 - name: Check if first-time contributor id: check-first-time env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_AUTHOR: ${{ github.event.issue.user.login }} run: | ISSUE_COUNT=$(gh api "/repos/${{ github.repository }}/issues" \ --jq "map(select(.user.login == \"$ISSUE_AUTHOR\" and .number != ${{ github.event.issue.number }})) | length" \ --paginate || echo "0") if [ "$ISSUE_COUNT" = "0" ]; then echo "is_first_time=true" >> $GITHUB_OUTPUT else echo "is_first_time=false" >> $GITHUB_OUTPUT fi - name: Parse validation result and take action env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RESPONSE_FILE: ${{ steps.validate.outputs.response-file }} run: | if [ -n "$RESPONSE_FILE" ] && [ -f "$RESPONSE_FILE" ]; then RAW_OUTPUT=$(cat "$RESPONSE_FILE") else echo "::error::Response file not found: $RESPONSE_FILE" exit 1 fi JSON_RESULT=$(printf "%s" "$RAW_OUTPUT" | sed -n '/```json/,/```/p' | sed '1d;$d') if [ -z "$JSON_RESULT" ]; then JSON_RESULT="$RAW_OUTPUT" fi if ! echo "$JSON_RESULT" | jq empty 2>/dev/null; then echo "::warning::Invalid JSON in AI response, using fallback" JSON_RESULT='{"is_valid":true,"issue_type":"other","missing_info":[],"suggestions":[],"overall_assessment":"Unable to validate automatically"}' fi IS_VALID=$(echo "$JSON_RESULT" | jq -r '.is_valid // false') ISSUE_TYPE=$(echo "$JSON_RESULT" | jq -r '.issue_type // "other"') MISSING_INFO=$(echo "$JSON_RESULT" | jq -r '.missing_info[]? // empty' | sed 's/^/- /') SUGGESTIONS=$(echo "$JSON_RESULT" | jq -r '.suggestions[]? // empty' | sed 's/^/- /') ASSESSMENT=$(echo "$JSON_RESULT" | jq -r '.overall_assessment // "No assessment provided"') IS_FIRST_TIME="${{ steps.check-first-time.outputs.is_first_time }}" GREETING_SECTION="" if [ "$IS_FIRST_TIME" = "true" ]; then GREETING_SECTION="## 👋 Welcome!\n\nThank you for your first issue ❤️ If this is a feature request, please make sure it is clear what you want, why you want it, and how important it is to you. If you posted a bug report, please make sure it includes as much detail as possible.\n\n---\n\n" fi if [ "$IS_VALID" = "false" ]; then { printf "%b" "$GREETING_SECTION" printf "## 🤖 Issue Validation\n\n" printf "Thank you for submitting this issue! However, it appears that some required information might be missing to help us better understand and address your concern.\n\n" printf "**Issue Type Detected:** \`%s\`\n\n" "$ISSUE_TYPE" printf "**Assessment:** %s\n\n" "$ASSESSMENT" printf "### 📋 Missing Information:\n%s\n\n" "$MISSING_INFO" printf "### 💡 Suggestions for Improvement:\n%s\n\n" "$SUGGESTIONS" printf "### 📝 How to Provide Additional Information:\n\n" printf "Please edit your original issue description to include the missing information. Here are our issue templates for reference:\n\n" printf -- "- **Bug Report Template:** [View Template](.github/ISSUE_TEMPLATE/01-bug-report.md)\n" printf -- "- **Feature Request Template:** [View Template](.github/ISSUE_TEMPLATE/02-feature-request.md)\n\n" printf "### 🔧 Quick Tips:\n" printf -- "- For **bug reports**: Include step-by-step reproduction instructions, your environment details, and any error messages\n" printf -- "- For **feature requests**: Describe the use case, expected behavior, and why this feature would be valuable\n" printf -- "- Add **screenshots** or **logs** when applicable\n\n" printf "Once you have updated the issue with the missing information, feel free to remove this comment or reply to let us know you have made the updates.\n\n" printf -- "---\n*This validation was performed automatically to ensure we have all the information needed to help you effectively.*\n" } > comment.md gh issue comment ${{ github.event.issue.number }} --body-file comment.md gh issue edit ${{ github.event.issue.number }} --add-label "needs-info" else SUGGESTIONS_SECTION="" if [ -n "$SUGGESTIONS" ]; then SUGGESTIONS_SECTION=$(printf "### 💡 Suggestions:\n%s\n\n" "$SUGGESTIONS") fi { printf "%b" "$GREETING_SECTION" printf "## 🤖 Issue Validation\n\n" printf "**Issue Type Detected:** \`%s\`\n\n" "$ISSUE_TYPE" printf "**Assessment:** %s\n\n" "$ASSESSMENT" printf "%b" "$SUGGESTIONS_SECTION" printf -- "---\n*This validation was performed automatically to help triage issues.*\n" } > comment.md gh issue comment ${{ github.event.issue.number }} --body-file comment.md case "$ISSUE_TYPE" in "bug_report") gh issue edit ${{ github.event.issue.number }} --add-label "bug" ;; "feature_request") gh issue edit ${{ github.event.issue.number }} --add-label "enhancement" ;; esac fi - name: Run opencode analysis uses: anomalyco/opencode/github@296250f1b7e1ec992a3a33bee999f5e09a1697d0 #v1.2.10 env: ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} with: model: zai-coding-plan/glm-4.7 - name: Cleanup run: rm -f issue_body.txt comment.md handle-pr: if: github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 with: fetch-depth: 0 - name: Check if first-time contributor id: check-first-time env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} run: | PR_COUNT=$(gh api "/repos/${{ github.repository }}/pulls?state=all&per_page=100" \ --jq "[.[] | select(.user.login == \"$PR_AUTHOR\" and .number != ${{ github.event.pull_request.number }})] | length" \ || echo "0") if [ "$PR_COUNT" = "0" ]; then echo "is_first_time=true" >> $GITHUB_OUTPUT else echo "is_first_time=false" >> $GITHUB_OUTPUT fi - name: Get PR diff id: get-diff env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr diff ${{ github.event.pull_request.number }} > pr_diff.txt head -c 10000 pr_diff.txt > pr_diff_truncated.txt - name: Save PR body to file env: PR_BODY: ${{ github.event.pull_request.body }} run: printf '%s' "${PR_BODY:-No description provided}" > pr_body.txt - name: Analyze PR with AI id: analyze uses: actions/ai-inference@a380166897b5408b8fb7dddd148142794cb5624a # v2.0.6 with: prompt-file: .github/prompts/pr-review.prompt.yml input: | pr_title: ${{ github.event.pull_request.title }} file_input: | pr_body: ./pr_body.txt pr_diff: ./pr_diff_truncated.txt max-tokens: 1024 - name: Post PR feedback comment env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RESPONSE_FILE: ${{ steps.analyze.outputs.response-file }} run: | if [ -n "$RESPONSE_FILE" ] && [ -f "$RESPONSE_FILE" ]; then RAW_OUTPUT=$(cat "$RESPONSE_FILE") else echo "::error::Response file not found" exit 1 fi JSON_RESULT=$(printf "%s" "$RAW_OUTPUT" | sed -n '/```json/,/```/p' | sed '1d;$d') if [ -z "$JSON_RESULT" ]; then JSON_RESULT="$RAW_OUTPUT" fi if ! echo "$JSON_RESULT" | jq empty 2>/dev/null; then echo "::warning::Invalid JSON in AI response, using fallback" JSON_RESULT='{"summary":"Unable to analyze automatically","quality_score":"good","feedback":[],"suggestions":[],"security_notes":[]}' fi SUMMARY=$(echo "$JSON_RESULT" | jq -r '.summary // "No summary"') QUALITY=$(echo "$JSON_RESULT" | jq -r '.quality_score // "good"') FEEDBACK=$(echo "$JSON_RESULT" | jq -r '.feedback[]? // empty' | sed 's/^/- /') SUGGESTIONS=$(echo "$JSON_RESULT" | jq -r '.suggestions[]? // empty' | sed 's/^/- /') SECURITY=$(echo "$JSON_RESULT" | jq -r '.security_notes[]? // empty' | sed 's/^/- ⚠️ /') IS_FIRST_TIME="${{ steps.check-first-time.outputs.is_first_time }}" { if [ "$IS_FIRST_TIME" = "true" ]; then printf "## 👋 Welcome!\n\n" printf "Thank you for your first contribution ❤️ A human will review your PR shortly. Make sure that the pipelines are green, so that the PR is considered ready for review and could be merged.\n\n" printf -- "---\n\n" fi printf "## 🤖 PR Review\n\n" printf "**Summary:** %s\n\n" "$SUMMARY" case "$QUALITY" in "good") printf "**Status:** ✅ Looking good!\n\n" ;; "needs_work") printf "**Status:** 🔧 Some improvements suggested\n\n" ;; "critical_issues") printf "**Status:** ⚠️ Please address the issues below\n\n" ;; esac if [ -n "$FEEDBACK" ]; then printf "### 📝 Feedback:\n%s\n\n" "$FEEDBACK" fi if [ -n "$SUGGESTIONS" ]; then printf "### 💡 Suggestions:\n%s\n\n" "$SUGGESTIONS" fi if [ -n "$SECURITY" ]; then printf "### 🔒 Security Notes:\n%s\n\n" "$SECURITY" fi printf -- "---\n*This review was performed automatically. A human maintainer will also review your changes.*\n" } > comment.md gh pr comment ${{ github.event.pull_request.number }} --body-file comment.md - name: Run opencode analysis uses: anomalyco/opencode/github@296250f1b7e1ec992a3a33bee999f5e09a1697d0 #v1.2.10 env: ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} with: model: zai-coding-plan/glm-4.7 - name: Cleanup run: rm -f pr_diff.txt pr_diff_truncated.txt pr_body.txt comment.md opencode-command: if: | (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body, ' /oc') || startsWith(github.event.comment.body, '/oc') || contains(github.event.comment.body, ' /opencode') || startsWith(github.event.comment.body, '/opencode')) runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1 - name: Run opencode uses: anomalyco/opencode/github@296250f1b7e1ec992a3a33bee999f5e09a1697d0 #v1.2.10 env: ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} with: model: zai-coding-plan/glm-4.7