mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-23 04:16:29 +02:00
1bd3a9d123
Bumps the github-actions group with 3 updates: [pnpm/action-setup](https://github.com/pnpm/action-setup), [anomalyco/opencode](https://github.com/anomalyco/opencode) and [crate-ci/typos](https://github.com/crate-ci/typos). Updates `pnpm/action-setup` from 6.0.0 to 6.0.1 - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/08c4be7e2e672a47d11bd04269e27e5f3e8529cb...078e9d416474b29c0c387560859308974f7e9c53) Updates `anomalyco/opencode` from 1.4.3 to 1.4.11 - [Release notes](https://github.com/anomalyco/opencode/releases) - [Commits](https://github.com/anomalyco/opencode/compare/877be7e8e04142cd8fbebcb5e6c4b9617bf28cce...a35b8a95c27d28e979a3826e1289d7ee87f40251) Updates `crate-ci/typos` from 1.45.0 to 1.45.1 - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/02ea592e44b3a53c302f697cddca7641cd051c3d...cf5f1c29a8ac336af8568821ec41919923b05a83) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: anomalyco/opencode dependency-version: 1.4.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: crate-ci/typos dependency-version: 1.45.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com>
336 lines
17 KiB
YAML
336 lines
17 KiB
YAML
name: Issue & PR Automation
|
|
|
|
on:
|
|
issues:
|
|
types: [opened]
|
|
pull_request_target:
|
|
types: [opened]
|
|
issue_comment:
|
|
types: [created]
|
|
pull_request_review_comment:
|
|
types: [created]
|
|
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
pull-requests: write
|
|
id-token: write
|
|
|
|
jobs:
|
|
analyze-issue:
|
|
if: github.repository == 'zhom/donutbrowser' && github.event_name == 'issues'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
|
|
|
- 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?state=all&creator=$ISSUE_AUTHOR&per_page=100" \
|
|
--jq "[.[] | select(.number != ${{ github.event.issue.number }}) ] | length" \
|
|
|| echo "0")
|
|
|
|
if [ "$ISSUE_COUNT" = "0" ]; then
|
|
echo "is_first_time=true" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "is_first_time=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Build repo context and find related files
|
|
env:
|
|
ISSUE_TITLE: ${{ github.event.issue.title }}
|
|
ISSUE_BODY: ${{ github.event.issue.body }}
|
|
run: |
|
|
# Read project guidelines (contains repo structure)
|
|
cp CLAUDE.md /tmp/repo-context.txt
|
|
|
|
printf '%s' "$ISSUE_TITLE" > /tmp/issue-title.txt
|
|
printf '%s' "${ISSUE_BODY:-}" > /tmp/issue-body.txt
|
|
|
|
# List all source files for the AI to pick from
|
|
find . -type f \( -name "*.rs" -o -name "*.ts" -o -name "*.tsx" \) \
|
|
! -path "*/node_modules/*" ! -path "*/target/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
|
! -path "*/.git/*" ! -path "*/gen/*" ! -path "*/data/*" \
|
|
| sed 's|^\./||' | sort > /tmp/all-source-files.txt
|
|
|
|
- name: Select relevant files with AI
|
|
env:
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
run: |
|
|
PAYLOAD=$(jq -n \
|
|
--rawfile title /tmp/issue-title.txt \
|
|
--rawfile body /tmp/issue-body.txt \
|
|
--rawfile files /tmp/all-source-files.txt \
|
|
'{
|
|
model: "anthropic/claude-opus-4.6",
|
|
messages: [
|
|
{
|
|
role: "system",
|
|
content: "You are a file selector for Donut Browser (Tauri + Next.js + Rust anti-detect browser). Given an issue and a list of source files, output ONLY the 10 most likely relevant file paths, one per line. No explanations, no numbering, just paths."
|
|
},
|
|
{
|
|
role: "user",
|
|
content: ("Issue: " + $title + "\n\n" + $body + "\n\nFiles:\n" + $files)
|
|
}
|
|
]
|
|
}')
|
|
|
|
RESPONSE=$(curl -fsSL https://openrouter.ai/api/v1/chat/completions \
|
|
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$PAYLOAD")
|
|
|
|
jq -r '.choices[0].message.content // empty' <<< "$RESPONSE" > /tmp/selected-files.txt
|
|
|
|
# Read the selected files in full (skip binary files)
|
|
echo "" > /tmp/file-contents.txt
|
|
while IFS= read -r filepath; do
|
|
filepath=$(echo "$filepath" | xargs)
|
|
[ -z "$filepath" ] && continue
|
|
if [ -f "$filepath" ] && file --mime "$filepath" | grep -q "text/"; then
|
|
echo "=== $filepath ===" >> /tmp/file-contents.txt
|
|
cat "$filepath" >> /tmp/file-contents.txt
|
|
echo "" >> /tmp/file-contents.txt
|
|
fi
|
|
done < /tmp/selected-files.txt
|
|
|
|
# Cap total context at 100KB
|
|
head -c 100000 /tmp/file-contents.txt > /tmp/file-context.txt
|
|
|
|
- name: Analyze issue with AI
|
|
env:
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
ISSUE_TITLE: ${{ github.event.issue.title }}
|
|
ISSUE_BODY: ${{ github.event.issue.body }}
|
|
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
|
|
IS_FIRST_TIME: ${{ steps.check-first-time.outputs.is_first_time }}
|
|
run: |
|
|
GREETING=""
|
|
if [ "$IS_FIRST_TIME" = "true" ]; then
|
|
GREETING='This is a first-time contributor. Start your comment with: "Thanks for opening your first issue!"'
|
|
fi
|
|
|
|
printf '%s' "$ISSUE_TITLE" > /tmp/issue-title.txt
|
|
printf '%s' "${ISSUE_BODY:-}" > /tmp/issue-body.txt
|
|
printf '%s' "$ISSUE_AUTHOR" > /tmp/issue-author.txt
|
|
printf '%s' "$GREETING" > /tmp/greeting.txt
|
|
|
|
PAYLOAD=$(jq -n \
|
|
--rawfile title /tmp/issue-title.txt \
|
|
--rawfile body /tmp/issue-body.txt \
|
|
--rawfile author /tmp/issue-author.txt \
|
|
--rawfile greeting /tmp/greeting.txt \
|
|
--rawfile repo_context /tmp/repo-context.txt \
|
|
--rawfile context /tmp/file-context.txt \
|
|
'{
|
|
model: "anthropic/claude-opus-4.6",
|
|
messages: [
|
|
{
|
|
role: "system",
|
|
content: ("You are a triage bot for Donut Browser, an open-source anti-detect browser (Tauri desktop app: Rust backend + Next.js frontend).\n\nProject guidelines and structure:\n" + $repo_context + "\n\nYou have access to relevant source files for context.\n\nAnalyze the issue and produce a single comment. Your job is to collect missing information needed to diagnose the issue, NOT to guess the cause.\n\nFormat:\n\n1. One sentence acknowledging the issue.\n2. **Missing information** - Ask specific questions about what is missing from the report. Focus on reproducing the issue. Do NOT speculate about root causes or mention internal code/files — you will almost certainly be wrong without logs. Instead, ask for:\n - Exact steps to reproduce (if not provided)\n - Expected vs actual behavior (if unclear)\n - Error messages or screenshots (if not provided)\n - OS and app version (if not provided)\n - For bug reports: if logs are needed, tell the user EXACTLY how to get them:\n - macOS app logs: `~/Library/Logs/Donut Browser/`\n - Linux app logs: `~/.local/share/DonutBrowser/logs/`\n - Windows app logs: `%APPDATA%\\DonutBrowser\\logs\\`\n - Sync server logs: `docker logs <container>` or check the server console\n - Provide a ready-to-run shell command when possible.\n - For self-hosted sync issues: check if the user is using the latest Docker image (`docker pull donutbrowser/donut-sync:latest`).\n - Only ask for information that is actually missing. If the issue is already detailed, just acknowledge it.\n3. Suggest a label: `Label: bug` or `Label: enhancement` on its own line.\n\nRules:\n- Do NOT include a \"Possible cause\" section. Do not speculate about what code might be causing the issue.\n- Be brief and focused on collecting actionable information from the reporter.\n- If the issue already has everything needed (steps to reproduce, logs, version, OS), just acknowledge it.\n- Never exceed 15 lines.")
|
|
},
|
|
{
|
|
role: "user",
|
|
content: (
|
|
(if ($greeting | length) > 0 then $greeting + "\n\n" else "" end) +
|
|
"Analyze this issue:\n\nTitle: " + $title +
|
|
"\nAuthor: " + $author +
|
|
"\n\nBody:\n" + $body +
|
|
"\n\nRelevant source files:\n" + $context
|
|
)
|
|
}
|
|
]
|
|
}')
|
|
|
|
RESPONSE=$(curl -fsSL https://openrouter.ai/api/v1/chat/completions \
|
|
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$PAYLOAD")
|
|
|
|
jq -r '.choices[0].message.content // empty' <<< "$RESPONSE" > /tmp/ai-comment.txt
|
|
|
|
if [ ! -s /tmp/ai-comment.txt ]; then
|
|
echo "::error::AI response was empty"
|
|
echo "Raw response:"
|
|
echo "$RESPONSE"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Post comment and label
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
run: |
|
|
LABEL=$(grep -oP '^Label:\s*\K.*' /tmp/ai-comment.txt | tail -1 | tr '[:upper:]' '[:lower:]' | xargs)
|
|
sed -i '/^Label:/d' /tmp/ai-comment.txt
|
|
|
|
gh issue comment "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file /tmp/ai-comment.txt
|
|
|
|
if [ "$LABEL" = "bug" ]; then
|
|
gh issue edit "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --add-label "bug" 2>/dev/null || true
|
|
elif [ "$LABEL" = "enhancement" ]; then
|
|
gh issue edit "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --add-label "enhancement" 2>/dev/null || true
|
|
fi
|
|
|
|
analyze-pr:
|
|
if: github.repository == 'zhom/donutbrowser' && github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
|
|
|
- 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: Gather PR context
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
run: |
|
|
# Get changed files list
|
|
gh api "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" \
|
|
--jq '.[] | "- \(.filename) (\(.status)) +\(.additions)/-\(.deletions)"' \
|
|
> /tmp/pr-files.txt
|
|
|
|
# Get the actual diff
|
|
gh api "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
|
|
--header "Accept: application/vnd.github.diff" \
|
|
> /tmp/pr-diff-full.txt 2>/dev/null || true
|
|
head -c 20000 /tmp/pr-diff-full.txt > /tmp/pr-diff.txt
|
|
|
|
# Get CONTRIBUTING.md and README.md for context
|
|
cat CONTRIBUTING.md > /tmp/contributing.txt 2>/dev/null || echo "Not found" > /tmp/contributing.txt
|
|
head -50 README.md > /tmp/readme.txt 2>/dev/null || echo "Not found" > /tmp/readme.txt
|
|
|
|
# Read project guidelines (contains repo structure)
|
|
cp CLAUDE.md /tmp/repo-context.txt
|
|
|
|
# Read full contents of all changed files (skip binary)
|
|
echo "" > /tmp/related-file-contents.txt
|
|
gh api "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --jq '.[].filename' | while IFS= read -r filepath; do
|
|
if [ -f "$filepath" ] && file --mime "$filepath" | grep -q "text/"; then
|
|
echo "=== $filepath (full file) ===" >> /tmp/related-file-contents.txt
|
|
cat "$filepath" >> /tmp/related-file-contents.txt
|
|
echo "" >> /tmp/related-file-contents.txt
|
|
fi
|
|
done
|
|
head -c 100000 /tmp/related-file-contents.txt > /tmp/pr-file-context.txt
|
|
|
|
- name: Analyze PR with AI
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
PR_BODY: ${{ github.event.pull_request.body }}
|
|
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
|
PR_BASE: ${{ github.event.pull_request.base.ref }}
|
|
PR_HEAD: ${{ github.event.pull_request.head.ref }}
|
|
IS_FIRST_TIME: ${{ steps.check-first-time.outputs.is_first_time }}
|
|
run: |
|
|
GREETING=""
|
|
if [ "$IS_FIRST_TIME" = "true" ]; then
|
|
GREETING='This is a first-time contributor. Start your comment with: "Thanks for your first PR!"'
|
|
fi
|
|
|
|
printf '%s' "$PR_TITLE" > /tmp/pr-title.txt
|
|
printf '%s' "${PR_BODY:-}" > /tmp/pr-body.txt
|
|
printf '%s' "$PR_AUTHOR" > /tmp/pr-author.txt
|
|
printf '%s' "$PR_BASE" > /tmp/pr-base.txt
|
|
printf '%s' "$PR_HEAD" > /tmp/pr-head.txt
|
|
printf '%s' "$GREETING" > /tmp/greeting.txt
|
|
|
|
PAYLOAD=$(jq -n \
|
|
--rawfile title /tmp/pr-title.txt \
|
|
--rawfile body /tmp/pr-body.txt \
|
|
--rawfile author /tmp/pr-author.txt \
|
|
--rawfile base /tmp/pr-base.txt \
|
|
--rawfile head /tmp/pr-head.txt \
|
|
--rawfile files /tmp/pr-files.txt \
|
|
--rawfile diff /tmp/pr-diff.txt \
|
|
--rawfile greeting /tmp/greeting.txt \
|
|
--rawfile repo_context /tmp/repo-context.txt \
|
|
--rawfile contributing /tmp/contributing.txt \
|
|
--rawfile file_context /tmp/pr-file-context.txt \
|
|
'{
|
|
model: "anthropic/claude-opus-4.6",
|
|
messages: [
|
|
{
|
|
role: "system",
|
|
content: ("You are a code review bot for Donut Browser, an open-source anti-detect browser (Tauri desktop app: Rust backend + Next.js frontend).\n\nProject guidelines and structure:\n" + $repo_context + "\n\nContributing guidelines:\n" + $contributing + "\n\nYou have access to the full changed files and the diff. Use them to give a substantive review.\n\nReview this PR and produce a single comment. Format:\n\n1. One sentence summarizing what this PR does and whether the approach is sound.\n2. **Code review** - Specific observations about the actual code changes. Mention file names and what you see in the diff. Look for:\n - Bugs or logic errors in the changed code\n - Security issues (SQL injection, path traversal, XSS, command injection)\n - Missing error handling or edge cases\n - Breaking changes to existing APIs or behavior\n - If UI text was added/changed, check if all 7 translation files (en, es, fr, ja, pt, ru, zh) in src/i18n/locales/ were updated\n - If Tauri commands were added/removed, the unused-commands test in lib.rs needs updating\n3. **Suggestions** - Concrete improvements if any. Skip if the PR looks good.\n\nRules:\n- Be substantive. Review the actual diff, not just the description.\n- Do NOT nitpick formatting or style — the project has automated linting (biome + clippy + rustfmt).\n- Do NOT just summarize the PR description back to the user — they wrote it, they know what it says.\n- If the PR is good, say so briefly.\n- Never exceed 20 lines.")
|
|
},
|
|
{
|
|
role: "user",
|
|
content: (
|
|
(if ($greeting | length) > 0 then $greeting + "\n\n" else "" end) +
|
|
"Review this PR:\n\nTitle: " + $title +
|
|
"\nAuthor: " + $author +
|
|
"\nBase: " + $base + " <- Head: " + $head +
|
|
"\n\nDescription:\n" + $body +
|
|
"\n\nChanged files:\n" + $files +
|
|
"\n\nDiff:\n" + $diff +
|
|
"\n\nFull file contents:\n" + $file_context
|
|
)
|
|
}
|
|
]
|
|
}')
|
|
|
|
RESPONSE=$(curl -fsSL https://openrouter.ai/api/v1/chat/completions \
|
|
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$PAYLOAD")
|
|
|
|
jq -r '.choices[0].message.content // empty' <<< "$RESPONSE" > /tmp/ai-comment.txt
|
|
|
|
if [ ! -s /tmp/ai-comment.txt ]; then
|
|
echo "::error::AI response was empty"
|
|
echo "Raw response:"
|
|
echo "$RESPONSE"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Post comment
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
run: |
|
|
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file /tmp/ai-comment.txt
|
|
|
|
opencode-command:
|
|
if: |
|
|
github.repository == 'zhom/donutbrowser' &&
|
|
(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@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
|
|
|
- name: Run opencode
|
|
uses: anomalyco/opencode/github@a35b8a95c27d28e979a3826e1289d7ee87f40251 #v1.4.11
|
|
env:
|
|
ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }}
|
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
model: zai-coding-plan/glm-4.7
|