autopilot-code 0.1.0 β†’ 0.2.1

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.
@@ -31,5 +31,6 @@
31
31
  "conflictResolutionMaxAttempts": 3,
32
32
  "autoFixChecks": true,
33
33
  "autoFixChecksMaxAttempts": 3,
34
- "autoUpdate": true
34
+ "autoUpdate": true,
35
+ "enablePlanningStep": true
35
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autopilot-code",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "Repo-issue–driven autopilot runner",
6
6
  "license": "MIT",
@@ -792,17 +792,24 @@ def run_cycle(
792
792
  continue
793
793
 
794
794
  for issue in issues[:max_to_claim]:
795
- msg = (
796
- f"Autopilot claimed this issue at {time.strftime('%Y-%m-%d %H:%M:%S %Z')}.\n\n"
797
- "Next: implement fix and open PR.\n\n"
798
- )
799
795
  print(f"[{cfg.repo}] next issue: #{issue['number']} {issue['title']}", flush=True)
800
796
  if dry_run:
801
797
  claimed_count += 1
802
798
  continue
803
- claim_issue(cfg, issue, msg)
799
+
800
+ # Post initial claim comment
801
+ claim_msg = (
802
+ f"Autopilot claimed this issue at {time.strftime('%Y-%m-%d %H:%M:%S %Z')}.\n\n"
803
+ f"I'm now analyzing the issue and will begin implementation shortly.\n\n"
804
+ f"**Issue:** {issue['title']}"
805
+ )
806
+ claim_issue(cfg, issue, claim_msg)
804
807
  claimed_count += 1
805
808
 
809
+ # Post comment indicating agent is about to start
810
+ start_msg = f"πŸš€ Autopilot is now starting work on issue #{issue['number']}.\n\nI'll post regular progress updates as I work through the implementation."
811
+ sh(["gh", "issue", "comment", str(issue["number"]), "--repo", cfg.repo, "--body", start_msg])
812
+
806
813
  # If agent==opencode, delegate to bash script
807
814
  if cfg.agent == "opencode":
808
815
  # Script is in the same directory as this Python file
@@ -22,6 +22,7 @@ if command -v jq >/dev/null 2>&1; then
22
22
  AUTO_FIX_CHECKS_MAX_ATTEMPTS=$(jq -r '.autoFixChecksMaxAttempts // 3' < .autopilot/autopilot.json)
23
23
  ALLOWED_USERS=$(jq -r '.allowedMergeUsers[]' < .autopilot/autopilot.json 2>/dev/null || true)
24
24
  AGENT_PATH=$(jq -r '.agentPath // ""' < .autopilot/autopilot.json)
25
+ ENABLE_PLANNING_STEP=$(jq -r '.enablePlanningStep // true' < .autopilot/autopilot.json)
25
26
  else
26
27
  REPO=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json"))["repo"])')
27
28
  AUTO_MERGE=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoMerge", True))')
@@ -32,6 +33,7 @@ else
32
33
  AUTO_FIX_CHECKS_MAX_ATTEMPTS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoFixChecksMaxAttempts", 3))')
33
34
  ALLOWED_USERS=$(python3 -c 'import json,sys; users=json.load(open(".autopilot/autopilot.json")).get("allowedMergeUsers", []); print("\n".join(users))' 2>/dev/null || true)
34
35
  AGENT_PATH=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("agentPath", ""))' 2>/dev/null || true)
36
+ ENABLE_PLANNING_STEP=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("enablePlanningStep", True))' 2>/dev/null || echo "true")
35
37
  fi
36
38
 
37
39
  # Find opencode binary - check config, PATH, then common locations
@@ -122,30 +124,99 @@ Work rules:
122
124
  - Commit with message: \"autopilot: work for issue #$ISSUE_NUMBER\".
123
125
  - Push your changes to the remote branch $BRANCH.
124
126
  - If the issue is a simple file-addition, just do it directly (no extra refactors)."
