autopilot-code 0.8.0 → 0.10.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/README.md +51 -2
- package/dist/cli.js +59 -0
- package/package.json +1 -1
- package/scripts/run_autopilot.py +67 -11
- package/templates/autopilot.json +17 -5
package/README.md
CHANGED
|
@@ -15,6 +15,53 @@ A repo is considered autopilot-enabled when it contains:
|
|
|
15
15
|
|
|
16
16
|
- `.autopilot/autopilot.json` with `enabled: true`
|
|
17
17
|
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Choosing an Agent
|
|
21
|
+
|
|
22
|
+
Autopilot supports multiple agent types:
|
|
23
|
+
|
|
24
|
+
**opencode** (recommended): Full-featured agent with extensive code understanding
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"agent": "opencode"
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**claude**: Fast, targeted code changes
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"agent": "claude"
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Enabling the New Runner
|
|
39
|
+
|
|
40
|
+
The new runner provides enhanced progress tracking and session continuity:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"useNewRunner": true,
|
|
45
|
+
"enablePlanningStep": true
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Understanding Step Labels
|
|
50
|
+
|
|
51
|
+
When using the new runner, issues progress through these labels:
|
|
52
|
+
|
|
53
|
+
- `autopilot:planning` - Creating implementation plan
|
|
54
|
+
- `autopilot:implementing` - Writing code
|
|
55
|
+
- `autopilot:pr-created` - Pull request created
|
|
56
|
+
- `autopilot:waiting-checks` - Waiting for CI
|
|
57
|
+
- `autopilot:fixing-checks` - Fixing failing CI
|
|
58
|
+
- `autopilot:merging` - Merging PR
|
|
59
|
+
|
|
60
|
+
To create these labels in your repository:
|
|
61
|
+
```bash
|
|
62
|
+
autopilot setup-labels --repo owner/repo
|
|
63
|
+
```
|
|
64
|
+
|
|
18
65
|
Example:
|
|
19
66
|
|
|
20
67
|
```json
|
|
@@ -47,7 +94,8 @@ Example:
|
|
|
47
94
|
|
|
48
95
|
Notes:
|
|
49
96
|
- `repo` must be the GitHub `owner/name`.
|
|
50
|
-
- `agent` (optional, default `"
|
|
97
|
+
- `agent` (optional, default `"opencode"`): set to `"opencode"` or `"claude"` to choose which coding agent to use.
|
|
98
|
+
- `useNewRunner` (optional, default `false`): enable the new runner with step labels and session continuity.
|
|
51
99
|
- `autoMerge` (optional, default `true`): if `true`, autopilot will automatically merge PRs after checks pass.
|
|
52
100
|
- `mergeMethod` (optional, default `"squash"`): merge strategy to use. Options: `"squash"`, `"merge"`, or `"rebase"`.
|
|
53
101
|
- `allowedMergeUsers` (required when `autoMerge=true`): list of GitHub usernames allowed to auto-merge. The runner verifies the authenticated GitHub user is in this list before merging.
|
|
@@ -57,7 +105,8 @@ Notes:
|
|
|
57
105
|
- `conflictResolutionMaxAttempts` (optional, default `3`): maximum number of attempts to resolve merge conflicts.
|
|
58
106
|
- `autoFixChecks` (optional, default `true`): if `true`, autopilot will attempt to automatically fix failing CI checks.
|
|
59
107
|
- `autoFixChecksMaxAttempts` (optional, default `3`): maximum number of attempts to fix failing checks.
|
|
60
|
-
- `
|
|
108
|
+
- `enablePlanningStep` (optional, default `true`): if `true`, add an explicit planning phase before implementation (requires `useNewRunner: true`).
|
|
109
|
+
- `agentPath` (optional): custom path to agent executable (defaults to searching PATH).
|
|
61
110
|
|
|
62
111
|
## Workflow (labels)
|
|
63
112
|
Autopilot uses labels as a kanban state machine:
|
package/dist/cli.js
CHANGED
|
@@ -548,4 +548,63 @@ program
|
|
|
548
548
|
.action(() => {
|
|
549
549
|
logsSystemdService();
|
|
550
550
|
});
|
|
551
|
+
program
|
|
552
|
+
.command("setup-labels")
|
|
553
|
+
.description("Create required GitHub labels for autopilot workflow")
|
|
554
|
+
.option("--repo <owner/repo>", "Repository in owner/repo format")
|
|
555
|
+
.action((opts) => {
|
|
556
|
+
if (!opts.repo) {
|
|
557
|
+
const cwd = process.cwd();
|
|
558
|
+
const repoName = getRepoName(cwd);
|
|
559
|
+
if (!repoName) {
|
|
560
|
+
console.error("No --repo option provided and could not detect repo from current directory.");
|
|
561
|
+
console.error("Usage: autopilot setup-labels --repo owner/repo");
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
opts.repo = repoName;
|
|
565
|
+
}
|
|
566
|
+
const labels = [
|
|
567
|
+
{ name: "autopilot:todo", color: "7057ff", description: "Ready to be picked up by autopilot" },
|
|
568
|
+
{ name: "autopilot:in-progress", color: "0075ca", description: "Claimed by autopilot" },
|
|
569
|
+
{ name: "autopilot:blocked", color: "d93f0b", description: "Needs human input or missing heartbeat" },
|
|
570
|
+
{ name: "autopilot:done", color: "3fb950", description: "Completed" },
|
|
571
|
+
{ name: "autopilot:backlog", color: "d4c5f9", description: "Captured, not ready" },
|
|
572
|
+
{ name: "autopilot:planning", color: "5319e7", description: "Creating implementation plan" },
|
|
573
|
+
{ name: "autopilot:implementing", color: "1f883d", description: "Writing code" },
|
|
574
|
+
{ name: "autopilot:pr-created", color: "fbca04", description: "Pull request created" },
|
|
575
|
+
{ name: "autopilot:waiting-checks", color: "0e8a16", description: "Waiting for CI checks" },
|
|
576
|
+
{ name: "autopilot:fixing-checks", color: "cfd3d7", description: "Fixing failing CI checks" },
|
|
577
|
+
{ name: "autopilot:merging", color: "bfd4f2", description: "Merging pull request" }
|
|
578
|
+
];
|
|
579
|
+
console.log(`Creating labels for ${opts.repo}...`);
|
|
580
|
+
labels.forEach(label => {
|
|
581
|
+
const args = [
|
|
582
|
+
"api",
|
|
583
|
+
"--method",
|
|
584
|
+
"POST",
|
|
585
|
+
"-H",
|
|
586
|
+
"Accept: application/vnd.github+json",
|
|
587
|
+
"repos/${opts.repo}/labels",
|
|
588
|
+
"-f",
|
|
589
|
+
`name=${label.name}`,
|
|
590
|
+
"-f",
|
|
591
|
+
`color=${label.color}`,
|
|
592
|
+
"-f",
|
|
593
|
+
`description=${label.description}`
|
|
594
|
+
];
|
|
595
|
+
const res = (0, node_child_process_1.spawnSync)("gh", args, { stdio: "pipe" });
|
|
596
|
+
if (res.status === 0) {
|
|
597
|
+
console.log(`✅ Created: ${label.name}`);
|
|
598
|
+
}
|
|
599
|
+
else if (res.stderr && res.stderr.toString().includes("Label already exists")) {
|
|
600
|
+
console.log(`ℹ️ Exists: ${label.name}`);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
console.error(`❌ Failed to create ${label.name}`);
|
|
604
|
+
if (res.stderr)
|
|
605
|
+
console.error(res.stderr.toString());
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
console.log("\n✅ Label setup complete!");
|
|
609
|
+
});
|
|
551
610
|
program.parse();
|
package/package.json
CHANGED
package/scripts/run_autopilot.py
CHANGED
|
@@ -25,6 +25,12 @@ from dataclasses import dataclass
|
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import Any
|
|
27
27
|
import shutil
|
|
28
|
+
from sys import path as sys_path
|
|
29
|
+
|
|
30
|
+
# Add scripts directory to path for imports
|
|
31
|
+
script_dir = Path(__file__).parent
|
|
32
|
+
sys_path.insert(0, str(script_dir))
|
|
33
|
+
from issue_runner import IssueRunner
|
|
28
34
|
|
|
29
35
|
STATE_DIR = ".autopilot"
|
|
30
36
|
STATE_FILE = "state.json"
|
|
@@ -87,6 +93,8 @@ class RepoConfig:
|
|
|
87
93
|
auto_fix_checks: bool
|
|
88
94
|
auto_fix_checks_max_attempts: int
|
|
89
95
|
auto_update: bool
|
|
96
|
+
use_new_runner: bool = False
|
|
97
|
+
config: dict[str, Any] = None
|
|
90
98
|
|
|
91
99
|
|
|
92
100
|
def load_config(repo_root: Path) -> RepoConfig | None:
|
|
@@ -136,6 +144,8 @@ def load_config(repo_root: Path) -> RepoConfig | None:
|
|
|
136
144
|
auto_fix_checks=auto_fix_checks,
|
|
137
145
|
auto_fix_checks_max_attempts=auto_fix_checks_max_attempts,
|
|
138
146
|
auto_update=data.get("autoUpdate", False),
|
|
147
|
+
use_new_runner=data.get("useNewRunner", False),
|
|
148
|
+
config=data,
|
|
139
149
|
)
|
|
140
150
|
|
|
141
151
|
|
|
@@ -366,6 +376,18 @@ def claim_issue(cfg: RepoConfig, issue: dict[str, Any], note: str) -> None:
|
|
|
366
376
|
touch_heartbeat(cfg, num)
|
|
367
377
|
|
|
368
378
|
|
|
379
|
+
def clear_active_issue(cfg: RepoConfig, issue_number: int) -> None:
|
|
380
|
+
"""Clear an issue from active issues state."""
|
|
381
|
+
state = load_state(cfg.root)
|
|
382
|
+
active_issues = state.get("activeIssues", {})
|
|
383
|
+
if isinstance(active_issues, dict):
|
|
384
|
+
issue_key = str(issue_number)
|
|
385
|
+
if issue_key in active_issues:
|
|
386
|
+
del active_issues[issue_key]
|
|
387
|
+
state["activeIssues"] = active_issues
|
|
388
|
+
write_state(cfg.root, state)
|
|
389
|
+
|
|
390
|
+
|
|
369
391
|
def list_in_progress_issues(cfg: RepoConfig, limit: int = 20) -> list[dict[str, Any]]:
|
|
370
392
|
cmd = [
|
|
371
393
|
"gh",
|
|
@@ -736,6 +758,49 @@ def maybe_mark_blocked(cfg: RepoConfig, issue: dict[str, Any]) -> None:
|
|
|
736
758
|
)
|
|
737
759
|
|
|
738
760
|
|
|
761
|
+
def run_issue(cfg: RepoConfig, issue_number: int) -> bool:
|
|
762
|
+
"""
|
|
763
|
+
Run the agent on an issue.
|
|
764
|
+
|
|
765
|
+
Uses either the new Python runner or legacy bash script
|
|
766
|
+
based on configuration.
|
|
767
|
+
"""
|
|
768
|
+
if cfg.use_new_runner:
|
|
769
|
+
return run_issue_new(cfg, issue_number)
|
|
770
|
+
else:
|
|
771
|
+
return run_issue_legacy(cfg, issue_number)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def run_issue_new(cfg: RepoConfig, issue_number: int) -> bool:
|
|
775
|
+
"""Run issue using new Python state machine runner."""
|
|
776
|
+
# Touch heartbeat before starting
|
|
777
|
+
touch_heartbeat(cfg, issue_number)
|
|
778
|
+
|
|
779
|
+
runner = IssueRunner(
|
|
780
|
+
repo=cfg.repo,
|
|
781
|
+
repo_root=cfg.root,
|
|
782
|
+
config=cfg.config
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
try:
|
|
786
|
+
success = runner.run(issue_number)
|
|
787
|
+
finally:
|
|
788
|
+
# Clear from active issues when done
|
|
789
|
+
clear_active_issue(cfg, issue_number)
|
|
790
|
+
|
|
791
|
+
return success
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def run_issue_legacy(cfg: RepoConfig, issue_number: int) -> bool:
|
|
795
|
+
"""Run issue using legacy bash script (existing behavior)."""
|
|
796
|
+
script_path = Path(__file__).parent / "run_opencode_issue.sh"
|
|
797
|
+
result = subprocess.run(
|
|
798
|
+
[str(script_path), str(cfg.root), str(issue_number)],
|
|
799
|
+
cwd=cfg.root
|
|
800
|
+
)
|
|
801
|
+
return result.returncode == 0
|
|
802
|
+
|
|
803
|
+
|
|
739
804
|
def run_cycle(
|
|
740
805
|
all_configs: list[RepoConfig],
|
|
741
806
|
dry_run: bool = False,
|
|
@@ -816,18 +881,9 @@ def run_cycle(
|
|
|
816
881
|
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."
|
|
817
882
|
sh(["gh", "issue", "comment", str(issue["number"]), "--repo", cfg.repo, "--body", start_msg])
|
|
818
883
|
|
|
819
|
-
#
|
|
884
|
+
# Run the issue using the appropriate runner
|
|
820
885
|
if cfg.agent == "opencode":
|
|
821
|
-
|
|
822
|
-
script_dir = Path(__file__).parent
|
|
823
|
-
script_path = script_dir / "run_opencode_issue.sh"
|
|
824
|
-
sh(
|
|
825
|
-
[
|
|
826
|
-
str(script_path),
|
|
827
|
-
str(cfg.root),
|
|
828
|
-
str(issue["number"]),
|
|
829
|
-
]
|
|
830
|
-
)
|
|
886
|
+
run_issue(cfg, issue["number"])
|
|
831
887
|
|
|
832
888
|
# Check if autopilot needs to update
|
|
833
889
|
if not dry_run and check_autopilot_needs_update():
|
package/templates/autopilot.json
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"enabled": true,
|
|
3
|
-
"repo": "
|
|
3
|
+
"repo": "owner/repo",
|
|
4
4
|
"agent": "opencode",
|
|
5
|
-
"
|
|
5
|
+
"useNewRunner": false,
|
|
6
|
+
"autoMerge": true,
|
|
7
|
+
"mergeMethod": "squash",
|
|
8
|
+
"allowedMergeUsers": [],
|
|
6
9
|
"issueLabels": {
|
|
7
10
|
"queue": ["autopilot:todo"],
|
|
8
|
-
"blocked": "autopilot:blocked",
|
|
9
11
|
"inProgress": "autopilot:in-progress",
|
|
12
|
+
"blocked": "autopilot:blocked",
|
|
10
13
|
"done": "autopilot:done"
|
|
11
14
|
},
|
|
15
|
+
"stepLabels": {
|
|
16
|
+
"planning": "autopilot:planning",
|
|
17
|
+
"implementing": "autopilot:implementing",
|
|
18
|
+
"prCreated": "autopilot:pr-created",
|
|
19
|
+
"waitingChecks": "autopilot:waiting-checks",
|
|
20
|
+
"fixingChecks": "autopilot:fixing-checks",
|
|
21
|
+
"merging": "autopilot:merging"
|
|
22
|
+
},
|
|
12
23
|
"priorityLabels": ["p0", "p1", "p2"],
|
|
13
24
|
"minPriority": null,
|
|
14
|
-
"maxParallel": 2,
|
|
15
25
|
"ignoreIssueLabels": ["autopilot:backlog"],
|
|
26
|
+
"maxParallel": 2,
|
|
16
27
|
"branchPrefix": "autopilot/",
|
|
17
28
|
"allowedBaseBranch": "main",
|
|
18
29
|
"autoResolveConflicts": true,
|
|
@@ -20,5 +31,6 @@
|
|
|
20
31
|
"autoFixChecks": true,
|
|
21
32
|
"autoFixChecksMaxAttempts": 3,
|
|
22
33
|
"autoUpdate": true,
|
|
23
|
-
"enablePlanningStep": true
|
|
34
|
+
"enablePlanningStep": true,
|
|
35
|
+
"agentPath": ""
|
|
24
36
|
}
|