autopilot-code 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,690 +0,0 @@
1
- #!/usr/bin/env bash
2
- # =============================================================================
3
- # DEPRECATED: This script is deprecated and will be removed in a future version.
4
- #
5
- # The functionality has been replaced by Python IssueRunner module at:
6
- # scripts/issue_runner/
7
- #
8
- # To use the new runner, ensure "useNewRunner" is not set to false in your
9
- # autopilot.json config (it defaults to true).
10
- #
11
- # This script is maintained only for backward compatibility.
12
- # =============================================================================
13
- set -euo pipefail
14
-
15
- REPO_DIR="$1"
16
- ISSUE_NUMBER="$2"
17
-
18
- if [[ -z "$REPO_DIR" || -z "$ISSUE_NUMBER" ]]; then
19
- echo "Usage: $0 <repoDir> <issueNumber>" >&2
20
- exit 1
21
- fi
22
-
23
- cd "$REPO_DIR"
24
-
25
- # Read config (prefer jq, fallback to python3)
26
- if command -v jq >/dev/null 2>&1; then
27
- REPO=$(jq -r '.repo' < .autopilot/autopilot.json)
28
- AUTO_MERGE=$(jq -r '.autoMerge // true' < .autopilot/autopilot.json)
29
- MERGE_METHOD=$(jq -r '.mergeMethod // "squash"' < .autopilot/autopilot.json)
30
- AUTO_RESOLVE_CONFLICTS=$(jq -r '.autoResolveConflicts // true' < .autopilot/autopilot.json)
31
- CONFLICT_RESOLUTION_MAX_ATTEMPTS=$(jq -r '.conflictResolutionMaxAttempts // 3' < .autopilot/autopilot.json)
32
- AUTO_FIX_CHECKS=$(jq -r '.autoFixChecks // true' < .autopilot/autopilot.json)
33
- AUTO_FIX_CHECKS_MAX_ATTEMPTS=$(jq -r '.autoFixChecksMaxAttempts // 3' < .autopilot/autopilot.json)
34
- ALLOWED_USERS=$(jq -r '.allowedMergeUsers[]' < .autopilot/autopilot.json 2>/dev/null || true)
35
- AGENT_PATH=$(jq -r '.agentPath // ""' < .autopilot/autopilot.json)
36
- ENABLE_PLANNING_STEP=$(jq -r '.enablePlanningStep // true' < .autopilot/autopilot.json)
37
- else
38
- REPO=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json"))["repo"])')
39
- AUTO_MERGE=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoMerge", True))')
40
- MERGE_METHOD=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("mergeMethod", "squash"))')
41
- AUTO_RESOLVE_CONFLICTS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoResolveConflicts", True))')
42
- CONFLICT_RESOLUTION_MAX_ATTEMPTS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("conflictResolutionMaxAttempts", 3))')
43
- AUTO_FIX_CHECKS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoFixChecks", True))')
44
- AUTO_FIX_CHECKS_MAX_ATTEMPTS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoFixChecksMaxAttempts", 3))')
45
- ALLOWED_USERS=$(python3 -c 'import json,sys; users=json.load(open(".autopilot/autopilot.json")).get("allowedMergeUsers", []); print("\n".join(users))' 2>/dev/null || true)
46
- AGENT_PATH=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("agentPath", ""))' 2>/dev/null || true)
47
- ENABLE_PLANNING_STEP=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("enablePlanningStep", True))' 2>/dev/null || echo "true")
48
- fi
49
-
50
- # Find opencode binary - check config, PATH, then common locations
51
- find_opencode() {
52
- # 1. Config-specified path
53
- if [[ -n "$AGENT_PATH" ]] && [[ -x "$AGENT_PATH" ]]; then
54
- echo "$AGENT_PATH"
55
- return
56
- fi
57
-
58
- # 2. Already in PATH
59
- if command -v opencode >/dev/null 2>&1; then
60
- command -v opencode
61
- return
62
- fi
63
-
64
- # 3. Common nvm locations
65
- for node_dir in "$HOME/.nvm/versions/node"/*/bin; do
66
- if [[ -x "$node_dir/opencode" ]]; then
67
- echo "$node_dir/opencode"
68
- return
69
- fi
70
- done
71
-
72
- # 4. Other common locations
73
- for path in "$HOME/.local/bin/opencode" "/usr/local/bin/opencode" "$HOME/.npm-global/bin/opencode"; do
74
- if [[ -x "$path" ]]; then
75
- echo "$path"
76
- return
77
- fi
78
- done
79
-
80
- # Not found
81
- return 1
82
- }
83
-
84
- OPENCODE_BIN=$(find_opencode) || {
85
- echo "[run_opencode_issue.sh] ERROR: opencode not found. Set 'agentPath' in autopilot.json or ensure opencode is installed." >&2
86
- exit 1
87
- }
88
- echo "[run_opencode_issue.sh] Using opencode: $OPENCODE_BIN"
89
-
90
- WORKTREE="/tmp/autopilot-issue-$ISSUE_NUMBER"
91
- BRANCH="autopilot/issue-$ISSUE_NUMBER"
92
-
93
- echo "[run_opencode_issue.sh] repo=$REPO issue=$ISSUE_NUMBER worktree=$WORKTREE"
94
-
95
- # 1. Create or reuse git worktree from main
96
- # First, prune any stale worktrees (e.g., if directory was deleted but still registered)
97
- git worktree prune 2>/dev/null || true
98
-
99
- if [[ -d "$WORKTREE" ]]; then
100
- echo "[run_opencode_issue.sh] Reusing existing worktree: $WORKTREE"
101
- else
102
- # If branch exists already, add worktree from that branch; otherwise create from main.
103
- if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
104
- git worktree add "$WORKTREE" "$BRANCH"
105
- else
106
- git worktree add "$WORKTREE" -b "$BRANCH" main
107
- fi
108
- fi
109
-
110
- # 1.5. Install dependencies if package.json exists (for Node.js projects)
111
- if [[ -f "$WORKTREE/package.json" ]]; then
112
- echo "[run_opencode_issue.sh] Installing npm dependencies..."
113
- (cd "$WORKTREE" && npm install --silent) || true
114
- fi
115
-
116
- # 2. Fetch issue title/body
117
- ISSUE_JSON=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title,body)
118
- if command -v jq >/dev/null 2>&1; then
119
- ISSUE_TITLE=$(echo "$ISSUE_JSON" | jq -r '.title')
120
- ISSUE_BODY=$(echo "$ISSUE_JSON" | jq -r '.body')
121
- else
122
- ISSUE_TITLE=$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["title"])' <<<"$ISSUE_JSON")
123
- ISSUE_BODY=$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("body") or "")' <<<"$ISSUE_JSON")
124
- fi
125
-
126
- # 3. Build prompt
127
- PROMPT="Please implement the following GitHub issue.
128
-
129
- Issue #$ISSUE_NUMBER: $ISSUE_TITLE
130
-
131
- $ISSUE_BODY
132
-
133
- Work rules:
134
- - Make the necessary code changes.
135
- - Commit with message: \"autopilot: work for issue #$ISSUE_NUMBER\".
136
- - Push your changes to the remote branch $BRANCH.
137
- - If the issue is a simple file-addition, just do it directly (no extra refactors)."
138
-
139
- # 3.5. Planning step - analyze and plan before implementation
140
- if [[ "$ENABLE_PLANNING_STEP" == "true" ]]; then
141
- echo "[run_opencode_issue.sh] Running planning/analysis step..."
142
-
143
- PLANNING_PROMPT="Please analyze the following GitHub issue and provide a detailed implementation plan. Do NOT make any code changes yet - only analyze and plan.
144
-
145
- Issue #$ISSUE_NUMBER: $ISSUE_TITLE
146
-
147
- $ISSUE_BODY
148
-
149
- Your task:
150
- 1. Analyze the issue requirements thoroughly
151
- 2. Explore the codebase to understand the relevant components
152
- 3. Identify the files that need to be modified or created
153
- 4. Outline the step-by-step approach for implementing this change
154
- 5. Note any potential issues, dependencies, or risks
155
- 6. List any assumptions you're making
156
-
157
- Provide your analysis and plan in a clear, structured format. Focus on WHAT needs to be done and HOW you'll approach it."
158
-
159
- # Run opencode to generate the plan
160
- cd "$WORKTREE"
161
- RAW_OUTPUT=$("$OPENCODE_BIN" run "$PLANNING_PROMPT" 2>&1 || echo "Planning failed")
162
-
163
- # Filter out noise - keep only the final plan output
164
- # Remove tool calls, internal logs, and system messages
165
- PLAN_OUTPUT=$(echo "$RAW_OUTPUT" | grep -vE '^\s*(bash|read|write|edit|grep|glob|question|task|webfetch|google_search|morph-mcp|todowrite|todoread|skill|search)\s' | \
166
- grep -vE '^\s*(Running|Executing|File:|Offset:|Limit:|parameters:|pattern:|path:|include:)' | \
167
- grep -vE '^<[a-z_]+>|^<\/[a-z_]+>|^system:|^user:|^assistant:|^model:' | \
168
- grep -vE '^\s*\$|^\s*\[.*\]|^Using|Called|Invoked' | \
169
- grep -vE '^\s*$|^(command|description|filePath|oldString|newString|questions|header|options):' | \
170
- tail -100)
171
-
172
- # If filtering removed everything, use a fallback
173
- if [[ -z "$PLAN_OUTPUT" ]]; then
174
- PLAN_OUTPUT="Unable to extract a clean plan. The raw output contained excessive tool logs."
175
- fi
176
-
177
- # Post the plan as a comment on the issue
178
- PLAN_COMMENT="## 📋 Implementation Plan
179
-
180
- I've analyzed the issue and codebase. Here's my planned approach:
181
-
182
- \`\`\`
183
- $PLAN_OUTPUT
184
- \`\`\`
185
-
186
- I will now proceed with implementation based on this plan. If you'd like me to adjust my approach, please add a comment with feedback."
187
-
188
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$PLAN_COMMENT" || true
189
- echo "[run_opencode_issue.sh] Planning step completed and posted to issue."
190
- fi
191
-
192
- # 4. Run opencode inside worktree
193
- cd "$WORKTREE"
194
- "$OPENCODE_BIN" run "$PROMPT"
195
-
196
- # 4.5. Comment that implementation is complete
197
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "✅ Implementation complete.
198
-
199
- I've finished implementing the solution. Now committing changes and creating PR..." || true
200
-
201
- # 5. Commit any changes OpenCode made
202
- if [[ -n "$(git status --porcelain)" ]]; then
203
- git add -A
204
- git commit -m "autopilot: work for issue #$ISSUE_NUMBER"
205
-
206
- # Comment with commit info
207
- COMMIT_SHA=$(git rev-parse --short HEAD)
208
- CHANGES_COUNT=$(git diff-tree --no-commit-id --name-only -r HEAD | wc -l | xargs)
209
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "📝 Changes committed.
210
-
211
- - Commit: \`$COMMIT_SHA\`
212
- - Files changed: $CHANGES_COUNT
213
- - Branch: \`$BRANCH\`
214
-
215
- Next step: pushing to remote..." || true
216
- else
217
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "ℹ️ No changes were made by the AI agent. This might indicate the issue is already resolved or requires manual intervention." || true
218
- fi
219
-
220
- # 6. Ensure branch is pushed (no-op if already up to date)
221
- git push -u origin "$BRANCH" || true
222
-
223
- # 6.5. Comment after push
224
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "📤 Changes pushed to remote branch \`$BRANCH\`.
225
-
226
- Now creating pull request..." || true
227
-
228
- # 7. Create PR if one doesn't already exist
229
- PR_URL=""
230
- if gh pr view --repo "$REPO" --head "$BRANCH" --json url --jq .url >/dev/null 2>&1; then
231
- PR_URL=$(gh pr view --repo "$REPO" --head "$BRANCH" --json url --jq .url)
232
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "📋 PR already exists: $PR_URL" || true
233
- else
234
- PR_URL=$(gh pr create --repo "$REPO" --title "Autopilot: Issue #$ISSUE_NUMBER" --body "Closes #$ISSUE_NUMBER" --base main --head "$BRANCH")
235
-
236
- # Add short delay after PR creation to allow GitHub to calculate merge state
237
- sleep 10
238
- fi
239
-
240
- # 8. Comment on issue with PR URL (best-effort)
241
- if [[ -n "$PR_URL" ]]; then
242
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "🎉 PR created: $PR_URL
243
-
244
- Autopilot has completed the initial implementation. The PR is now open and ready for review." || true
245
- fi
246
-
247
- # 8.5. Handle merge conflicts if auto-resolve is enabled
248
- if [[ "$AUTO_RESOLVE_CONFLICTS" == "true" ]] && [[ -n "$PR_URL" ]]; then
249
- echo "[run_opencode_issue.sh] Checking for merge conflicts..."
250
-
251
- # Get PR merge status
252
- MERGE_STATUS=$(gh pr view --repo "$REPO" --head "$BRANCH" --json mergeable,mergeStateStatus --jq '{mergeable, mergeStateStatus}' 2>/dev/null || echo '{"mergeable":"UNKNOWN"}')
253
-
254
- if command -v jq >/dev/null 2>&1; then
255
- IS_CONFLICTING=$(echo "$MERGE_STATUS" | jq -r 'if .mergeable == "CONFLICTING" or .mergeStateStatus == "DIRTY" then "true" else "false" end')
256
- else
257
- IS_CONFLICTING=$(python3 -c 'import json,sys; d=json.load(sys.stdin); print("true" if d.get("mergeable") == "CONFLICTING" or d.get("mergeStateStatus") == "DIRTY" else "false")' <<<"$MERGE_STATUS")
258
- fi
259
-
260
- if [[ "$IS_CONFLICTING" == "true" ]]; then
261
- echo "[run_opencode_issue.sh] PR has merge conflicts. Attempting auto-resolution..."
262
-
263
- RESOLVE_ATTEMPT=0
264
- MAX_ATTEMPTS=$((CONFLICT_RESOLUTION_MAX_ATTEMPTS + 0))
265
-
266
- while [[ $RESOLVE_ATTEMPT -lt $MAX_ATTEMPTS ]]; do
267
- RESOLVE_ATTEMPT=$((RESOLVE_ATTEMPT + 1))
268
- echo "[run_opencode_issue.sh] Conflict resolution attempt $RESOLVE_ATTEMPT/$MAX_ATTEMPTS"
269
-
270
- # Fetch latest main
271
- echo "[run_opencode_issue.sh] Fetching latest main..."
272
- git fetch origin main || true
273
-
274
- # Checkout branch and try to rebase
275
- echo "[run_opencode_issue.sh] Rebasing branch onto main..."
276
- cd "$WORKTREE"
277
- if git rebase origin/main; then
278
- echo "[run_opencode_issue.sh] Rebase successful."
279
-
280
- # Push rebased changes
281
- git push -f origin "$BRANCH" || true
282
-
283
- # Check if conflicts are resolved
284
- MERGE_STATUS=$(gh pr view --repo "$REPO" --head "$BRANCH" --json mergeable,mergeStateStatus --jq '{mergeable, mergeStateStatus}' 2>/dev/null || echo '{"mergeable":"UNKNOWN"}')
285
-
286
- if command -v jq >/dev/null 2>&1; then
287
- IS_CONFLICTING=$(echo "$MERGE_STATUS" | jq -r 'if .mergeable == "CONFLICTING" or .mergeStateStatus == "DIRTY" then "true" else "false" end')
288
- else
289
- IS_CONFLICTING=$(python3 -c 'import json,sys; d=json.load(sys.stdin); print("true" if d.get("mergeable") == "CONFLICTING" or d.get("mergeStateStatus") == "DIRTY" else "false")' <<<"$MERGE_STATUS")
290
- fi
291
-
292
- if [[ "$IS_CONFLICTING" == "false" ]]; then
293
- echo "[run_opencode_issue.sh] Conflicts resolved successfully."
294
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "✅ Autopilot successfully resolved merge conflicts." || true
295
- break
296
- fi
297
- else
298
- # Rebase failed - there are conflicts to resolve
299
- echo "[run_opencode_issue.sh] Rebase encountered conflicts. Attempting resolution with agent..."
300
-
301
- # Abort the rebase to get back to a clean state
302
- git rebase --abort 2>/dev/null || true
303
-
304
- # Try merging main instead (may be easier to resolve)
305
- if git merge origin/main; then
306
- echo "[run_opencode_issue.sh] Merge successful."
307
- git push -f origin "$BRANCH" || true
308
-
309
- # Check if conflicts are resolved
310
- MERGE_STATUS=$(gh pr view --repo "$REPO" --head "$BRANCH" --json mergeable,mergeStateStatus --jq '{mergeable, mergeStateStatus}' 2>/dev/null || echo '{"mergeable":"UNKNOWN"}')
311
-
312
- if command -v jq >/dev/null 2>&1; then
313
- IS_CONFLICTING=$(echo "$MERGE_STATUS" | jq -r 'if .mergeable == "CONFLICTING" or .mergeStateStatus == "DIRTY" then "true" else "false" end')
314
- else
315
- IS_CONFLICTING=$(python3 -c 'import json,sys; d=json.load(sys.stdin); print("true" if d.get("mergeable") == "CONFLICTING" or d.get("mergeStateStatus") == "DIRTY" else "false")' <<<"$MERGE_STATUS")
316
- fi
317
-
318
- if [[ "$IS_CONFLICTING" == "false" ]]; then
319
- echo "[run_opencode_issue.sh] Conflicts resolved successfully."
320
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "✅ Autopilot successfully resolved merge conflicts." || true
321
- break
322
- fi
323
- else
324
- # Merge also has conflicts - abort and try to resolve with agent
325
- git merge --abort 2>/dev/null || true
326
-
327
- echo "[run_opencode_issue.sh] Attempting to resolve conflicts with opencode agent..."
328
-
329
- # Use opencode to resolve conflicts
330
- CONFLICT_PROMPT="This PR has merge conflicts. Please resolve them by examining the conflicted files and making the necessary edits to resolve the conflicts. Follow these steps:
331
- 1. Run 'git status' to see all conflicted files
332
- 2. For each conflicted file, examine the conflict markers (<<<<<<<, =======, >>>>>>>)
333
- 3. Resolve the conflicts by choosing the appropriate code
334
- 4. Stage the resolved files with 'git add <file>'
335
- 5. Commit the resolved files if needed
336
- 6. The goal is to make the branch mergeable with main
337
-
338
- After resolving all conflicts, report the files that were resolved."
339
-
340
- if "$OPENCODE_BIN" run "$CONFLICT_PROMPT"; then
341
- # Check if there are still conflicts
342
- if [[ -z "$(git diff --name-only --diff-filter=U)" ]]; then
343
- echo "[run_opencode_issue.sh] Conflicts resolved by agent."
344
-
345
- # Complete the merge or rebase
346
- git add -A
347
-
348
- # If we were in the middle of a merge, complete it
349
- if [[ -f ".git/MERGE_HEAD" ]]; then
350
- git commit --no-edit || true
351
- fi
352
-
353
- # Push resolved changes
354
- git push -f origin "$BRANCH" || true
355
-
356
- # Check if conflicts are resolved
357
- MERGE_STATUS=$(gh pr view --repo "$REPO" --head "$BRANCH" --json mergeable,mergeStateStatus --jq '{mergeable, mergeStateStatus}' 2>/dev/null || echo '{"mergeable":"UNKNOWN"}')
358
-
359
- if command -v jq >/dev/null 2>&1; then
360
- IS_CONFLICTING=$(echo "$MERGE_STATUS" | jq -r 'if .mergeable == "CONFLICTING" or .mergeStateStatus == "DIRTY" then "true" else "false" end')
361
- else
362
- IS_CONFLICTING=$(python3 -c 'import json,sys; d=json.load(sys.stdin); print("true" if d.get("mergeable") == "CONFLICTING" or d.get("mergeStateStatus") == "DIRTY" else "false")' <<<"$MERGE_STATUS")
363
- fi
364
-
365
- if [[ "$IS_CONFLICTING" == "false" ]]; then
366
- echo "[run_opencode_issue.sh] Conflicts resolved successfully."
367
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "✅ Autopilot successfully resolved merge conflicts." || true
368
- break
369
- fi
370
- fi
371
- fi
372
- fi
373
- fi
374
-
375
- # If we're here, resolution failed and we should try again
376
- echo "[run_opencode_issue.sh] Conflict resolution attempt $RESOLVE_ATTEMPT failed."
377
-
378
- if [[ $RESOLVE_ATTEMPT -ge $MAX_ATTEMPTS ]]; then
379
- echo "[run_opencode_issue.sh] Failed to resolve conflicts after $MAX_ATTEMPTS attempts."
380
-
381
- # Mark issue as blocked
382
- if command -v jq >/dev/null 2>&1; then
383
- LABEL_BLOCKED=$(jq -r '.issueLabels.blocked' < .autopilot/autopilot.json)
384
- else
385
- LABEL_BLOCKED=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json"))["issueLabels"]["blocked"])')
386
- fi
387
-
388
- BLOCKED_MSG="❌ Autopilot failed to resolve merge conflicts after $MAX_ATTEMPTS attempts.\n\nThe PR will remain open but autopilot has stopped attempting to resolve conflicts.\n\nTo resolve manually:\n1. Checkout the branch: \`git checkout $BRANCH\`\n2. Fetch and rebase main: \`git fetch origin main && git rebase origin/main\`\n3. Resolve conflicts as needed\n4. Push resolved changes: \`git push -f origin $BRANCH\`\n\nOnce resolved, autopilot can attempt to merge the PR."
389
-
390
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$BLOCKED_MSG" || true
391
- gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --add-label "$LABEL_BLOCKED" || true
392
-
393
- exit 1
394
- fi
395
-
396
- # Wait a bit before next attempt
397
- sleep 5
398
- done
399
- fi
400
- fi
401
-
402
- # 9. Auto-merge if enabled and user is allowed
403
- if [[ "$AUTO_MERGE" == "true" ]] && [[ -n "$PR_URL" ]]; then
404
- # Get authenticated GitHub user
405
- AUTH_USER=$(gh api user --jq .login)
406
-
407
- # Check if user is in allowed list
408
- USER_ALLOWED=false
409
- if [[ -n "$ALLOWED_USERS" ]]; then
410
- while IFS= read -r user; do
411
- if [[ "$AUTH_USER" == "$user" ]]; then
412
- USER_ALLOWED=true
413
- break
414
- fi
415
- done <<< "$ALLOWED_USERS"
416
- fi
417
-
418
- if [[ "$USER_ALLOWED" == "true" ]]; then
419
- echo "[run_opencode_issue.sh] Auto-merge enabled. Waiting for PR checks to pass..."
420
-
421
- # Comment that we're waiting for checks
422
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "⏳ Waiting for PR checks to complete...
423
-
424
- I'm monitoring the CI checks and will auto-merge once they pass. This may take a few minutes." || true
425
-
426
- # Initialize auto-fix attempt counter
427
- FIX_ATTEMPT=0
428
-
429
- # Poll PR checks until they pass or fail
430
- MAX_POLL_ATTEMPTS=60
431
- POLL_INTERVAL=30
432
- POLL_ATTEMPT=0
433
-
434
- while [[ $POLL_ATTEMPT -lt $MAX_POLL_ATTEMPTS ]]; do
435
- POLL_ATTEMPT=$((POLL_ATTEMPT + 1))
436
-
437
- # Get full PR status for debugging and decision making
438
- PR_STATUS_JSON=$(gh pr view --repo "$REPO" --head "$BRANCH" --json mergeable,mergeStateStatus,statusCheckRollup 2>/dev/null || echo '{"mergeable":"UNKNOWN","mergeStateStatus":"UNKNOWN","statusCheckRollup":null}')
439
-
440
- # Log raw API response for debugging
441
- echo "[run_opencode_issue.sh] Debug: Raw PR status = $PR_STATUS_JSON"
442
-
443
- # Parse mergeStateStatus
444
- if command -v jq >/dev/null 2>&1; then
445
- MERGE_STATE_STATUS=$(echo "$PR_STATUS_JSON" | jq -r '.mergeStateStatus')
446
- else
447
- MERGE_STATE_STATUS=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("mergeStateStatus", "UNKNOWN"))' <<<"$PR_STATUS_JSON")
448
- fi
449
-
450
- # Determine check status based on both statusCheckRollup and mergeStateStatus
451
- # If mergeStateStatus is CLEAN or HAS_HOOKS and statusCheckRollup is null/empty → PASSED
452
- # If mergeStateStatus is UNKNOWN → wait and retry
453
- if [[ "$MERGE_STATE_STATUS" == "UNKNOWN" ]]; then
454
- # Check if there are any CI checks configured
455
- if command -v jq >/dev/null 2>&1; then
456
- SCR_IS_NULL=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup == null')
457
- SCR_LENGTH=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup | length // 0')
458
- else
459
- SCR_VALUE=$(python3 -c 'import json,sys; data=json.load(sys.stdin); print(data.get("statusCheckRollup", "null"))' <<<"$PR_STATUS_JSON")
460
- SCR_IS_NULL=$([[ "$SCR_VALUE" == "null" ]] && echo "true" || echo "false")
461
- SCR_LENGTH=$(python3 -c 'import json,sys; data=json.load(sys.stdin); scr=data.get("statusCheckRollup"); print(len(scr) if scr else 0)' <<<"$PR_STATUS_JSON")
462
- fi
463
-
464
- # If no checks configured, wait at most 20 seconds for them to appear
465
- if [[ "$SCR_IS_NULL" == "true" ]] || [[ "$SCR_LENGTH" == "0" ]]; then
466
- if [[ $POLL_ATTEMPT -lt 1 ]]; then
467
- CHECK_STATUS="PENDING"
468
- echo "[run_opencode_issue.sh] No CI checks configured, waiting up to 20 seconds for checks to appear..."
469
- sleep 20
470
- continue
471
- else
472
- CHECK_STATUS="PASSED"
473
- echo "[run_opencode_issue.sh] No checks found after waiting 20 seconds, continuing to merge..."
474
- fi
475
- else
476
- CHECK_STATUS="PENDING"
477
- echo "[run_opencode_issue.sh] mergeStateStatus is UNKNOWN, waiting for GitHub to calculate..."
478
- fi
479
- elif [[ "$MERGE_STATE_STATUS" == "CLEAN" || "$MERGE_STATE_STATUS" == "HAS_HOOKS" ]]; then
480
- # Check statusCheckRollup - if null/empty, no checks configured, so PASSED
481
- if command -v jq >/dev/null 2>&1; then
482
- # Debug: log the statusCheckRollup value
483
- SCR_VALUE=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup')
484
- echo "[run_opencode_issue.sh] Debug: statusCheckRollup value = $SCR_VALUE"
485
-
486
- # Check if statusCheckRollup is null or empty array
487
- SCR_IS_NULL=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup == null')
488
- SCR_LENGTH=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup | length // 0')
489
- echo "[run_opencode_issue.sh] Debug: statusCheckRollup is_null=$SCR_IS_NULL, length=$SCR_LENGTH"
490
-
491
- if [[ "$SCR_IS_NULL" == "true" ]] || [[ "$SCR_LENGTH" == "0" ]]; then
492
- CHECK_STATUS="PASSED"
493
- echo "[run_opencode_issue.sh] No CI checks configured (statusCheckRollup is null/empty)"
494
- else
495
- # Has checks - determine status
496
- CHECK_STATUS=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup | map(.conclusion) | if any(. == "FAILURE") then "FAILED" elif any(. == "PENDING") or any(. == "QUEUED") then "PENDING" else "PASSED" end')
497
- echo "[run_opencode_issue.sh] CI checks found, status = $CHECK_STATUS"
498
- fi
499
- else
500
- # Python fallback
501
- SCR_VALUE=$(python3 -c 'import json,sys; data=json.load(sys.stdin); print(data.get("statusCheckRollup", "null"))' <<<"$PR_STATUS_JSON")
502
- echo "[run_opencode_issue.sh] Debug: statusCheckRollup value = $SCR_VALUE"
503
-
504
- CHECK_STATUS=$(python3 -c 'import json,sys; data=json.load(sys.stdin); scr=data.get("statusCheckRollup"); print("PASSED" if not scr or len(scr)==0 else "FAILED" if any(c.get("conclusion")=="FAILURE" for c in scr) else "PENDING" if any(c.get("conclusion") in ["PENDING","QUEUED"] for c in scr) else "PASSED")' <<<"$PR_STATUS_JSON")
505
-
506
- if [[ "$SCR_VALUE" == "null" ]] || [[ "$SCR_VALUE" == "[]" ]]; then
507
- echo "[run_opencode_issue.sh] No CI checks configured (statusCheckRollup is null/empty)"
508
- fi
509
- fi
510
- elif [[ "$MERGE_STATE_STATUS" == "BLOCKED" ]]; then
511
- # If mergeStateStatus is BLOCKED, check if it's due to failed checks
512
- echo "[run_opencode_issue.sh] mergeStateStatus is BLOCKED, checking if due to failed checks"
513
- if command -v jq >/dev/null 2>&1; then
514
- # Debug: log the statusCheckRollup value
515
- SCR_VALUE=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup')
516
- echo "[run_opencode_issue.sh] Debug: statusCheckRollup value = $SCR_VALUE"
517
-
518
- # Check if statusCheckRollup is null or empty array
519
- SCR_IS_NULL=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup == null')
520
- SCR_LENGTH=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup | length // 0')
521
- echo "[run_opencode_issue.sh] Debug: statusCheckRollup is_null=$SCR_IS_NULL, length=$SCR_LENGTH"
522
-
523
- if [[ "$SCR_IS_NULL" == "true" ]] || [[ "$SCR_LENGTH" == "0" ]]; then
524
- CHECK_STATUS="FAILED"
525
- echo "[run_opencode_issue.sh] No CI checks but mergeStateStatus is BLOCKED - other blocking issue"
526
- else
527
- # Has checks - determine status
528
- CHECK_STATUS=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup | map(.conclusion) | if any(. == "FAILURE") then "FAILED" elif any(. == "PENDING") or any(. == "QUEUED") then "PENDING" else "PASSED" end')
529
- echo "[run_opencode_issue.sh] CI checks found, status = $CHECK_STATUS"
530
- fi
531
- else
532
- # Python fallback
533
- SCR_VALUE=$(python3 -c 'import json,sys; data=json.load(sys.stdin); print(data.get("statusCheckRollup", "null"))' <<<"$PR_STATUS_JSON")
534
- echo "[run_opencode_issue.sh] Debug: statusCheckRollup value = $SCR_VALUE"
535
-
536
- CHECK_STATUS=$(python3 -c 'import json,sys; data=json.load(sys.stdin); scr=data.get("statusCheckRollup"); print("PASSED" if not scr or len(scr)==0 else "FAILED" if any(c.get("conclusion")=="FAILURE" for c in scr) else "PENDING" if any(c.get("conclusion") in ["PENDING","QUEUED"] for c in scr) else "PASSED")' <<<"$PR_STATUS_JSON")
537
-
538
- if [[ "$SCR_VALUE" == "null" ]] || [[ "$SCR_VALUE" == "[]" ]]; then
539
- echo "[run_opencode_issue.sh] No CI checks but mergeStateStatus is BLOCKED - other blocking issue"
540
- fi
541
- fi
542
- else
543
- # Other states (DRAFT, DIRTY, BEHIND) - mark as FAILED to exit
544
- CHECK_STATUS="FAILED"
545
- fi
546
-
547
- echo "[run_opencode_issue.sh] Poll attempt $POLL_ATTEMPT/$MAX_POLL_ATTEMPTS: Check status = $CHECK_STATUS, mergeStateStatus = $MERGE_STATE_STATUS"
548
-
549
- if [[ "$CHECK_STATUS" == "PASSED" ]]; then
550
- echo "[run_opencode_issue.sh] All checks passed. Proceeding with merge..."
551
- break
552
- elif [[ "$CHECK_STATUS" == "FAILED" ]]; then
553
- echo "[run_opencode_issue.sh] PR checks failed."
554
-
555
- # Check if auto-fix is enabled
556
- if [[ "$AUTO_FIX_CHECKS" == "true" ]] && [[ $FIX_ATTEMPT -lt $AUTO_FIX_CHECKS_MAX_ATTEMPTS ]]; then
557
- FIX_ATTEMPT=$((FIX_ATTEMPT + 1))
558
- echo "[run_opencode_issue.sh] Auto-fix enabled. Attempting to fix failing checks ($FIX_ATTEMPT/$AUTO_FIX_CHECKS_MAX_ATTEMPTS)..."
559
-
560
- # Fetch failed check details and logs
561
- CHECK_RUNS_JSON=$(gh api "repos/$REPO/commits/$(git rev-parse HEAD)/check-runs" --jq '.check_runs[] | select(.conclusion == "FAILURE") | {name: .name, conclusion: .conclusion, details_url: .details_url, output: {title: .output.title, summary: .output.summary, text: .output.text}}' 2>/dev/null || echo "[]")
562
-
563
- # Build failure context
564
- FAILURE_CONTEXT="The following CI checks failed:\n"
565
-
566
- # Add failure details to context
567
- if [[ -n "$CHECK_RUNS_JSON" ]]; then
568
- FAILURE_CONTEXT+="$CHECK_RUNS_JSON\n"
569
- fi
570
-
571
- # Generate repair prompt
572
- REPAIR_PROMPT="The PR checks have failed. Please analyze the CI failures and fix the issues.\n\n$FAILURE_CONTEXT\n\nWork rules:\n- Examine the failed checks and identify the root cause\n- Make the necessary code changes to fix the failures\n- Commit with message: \"autopilot: fix CI check failures (attempt $FIX_ATTEMPT/$AUTO_FIX_CHECKS_MAX_ATTEMPTS)\"\n- Push your changes to the branch $BRANCH\n- Focus only on fixing the CI failures, do not make unrelated changes"
573
-
574
- echo "[run_opencode_issue.sh] Running opencode agent to fix CI failures..."
575
-
576
- # Run opencode to fix the issue
577
- cd "$WORKTREE"
578
- if "$OPENCODE_BIN" run "$REPAIR_PROMPT"; then
579
- # Commit any changes
580
- if [[ -n "$(git status --porcelain)" ]]; then
581
- git add -A
582
- git commit -m "autopilot: fix CI check failures (attempt $FIX_ATTEMPT/$AUTO_FIX_CHECKS_MAX_ATTEMPTS)"
583
- echo "[run_opencode_issue.sh] Committed fixes for CI failures."
584
-
585
- # Push changes
586
- git push origin "$BRANCH"
587
- echo "[run_opencode_issue.sh] Pushed fixes for CI failures."
588
-
589
- # Reset poll counter to re-check CI status
590
- POLL_ATTEMPT=0
591
-
592
- # Wait a moment for checks to start
593
- sleep 10
594
-
595
- # Continue polling
596
- continue
597
- else
598
- echo "[run_opencode_issue.sh] No changes made by opencode agent."
599
- fi
600
- else
601
- echo "[run_opencode_issue.sh] Opencode agent failed to fix CI issues."
602
- fi
603
-
604
- # If we're here, auto-fix failed or made no changes
605
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "⚠️ Autopilot attempted to fix CI failures (attempt $FIX_ATTEMPT/$AUTO_FIX_CHECKS_MAX_ATTEMPTS) but was unable to resolve all issues." || true
606
- fi
607
-
608
- # If auto-fix is disabled or max attempts reached, fail
609
- if [[ "$FIX_ATTEMPT" -ge "$AUTO_FIX_CHECKS_MAX_ATTEMPTS" ]]; then
610
- FAILED_MSG="❌ Autopilot cannot auto-merge PR: checks failed after $AUTO_FIX_CHECKS_MAX_ATTEMPTS auto-fix attempts.\n\nPR will remain open for review."
611
- else
612
- FAILED_MSG="❌ Autopilot cannot auto-merge PR: checks failed (auto-fix disabled).\n\nPR will remain open for review."
613
- fi
614
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$FAILED_MSG" || true
615
- exit 1
616
- fi
617
-
618
- # Wait before next poll
619
- if [[ $POLL_ATTEMPT -lt $MAX_POLL_ATTEMPTS ]]; then
620
- echo "[run_opencode_issue.sh] Waiting ${POLL_INTERVAL}s before next check..."
621
- sleep "$POLL_INTERVAL"
622
- fi
623
- done
624
-
625
- # After polling loop: merge if checks passed
626
- if [[ "$CHECK_STATUS" == "PASSED" ]]; then
627
- echo "[run_opencode_issue.sh] Merging PR using method: $MERGE_METHOD"
628
-
629
- # Build merge command based on method
630
- MERGE_CMD="gh pr merge $PR_URL --delete-branch"
631
- case "$MERGE_METHOD" in
632
- squash)
633
- MERGE_CMD="$MERGE_CMD --squash"
634
- ;;
635
- merge)
636
- MERGE_CMD="$MERGE_CMD --merge"
637
- ;;
638
- rebase)
639
- MERGE_CMD="$MERGE_CMD --rebase"
640
- ;;
641
- *)
642
- echo "[run_opencode_issue.sh] Unknown merge method '$MERGE_METHOD', defaulting to squash"
643
- MERGE_CMD="$MERGE_CMD --squash"
644
- ;;
645
- esac
646
-
647
- # Attempt merge
648
- if eval "$MERGE_CMD"; then
649
- echo "[run_opencode_issue.sh] PR merged successfully!"
650
-
651
- # Update issue with success message and mark as done
652
- SUCCESS_MSG="✅ Autopilot successfully merged PR #${PR_URL##*/} using $MERGE_METHOD method.\n\nThe fix has been merged to the main branch."
653
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$SUCCESS_MSG" || true
654
-
655
- # Get done label and mark issue as done
656
- if command -v jq >/dev/null 2>&1; then
657
- LABEL_DONE=$(jq -r '.issueLabels.done' < .autopilot/autopilot.json)
658
- LABEL_IN_PROGRESS=$(jq -r '.issueLabels.inProgress' < .autopilot/autopilot.json)
659
- else
660
- LABEL_DONE=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json"))["issueLabels"]["done"])')
661
- LABEL_IN_PROGRESS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json"))["issueLabels"]["inProgress"])')
662
- fi
663
-
664
- gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --add-label "$LABEL_DONE" --remove-label "$LABEL_IN_PROGRESS" || true
665
- gh issue close "$ISSUE_NUMBER" --repo "$REPO" || true
666
- else
667
- echo "[run_opencode_issue.sh] Failed to merge PR"
668
- MERGE_FAIL_MSG="❌ Autopilot failed to merge PR automatically.\n\nThe PR is ready (checks passed) but the merge operation failed. You may need to merge manually."
669
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$MERGE_FAIL_MSG" || true
670
- fi
671
- elif [[ $POLL_ATTEMPT -ge $MAX_POLL_ATTEMPTS ]]; then
672
- echo "[run_opencode_issue.sh] Timed out waiting for checks to pass"
673
- TIMEOUT_MSG="⏱️ Autopilot timed out waiting for PR checks to complete.\n\nThe PR will remain open for manual review."
674
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$TIMEOUT_MSG" || true
675
- fi
676
- else
677
- BLOCKED_MSG="❌ Autopilot cannot auto-merge PR: authenticated user '$AUTH_USER' is not in the allowedMergeUsers list."
678
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$BLOCKED_MSG" || true
679
-
680
- # Mark issue as blocked
681
- if command -v jq >/dev/null 2>&1; then
682
- LABEL_BLOCKED=$(jq -r '.issueLabels.blocked' < .autopilot/autopilot.json)
683
- else
684
- LABEL_BLOCKED=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json"))["issueLabels"]["blocked"])')
685
- fi
686
- gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --add-label "$LABEL_BLOCKED" || true
687
- fi
688
- fi
689
-
690
- echo "[run_opencode_issue.sh] Done: $PR_URL"