127
+
128
+ # 3.5. Planning step - analyze and plan before implementation
129
+ if [[ "$ENABLE_PLANNING_STEP" == "true" ]]; then
130
+ echo "[run_opencode_issue.sh] Running planning/analysis step..."
131
+
132
+ 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.
133
+
134
+ Issue #$ISSUE_NUMBER: $ISSUE_TITLE
135
+
136
+ $ISSUE_BODY
137
+
138
+ Your task:
139
+ 1. Analyze the issue requirements thoroughly
140
+ 2. Explore the codebase to understand the relevant components
141
+ 3. Identify the files that need to be modified or created
142
+ 4. Outline the step-by-step approach for implementing this change
143
+ 5. Note any potential issues, dependencies, or risks
144
+ 6. List any assumptions you're making
145
+
146
+ Provide your analysis and plan in a clear, structured format. Focus on WHAT needs to be done and HOW you'll approach it."
147
+
148
+ # Run opencode to generate the plan
149
+ cd "$WORKTREE"
150
+ PLAN_OUTPUT=$("$OPENCODE_BIN" run "$PLANNING_PROMPT" 2>&1 || echo "Planning failed")
151
+
152
+ # Post the plan as a comment on the issue
153
+ PLAN_COMMENT="## πŸ“‹ Implementation Plan
154
+
155
+ I've analyzed the issue and codebase. Here's my planned approach:
156
+
157
+ \`\`\`
158
+ $PLAN_OUTPUT
159
+ \`\`\`
160
+
161
+ 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."
162
+
163
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$PLAN_COMMENT" || true
164
+ echo "[run_opencode_issue.sh] Planning step completed and posted to issue."
165
+ fi
166
+
125
167
  # 4. Run opencode inside worktree
126
168
  cd "$WORKTREE"
127
169
  "$OPENCODE_BIN" run "$PROMPT"
128
170
 
171
+ # 4.5. Comment that implementation is complete
172
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "βœ… Implementation complete.
173
+
174
+ I've finished implementing the solution. Now committing changes and creating PR..." || true
175
+
129
176
  # 5. Commit any changes OpenCode made
130
177
  if [[ -n "$(git status --porcelain)" ]]; then
131
178
  git add -A
132
179
  git commit -m "autopilot: work for issue #$ISSUE_NUMBER"
180
+
181
+ # Comment with commit info
182
+ COMMIT_SHA=$(git rev-parse --short HEAD)
183
+ CHANGES_COUNT=$(git diff-tree --no-commit-id --name-only -r HEAD | wc -l | xargs)
184
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "πŸ“ Changes committed.
185
+
186
+ - Commit: \`$COMMIT_SHA\`
187
+ - Files changed: $CHANGES_COUNT
188
+ - Branch: \`$BRANCH\`
189
+
190
+ Next step: pushing to remote..." || true
191
+ else
192
+ 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
133
193
  fi
134
194
 
135
195
  # 6. Ensure branch is pushed (no-op if already up to date)
136
196
  git push -u origin "$BRANCH" || true
137
197
 
198
+ # 6.5. Comment after push
199
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "πŸ“€ Changes pushed to remote branch \`$BRANCH\`.
200
+
201
+ Now creating pull request..." || true
202
+
138
203
  # 7. Create PR if one doesn't already exist
139
204
  PR_URL=""
140
205
  if gh pr view --repo "$REPO" --head "$BRANCH" --json url --jq .url >/dev/null 2>&1; then
141
206
  PR_URL=$(gh pr view --repo "$REPO" --head "$BRANCH" --json url --jq .url)
207
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "πŸ“‹ PR already exists: $PR_URL" || true
142
208
  else
143
209
  PR_URL=$(gh pr create --repo "$REPO" --title "Autopilot: Issue #$ISSUE_NUMBER" --body "Closes #$ISSUE_NUMBER" --base main --head "$BRANCH")
210
+
211
+ # Add short delay after PR creation to allow GitHub to calculate merge state
212
+ sleep 10
144
213
  fi
145
214
 
146
215
  # 8. Comment on issue with PR URL (best-effort)
147
216
  if [[ -n "$PR_URL" ]]; then
148
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "Autopilot opened PR: $PR_URL" || true
217
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "πŸŽ‰ PR created: $PR_URL
218
+
219
+ Autopilot has completed the initial implementation. The PR is now open and ready for review." || true
149
220
  fi
150
221
 
151
222
  # 8.5. Handle merge conflicts if auto-resolve is enabled
@@ -322,6 +393,11 @@ if [[ "$AUTO_MERGE" == "true" ]] && [[ -n "$PR_URL" ]]; then
322
393
  if [[ "$USER_ALLOWED" == "true" ]]; then
323
394
  echo "[run_opencode_issue.sh] Auto-merge enabled. Waiting for PR checks to pass..."
324
395
 
396
+ # Comment that we're waiting for checks
397
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "⏳ Waiting for PR checks to complete...
398
+
399
+ I'm monitoring the CI checks and will auto-merge once they pass. This may take a few minutes." || true
400
+
325
401
  # Initialize auto-fix attempt counter
326
402
  FIX_ATTEMPT=0
327
403
 
@@ -333,10 +409,45 @@ if [[ "$AUTO_MERGE" == "true" ]] && [[ -n "$PR_URL" ]]; then
333
409
  while [[ $POLL_ATTEMPT -lt $MAX_POLL_ATTEMPTS ]]; do
334
410
  POLL_ATTEMPT=$((POLL_ATTEMPT + 1))
335
411
 
336
- # Get PR check status (handle null/empty statusCheckRollup as PASSED - no checks configured)
337
- CHECK_STATUS=$(gh pr view --repo "$REPO" --head "$BRANCH" --json mergeable,mergeStateStatus,statusCheckRollup --jq '.statusCheckRollup | if . == null or length == 0 then "PASSED" elif map(.conclusion) | any(. == "FAILURE") then "FAILED" elif map(.conclusion) | any(. == "PENDING") or any(. == "QUEUED") then "PENDING" else "PASSED" end' 2>/dev/null || echo "PENDING")
412
+ # Get full PR status for debugging and decision making
413
+ PR_STATUS_JSON=$(gh pr view --repo "$REPO" --head "$BRANCH" --json mergeable,mergeStateStatus,statusCheckRollup 2>/dev/null || echo '{"mergeable":"UNKNOWN","mergeStateStatus":"UNKNOWN","statusCheckRollup":null}')
414
+
415
+ # Log raw API response for debugging
416
+ echo "[run_opencode_issue.sh] Debug: Raw PR status = $PR_STATUS_JSON"
417
+
418
+ # Parse mergeStateStatus
419
+ if command -v jq >/dev/null 2>&1; then
420
+ MERGE_STATE_STATUS=$(echo "$PR_STATUS_JSON" | jq -r '.mergeStateStatus')
421
+ else
422
+ MERGE_STATE_STATUS=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("mergeStateStatus", "UNKNOWN"))' <<<"$PR_STATUS_JSON")
423
+ fi
424
+
425
+ # Determine check status based on both statusCheckRollup and mergeStateStatus
426
+ # If mergeStateStatus is CLEAN or HAS_HOOKS and statusCheckRollup is null/empty β†’ PASSED
427
+ # If mergeStateStatus is UNKNOWN β†’ wait and retry
428
+ if [[ "$MERGE_STATE_STATUS" == "UNKNOWN" ]]; then
429
+ CHECK_STATUS="PENDING"
430
+ echo "[run_opencode_issue.sh] mergeStateStatus is UNKNOWN, waiting for GitHub to calculate..."
431
+ elif [[ "$MERGE_STATE_STATUS" == "CLEAN" || "$MERGE_STATE_STATUS" == "HAS_HOOKS" ]]; then
432
+ # Check statusCheckRollup - if null/empty, no checks configured, so PASSED
433
+ if command -v jq >/dev/null 2>&1; then
434
+ CHECK_STATUS=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup | if . == null or length == 0 then "PASSED" elif map(.conclusion) | any(. == "FAILURE") then "FAILED" elif map(.conclusion) | any(. == "PENDING") or any(. == "QUEUED") then "PENDING" else "PASSED" end')
435
+ else
436
+ 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")
437
+ fi
438
+ elif [[ "$MERGE_STATE_STATUS" == "BLOCKED" ]]; then
439
+ # If mergeStateStatus is BLOCKED, check if it's due to failed checks
440
+ if command -v jq >/dev/null 2>&1; then
441
+ CHECK_STATUS=$(echo "$PR_STATUS_JSON" | jq -r '.statusCheckRollup | if . == null or length == 0 then "PASSED" elif map(.conclusion) | any(. == "FAILURE") then "FAILED" elif map(.conclusion) | any(. == "PENDING") or any(. == "QUEUED") then "PENDING" else "PASSED" end')
442
+ else
443
+ 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")
444
+ fi
445
+ else
446
+ # Other states (DRAFT, DIRTY, BEHIND) - mark as FAILED to exit
447
+ CHECK_STATUS="FAILED"
448
+ fi
338
449
 
339
- echo "[run_opencode_issue.sh] Poll attempt $POLL_ATTEMPT/$MAX_POLL_ATTEMPTS: Check status = $CHECK_STATUS"
450
+ echo "[run_opencode_issue.sh] Poll attempt $POLL_ATTEMPT/$MAX_POLL_ATTEMPTS: Check status = $CHECK_STATUS, mergeStateStatus = $MERGE_STATE_STATUS"
340
451
 
341
452
  if [[ "$CHECK_STATUS" == "PASSED" ]]; then
342
453
  echo "[run_opencode_issue.sh] All checks passed. Proceeding with merge..."
@@ -19,5 +19,6 @@
19
19
  "conflictResolutionMaxAttempts": 3,
20
20
  "autoFixChecks": true,
21
21
  "autoFixChecksMaxAttempts": 3,
22
- "autoUpdate": true
22
+ "autoUpdate": true,
23
+ "enablePlanningStep": true
23
24
  }