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.
package/dist/cli.js
CHANGED
package/package.json
CHANGED
package/scripts/run_autopilot.py
CHANGED
|
@@ -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
|
-
|
|
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 "
|
|
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
|
|
package/templates/autopilot.json
CHANGED