forgeo-cli 0.4.0__tar.gz → 0.5.0__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 (80) hide show
  1. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/.github/workflows/ci.yml +60 -1
  2. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/.gitignore +1 -0
  3. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/CHANGELOG.md +110 -1
  4. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/CONTRIBUTING.md +10 -3
  5. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/PKG-INFO +29 -11
  6. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/README.md +27 -9
  7. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/agent-contract.md +30 -2
  8. forgeo_cli-0.5.0/docs/backlog.md +284 -0
  9. forgeo_cli-0.5.0/docs/cli-reference.md +346 -0
  10. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/configuration.md +116 -2
  11. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/getting-started.md +50 -20
  12. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/index.md +28 -11
  13. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/web-console-api.md +201 -41
  14. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/install.sh +29 -3
  15. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/pyproject.toml +1 -1
  16. forgeo_cli-0.5.0/scripts/__init__.py +0 -0
  17. forgeo_cli-0.5.0/scripts/render_homebrew_formula.py +119 -0
  18. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/__init__.py +1 -1
  19. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/agent.py +25 -1
  20. forgeo_cli-0.5.0/src/forgeo/backlog.py +554 -0
  21. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/central.py +504 -38
  22. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/cli.py +359 -43
  23. forgeo_cli-0.5.0/src/forgeo/daemon.py +425 -0
  24. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/daemon_control.py +46 -13
  25. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/forgeo.py +221 -37
  26. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/git.py +14 -0
  27. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/models.py +158 -0
  28. forgeo_cli-0.5.0/src/forgeo/notify.py +131 -0
  29. forgeo_cli-0.5.0/src/forgeo/runs.py +131 -0
  30. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/setup.py +23 -3
  31. forgeo_cli-0.5.0/src/forgeo/update.py +172 -0
  32. forgeo_cli-0.5.0/src/forgeo/validate.py +221 -0
  33. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/web/central/central.css +329 -1
  34. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/web/central/central.js +448 -58
  35. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/web/central/instance.html +61 -13
  36. forgeo_cli-0.5.0/src/forgeo/web/central/login.html +76 -0
  37. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/web/style.css +7 -1
  38. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/web_common.py +2 -0
  39. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/conftest.py +11 -0
  40. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_agent.py +27 -0
  41. forgeo_cli-0.5.0/tests/test_backlog.py +899 -0
  42. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_cli.py +358 -9
  43. forgeo_cli-0.5.0/tests/test_daemon.py +383 -0
  44. forgeo_cli-0.5.0/tests/test_factory.py +947 -0
  45. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_git.py +20 -0
  46. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_install.py +3 -2
  47. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_models.py +105 -0
  48. forgeo_cli-0.5.0/tests/test_render_homebrew.py +83 -0
  49. forgeo_cli-0.5.0/tests/test_runs.py +515 -0
  50. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_setup.py +16 -1
  51. forgeo_cli-0.5.0/tests/test_update.py +205 -0
  52. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_web.py +515 -8
  53. forgeo_cli-0.5.0/tests/test_web_lock.py +521 -0
  54. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/www/index.html +3 -3
  55. forgeo_cli-0.4.0/docs/backlog.md +0 -135
  56. forgeo_cli-0.4.0/docs/cli-reference.md +0 -211
  57. forgeo_cli-0.4.0/src/forgeo/backlog.py +0 -296
  58. forgeo_cli-0.4.0/src/forgeo/daemon.py +0 -212
  59. forgeo_cli-0.4.0/src/forgeo/notify.py +0 -77
  60. forgeo_cli-0.4.0/src/forgeo/runs.py +0 -73
  61. forgeo_cli-0.4.0/tests/test_backlog.py +0 -373
  62. forgeo_cli-0.4.0/tests/test_daemon.py +0 -162
  63. forgeo_cli-0.4.0/tests/test_factory.py +0 -427
  64. forgeo_cli-0.4.0/tests/test_runs.py +0 -240
  65. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/LICENSE +0 -0
  66. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/config/nginx-forgeo.conf +0 -0
  67. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/img/console.png +0 -0
  68. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/img/logo.png +0 -0
  69. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/docs/img/title.svg +0 -0
  70. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/forgeo.spec +0 -0
  71. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/mkdocs.yml +0 -0
  72. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/__main__.py +0 -0
  73. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/config.py +0 -0
  74. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/instances.py +0 -0
  75. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/io.py +0 -0
  76. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/src/forgeo/web/central/index.html +0 -0
  77. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_instances.py +0 -0
  78. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_io.py +0 -0
  79. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/tests/test_web_common.py +0 -0
  80. {forgeo_cli-0.4.0 → forgeo_cli-0.5.0}/www/404.html +0 -0
