-
Notifications
You must be signed in to change notification settings - Fork 0
141 lines (122 loc) · 11.8 KB
/
Copy pathopencode-autofix.yml
File metadata and controls
141 lines (122 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
name: OpenCode AutoFix Pipeline
on:
workflow_dispatch:
schedule:
- cron: '*/5 * * * *'
permissions:
contents: write
pull-requests: write
issues: write
jobs:
autofix:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GH_PAT }}
- name: Clean up stale branches
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
for branch in $(gh api repos/${{ github.repository }}/git/refs/heads --jq '.[].ref' | sed 's|refs/heads/||' | grep -E '^(opencode/|autofix/)' || echo ""); do
PR_STATE=$(gh pr list --repo ${{ github.repository }} --head "$branch" --state all --json state --jq '.[0].state' 2>/dev/null || echo "")
if [ "$PR_STATE" = "MERGED" ] || [ "$PR_STATE" = "CLOSED" ]; then
echo "Deleting stale branch: $branch"
gh api -X DELETE "repos/${{ github.repository }}/git/refs/heads/$branch" --silent 2>/dev/null || true
fi
done
- name: Fetch latest Sentry issue
id: sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
ISSUE_JSON=$(gh api \
-H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
"https://de.sentry.io/api/0/organizations/bikramgole/issues/?query=is:unresolved%20project:${PROJECT_SLUG}&limit=1&statsPeriod=14d" \
--jq '.[0] // empty' 2>/dev/null || echo "")
if [ -z "$ISSUE_JSON" ] || [ "$ISSUE_JSON" = "null" ]; then
echo "No unresolved Sentry issues found."
echo "has_issue=false" >> $GITHUB_OUTPUT
exit 0
fi
SHORT_ID=$(echo "$ISSUE_JSON" | jq -r '.shortId')
TITLE=$(echo "$ISSUE_JSON" | jq -r '.title')
ID=$(echo "$ISSUE_JSON" | jq -r '.id')
echo "Found: $SHORT_ID -- $TITLE"
echo "has_issue=true" >> $GITHUB_OUTPUT
echo "issue_id=$SHORT_ID" >> $GITHUB_OUTPUT
echo "sentry_numeric_id=$ID" >> $GITHUB_OUTPUT
echo "title=$TITLE" >> $GITHUB_OUTPUT
echo "$ISSUE_JSON" | jq -r '.metadata.value // .metadata.title // empty' > /tmp/sentry_error.txt
- name: Fix with AI (direct API calls)
id: fix
if: steps.sentry.outputs.has_issue == 'true'
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
set -e
ISSUE_ID="${{ steps.sentry.outputs.issue_id }}"
TITLE="${{ steps.sentry.outputs.title }}"
TS=$(date +%s)
BRANCH="autofix/${ISSUE_ID}-${TS}"
ERROR_MSG=$(cat /tmp/sentry_error.txt)
FILE_LIST=$(git ls-files | head -100)
git config user.name "opencode-autofix"
git config user.email "opencode-autofix@users.noreply.github.com"
git checkout -b "$BRANCH"
echo 'aW1wb3J0IGpzb24sIG9zLCBzeXMsIHN1YnByb2Nlc3MsIHVybGxpYi5yZXF1ZXN0LCB1cmxsaWIuZXJyb3IsIHVybGxpYi5wYXJzZQoKVUEgPSAiTW96aWxsYS81LjAgKGNvbXBhdGlibGU7IG9wZW5jb2RlLWF1dG9maXgvMS4wKSIKCmRlZiBjYWxsX3plbihwcm9tcHQpOgogICAgYXBpX2tleSA9IG9zLmVudmlyb24uZ2V0KCdPUEVOQ09ERV9BUElfS0VZJywgJycpCiAgICBkYXRhID0ganNvbi5kdW1wcyh7Im1vZGVsIjogImRlZXBzZWVrLXY0LWZsYXNoLWZyZWUiLCAibWVzc2FnZXMiOiBbeyJyb2xlIjogInVzZXIiLCAiY29udGVudCI6IHByb21wdH1dfSkuZW5jb2RlKCkKICAgIHJlcSA9IHVybGxpYi5yZXF1ZXN0LlJlcXVlc3QoImh0dHBzOi8vb3BlbmNvZGUuYWkvemVuL3YxL2NoYXQvY29tcGxldGlvbnMiLAogICAgICAgIGRhdGE9ZGF0YSwgaGVhZGVycz17IkF1dGhvcml6YXRpb24iOiBmIkJlYXJlciB7YXBpX2tleX0iLCAiQ29udGVudC1UeXBlIjogImFwcGxpY2F0aW9uL2pzb24iLCAiVXNlci1BZ2VudCI6IFVBfSkKICAgIHJlc3AgPSB1cmxsaWIucmVxdWVzdC51cmxvcGVuKHJlcSkKICAgIHJldHVybiBqc29uLmxvYWRzKHJlc3AucmVhZCgpKQoKZGVmIGNhbGxfZ2VtaW5pKHByb21wdCk6CiAgICBhcGlfa2V5ID0gb3MuZW52aXJvbi5nZXQoJ0dPT0dMRV9BUElfS0VZJywgJycpCiAgICBkYXRhID0ganNvbi5kdW1wcyh7ImNvbnRlbnRzIjogW3sicGFydHMiOiBbeyJ0ZXh0IjogcHJvbXB0fV19XX0pLmVuY29kZSgpCiAgICByZXEgPSB1cmxsaWIucmVxdWVzdC5SZXF1ZXN0KAogICAgICAgIGYiaHR0cHM6Ly9nZW5lcmF0aXZlbGFuZ3VhZ2UuZ29vZ2xlYXBpcy5jb20vdjFiZXRhL21vZGVscy9nZW1pbmktMi4wLWZsYXNoLTAwMTpnZW5lcmF0ZUNvbnRlbnQ/a2V5PXthcGlfa2V5fSIsCiAgICAgICAgZGF0YT1kYXRhLCBoZWFkZXJzPXsiQ29udGVudC1UeXBlIjogImFwcGxpY2F0aW9uL2pzb24iLCAiVXNlci1BZ2VudCI6IFVBfSkKICAgIHJlc3AgPSB1cmxsaWIucmVxdWVzdC51cmxvcGVuKHJlcSkKICAgIHJlc3VsdCA9IGpzb24ubG9hZHMocmVzcC5yZWFkKCkpCiAgICB0ZXh0ID0gcmVzdWx0LmdldCgiY2FuZGlkYXRlcyIsIFt7fV0pWzBdLmdldCgiY29udGVudCIsIHt9KS5nZXQoInBhcnRzIiwgW3t9XSlbMF0uZ2V0KCJ0ZXh0IiwgIiIpCiAgICByZXR1cm4geyJjaG9pY2VzIjogW3sibWVzc2FnZSI6IHsiY29udGVudCI6IHRleHR9fV19CgpkZWYgY2FsbF9vcGVucm91dGVyKHByb21wdCk6CiAgICBhcGlfa2V5ID0gb3MuZW52aXJvbi5nZXQoJ09QRU5ST1VURVJfQVBJX0tFWScsICcnKQogICAgZGF0YSA9IGpzb24uZHVtcHMoeyJtb2RlbCI6ICJvcGVucm91dGVyL2F1dG8iLCAibWVzc2FnZXMiOiBbeyJyb2xlIjogInVzZXIiLCAiY29udGVudCI6IHByb21wdH1dfSkuZW5jb2RlKCkKICAgIHJlcSA9IHVybGxpYi5yZXF1ZXN0LlJlcXVlc3QoImh0dHBzOi8vb3BlbnJvdXRlci5haS9hcGkvdjEvY2hhdC9jb21wbGV0aW9ucyIsCiAgICAgICAgZGF0YT1kYXRhLCBoZWFkZXJzPXsiQXV0aG9yaXphdGlvbiI6IGYiQmVhcmVyIHthcGlfa2V5fSIsICJDb250ZW50LVR5cGUiOiAiYXBwbGljYXRpb24vanNvbiIsICJVc2VyLUFnZW50IjogVUF9KQogICAgcmVzcCA9IHVybGxpYi5yZXF1ZXN0LnVybG9wZW4ocmVxKQogICAgcmV0dXJuIGpzb24ubG9hZHMocmVzcC5yZWFkKCkpCgpkZWYgYXBwbHlfY2hhbmdlcyhyZXNwb25zZSk6CiAgICBpZiAnY2hvaWNlcycgaW4gcmVzcG9uc2U6CiAgICAgICAgY29udGVudCA9IHJlc3BvbnNlWydjaG9pY2VzJ11bMF1bJ21lc3NhZ2UnXVsnY29udGVudCddCiAgICBlbHNlOgogICAgICAgIHByaW50KCJGQUlMOiBObyBjaG9pY2VzIGluIHJlc3BvbnNlIikKICAgICAgICByZXR1cm4gRmFsc2UKICAgIGlmIG5vdCBjb250ZW50OgogICAgICAgIHByaW50KCJGQUlMOiBFbXB0eSByZXNwb25zZSBjb250ZW50IikKICAgICAgICByZXR1cm4gRmFsc2UKCiAgICBzdGFydCA9IGNvbnRlbnQuZmluZCgnWycpCiAgICBlbmQgPSBjb250ZW50LnJmaW5kKCddJykgKyAxCiAgICBpZiBzdGFydCA8IDAgb3IgZW5kIDw9IHN0YXJ0OgogICAgICAgIGZvciBtYXJrZXIgaW4gWydgYGBqc29uJywgJ2BgYCddOgogICAgICAgICAgICBpZiBtYXJrZXIgaW4gY29udGVudDoKICAgICAgICAgICAgICAgIHBhcnRzID0gY29udGVudC5zcGxpdChtYXJrZXIpCiAgICAgICAgICAgICAgICBmb3IgcGFydCBpbiBwYXJ0czoKICAgICAgICAgICAgICAgICAgICBzID0gcGFydC5maW5kKCdbJykKICAgICAgICAgICAgICAgICAgICBlID0gcGFydC5yZmluZCgnXScpICsgMQogICAgICAgICAgICAgICAgICAgIGlmIHMgPj0gMCBhbmQgZSA+IHM6CiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRlbnQgPSBwYXJ0W3M6ZV0KICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHJpbnQoIkZBSUw6IE5vIEpTT04gYXJyYXkgZm91bmQiKQogICAgICAgICAgICByZXR1cm4gRmFsc2UKICAgIGVsc2U6CiAgICAgICAgY29udGVudCA9IGNvbnRlbnRbc3RhcnQ6ZW5kXQoKICAgIHRyeToKICAgICAgICBjaGFuZ2VzID0ganNvbi5sb2Fkcyhjb250ZW50KQogICAgZXhjZXB0IGpzb24uSlNPTkRlY29kZUVycm9yIGFzIGU6CiAgICAgICAgcHJpbnQoZiJGQUlMOiBKU09OIHBhcnNlIGVycm9yOiB7ZX0iKQogICAgICAgIHJldHVybiBGYWxzZQoKICAgIGlmIG5vdCBpc2luc3RhbmNlKGNoYW5nZXMsIGxpc3QpOgogICAgICAgIGNoYW5nZXMgPSBbY2hhbmdlc10KCiAgICBjb3VudCA9IDAKICAgIGZvciBjIGluIGNoYW5nZXM6CiAgICAgICAgZnAgPSBjLmdldCgnZmlsZScsICcnKQogICAgICAgIGNvbnRlbnQgPSBjLmdldCgnY29udGVudCcsICcnKQogICAgICAgIGlmIG5vdCBmcCBvciBub3QgY29udGVudDoKICAgICAgICAgICAgY29udGludWUKICAgICAgICBkID0gb3MucGF0aC5kaXJuYW1lKGZwKQogICAgICAgIGlmIGQ6CiAgICAgICAgICAgIG9zLm1ha2VkaXJzKGQsIGV4aXN0X29rPVRydWUpCiAgICAgICAgd2l0aCBvcGVuKGZwLCAndycpIGFzIGY6CiAgICAgICAgICAgIGYud3JpdGUoY29udGVudCkKICAgICAgICBjb3VudCArPSAxCiAgICAgICAgcHJpbnQoZiJBcHBsaWVkOiB7ZnB9IikKCiAgICBpZiBjb3VudCA9PSAwOgogICAgICAgIHByaW50KCJGQUlMOiBObyB2YWxpZCBmaWxlIGNoYW5nZXMgaW4gSlNPTiIpCiAgICAgICAgcmV0dXJuIEZhbHNlCiAgICBwcmludChmIk9LOiB7Y291bnR9IGZpbGVzIGNoYW5nZWQiKQogICAgcmV0dXJuIFRydWUKCmRlZiBtYWluKCk6CiAgICBlcnJvcl9tc2cgPSBzeXMuYXJndlsxXQogICAgdGl0bGUgPSBzeXMuYXJndlsyXQogICAgZmlsZV9saXN0ID0gc3lzLmFyZ3ZbM10KCiAgICBwcm9tcHQgPSBmIiIiRml4IHRoaXMgU2VudHJ5IGVycm9yIGluIHRoZSBjb2RlYmFzZS4KRXJyb3I6IHt0aXRsZX0KRGV0YWlsczoge2Vycm9yX21zZ30KClJlcG9zaXRvcnkgY29udGVudHM6CntmaWxlX2xpc3R9CgpPdXRwdXQgT05MWSBhIEpTT04gYXJyYXkgb2YgZmlsZSBjaGFuZ2VzLiBFYWNoIGNoYW5nZSBtdXN0IGhhdmU6Ci0gImZpbGUiOiByZWxhdGl2ZSBmaWxlIHBhdGgKLSAiY29udGVudCI6IHRoZSBDT01QTEVURSBuZXcgZmlsZSBjb250ZW50ICh0aGUgd2hvbGUgZmlsZSwgbm90IGp1c3QgdGhlIGRpZmYpCgpFeGFtcGxlOgpbe3siZmlsZSI6ICJwYXRoL3RvL2ZpbGUucHkiLCAiY29udGVudCI6ICJuZXcgY29udGVudCBoZXJlIn19XQoKTWFrZSBzdXJlIHRoZSBKU09OIGlzIHZhbGlkIGFuZCBjb21wbGV0ZS4gRG8gTk9UIGluY2x1ZGUgYW55IG90aGVyIHRleHQgb3V0c2lkZSB0aGUgSlNPTi4iIiIKCiAgICB0aWVycyA9IFsKICAgICAgICAoIlRJRVIgMTogT3BlbkNvZGUgWmVuIiwgY2FsbF96ZW4pLAogICAgICAgICgiVElFUiAyOiBHZW1pbmkiLCBjYWxsX2dlbWluaSksCiAgICAgICAgKCJUSUVSIDM6IE9wZW5Sb3V0ZXIiLCBjYWxsX29wZW5yb3V0ZXIpLAogICAgXQoKICAgIGZvciBuYW1lLCBjYWxsZXIgaW4gdGllcnM6CiAgICAgICAgcHJpbnQoZiI9PT0ge25hbWV9ID09PSIpCiAgICAgICAgdHJ5OgogICAgICAgICAgICByZXNwb25zZSA9IGNhbGxlcihwcm9tcHQpCiAgICAgICAgICAgIGlmIGFwcGx5X2NoYW5nZXMocmVzcG9uc2UpOgogICAgICAgICAgICAgICAgcHJpbnQoZiJ7bmFtZX0gT0siKQogICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICBwcmludChmIntuYW1lfSBFUlJPUjoge2V9IikKCiAgICAgICAgc3VicHJvY2Vzcy5ydW4oWyJnaXQiLCAiY2hlY2tvdXQiLCAiLS0iLCAiLiJdLCBjYXB0dXJlX291dHB1dD1UcnVlKQogICAgICAgIHN1YnByb2Nlc3MucnVuKFsiZ2l0IiwgImNsZWFuIiwgIi1mZCJdLCBjYXB0dXJlX291dHB1dD1UcnVlKQoKICAgIHByaW50KCJObyBjaGFuZ2VzIG1hZGUgYnkgYW55IG1vZGVsLiIpCiAgICBzeXMuZXhpdCgxKQoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIG1haW4oKQo=' | base64 -d > /tmp/autofix.py
python3 /tmp/autofix.py "$ERROR_MSG" "$TITLE" "$FILE_LIST" || true
CHANGED=$( { git diff --name-only HEAD && git status --porcelain; } 2>/dev/null | sort -u | grep -v '^$' | wc -l || echo "0")
if [ "$CHANGED" = "0" ]; then
echo "No changes made by any model."
echo "pr_created=false" >> $GITHUB_OUTPUT
exit 0
fi
git add -A
git commit -m "fix: $TITLE [autofix]"
git push origin "$BRANCH" 2>&1 || { echo "Push failed"; echo "pr_created=false" >> $GITHUB_OUTPUT; exit 0; }
PR_URL=$(gh pr create \
--base main \
--head "$BRANCH" \
--title "fix: $TITLE [autofix]" \
--body "Auto-fix for Sentry issue $ISSUE_ID.
Error: $TITLE
Auto-fixed by OpenCode pipeline." 2>&1 || true)
if echo "$PR_URL" | grep -q "https://"; then
PR_NUMBER=$(echo "$PR_URL" | grep -oP '\d+$' || echo "")
if [ -n "$PR_NUMBER" ]; then
PR_NODE=$(gh pr view "$PR_NUMBER" --json id --jq '.id' 2>/dev/null || echo "")
gh pr merge "$PR_NUMBER" --repo ${{ github.repository }} --squash --delete-branch 2>&1 || echo "Direct merge failed"
echo "pr_created=true" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
fi
else
echo "$PR_URL"
echo "pr_created=false" >> $GITHUB_OUTPUT
fi
- name: Resolve issue
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
SENTRY_NUMERIC_ID="${{ steps.sentry.outputs.sentry_numeric_id }}"
ISSUE_ID="${{ steps.sentry.outputs.issue_id }}"
if [ -n "$SENTRY_NUMERIC_ID" ]; then
gh api -X PUT \
-H "Authorization: Bearer ${{ secrets.SENTRY_AUTH_TOKEN }}" \
"https://de.sentry.io/api/0/issues/$SENTRY_NUMERIC_ID/" \
-F "status=resolved" --silent 2>/dev/null && echo "Sentry issue resolved" || true
fi
if [ -n "$ISSUE_ID" ]; then
OPEN_ISSUE=$(gh issue list --repo "${{ github.repository }}" --state open --json number,title --jq ".[] | select(.title | contains(\"$ISSUE_ID\")) | .number" 2>/dev/null | head -1)
if [ -n "$OPEN_ISSUE" ]; then
gh issue close "$OPEN_ISSUE" --repo "${{ github.repository }}" --comment "Auto-closed: Sentry issue $ISSUE_ID was resolved." 2>/dev/null && echo "GitHub issue #$OPEN_ISSUE closed" || true
fi
fi