autopilot-code 0.0.28 โ†’ 0.2.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.
@@ -30,5 +30,7 @@
30
30
  "autoResolveConflicts": true,
31
31
  "conflictResolutionMaxAttempts": 3,
32
32
  "autoFixChecks": true,
33
- "autoFixChecksMaxAttempts": 3
33
+ "autoFixChecksMaxAttempts": 3,
34
+ "autoUpdate": true,
35
+ "enablePlanningStep": true
34
36
  }
package/dist/cli.js CHANGED
@@ -347,7 +347,8 @@ async function initCommand() {
347
347
  priorityLabels: ["p0", "p1", "p2"],
348
348
  maxParallel: 1,
349
349
  branchPrefix: "autopilot/",
350
- allowedBaseBranch: "main"
350
+ allowedBaseBranch: "main",
351
+ autoUpdate: true
351
352
  };
352
353
  }
353
354
  template.repo = repoName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autopilot-code",
3
- "version": "0.0.28",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "Repo-issueโ€“driven autopilot runner",
6
6
  "license": "MIT",
@@ -84,6 +84,7 @@ class RepoConfig:
84
84
  conflict_resolution_max_attempts: int
85
85
  auto_fix_checks: bool
86
86
  auto_fix_checks_max_attempts: int
87
+ auto_update: bool
87
88
 
88
89
 
89
90
  def load_config(repo_root: Path) -> RepoConfig | None:
@@ -132,6 +133,7 @@ def load_config(repo_root: Path) -> RepoConfig | None:
132
133
  conflict_resolution_max_attempts=conflict_resolution_max_attempts,
133
134
  auto_fix_checks=auto_fix_checks,
134
135
  auto_fix_checks_max_attempts=auto_fix_checks_max_attempts,
136
+ auto_update=data.get("autoUpdate", False),
135
137
  )
136
138
 
137
139
 
@@ -245,6 +247,68 @@ def write_state(repo_root: Path, state: dict[str, Any]) -> None:
245
247
  p.write_text(json.dumps(state, indent=2, sort_keys=True) + "\n", encoding="utf-8")
246
248
 
247
249
 
250
+ def load_global_state() -> dict[str, Any]:
251
+ p = GLOBAL_STATE_FILE
252
+ if not p.exists():
253
+ GLOBAL_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
254
+ return {}
255
+ try:
256
+ return json.loads(p.read_text(encoding="utf-8"))
257
+ except Exception:
258
+ return {}
259
+
260
+
261
+ def write_global_state(state: dict[str, Any]) -> None:
262
+ GLOBAL_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
263
+ GLOBAL_STATE_FILE.write_text(json.dumps(state, indent=2, sort_keys=True) + "\n", encoding="utf-8")
264
+
265
+
266
+ def flag_autopilot_needs_update() -> None:
267
+ """Flag that autopilot needs to update itself."""
268
+ state = load_global_state()
269
+ state["needsUpdate"] = True
270
+ write_global_state(state)
271
+
272
+
273
+ def check_autopilot_needs_update() -> bool:
274
+ """Check if autopilot is flagged for update."""
275
+ state = load_global_state()
276
+ return state.get("needsUpdate", False)
277
+
278
+
279
+ def clear_autopilot_update_flag() -> None:
280
+ """Clear the update flag."""
281
+ state = load_global_state()
282
+ if "needsUpdate" in state:
283
+ del state["needsUpdate"]
284
+ write_global_state(state)
285
+
286
+
287
+ def is_autopilot_repo(repo: str) -> bool:
288
+ """Check if the repo is the autopilot repository."""
289
+ return repo in ("bakkensoftware/autopilot", "anomalyco/autopilot")
290
+
291
+
292
+ def update_autopilot() -> bool:
293
+ """Update autopilot globally and restart service."""
294
+ print("[autopilot] Updating autopilot-code...", flush=True)
295
+
296
+ try:
297
+ sh(["npm", "install", "-g", "autopilot-code"], check=True)
298
+ print("[autopilot] Successfully installed updated autopilot-code", flush=True)
299
+ except Exception as e:
300
+ print(f"[autopilot] Failed to install autopilot-code: {e}", flush=True)
301
+ return False
302
+
303
+ try:
304
+ sh(["systemctl", "--user", "restart", "autopilot"], check=True)
305
+ print("[autopilot] Restarted autopilot service", flush=True)
306
+ return True
307
+ except Exception as e:
308
+ print(f"[autopilot] Failed to restart service: {e}", flush=True)
309
+ return False
310
+
311
+
248
312
  def touch_heartbeat(cfg: RepoConfig, issue_number: int) -> None:
249
313
  """Update durable local heartbeat state for the repo.
250
314
 
@@ -585,6 +649,11 @@ def try_merge_pr(cfg: RepoConfig, issue_number: int, pr: dict[str, Any]) -> bool
585
649
  "--add-label", cfg.label_done, "--remove-label", cfg.label_in_progress])
586
650
  sh(["gh", "issue", "close", str(issue_number), "--repo", cfg.repo])
587
651
 
652
+ # Flag autopilot for update if we merged our own repo
653
+ if is_autopilot_repo(cfg.repo):
654
+ print(f"[{cfg.repo}] Merged PR to autopilot repo, flagging for self-update", flush=True)
655
+ flag_autopilot_needs_update()
656
+
588
657
  return True
589
658
  except Exception as e:
590
659
  print(f"[{cfg.repo}] Failed to merge PR #{pr_number}: {e}", flush=True)
@@ -723,17 +792,24 @@ def run_cycle(
723
792
  continue
724
793
 
725
794
  for issue in issues[:max_to_claim]:
726
- msg = (
727
- f"Autopilot claimed this issue at {time.strftime('%Y-%m-%d %H:%M:%S %Z')}.\n\n"
728
- "Next: implement fix and open PR.\n\n"
729
- )
730
795
  print(f"[{cfg.repo}] next issue: #{issue['number']} {issue['title']}", flush=True)
731
796
  if dry_run:
732
797
  claimed_count += 1
733
798
  continue
734
- 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)
735
807
  claimed_count += 1
736
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
+
737
813
  # If agent==opencode, delegate to bash script
738
814
  if cfg.agent == "opencode":
739
815
  # Script is in the same directory as this Python file
@@ -747,6 +823,24 @@ def run_cycle(
747
823
  ]
748
824
  )
749
825
 
826
+ # Check if autopilot needs to update
827
+ if not dry_run and check_autopilot_needs_update():
828
+ print("[autopilot] Detected need for self-update", flush=True)
829
+
830
+ # Check if any repo has auto_update enabled
831
+ any_auto_update = any(cfg.auto_update for cfg in all_configs)
832
+
833
+ if any_auto_update:
834
+ print("[autopilot] Performing self-update...", flush=True)
835
+ if update_autopilot():
836
+ clear_autopilot_update_flag()
837
+ print("[autopilot] Self-update completed, service restarted", flush=True)
838
+ else:
839
+ print("[autopilot] Self-update failed, flag preserved for next cycle", flush=True)
840
+ else:
841
+ print("[autopilot] auto_update is disabled for all repos, skipping self-update", flush=True)
842
+ clear_autopilot_update_flag()
843
+
750
844
  return claimed_count
751
845
 
752
846
 
@@ -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,96 @@ 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")
144
210
  fi
145
211
 
146
212
  # 8. Comment on issue with PR URL (best-effort)
147
213
  if [[ -n "$PR_URL" ]]; then
148
- gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "Autopilot opened PR: $PR_URL" || true
214
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "๐ŸŽ‰ PR created: $PR_URL
215
+
216
+ Autopilot has completed the initial implementation. The PR is now open and ready for review." || true
149
217
  fi
150
218
 
151
219
  # 8.5. Handle merge conflicts if auto-resolve is enabled
@@ -322,6 +390,11 @@ if [[ "$AUTO_MERGE" == "true" ]] && [[ -n "$PR_URL" ]]; then
322
390
  if [[ "$USER_ALLOWED" == "true" ]]; then
323
391
  echo "[run_opencode_issue.sh] Auto-merge enabled. Waiting for PR checks to pass..."
324
392
 
393
+ # Comment that we're waiting for checks
394
+ gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "โณ Waiting for PR checks to complete...
395
+
396
+ I'm monitoring the CI checks and will auto-merge once they pass. This may take a few minutes." || true
397
+
325
398
  # Initialize auto-fix attempt counter
326
399
  FIX_ATTEMPT=0
327
400
 
@@ -18,5 +18,7 @@
18
18
  "autoResolveConflicts": true,
19
19
  "conflictResolutionMaxAttempts": 3,
20
20
  "autoFixChecks": true,
21
- "autoFixChecksMaxAttempts": 3
21
+ "autoFixChecksMaxAttempts": 3,
22
+ "autoUpdate": true,
23
+ "enablePlanningStep": true
22
24
  }