@@ -35,7 +35,10 @@ jobs:
35
35
  fail-fast: false
36
36
  matrix:
37
37
  include:
38
- - os: ubuntu-latest
38
+ # Linux is built on ubuntu-22.04 (glibc 2.35) so the binary also
39
+ # runs on older distros (Homebrew-on-Linux, Ubuntu 22.04+): a build
40
+ # on ubuntu-latest (24.04) would require glibc >= 2.38.
41
+ - os: ubuntu-22.04
39
42
  asset: forgeo-linux-amd64
40
43
  binary: dist/forgeo
41
44
  - os: macos-15-intel
@@ -90,6 +93,24 @@ jobs:
90
93
  with:
91
94
  path: binaries
92
95
  merge-multiple: true
96
+ - name: Extract release notes from CHANGELOG.md
97
+ id: notes
98
+ run: |
99
+ python - "${{ github.ref_name }}" <<'EOF'
100
+ import re
101
+ import sys
102
+ import pathlib
103
+
104
+ version = sys.argv[1].removeprefix("v")
105
+ text = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8")
106
+ pattern = re.compile(
107
+ rf"^## \[{re.escape(version)}\] .*?\n(.*?)(?=^## \[|\Z)",
108
+ re.MULTILINE | re.DOTALL,
109
+ )
110
+ match = pattern.search(text)
111
+ notes = match.group(1).strip() if match else ""
112
+ pathlib.Path("release-body.md").write_text(notes, encoding="utf-8")
113
+ EOF
93
114
  - name: Publish GitHub Release
94
115
  uses: softprops/action-gh-release@v2
95
116
  with:
@@ -97,6 +118,44 @@ jobs:
97
118
  dist/*.whl
98
119
  dist/*.tar.gz
99
120
  binaries/*
121
+ body_path: release-body.md
122
+
123
+ publish-homebrew:
124
+ if: startsWith(github.ref, 'refs/tags/v')
125
+ needs: build-binaries
126
+ runs-on: ubuntu-latest
127
+ steps:
128
+ - uses: actions/checkout@v4
129
+ - uses: actions/setup-python@v5
130
+ with:
131
+ python-version: "3.11"
132
+ - name: Download binary artifacts
133
+ uses: actions/download-artifact@v4
134
+ with:
135
+ path: binaries
136
+ merge-multiple: true
137
+ - name: Render the Homebrew formula
138
+ run: python scripts/render_homebrew_formula.py "${{ github.ref_name }}" binaries > /tmp/forgeo.rb
139
+ - name: Publish the formula to the homebrew-forgeo tap
140
+ env:
141
+ TAP_REPO_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
142
+ run: |
143
+ set -eu
144
+ if [ -z "$TAP_REPO_TOKEN" ]; then
145
+ echo "::error::HOMEBREW_TAP_TOKEN secret is not set. Create a PAT with write access to lucaGazzola/homebrew-forgeo and store it as HOMEBREW_TAP_TOKEN; without it the Homebrew tap is not updated for this release."
146
+ exit 1
147
+ fi
148
+ git clone "https://x-access-token:${TAP_REPO_TOKEN}@github.com/lucaGazzola/homebrew-forgeo.git" tap
149
+ cp /tmp/forgeo.rb tap/Formula/forgeo.rb
150
+ git -C tap config user.name "Forgeo release bot"
151
+ git -C tap config user.email "forgeo-releases@users.noreply.github.com"
152
+ if git -C tap diff --quiet; then
153
+ echo "Formula unchanged; nothing to push."
154
+ else
155
+ git -C tap add Formula/forgeo.rb
156
+ git -C tap commit -m "forgeo ${{ github.ref_name }}"
157
+ git -C tap push origin main
158
+ fi
100
159
 
101
160
  publish-pypi:
102
161
  if: startsWith(github.ref, 'refs/tags/v')
@@ -27,6 +27,7 @@ build/
27
27
  dist/
28
28
 
29
29
  # AI assistant context
30
+ AGENTS.md
30
31
  CONTEXT.md
31
32
 
32
33
  # Local deploy script (server-specific paths)
@@ -7,6 +7,114 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-08-14
11
+
12
+ ### Added
13
+
14
+ - `forgeo validate` (and the pre-flight check before a detached
15
+ `forgeo start`) distinguishes a repository with no commits yet: a clean
16
+ tree is now a warning — the first cycle creates the initial commit — while
17
+ a non-clean tree is a problem that names the fix
18
+ (`git add -A && git commit -m "Initial commit"`), since every file is
19
+ untracked and every cycle would otherwise refuse as dirty. Previously
20
+ "no commits" was always a hard problem with a misleading message.
21
+
22
+ - `forgeo start` now starts the daemon **detached in the background and
23
+ exits**, like `forgeo restart` and the web console's start button already
24
+ did; the daemon is managed with `forgeo stop`/`forgeo restart`/`forgeo
25
+ status`. `forgeo start -f` (`--foreground`) keeps the historical
26
+ foreground behavior. A detached start refuses while the per-forgeo lock is
27
+ held and runs the same read-only checks as `forgeo validate` first, so a
28
+ broken config fails fast instead of leaving a silently dead daemon.
29
+ `--interval-minutes` is forwarded to the detached daemon.
30
+
31
+ - The daemon reloads `forgeo.yaml` on the next cycle boundary when the file
32
+ changes (or on `SIGHUP`): a valid change is revalidated, logged, and used
33
+ from the next cycle; an invalid change is logged and the last valid config
34
+ stays in use. The web console's config save reflects this
35
+ (`restart_required: false`). Path changes (`repo`, `backlog`,
36
+ `blocker_file`, `log_file`) stay pinned to the daemon's startup values and
37
+ still need `forgeo restart`, so the daemon's lock files are never detached
38
+ from the config.
39
+
40
+ - Optional bearer-token auth for the central web dashboard (`forgeo web`):
41
+ `forgeo web --token` (or a `token` key in `~/.config/forgeo/web.toml`)
42
+ requires `Authorization: Bearer <token>` on every `/api/*` route and
43
+ answers `401` otherwise. `forgeo web --token` with no value generates a
44
+ token, prints it once on startup, and saves it (mode `0600`); a generated
45
+ token is only ever printed once. Static assets and the new token prompt
46
+ page (`/central/login.html`) stay reachable without a token, and a
47
+ `?token=...` URL signs the browser in automatically. With no flag and no
48
+ token file the dashboard keeps its historical open-by-default behavior.
49
+
50
+ - `forgeo validate` — a read-only dry run that checks whether a forgeo is
51
+ ready to run before starting it: the config schema, the repository (exists,
52
+ is a git repo, `git` on PATH), the branch and remote resolution, the
53
+ backlog parsing, a non-blank agent command, and the run lock state. It
54
+ reports every problem at once, never invokes the agent, and makes no writes
55
+ (no lock, no backlog changes). Exit code `0` when healthy, `1` with a
56
+ summary of problems otherwise. Supports `--config` and `--name`.
57
+
58
+ - `run_history_keep` config key: `runs.jsonl` is trimmed to that many records
59
+ on append (default `2000`), so a busy Forgeo's run history never grows
60
+ forever. Trimming is atomic (temp file + rename) and a failed trim is
61
+ logged and skipped, never fatal to the cycle; `run_history_keep: 0`
62
+ disables retention entirely (the previous grow-forever behavior).
63
+
64
+ - Persisted agent output per run: each `RunRecord` now stores a bounded tail
65
+ of the agent's stdout/stderr (last `run_output_lines` lines, default `200`),
66
+ so failed and blocked runs keep the full tail of what the agent said. The
67
+ web console's **History** tab shows it in a read-only, monospace,
68
+ collapsible view; `run_output_lines: 0` disables persistence, and records
69
+ written before the field existed render as empty.
70
+
71
+ - Backlog snapshots: before every agent run (and on daemon startup) Forgeo
72
+ copies the backlog to a rotating snapshot (`backlog.json.bak`,
73
+ `backlog.json.bak.1`, ... keeping the last 2 by default), and a read that
74
+ finds the backlog corrupt restores the newest valid snapshot in place —
75
+ with the corrupt file still preserved — instead of falling back to an
76
+ empty store. A missing backlog is a no-op.
77
+
78
+ - Task dependencies are now enforced when picking the next task: Forgeo picks
79
+ the oldest `OPEN` task whose `dependencies` are all `COMPLETED` instead of
80
+ the plain oldest `OPEN` task, so a task never runs before the work it
81
+ depends on. Unsatisfied dependencies (including ids that don't exist in the
82
+ backlog) are surfaced on `forgeo status` (`waiting on:` line) and in the web
83
+ console's task detail modal (*Waiting on dependencies* banner); the
84
+ `GET /api/instances/<name>/tasks*` responses annotate each task with an
85
+ `unsatisfied_dependencies` field.
86
+
87
+ - Homebrew install support: `brew install lucaGazzola/forgeo/forgeo`
88
+ installs the prebuilt binary on macOS (arm64/Intel) and Linux (Intel). The
89
+ `publish-homebrew` CI job re-renders the tap formula (sha256 + version)
90
+ from `scripts/render_homebrew_formula.py` on every release; it needs the
91
+ `HOMEBREW_TAP_TOKEN` repository secret (PAT with write access to
92
+ `lucaGazzola/homebrew-forgeo`). The update notification now also names
93
+ `brew upgrade lucaGazzola/forgeo/forgeo`.
94
+
95
+ - Update notification: when `forgeo start` or `forgeo once` begins a cycle,
96
+ Forgeo checks PyPI at most once a day and, if a newer `forgeo-cli` release
97
+ exists, prints/logs a short notice with the upgrade command. The check is
98
+ best-effort (short timeout, failures logged and skipped), never modifies
99
+ the install, and can be disabled with `FORGEO_UPDATE_CHECK=0`.
100
+
101
+ - Automatic retries for `FAILED` tasks: `failed_retry_max` config key (default
102
+ `0`, unchanged behavior) plus `failed_retry_wait_cycles` (default `1`) let a
103
+ transiently failed task move back to `OPEN` after a backoff and be run
104
+ again. A task that exhausts its budget stays `FAILED` with its original
105
+ `failure_reason`; a per-task `retries_left` field overrides the budget for
106
+ one task. `BLOCKED` tasks are never auto-retried. The retry count is
107
+ recorded in `runs.jsonl` (the run record that succeeds carries it), shown
108
+ in the web console (task cards/modal and a History-tab **retry** column),
109
+ and exposed by the tasks/runs API.
110
+
111
+ ### Fixed
112
+
113
+ - The Linux prebuilt binary is now built on Ubuntu 22.04 (glibc 2.35) instead
114
+ of 24.04 (glibc 2.38), so it runs on older distros (e.g. Ubuntu 22.04,
115
+ Debian 12, Homebrew-on-Linux). The 0.4.0 `forgeo-linux-amd64` release
116
+ asset was rebuilt and re-uploaded with the same version number.
117
+
10
118
  ## [0.4.0] - 2026-08-10
11
119
 
12
120
  ### Added
@@ -142,7 +250,8 @@ Initial release of the scheduled, agent-driven software forgeo.
142
250
  overlapping-run skipping.
143
251
  - Dogfooding docs removed; local configs kept out of the repository.
144
252
 
145
- [Unreleased]: https://github.com/lucaGazzola/forgeo/compare/v0.3.0...HEAD
253
+ [Unreleased]: https://github.com/lucaGazzola/forgeo/compare/v0.5.0...HEAD
254
+ [0.5.0]: https://github.com/lucaGazzola/forgeo/compare/v0.4.0...v0.5.0
146
255
  [0.4.0]: https://github.com/lucaGazzola/forgeo/compare/v0.3.0...v0.4.0
147
256
  [0.3.0]: https://github.com/lucaGazzola/forgeo/compare/v0.2.0...v0.3.0
148
257
  [0.2.1]: https://github.com/lucaGazzola/forgeo/compare/v0.2.0...v0.2.1
@@ -80,15 +80,20 @@ Human contributions use the normal GitHub flow:
80
80
 
81
81
  Releases are cut from `main` and published as GitHub Releases. Tagging the
82
82
  repo triggers CI, which builds the wheel, sdist, **and prebuilt standalone
83
- binaries** and attaches them to the release — there is no PyPI publishing yet,
84
- and `install.sh` downloads the matching prebuilt binary from the release
85
- (`pipx`/`pip` fallback only when no binary matches the platform).
83
+ binaries** and attaches them to the release; `install.sh` downloads the
84
+ matching prebuilt binary from the release (`pipx`/`pip` fallback only when no
85
+ binary matches the platform), and the `publish-homebrew` job re-renders and
86
+ pushes the formula of the `lucaGazzola/homebrew-forgeo` tap.
86
87
 
87
88
  > Patch and minor releases **must** include the built binaries, otherwise the
88
89
  > `install.sh` binary path (the default, no-Python install) breaks. The CI
89
90
  > `build-binaries` job builds them automatically on any `v*` tag, but make
90
91
  > sure the release actually carries them — the `forgeo-<os>-<arch>` assets
91
92
  > listed below are what the installer downloads.
93
+ >
94
+ > The `publish-homebrew` job needs the `HOMEBREW_TAP_TOKEN` repository secret
95
+ > (a PAT with write access to `lucaGazzola/homebrew-forgeo`); a release cut
96
+ > without it fails that job and leaves the tap outdated until re-run.
92
97
 
93
98
  1. Confirm the [quality gates](#quality-gates) are green on `main`.
94
99
  2. Bump the version in `pyproject.toml` (`version = "x.y.z"`) and in
@@ -114,6 +119,8 @@ and `install.sh` downloads the matching prebuilt binary from the release
114
119
  `forgeo-linux-amd64`, `forgeo-darwin-amd64`, `forgeo-darwin-arm64`,
115
120
  `forgeo-windows-amd64.exe` binaries) are listed under
116
121
  <https://github.com/lucaGazzola/forgeo/releases>.
122
+ 8. Confirm the `publish-homebrew` job updated the tap: `brew update` and
123
+ `brew upgrade lucaGazzola/forgeo/forgeo` should now install the new version.
117
124
 
118
125
  ## License
119
126
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: forgeo-cli
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: A scheduled software forgeo: executes backlog tasks on main, refactors when idle, and writes BLOCKER.md when it needs human input.
5
5
  Project-URL: Homepage, https://forgeo.org
6
6
  Project-URL: Documentation, https://forgeo.org
@@ -71,7 +71,10 @@ a backlog, and it decides what to work on next, runs your
71
71
  agent on it, and commits the result. Progress, pending decisions, and history
72
72
  are tracked in plain files you can inspect at any time, plus a web dashboard.
73
73
  Forgeo only interrupts you when a decision is genuinely yours to
74
- make, everything else happens autonomously.
74
+ make, everything else happens autonomously. Transient failures (a network
75
+ blip, a flaky test) are retried automatically when the retry policy is
76
+ enabled, and only a task that keeps failing or genuinely needs a human
77
+ decision ever reaches you.
75
78
 
76
79
  All you need is basic comfort with a terminal, a git repository, and any coding
77
80
  agent CLI.
@@ -81,29 +84,41 @@ agent CLI.
81
84
  The full walkthrough is in [Getting started](docs/getting-started.md).
82
85
 
83
86
  ```bash
84
- # 1. Install
85
- curl -fsSL https://forgeo.org/install.sh | bash
87
+ # 1. Install (any one of these)
88
+
89
+ # Homebrew (macOS / Linux)
90
+ brew install lucaGazzola/forgeo/forgeo
86
91
 
87
- or
92
+ # or: the one-liner (prebuilt binary, no Python required)
93
+ curl -fsSL https://forgeo.org/install.sh | bash
88
94
 
95
+ # or: pipx
89
96
  pipx install forgeo-cli
90
97
 
91
98
  # 2. Create your Forgeo (guided wizard, run from your project root)
92
99
  forgeo init
93
100
 
94
- # 3. Start Forgeo
95
- forgeo start # run forever: every interval_minutes, implement the oldest OPEN task
101
+ # 3. Start Forgeo (detached in the background; exits immediately)
102
+ forgeo start # every interval_minutes, implement the oldest OPEN task (dependencies first)
103
+ forgeo stop # stop the daemon again
96
104
  ```
97
105
 
98
106
  `forgeo init` writes `forgeo.yaml` and a `.forgeo/` folder for the backlog
99
107
  and logs. Fill the backlog with plain JSON tasks (see
100
108
  [Backlog format](docs/backlog.md)) or add them from the web console while
101
- it runs, Forgeo does the rest. Open the dashboard with `forgeo web` (default <http://0.0.0.0:8790>):
109
+ it runs, Forgeo does the rest. Open the dashboard with `forgeo web` (default <http://0.0.0.0:8790>), or keep it always-on with `forgeo web -d` (stop it with `forgeo web stop`, check it with `forgeo web status`):
102
110
 
103
111
  ![Forgeo web console](docs/img/console.png)
104
112
 
113
+ By default the dashboard is open to anyone who can reach the port. On a
114
+ shared host, protect it with bearer-token auth: `forgeo web --token`
115
+ generates a token (printed once, saved to `~/.config/forgeo/web.toml`) and
116
+ requires `Authorization: Bearer <token>` on every `/api/*` route — see
117
+ [Web console & HTTP API](docs/web-console-api.md).
118
+
105
119
  One-off commands: `forgeo once` (single cycle), `forgeo status` (summary),
106
- `forgeo stop`, `forgeo restart`, every command is in the
120
+ `forgeo validate` (read-only dry run before starting), `forgeo stop`,
121
+ `forgeo restart`, every command is in the
107
122
  [CLI reference](docs/cli-reference.md).
108
123
 
109
124
  You can run several factories at once, one per repository, each config is
@@ -125,7 +140,10 @@ central dashboard, `forgeo web`.
125
140
  | Web dashboard & HTTP API | [Web console & HTTP API](docs/web-console-api.md) |
126
141
 
127
142
  Everything is stored in plain files: the backlog, `forgeo.log`, and
128
- `BLOCKER.md` whenever a decision is pending.
143
+ `BLOCKER.md` whenever a decision is pending. The backlog is snapshotted
144
+ (rotating `backlog.json.bak` files) before every agent run and on daemon
145
+ startup, and restored automatically if it is ever found corrupt — a bad write
146
+ never loses your tasks.
129
147
 
130
148
  ## Develop
131
149
 
@@ -17,7 +17,10 @@ a backlog, and it decides what to work on next, runs your
17
17
  agent on it, and commits the result. Progress, pending decisions, and history
18
18
  are tracked in plain files you can inspect at any time, plus a web dashboard.
19
19
  Forgeo only interrupts you when a decision is genuinely yours to
20
- make, everything else happens autonomously.
20
+ make, everything else happens autonomously. Transient failures (a network
21
+ blip, a flaky test) are retried automatically when the retry policy is
22
+ enabled, and only a task that keeps failing or genuinely needs a human
23
+ decision ever reaches you.
21
24
 
22
25
  All you need is basic comfort with a terminal, a git repository, and any coding
23
26
  agent CLI.
@@ -27,29 +30,41 @@ agent CLI.
27
30
  The full walkthrough is in [Getting started](docs/getting-started.md).
28
31
 
29
32
  ```bash
30
- # 1. Install
31
- curl -fsSL https://forgeo.org/install.sh | bash
33
+ # 1. Install (any one of these)
34
+
35
+ # Homebrew (macOS / Linux)
36
+ brew install lucaGazzola/forgeo/forgeo
32
37
 
33
- or
38
+ # or: the one-liner (prebuilt binary, no Python required)
39
+ curl -fsSL https://forgeo.org/install.sh | bash
34
40
 
41
+ # or: pipx
35
42
  pipx install forgeo-cli
36
43
 
37
44
  # 2. Create your Forgeo (guided wizard, run from your project root)
38
45
  forgeo init
39
46
 
40
- # 3. Start Forgeo
41
- forgeo start # run forever: every interval_minutes, implement the oldest OPEN task
47
+ # 3. Start Forgeo (detached in the background; exits immediately)
48
+ forgeo start # every interval_minutes, implement the oldest OPEN task (dependencies first)
49
+ forgeo stop # stop the daemon again
42
50
  ```
43
51
 
44
52
  `forgeo init` writes `forgeo.yaml` and a `.forgeo/` folder for the backlog
45
53
  and logs. Fill the backlog with plain JSON tasks (see
46
54
  [Backlog format](docs/backlog.md)) or add them from the web console while
47
- it runs, Forgeo does the rest. Open the dashboard with `forgeo web` (default <http://0.0.0.0:8790>):
55
+ it runs, Forgeo does the rest. Open the dashboard with `forgeo web` (default <http://0.0.0.0:8790>), or keep it always-on with `forgeo web -d` (stop it with `forgeo web stop`, check it with `forgeo web status`):
48
56
 
49
57
  ![Forgeo web console](docs/img/console.png)
50
58
 
59
+ By default the dashboard is open to anyone who can reach the port. On a
60
+ shared host, protect it with bearer-token auth: `forgeo web --token`
61
+ generates a token (printed once, saved to `~/.config/forgeo/web.toml`) and
62
+ requires `Authorization: Bearer <token>` on every `/api/*` route — see
63
+ [Web console & HTTP API](docs/web-console-api.md).
64
+
51
65
  One-off commands: `forgeo once` (single cycle), `forgeo status` (summary),
52
- `forgeo stop`, `forgeo restart`, every command is in the
66
+ `forgeo validate` (read-only dry run before starting), `forgeo stop`,
67
+ `forgeo restart`, every command is in the
53
68
  [CLI reference](docs/cli-reference.md).
54
69
 
55
70
  You can run several factories at once, one per repository, each config is
@@ -71,7 +86,10 @@ central dashboard, `forgeo web`.
71
86
  | Web dashboard & HTTP API | [Web console & HTTP API](docs/web-console-api.md) |
72
87
 
73
88
  Everything is stored in plain files: the backlog, `forgeo.log`, and
74
- `BLOCKER.md` whenever a decision is pending.
89
+ `BLOCKER.md` whenever a decision is pending. The backlog is snapshotted
90
+ (rotating `backlog.json.bak` files) before every agent run and on daemon
91
+ startup, and restored automatically if it is ever found corrupt — a bad write
92
+ never loses your tasks.
75
93
 
76
94
  ## Develop
77
95
 
@@ -39,11 +39,37 @@ The exit code decides the outcome of the run:
39
39
  | Exit code | Outcome | What happens |
40
40
  | --- | --- | --- |
41
41
  | `0` | **SUCCESS** | Everything is committed (`git add -A && git commit`) with the message `<title> (#<id>)`, pushed when a remote is set, and the task is marked `COMPLETED`. |
42
- | `blocked_exit_code` (default `2`) | **BLOCKED** | The agent needs a human decision. Partial work is committed as `<title> (#<id>) [partial]`, the agent's reason is persisted on the task (`blocker_reason`), an optional Telegram notification is sent, and the task is marked `BLOCKED`. `BLOCKER.md` is rendered from the backlog's `BLOCKED` tasks on the next cycle — real per-task reasons, never generic text — and disappears once the last one is resolved (reopen it from the web console). |
42
+ | `no_changes_exit_code` (default `3`) | **SUCCESS, no changes** | The agent explicitly reports the task needs **no code change**: the task is marked `COMPLETED` without a commit (and the run record notes why). Only accepted when the working tree is clean. |
43
+ | `blocked_exit_code` (default `2`) | **BLOCKED** | The agent needs a human decision. Partial work is committed as `<title> [partial]`, the agent's reason is persisted on the task (`blocker_reason`), optional Telegram and/or webhook notifications are sent, and the task is marked `BLOCKED`. `BLOCKER.md` is rendered from the backlog's `BLOCKED` tasks on the next cycle — real per-task reasons, never generic text — and disappears once the last one is resolved (reopen it from the web console). |
43
44
  | anything else | **ERROR** | Changes are discarded (`git reset --hard` + `git clean -fd`), the failure is logged, and the task is marked `FAILED`. |
44
45
 
45
46
  The blocked exit code is configurable via `blocked_exit_code` in
46
- [forgeo.yaml](configuration.md).
47
+ [forgeo.yaml](configuration.md), and the no-change exit code via
48
+ `no_changes_exit_code`.
49
+
50
+ A `FAILED` task stays `FAILED` until a human reopens it — unless the retry
51
+ policy is enabled (`failed_retry_max`, see [Configuration](configuration.md)),
52
+ in which case Forgeo moves the task back to `OPEN` after
53
+ `failed_retry_wait_cycles` cycles and runs it again, incrementing its retry
54
+ count. `BLOCKED` is never retried automatically: it always waits for a human.
55
+
56
+ ## The no-change contract
57
+
58
+ Forgeo cannot tell "the agent deliberately made no changes" from "the agent
59
+ did nothing". A `SUCCESS` exit that produces **no changes is therefore not a
60
+ valid completion for a task**:
61
+
62
+ - exiting `0` while leaving the working tree **unchanged** fails the task
63
+ (`FAILED`, reason: *"Agent exited 0 but produced no changes"*);
64
+ - to complete a task **without touching the code**, exit
65
+ `no_changes_exit_code` (default `3`). The working tree must be clean — an
66
+ agent that reports "no changes" while leaving uncommitted work behind fails
67
+ instead.
68
+
69
+ Refactoring passes are the exception: when the backlog is empty, a refactor
70
+ that finds nothing to improve is a normal, successful run (the default
71
+ refactor prompt already says "if nothing needs refactoring, make no
72
+ changes").
47
73
 
48
74
  ## Timeouts
49
75
 
@@ -84,6 +110,8 @@ that, based on the exit code. The working contract is:
84
110
  - make your changes in the repository;
85
111
  - **do not** run `git add`, `git commit`, `git push`, or reset the tree;
86
112
  - exit `0` to have your changes committed and pushed as one commit;
113
+ - exit `no_changes_exit_code` when the task needs no code change (never exit
114
+ `0` with an empty tree — that fails the task);
87
115
  - exit `blocked_exit_code` to have partial work preserved and a blocker
88
116
  written;
89
117
  - exit anything else to have your changes discarded.