autoforge-cli 0.1.0a0__tar.gz

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.
Files changed (87) hide show
  1. autoforge_cli-0.1.0a0/LICENSE +21 -0
  2. autoforge_cli-0.1.0a0/MANIFEST.in +7 -0
  3. autoforge_cli-0.1.0a0/PKG-INFO +525 -0
  4. autoforge_cli-0.1.0a0/README.md +496 -0
  5. autoforge_cli-0.1.0a0/pyproject.toml +90 -0
  6. autoforge_cli-0.1.0a0/setup.cfg +4 -0
  7. autoforge_cli-0.1.0a0/src/autoforge/__init__.py +5 -0
  8. autoforge_cli-0.1.0a0/src/autoforge/agent_worktree.py +653 -0
  9. autoforge_cli-0.1.0a0/src/autoforge/certificate.py +495 -0
  10. autoforge_cli-0.1.0a0/src/autoforge/child_env.py +871 -0
  11. autoforge_cli-0.1.0a0/src/autoforge/claims.py +726 -0
  12. autoforge_cli-0.1.0a0/src/autoforge/claude_stream.py +501 -0
  13. autoforge_cli-0.1.0a0/src/autoforge/cli.py +2884 -0
  14. autoforge_cli-0.1.0a0/src/autoforge/config.py +2178 -0
  15. autoforge_cli-0.1.0a0/src/autoforge/config_fingerprint.py +374 -0
  16. autoforge_cli-0.1.0a0/src/autoforge/doctor.py +1551 -0
  17. autoforge_cli-0.1.0a0/src/autoforge/doctor_sandbox.py +691 -0
  18. autoforge_cli-0.1.0a0/src/autoforge/effect_ops.py +1272 -0
  19. autoforge_cli-0.1.0a0/src/autoforge/effects.py +3210 -0
  20. autoforge_cli-0.1.0a0/src/autoforge/engine.py +16637 -0
  21. autoforge_cli-0.1.0a0/src/autoforge/epic_commands.py +1486 -0
  22. autoforge_cli-0.1.0a0/src/autoforge/epic_graph.py +852 -0
  23. autoforge_cli-0.1.0a0/src/autoforge/errors.py +536 -0
  24. autoforge_cli-0.1.0a0/src/autoforge/evidence.py +1722 -0
  25. autoforge_cli-0.1.0a0/src/autoforge/executor.py +1601 -0
  26. autoforge_cli-0.1.0a0/src/autoforge/executor_duplex.py +985 -0
  27. autoforge_cli-0.1.0a0/src/autoforge/executor_supervised.py +144 -0
  28. autoforge_cli-0.1.0a0/src/autoforge/git_transport.py +970 -0
  29. autoforge_cli-0.1.0a0/src/autoforge/github.py +3402 -0
  30. autoforge_cli-0.1.0a0/src/autoforge/interaction.py +1027 -0
  31. autoforge_cli-0.1.0a0/src/autoforge/local_workspace.py +1253 -0
  32. autoforge_cli-0.1.0a0/src/autoforge/locking.py +1050 -0
  33. autoforge_cli-0.1.0a0/src/autoforge/loop_detect.py +660 -0
  34. autoforge_cli-0.1.0a0/src/autoforge/loop_guard.py +404 -0
  35. autoforge_cli-0.1.0a0/src/autoforge/materialize.py +722 -0
  36. autoforge_cli-0.1.0a0/src/autoforge/ownership.py +240 -0
  37. autoforge_cli-0.1.0a0/src/autoforge/pi_rpc.py +713 -0
  38. autoforge_cli-0.1.0a0/src/autoforge/premerge.py +181 -0
  39. autoforge_cli-0.1.0a0/src/autoforge/profiles.py +106 -0
  40. autoforge_cli-0.1.0a0/src/autoforge/progress.py +392 -0
  41. autoforge_cli-0.1.0a0/src/autoforge/prompts/__init__.py +173 -0
  42. autoforge_cli-0.1.0a0/src/autoforge/prompts/acceptance_actions.md +39 -0
  43. autoforge_cli-0.1.0a0/src/autoforge/prompts/analyze_execute.md +128 -0
  44. autoforge_cli-0.1.0a0/src/autoforge/prompts/common.md +132 -0
  45. autoforge_cli-0.1.0a0/src/autoforge/prompts/correction.md +12 -0
  46. autoforge_cli-0.1.0a0/src/autoforge/prompts/fix.md +252 -0
  47. autoforge_cli-0.1.0a0/src/autoforge/prompts/local_analyze_execute.md +69 -0
  48. autoforge_cli-0.1.0a0/src/autoforge/prompts/local_common.md +149 -0
  49. autoforge_cli-0.1.0a0/src/autoforge/prompts/local_fix.md +96 -0
  50. autoforge_cli-0.1.0a0/src/autoforge/prompts/local_review.md +103 -0
  51. autoforge_cli-0.1.0a0/src/autoforge/prompts/operator_acceptance.md +61 -0
  52. autoforge_cli-0.1.0a0/src/autoforge/prompts/replan_reexecute.md +991 -0
  53. autoforge_cli-0.1.0a0/src/autoforge/prompts/review.md +312 -0
  54. autoforge_cli-0.1.0a0/src/autoforge/prompts/update_epic.md +120 -0
  55. autoforge_cli-0.1.0a0/src/autoforge/prompts/update_epic_rerequest.md +85 -0
  56. autoforge_cli-0.1.0a0/src/autoforge/prompts/validation_evidence.md +26 -0
  57. autoforge_cli-0.1.0a0/src/autoforge/prompts/validation_failure.md +22 -0
  58. autoforge_cli-0.1.0a0/src/autoforge/prompts/worktree_state.md +45 -0
  59. autoforge_cli-0.1.0a0/src/autoforge/providers.py +2893 -0
  60. autoforge_cli-0.1.0a0/src/autoforge/read_context.py +389 -0
  61. autoforge_cli-0.1.0a0/src/autoforge/redaction.py +408 -0
  62. autoforge_cli-0.1.0a0/src/autoforge/replan.py +208 -0
  63. autoforge_cli-0.1.0a0/src/autoforge/replan_txn.py +1930 -0
  64. autoforge_cli-0.1.0a0/src/autoforge/result_parser.py +2234 -0
  65. autoforge_cli-0.1.0a0/src/autoforge/roadmap.py +151 -0
  66. autoforge_cli-0.1.0a0/src/autoforge/run_contract.py +408 -0
  67. autoforge_cli-0.1.0a0/src/autoforge/run_namespace.py +839 -0
  68. autoforge_cli-0.1.0a0/src/autoforge/runlog.py +764 -0
  69. autoforge_cli-0.1.0a0/src/autoforge/safefs.py +1717 -0
  70. autoforge_cli-0.1.0a0/src/autoforge/sandbox.py +1313 -0
  71. autoforge_cli-0.1.0a0/src/autoforge/sandbox_bwrap.py +815 -0
  72. autoforge_cli-0.1.0a0/src/autoforge/sandbox_pin.py +170 -0
  73. autoforge_cli-0.1.0a0/src/autoforge/sandbox_policy.py +1107 -0
  74. autoforge_cli-0.1.0a0/src/autoforge/scheduler.py +1143 -0
  75. autoforge_cli-0.1.0a0/src/autoforge/scheduler_state.py +2316 -0
  76. autoforge_cli-0.1.0a0/src/autoforge/scheduler_stop.py +421 -0
  77. autoforge_cli-0.1.0a0/src/autoforge/sdk_policy.py +988 -0
  78. autoforge_cli-0.1.0a0/src/autoforge/state.py +2417 -0
  79. autoforge_cli-0.1.0a0/src/autoforge/transitions.py +388 -0
  80. autoforge_cli-0.1.0a0/src/autoforge/validation.py +324 -0
  81. autoforge_cli-0.1.0a0/src/autoforge/worker.py +568 -0
  82. autoforge_cli-0.1.0a0/src/autoforge_cli.egg-info/PKG-INFO +525 -0
  83. autoforge_cli-0.1.0a0/src/autoforge_cli.egg-info/SOURCES.txt +85 -0
  84. autoforge_cli-0.1.0a0/src/autoforge_cli.egg-info/dependency_links.txt +1 -0
  85. autoforge_cli-0.1.0a0/src/autoforge_cli.egg-info/entry_points.txt +2 -0
  86. autoforge_cli-0.1.0a0/src/autoforge_cli.egg-info/requires.txt +3 -0
  87. autoforge_cli-0.1.0a0/src/autoforge_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ruoshi Li
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ # The sdist carries what building the wheel needs: pyproject.toml, README.md,
2
+ # LICENSE and src/, whose resources [tool.setuptools.package-data] declares.
3
+ # setuptools would also add tests/test_*.py by default, but not the fixtures
4
+ # and fakes those need (tests/conftest.py, tests/fixtures/), so that copy
5
+ # could not run; the suite runs from a checkout (docs/development.md).
6
+ # tests/dist_check.py checks the sdist's file list.
7
+ prune tests
@@ -0,0 +1,525 @@
1
+ Metadata-Version: 2.4
2
+ Name: autoforge-cli
3
+ Version: 0.1.0a0
4
+ Summary: Autonomous orchestration for AI-driven software development (deterministic controller over Claude Code / OpenCode / gh).
5
+ Author: Ruoshi Li
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/iceboundrock/AutoForge
8
+ Project-URL: Documentation, https://github.com/iceboundrock/AutoForge/tree/main/docs
9
+ Project-URL: Source, https://github.com/iceboundrock/AutoForge
10
+ Project-URL: Issues, https://github.com/iceboundrock/AutoForge/issues
11
+ Project-URL: Changelog, https://github.com/iceboundrock/AutoForge/blob/main/CHANGELOG.md
12
+ Keywords: ai,agents,orchestration,code-review,github,claude-code,opencode
13
+ Classifier: Development Status :: 2 - Pre-Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: Implementation :: CPython
22
+ Classifier: Topic :: Software Development
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: yaml
27
+ Requires-Dist: pyyaml>=6; extra == "yaml"
28
+ Dynamic: license-file
29
+
30
+ # AutoForge / 智铸
31
+
32
+ **Autonomous orchestration for AI-driven software development.**
33
+
34
+ 智铸:AI 驱动的软件工程自动化控制器。
35
+
36
+ > **Early development (Phase 2).** The first real AI development loop is
37
+ > wired end to end: GitHub issue → Claude Code implementation → controller
38
+ > push and PR → OpenCode review → Claude Code remediation → repeated review
39
+ > → `READY_FOR_MERGE`. Merging is opt-in and controller-owned: by default the
40
+ > loop stops at `READY_FOR_MERGE` for a human; with the merge safety gate
41
+ > open (`safety.allow_merge: true` in config and `--allow-merge` on the CLI)
42
+ > the controller itself verifies the PR on GitHub and merges it. Agents never
43
+ > merge. Nothing here is production ready yet.
44
+
45
+ ## What AutoForge is (and is not)
46
+
47
+ AutoForge is not another coding agent. It is a deterministic orchestration
48
+ layer that drives existing agents through the software lifecycle and refuses
49
+ to trust anything they claim without checking it against GitHub:
50
+
51
+ ```text
52
+ Issue → ANALYZE_EXECUTE (Claude Code) → controller pushes, opens PR → REVIEW (OpenCode)
53
+ → clean ───────────────────────────────▶ READY_FOR_MERGE
54
+ → findings ────────────────────────────▶ FIX (Claude Code) → REVIEW …
55
+ → excessive / low-finding stagnation ──▶ REPLAN_REEXECUTE (OpenCode)
56
+ → controller pushes, opens replacement PR
57
+ → REVIEW round 1
58
+ ├─ merge gate closed (default): stops here; human merges
59
+ └─ gate open: controller verifies on GitHub
60
+ → MERGE (gh pr merge by the controller, no agent)
61
+ → UPDATE_EPIC (agent returns the progress text
62
+ and the roadmap section and picks the next
63
+ issue; the controller posts the progress comment,
64
+ splices the section into the EPIC body every N
65
+ merges, reads both back, and verifies the next
66
+ issue on GitHub, or DONE)
67
+ ```
68
+
69
+ AutoForge itself never writes business code. It holds the workflow state
70
+ machine, selects which provider, model and effort runs each phase, renders
71
+ strictly validated prompts from file templates, invokes `claude` /
72
+ `opencode` through provider adapters and `gh` through a typed client (argv
73
+ lists only, never a shell), parses the machine-readable `CONTROL_RESULT`
74
+ block from agent output, and verifies every claim on GitHub before it
75
+ advances state. Every invocation is logged, redacted, under
76
+ `<state dir>/logs/<run-id>/`; the state directory defaults to `.autoforge/`
77
+ for a remote run and to `<git dir>/autoforge/state` for a
78
+ [local run](docs/local-mode.md#resume-and-recovery).
79
+
80
+ It is a single-machine client tool, not a distributed service: one process
81
+ on your own machine, driving your `git`, `gh`, and agent CLIs under your
82
+ credentials, with local per-checkout state and a single
83
+ OS-level repository lock instead of any coordinator. The expected failure
84
+ modes are a `Ctrl-C`, a killed process, and a reboot, which is why side
85
+ effects are checkpointed before they are performed. What *is* remote and
86
+ concurrent is GitHub and the humans acting on it, which is why nothing
87
+ GitHub-side is believed without being read back. AutoForge never commits to
88
+ the default branch.
89
+
90
+ There is a second, explicit workflow for when there is no GitHub to
91
+ orchestrate: [Local mode](#local-mode-no-github) drives a feature Markdown
92
+ file against your working tree with the same controller and the same
93
+ verification discipline, and the controller makes zero `gh` invocations.
94
+
95
+ ## Key properties
96
+
97
+ Each line is a summary; the linked document is the precise statement.
98
+
99
+ - **Read the security boundaries before a first run.** Agents run as you,
100
+ with network access and your model credentials, and are not sandboxed
101
+ by default, so a deliberate read of your credential files by absolute
102
+ path is outside the credential guarantee below. On a public repository
103
+ anyone who can comment on the issue or PR an agent reads can try to
104
+ steer it, and everything an agent reads goes to your model vendor. Run
105
+ AutoForge on issues whose authors you trust, as a dedicated user or in
106
+ a VM or container, or under `sandbox.mode: required`.
107
+ [Security boundaries](docs/security.md) lists what AutoForge protects
108
+ and what it does not, the GitHub token permissions it needs and the
109
+ recommended settings; [SECURITY.md](SECURITY.md) says how to report a
110
+ vulnerability.
111
+ - **Agent output is a claim; the controller decides.** State advances only
112
+ on a strictly validated `CONTROL_RESULT` block plus independent
113
+ verification, never on prose such as "done" or "LGTM". An invalid
114
+ transition fails explicitly, and when recovery cannot determine the safe
115
+ state the run enters `BLOCKED` rather than guess.
116
+ ([CONTROL_RESULT protocol](docs/agent-guides/control-result-protocol.md),
117
+ [workflow](docs/agent-guides/workflow.md))
118
+ - **GitHub is read back, not believed.** Each phase re-reads what its agent
119
+ claims to have done, or what the controller did on its behalf: the
120
+ implementation commit is the worktree's own HEAD and descends from the
121
+ default branch, and the PR the controller opened for it is open, at that
122
+ HEAD, on the issue's branch; the review comment the controller rendered
123
+ from the reviewer's result is on that PR, exactly once, for the right
124
+ round and SHA; the fix commit is the fixer's own HEAD, descends from the
125
+ reviewed HEAD and is the PR's head once the controller has pushed it; the
126
+ follow-up issues the controller created or recorded a deferral in are the
127
+ one open issue per finding; the EPIC update and the next issue exist.
128
+ ([GitHub safety](docs/agent-guides/github-safety.md))
129
+ - **Agents never merge.** Every agent prompt carries the rule "never merge a
130
+ pull request" (LOCAL prompts forbid `git merge` and have no PR to merge).
131
+ The merge is performed by the controller itself
132
+ (`gh pr merge --<method> --match-head-commit <reviewed HEAD>`), with no
133
+ agent invocation.
134
+ - **Merging is off by default and opt-in.** `MERGE` is reachable only when
135
+ both `safety.allow_merge: true` is set in config *and* `--allow-merge` is
136
+ passed on the CLI; otherwise the run stops at `READY_FOR_MERGE` and a
137
+ human merges. Before merging, the controller re-verifies the PR on GitHub
138
+ and fails closed: a conclusive negative is `BLOCKED`, inconclusive data
139
+ keeps the phase for a bounded number of `resume --allow-merge` re-checks
140
+ and then is `BLOCKED`, and nothing is merged in either case. By default a PR that
141
+ changes a protected path (`.github/workflows/`) is never merged
142
+ unattended, and a required check is trusted only if it ran the
143
+ base branch's job and step structure. `doctor` checks that the default
144
+ branch still requires the CI check; the merge gate itself does not yet
145
+ consult branch rules. The merge is counted only after GitHub reports
146
+ `MERGED` at the reviewed HEAD into the reviewed base.
147
+ ([Merge safety](docs/agent-guides/github-safety.md#merge-safety))
148
+ - **Reviews are bound to a revision.** A clean review is bound to the PR it
149
+ was posted on (by repository and number), its HEAD SHA, its base branch
150
+ and its merge base. If the HEAD, the base branch or the merge base moves
151
+ after the review, the run goes back to `REVIEW` instead of merging, and a
152
+ different PR than the reviewed one is `BLOCKED`; a local review is bound
153
+ to a workspace fingerprint in the same way.
154
+ ([Review binding](docs/agent-guides/workflow.md#bind-reviews-to-pr-head-sha-and-to-the-pr-identity))
155
+ - **State is durable and runs are resumable.** State is written atomically
156
+ and side effects are checkpointed before they are performed. `resume`
157
+ re-enters the interrupted phase and reconciles with GitHub first; a
158
+ corrupt state file fails loudly instead of being reset. One controller
159
+ runs per checkout under an OS file lock keyed by the git common dir
160
+ (linked worktrees share it; independent clones are not coordinated). Review rounds, stagnation and a
161
+ cumulative step budget bound every run, so it cannot loop forever.
162
+ ([State and recovery](docs/agent-guides/state-and-recovery.md))
163
+ - **Agents are separated from your checkout and shell; sandboxing is
164
+ opt-in.** A remote agent runs in a per-issue `git worktree` the
165
+ controller creates, with an allow-listed environment rather than the
166
+ operator's whole shell, and the controller blocks if its own checkout's
167
+ HEAD or branch moved while the agent ran. Whatever an agent leaves running is killed when its
168
+ invocation ends, and what the kill could not remove is reported rather
169
+ than hidden; the orphans of a process that makes itself a subreaper, and
170
+ anything handed to a service manager (`systemd-run`, `at`), escape this
171
+ unseen
172
+ ([ADR 0002](docs/adr/0002-executor-nothing-outlives-the-invocation.md),
173
+ [Security boundaries](docs/security.md#other-limits)).
174
+ By default the agent is not sandboxed: it runs as the operator, with
175
+ network access and the allow-listed provider credentials.
176
+ ([Usage](docs/usage.md#how-agents-are-separated-from-your-checkout))
177
+ `sandbox.mode: required` (Linux, `bwrap`) runs every agent, LOCAL
178
+ validation command and pre-merge command inside an outer sandbox, or
179
+ refuses it: an empty root with only the system runtime, the workspace,
180
+ read-only git metadata, a private home and what the provider declares, so
181
+ controller state, other worktrees and your credential files are out of
182
+ reach. It still runs as your UID, and agents keep the host network. A
183
+ host that cannot enforce one is refused before the step, with nothing
184
+ saved, charged or launched; `preferred` falls back to logical isolation
185
+ only in LOCAL, visibly, and refuses like `required` in REMOTE; `doctor`
186
+ shows which this host gets
187
+ ([ADR 0005](docs/adr/0005-outer-sandbox-and-host-resource-capability-model.md),
188
+ [Usage](docs/usage.md#outer-sandbox),
189
+ [Configuration](docs/configuration.md#outer-sandbox)).
190
+ - **Agents hold no GitHub credential.** The controller makes every GitHub
191
+ write itself; agents, validation commands and pre-merge commands get no
192
+ GitHub or git credential through anything AutoForge controls: no token
193
+ variable `gh` or git reads can be allow-listed (a token you forward under
194
+ another name is yours to keep out), `gh` finds no login, credential helpers,
195
+ askpass and the SSH agent and keys are switched off where repository
196
+ configuration cannot switch them back on, curl and wget read no
197
+ configured header, a launch's shells read none of your zsh startup
198
+ files, the provider CLIs load no MCP server, plugin or hook, and a
199
+ launch is refused (by `doctor` too) while a repository setting,
200
+ `~/.netrc`, a login shell's startup file or an organization connected
201
+ to OpenCode would still hand one over. The agent reads the GitHub data its
202
+ phase needs from files the controller wrote, redacted and bounded. A
203
+ deliberate same-user read of a credential file by absolute path is
204
+ outside this guarantee.
205
+ ([GitHub safety](docs/agent-guides/github-safety.md#agents-hold-no-github-credential))
206
+ - **The controller's local run is GitHub-free by construction, not by
207
+ convention.** Local mode needs no GitHub: a local run never constructs the
208
+ GitHub client, so no authentication is needed and none is consulted. The
209
+ agent is outside that construction: it is asked not to touch GitHub and
210
+ gets no GitHub credential (as above), and only a moved git anchor (a
211
+ commit, reset, checkout or branch switch) is detected;
212
+ a `gh` write or a push from the agent is not
213
+ (see [Local mode](#local-mode-no-github)).
214
+ - **Logs and CLI output are redacted.** Tokens, API keys and authorization
215
+ headers are masked (a baseline, with no claim of completeness), and no
216
+ environment dump is ever written.
217
+ ([Secrets and logging](docs/agent-guides/secrets-and-logging.md))
218
+
219
+ ## Prerequisites
220
+
221
+ [Compatibility](https://github.com/iceboundrock/AutoForge/blob/main/docs/compatibility.md)
222
+ has the full matrix and the evidence for each entry.
223
+
224
+ - Linux on x86_64. macOS, Linux ARM64 and WSL2 are untested; Windows is
225
+ unsupported (AutoForge's lock needs POSIX `fcntl`)
226
+ - CPython 3.11 or 3.12, the versions CI tests; 3.13 and later are untested.
227
+ `uv` fetches one when the host has none (`--python 3.12` below)
228
+ - `uv` ([install](https://docs.astral.sh/uv/getting-started/installation/))
229
+ - `git`, and `gh` (GitHub CLI **2.48.0 or newer**, authenticated): the
230
+ client lists branch rules and a PR's changed files with
231
+ `gh api --paginate --slurp`, which older releases reject; `autoforge doctor`
232
+ refuses an older `gh` up front. `gh` is **not** needed for
233
+ [local mode](https://github.com/iceboundrock/AutoForge#local-mode-no-github)
234
+ - `claude` (Claude Code CLI) for `analyze_execute` / `fix` profiles
235
+ - `opencode` (OpenCode CLI) 2.0.0 or newer for the `review_*`,
236
+ `replan_reexecute` and `update_epic` profiles; 1.x is refused by `doctor`
237
+ (see [OpenCode profiles](https://github.com/iceboundrock/AutoForge/blob/main/docs/configuration.md#opencode-profiles))
238
+ - `pi` 1.0.0 or newer, only if a profile you route to uses `provider: pi`,
239
+ holding a DeepSeek API key or signed in with your ChatGPT subscription
240
+ (see the [Pi operator guide](https://github.com/iceboundrock/AutoForge/blob/main/docs/pi.md)). `doctor` checks
241
+ only the agent CLIs your configured profiles actually reach
242
+ - a state directory (`.autoforge/` by default; `--state-dir` or
243
+ `state_dir` in config) on a filesystem with hard links: run logs and
244
+ other controller files are published by `link(2)` so that a crash never
245
+ leaves one half-written, and there is no non-atomic fallback. vfat/exFAT
246
+ and some FUSE, SMB/CIFS and overlay mounts have no hard links; a run, and
247
+ `autoforge doctor`, refuse such a state directory and say why
248
+
249
+ Run `autoforge doctor` to check all of the above (read-only apart from
250
+ creating the state directory if it is missing and a probe file it removes),
251
+ or
252
+ `autoforge local doctor` for the local-mode subset (no `gh`, no GitHub
253
+ authentication, no `origin` remote).
254
+
255
+ CLI flag syntax in `autoforge.example.yaml` was checked against the locally
256
+ installed CLIs (Claude Code 2.1.263, OpenCode 2.0.23, gh 2.100.0); the
257
+ versions verified since are in [Compatibility](https://github.com/iceboundrock/AutoForge/blob/main/docs/compatibility.md#agent-clis).
258
+
259
+ ## Installation
260
+
261
+ The distribution is **`autoforge-cli`**; the command it installs is
262
+ `autoforge`. `autoforge` on PyPI is an unrelated project: never
263
+ `pip install autoforge`.
264
+
265
+ A git commit, or a wheel built from one, installs from any commit at or
266
+ after the one that introduced `autoforge-cli` (#256; an earlier commit
267
+ installs a package without its prompt templates); the PyPI method needs a
268
+ published version.
269
+
270
+ **From a git commit.** Install the command into its own environment with
271
+ `uv tool`. Pin a full commit SHA, or the tag of a published version
272
+ (`v<version>`):
273
+
274
+ ```bash
275
+ uv tool install --python 3.12 'git+https://github.com/iceboundrock/AutoForge@<commit>'
276
+ autoforge --version
277
+ ```
278
+
279
+ `uv tool install --reinstall` with another commit replaces it, and
280
+ `uv tool uninstall autoforge-cli` removes it. Add the YAML extra with
281
+ `'autoforge-cli[yaml] @ git+https://github.com/iceboundrock/AutoForge@<commit>'`.
282
+
283
+ **From a built wheel.** From a checkout of the commit you want:
284
+
285
+ ```bash
286
+ uv build # dist/autoforge_cli-<version>.tar.gz and -py3-none-any.whl
287
+ uv tool install --python 3.12 dist/autoforge_cli-<version>-py3-none-any.whl
288
+ ```
289
+
290
+ **From PyPI, once a version is published.** Until the first upload
291
+ ([#295](https://github.com/iceboundrock/AutoForge/issues/295)),
292
+ `autoforge-cli` is not on PyPI and the command below fails: uv reports that
293
+ `autoforge-cli` was not found in the package registry. The
294
+ [releases page](https://github.com/iceboundrock/AutoForge/releases) lists
295
+ each version that has been published, with the SHA-256 digests of its
296
+ files; if it lists none, install from a git commit or a built wheel
297
+ instead. Install a published version only after checking that its PyPI
298
+ project page links back to this repository:
299
+
300
+ ```bash
301
+ uv tool install --python 3.12 --prerelease allow autoforge-cli
302
+ ```
303
+
304
+ `--prerelease allow` is needed while every published version is a
305
+ prerelease (`0.x`, [Releasing](https://github.com/iceboundrock/AutoForge/blob/main/docs/releasing.md#version-scheme)).
306
+ [Releasing](https://github.com/iceboundrock/AutoForge/blob/main/docs/releasing.md)
307
+ describes how a version is built and published.
308
+
309
+ Whichever way it was installed, run `autoforge` from a checkout of the
310
+ repository whose issues it drives (or, in LOCAL mode, whose feature file it
311
+ implements); `autoforge doctor` checks the rest.
312
+
313
+ **For development** on AutoForge itself, from a clone of this repository:
314
+
315
+ ```bash
316
+ uv sync # create .venv, install autoforge-cli (editable) + dev tools
317
+ uv sync --extra yaml # optional: full YAML config support (PyYAML)
318
+ uv run autoforge --help
319
+ ```
320
+
321
+ `uv run autoforge` is the same command, run from that clone's `.venv`.
322
+ [Development](https://github.com/iceboundrock/AutoForge/blob/main/docs/development.md) has the checks CI runs.
323
+
324
+ To configure profiles, bounds or the merge gate, copy
325
+ `autoforge.example.yaml` to `autoforge.yaml` and pass it with `--config`
326
+ before the subcommand; see [Configuration](https://github.com/iceboundrock/AutoForge/blob/main/docs/configuration.md).
327
+
328
+ ## Quick start
329
+
330
+ Run these from a checkout of the repository the issues belong to. URLs must
331
+ be HTTPS GitHub issue URLs, and the EPIC and the issue must be in that same
332
+ repository (cross-repo runs are rejected).
333
+
334
+ ```bash
335
+ # 1. check the environment (writes nothing but a state-dir probe)
336
+ autoforge doctor
337
+
338
+ # 2. preview without touching anything (no subprocess, no gh call, no state file)
339
+ autoforge run --epic https://github.com/owner/repo/issues/1 \
340
+ --issue https://github.com/owner/repo/issues/2 --dry-run
341
+
342
+ # 3. create a run and drive it until READY_FOR_MERGE / BLOCKED / FAILED
343
+ autoforge run --epic https://github.com/owner/repo/issues/1 \
344
+ --issue https://github.com/owner/repo/issues/2
345
+
346
+ # 4. inspect it, and continue after an interruption
347
+ autoforge status
348
+ autoforge resume
349
+ ```
350
+
351
+ With the merge gate closed (the default) the run stops at
352
+ `READY_FOR_MERGE` with a banner naming the issue, PR, review round and
353
+ reviewed HEAD, and a human merges the PR. Only with `safety.allow_merge:
354
+ true` in config does `autoforge resume --allow-merge` let the
355
+ controller verify the PR on GitHub and merge it itself.
356
+
357
+ A run in `BLOCKED` stays there until you fix the cause and run
358
+ `autoforge unblock --reason "..."`, which re-inspects GitHub and either
359
+ re-enters the one phase that inspection supports or refuses. When a fixer
360
+ asked the controller to post an acceptance comment on the issue, the run
361
+ waits for you to approve or decline that exact comment
362
+ (`unblock --approve-action` / `--decline-action`). When it asked for a
363
+ comment on another issue (one the run's issue references, or the EPIC),
364
+ you post it yourself, bind an existing comment or withdraw it
365
+ (`unblock --bind-evidence` / `--decline-action`); the controller never posts
366
+ there, and verifies the comment on GitHub. Every command,
367
+ where a run stops, and how to recover are in
368
+ [Running the remote workflow](docs/usage.md).
369
+
370
+ To work through an EPIC's issues several at a time,
371
+ `autoforge epic run https://github.com/owner/repo/issues/1` runs up to
372
+ `scheduler.max_parallel` (1-4) of them at once, admitted in dependency order
373
+ from GitHub's native sub-issues and "blocked by" links, each in its own
374
+ worker, namespace and worktree. Every worker stops at its settlement
375
+ boundary (`READY_FOR_MERGE`, `BLOCKED`, `DONE`, `FAILED`), so nothing is
376
+ merged without a human. `epic status` shows each child's category and next
377
+ step without taking a lock, `epic stop` drains it, and `epic resume`
378
+ recovers after a crash, a kill or a reboot
379
+ ([Running an EPIC in parallel](docs/usage.md#running-an-epic-in-parallel)).
380
+ It is experimental: it is verified offline only and has not yet driven a
381
+ real EPIC ([Current maturity](#current-maturity)).
382
+
383
+ ## Local mode (no GitHub)
384
+
385
+ Sometimes there is no GitHub to orchestrate: a coding interview, a scratch
386
+ repository, an air-gapped machine, or simply a change you want driven by the
387
+ same review loop without opening an Issue. `autoforge local` is a separate,
388
+ explicit workflow for exactly that: the same controller, the same
389
+ "never trust the agent" discipline, none of the GitHub lifecycle.
390
+
391
+ ```text
392
+ features/<slug>.md → INITIALIZING → ANALYZE_EXECUTE → REVIEW
393
+ → clean ─────────────▶ DONE
394
+ → findings ──────────▶ FIX → REVIEW
395
+ → findings after the fix budget ─▶ BLOCKED
396
+ ```
397
+
398
+ The *controller* never touches GitHub in a local run: no `gh` invocation,
399
+ no Issue, no PR, no comment, no push, no branch, no merge, no EPIC update, no
400
+ follow-up issue, and no replan. The `GitHubClient` is not even constructed.
401
+ Nothing is faked either: there are no placeholder PR URLs in local state or
402
+ local prompts.
403
+
404
+ That is a property of the controller, not of the agents it launches. An agent
405
+ is a same-UID subprocess with `gh` on `PATH` and network access; it is
406
+ instructed not to use them, and it gets no GitHub or git credential through
407
+ anything AutoForge controls, so a `gh` write or a push fails unless the agent
408
+ finds a credential on its own (by reading the operator's files by absolute
409
+ path, say). Beyond that, the controller can neither prevent nor detect a
410
+ `gh` write, a `git push` or a network request made by the agent. What the
411
+ controller *does* detect is a moved git anchor: HEAD and the checked-out branch
412
+ are compared with the values the run was pinned to before and after every
413
+ phase, and before every correction relaunch within one, so a commit, reset,
414
+ checkout or branch switch blocks the run before another agent starts. It detects
415
+ nothing else. Real containment is a sandbox, which LOCAL v1 does not provide
416
+ under the default `sandbox.mode: disabled`
417
+ ([ADR 0001](docs/adr/0001-local-mode-workspace-identity-and-filesystem-boundary.md)
418
+ §2.2 and §8.1; isolation is tracked in #10). With `sandbox.mode: required`
419
+ every LOCAL agent and validation command runs inside ADR 0005's outer
420
+ sandbox, where the git directory is read-only, so a commit, stash or branch
421
+ switch fails outright; the agent still runs as your UID on the host
422
+ network. `preferred` does the same where the host can enforce and, where it
423
+ cannot, says at every launch that it runs with logical isolation only
424
+ ([Local mode](docs/local-mode.md#what-the-controller-does-and-does-not-do)).
425
+
426
+ ```bash
427
+ autoforge local doctor # config, git, agent CLIs; never gh
428
+ autoforge local init add-transaction-filter
429
+ # a human (with or without an AI) refines features/add-transaction-filter.md
430
+ autoforge local run features/add-transaction-filter.md --dry-run
431
+ autoforge local run features/add-transaction-filter.md
432
+ ```
433
+
434
+ The specification is frozen for the whole run (its SHA-256 is re-checked
435
+ before and after every agent phase), every review is bound to a fingerprint
436
+ of the working tree, and the controller never commits, stages, pushes,
437
+ stashes, resets or switches branches in a local run. The [Local mode guide](docs/local-mode.md) covers the
438
+ workspace fingerprint, dirty working trees, the review/fix bound, validation
439
+ commands, and resume and recovery.
440
+
441
+ ## Documentation
442
+
443
+ The [documentation index](docs/README.md) lists everything by intent. The
444
+ most common entry points:
445
+
446
+ | I want to… | Read |
447
+ |---|---|
448
+ | know which OS, Python, git, `gh`, agent CLI and sandbox host it runs on | [Compatibility](docs/compatibility.md) |
449
+ | run the remote workflow, merge, unblock, troubleshoot or recover a run | [Running the remote workflow](docs/usage.md), [troubleshooting and recovery](docs/usage.md#troubleshooting-and-recovery) |
450
+ | work without GitHub | [Local mode](docs/local-mode.md) |
451
+ | run phases through Pi on a DeepSeek API key or a ChatGPT subscription | [Pi operator guide](docs/pi.md) |
452
+ | configure profiles, bounds and the merge gate | [Configuration](docs/configuration.md), [`autoforge.example.yaml`](autoforge.example.yaml) |
453
+ | know what AutoForge protects, which GitHub token it needs, or report a vulnerability | [Security boundaries](docs/security.md), [SECURITY.md](SECURITY.md) |
454
+ | understand the phases and legal transitions | [Workflow](docs/agent-guides/workflow.md) |
455
+ | know what is verified on GitHub, and what gates a merge | [GitHub safety](docs/agent-guides/github-safety.md) |
456
+ | know how state, locking and crash recovery work | [State and recovery](docs/agent-guides/state-and-recovery.md) |
457
+ | see how the code is divided | [Architecture](docs/agent-guides/architecture.md), [module map](src/autoforge/AGENTS.md) |
458
+ | contribute, run the checks, write tests | [Development](docs/development.md), [Testing](docs/agent-guides/testing.md) |
459
+ | cut or publish a release | [Releasing](docs/releasing.md) |
460
+ | read the design records | [ADR 0001](docs/adr/0001-local-mode-workspace-identity-and-filesystem-boundary.md), [ADR 0002](docs/adr/0002-executor-nothing-outlives-the-invocation.md), [ADR 0003](docs/adr/0003-pi-agent-provider.md) |
461
+
462
+ Coding agents working on this repository start at [AGENTS.md](AGENTS.md).
463
+
464
+ ## Current maturity
465
+
466
+ **Supported now (Phase 2):** GitHub issue → Claude Code implementation → the
467
+ controller's push and PR, read back via `gh` → OpenCode review with
468
+ round-based model routing → Claude Code remediation of finding IDs →
469
+ repeated review with SHA binding → `READY_FOR_MERGE`; the bounded REVIEW/FIX
470
+ loop with stagnation detection and the `REPLAN_REEXECUTE` transaction;
471
+ journal-first recovery of an interrupted push or PR creation;
472
+ bounded correction retry for malformed results; `doctor`; redacted
473
+ per-invocation logs; LOCAL mode against a feature Markdown file.
474
+
475
+ **Opt-in, and only on a host that can enforce it:** the outer sandbox on
476
+ Linux (`sandbox.mode`, off by default), with its capability probe,
477
+ pre-launch gate and `doctor` rows. It needs Linux 6.12+ with Landlock ABI 6,
478
+ unprivileged user, PID and IPC namespaces (some distributions restrict
479
+ them), and a bubblewrap that can mount an overlay (0.11 or later)
480
+ ([ADR 0005](docs/adr/0005-outer-sandbox-and-host-resource-capability-model.md)).
481
+ `sandbox.mode: disabled` and a `preferred` fallback are not sandboxed. Its
482
+ boundary is tested across every provider path on a capable host
483
+ (`make test-sandbox`). Hosted CI measures its runner on every run (its
484
+ `bwrap`, user-namespace policy and Landlock ABI, in the job summary) and
485
+ requires the real-backend tests wherever the runner can enforce; where it
486
+ cannot, they skip with the probe's reason. It is experimental until a
487
+ `make test-sandbox` run on a capable host is recorded at the release
488
+ candidate.
489
+
490
+ **Experimental: implemented and verified, but exercised only against the
491
+ in-memory fake:** the controller-owned `MERGE` step behind the merge safety
492
+ gate (off by default), including its pre-merge verification, and
493
+ `UPDATE_EPIC`. Every claim these phases depend on is re-read from GitHub
494
+ before state advances, but no automated test has ever merged a real PR or
495
+ edited a real EPIC, so treat an opened gate as unproven against real
496
+ services. The supported path keeps the gate closed: a human reviews and
497
+ merges the PR the run leaves at `READY_FOR_MERGE`.
498
+
499
+ **Experimental: implemented and verified offline:** the parallel EPIC
500
+ scheduler (`autoforge epic run|resume|status|stop|archive`, ADR 0008). Its
501
+ crash windows are tested by killing real scheduler and worker processes at
502
+ each one, but against the in-memory GitHub and scripted agents only, and it
503
+ has not yet driven a real EPIC: run it on an EPIC you watch, with
504
+ `epic status` open.
505
+
506
+ **Experimental:** acceptance actions on other issues, which the operator
507
+ posts or binds (`unblock --bind-evidence`, #243). They are tested offline
508
+ and have been used once for real, in a run on this repository. They stay
509
+ experimental until they are used on a repository other than AutoForge.
510
+
511
+ **Experimental and opt-in:** Pi as a provider (`provider: pi`, the
512
+ [Pi operator guide](docs/pi.md)). No built-in profile uses it, and its one
513
+ recorded live run is a LOCAL review on DeepSeek (#135).
514
+
515
+ The Public Alpha release claims these capabilities at exactly these levels
516
+ ([#255](https://github.com/iceboundrock/AutoForge/issues/255), conditions
517
+ C1–C7). An earlier registration prerelease of the package (#295) is not the
518
+ Public Alpha and claims no more than this.
519
+
520
+ **Explicitly not yet:** unattended production operation; the controller
521
+ feeding CI results into the review prompt (the reviewer is asked to inspect
522
+ `gh pr checks` itself, and the merge gate reads the checks); the merge gate
523
+ consulting branch rules (`doctor` reads them, the gate does not); dequeuing a
524
+ PR from a merge queue (the controller refuses to merge into queue-protected
525
+ branches instead).