forgeo-cli 0.7.2__tar.gz → 0.8.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 (81) hide show
  1. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/.gitignore +2 -1
  2. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/CHANGELOG.md +26 -1
  3. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/PKG-INFO +33 -8
  4. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/README.md +32 -7
  5. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/backlog.md +78 -2
  6. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/cli-reference.md +3 -2
  7. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/configuration.md +89 -3
  8. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/getting-started.md +7 -1
  9. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/index.md +5 -5
  10. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/web-console-api.md +17 -13
  11. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/install.sh +1 -1
  12. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/pyproject.toml +1 -1
  13. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/__init__.py +1 -1
  14. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/backlog.py +139 -47
  15. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/backlog_http.py +4 -2
  16. forgeo_cli-0.8.0/src/forgeo/backlog_jira.py +1090 -0
  17. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/central.py +8 -9
  18. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/cli.py +6 -5
  19. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/config.py +7 -7
  20. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/daemon.py +1 -1
  21. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/forgeo.py +24 -10
  22. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/models.py +207 -12
  23. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/paths.py +3 -3
  24. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/validate.py +16 -3
  25. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/web/central/central.js +6 -0
  26. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/web/central/instance.html +10 -0
  27. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/conftest.py +13 -0
  28. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_backlog.py +115 -22
  29. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_backlog_http.py +2 -2
  30. forgeo_cli-0.8.0/tests/test_backlog_jira.py +455 -0
  31. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_factory.py +2 -2
  32. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_install.py +14 -1
  33. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_models.py +90 -0
  34. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_runs.py +7 -7
  35. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_web.py +28 -0
  36. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/.github/workflows/ci.yml +0 -0
  37. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/CONTRIBUTING.md +0 -0
  38. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/LICENSE +0 -0
  39. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/config/nginx-forgeo.conf +0 -0
  40. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/agent-contract.md +0 -0
  41. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/img/console.png +0 -0
  42. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/img/demo.gif +0 -0
  43. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/img/logo.png +0 -0
  44. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/img/og.png +0 -0
  45. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/docs/img/title.svg +0 -0
  46. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/forgeo.spec +0 -0
  47. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/mkdocs.yml +0 -0
  48. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/scripts/__init__.py +0 -0
  49. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/scripts/render_homebrew_formula.py +0 -0
  50. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/__main__.py +0 -0
  51. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/agent.py +0 -0
  52. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/daemon_control.py +0 -0
  53. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/git.py +0 -0
  54. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/instances.py +0 -0
  55. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/io.py +0 -0
  56. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/notify.py +0 -0
  57. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/oauth.py +0 -0
  58. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/runs.py +0 -0
  59. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/setup.py +0 -0
  60. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/update.py +0 -0
  61. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/web/central/central.css +0 -0
  62. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/web/central/index.html +0 -0
  63. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/web/central/login.html +0 -0
  64. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/web/style.css +0 -0
  65. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/src/forgeo/web_common.py +0 -0
  66. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_agent.py +0 -0
  67. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_cli.py +0 -0
  68. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_daemon.py +0 -0
  69. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_git.py +0 -0
  70. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_instances.py +0 -0
  71. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_io.py +0 -0
  72. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_oauth.py +0 -0
  73. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_paths.py +0 -0
  74. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_remote_backlog_cycle.py +0 -0
  75. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_render_homebrew.py +0 -0
  76. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_setup.py +0 -0
  77. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_update.py +0 -0
  78. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_web_common.py +0 -0
  79. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/tests/test_web_lock.py +0 -0
  80. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/www/404.html +0 -0
  81. {forgeo_cli-0.7.2 → forgeo_cli-0.8.0}/www/index.html +0 -0
@@ -29,6 +29,7 @@ dist/
29
29
  # AI assistant context
30
30
  AGENTS.md
31
31
  CONTEXT.md
32
+ CLAUDE.md
32
33
 
33
34
  # Local deploy script (server-specific paths)
34
- scripts/deploy-docs.sh
35
+ scripts/deploy-docs.sh
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-08-21
11
+
12
+ ### Added
13
+
14
+ - A Jira task provider selected with `backlog_provider: jira`, including Jira
15
+ Cloud cursor pagination, workflow-based claiming, optional custom-field
16
+ mappings, issue-property engine state, stale-claim recovery, and bearer or
17
+ basic authentication from environment variables.
18
+ - A provider-level claim hook so remote task sources can prevent duplicate
19
+ agent runs while preserving the existing JSON and HTTP backlog behavior.
20
+
21
+ ## [0.7.3] - 2026-08-19
22
+
23
+ ### Added
24
+
25
+ - The agent's stdout/stderr is now persisted on the task as `agent_response`
26
+ when a task transitions (BLOCKED/FAILED/COMPLETED), shown in the web
27
+ console's task modal. A transition that carries no output never wipes a
28
+ previously stored response.
29
+ - `agent_response_lines` config key: how many agent output lines the task's
30
+ `agent_response` keeps on a status transition. Unbounded by default; `0`
31
+ disables persisting agent output on the task.
32
+
10
33
  ## [0.7.2] - 2026-08-18
11
34
 
12
35
  ### Added
@@ -337,7 +360,9 @@ Initial release of the scheduled, agent-driven software forgeo.
337
360
  overlapping-run skipping.
338
361
  - Dogfooding docs removed; local configs kept out of the repository.
339
362
 
340
- [Unreleased]: https://github.com/lucaGazzola/forgeo/compare/v0.7.2...HEAD
363
+ [Unreleased]: https://github.com/lucaGazzola/forgeo/compare/v0.8.0...HEAD
364
+ [0.8.0]: https://github.com/lucaGazzola/forgeo/compare/v0.7.3...v0.8.0
365
+ [0.7.3]: https://github.com/lucaGazzola/forgeo/compare/v0.7.2...v0.7.3
341
366
  [0.7.2]: https://github.com/lucaGazzola/forgeo/compare/v0.7.1...v0.7.2
342
367
  [0.7.1]: https://github.com/lucaGazzola/forgeo/compare/v0.7.0...v0.7.1
343
368
  [0.7.0]: https://github.com/lucaGazzola/forgeo/compare/v0.6.0...v0.7.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: forgeo-cli
3
- Version: 0.7.2
3
+ Version: 0.8.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
@@ -121,6 +121,9 @@ configuration, start the daemon.
121
121
  # Backlog format), created on first use:
122
122
  # .forgeo/backlog.json
123
123
 
124
+ # Or: configure `backlog_provider: jira` and set `backlog` to a Jira base URL
125
+ # (see the Jira backlog documentation).
126
+
124
127
  # Or: add tasks from the web console once your forgeo is registered
125
128
  # (first `forgeo start` registers it automatically):
126
129
  forgeo web # dashboard at http://0.0.0.0:8790, or keep it on with `forgeo web -d`
@@ -167,6 +170,27 @@ shared host protect it with `forgeo web --token` (requires
167
170
  `Authorization: Bearer <token>` on every `/api/*` route — see
168
171
  [Web console & HTTP API](docs/web-console-api.md)).
169
172
 
173
+ ### Running the agent in a container
174
+
175
+ By default Forgeo runs the agent directly on the host. To run it inside a
176
+ Docker container instead, set `agent_sandbox: docker` in `forgeo.yaml`:
177
+
178
+ ```yaml
179
+ agent_sandbox: docker
180
+ agent_sandbox_image: your-image
181
+ agent_sandbox_network: none # default; set bridge/host to allow network
182
+ agent_sandbox_mounts: # optional, read-only, e.g. ~/.claude
183
+ - ~/.claude
184
+ ```
185
+
186
+ The image must already contain the agent CLI your `agent_command` uses plus a
187
+ shell (nothing is installed at run time). Forgeo bind-mounts the repository
188
+ into the container at the same path, so the agent's edits land on your
189
+ checkout and are committed as usual; the task is passed through as
190
+ `FORGEO_TASK`. Networking is off by default (`none`) and nothing else is
191
+ visible inside the container unless you list it in `agent_sandbox_mounts`.
192
+ See the [Configuration](docs/configuration.md) docs for details.
193
+
170
194
  ### Multiple repositories (instances)
171
195
 
172
196
  Run several factories at once, one per repository; each config is fully
@@ -186,14 +210,15 @@ and get one aggregate overview with the central dashboard, `forgeo web`.
186
210
  | All CLI commands | [CLI reference](docs/cli-reference.md) |
187
211
  | Web dashboard & HTTP API | [Web console & HTTP API](docs/web-console-api.md) |
188
212
 
189
- Everything is stored in plain files: the backlog, `forgeo.log`, and
213
+ Everything is stored in plain files: the local backlog, `forgeo.log`, and
190
214
  `BLOCKER.md` whenever a decision is pending. The backlog can also live in
191
- another application behind an `http(s)` URL Forgeo reads the whole task
192
- document with `GET` and writes it back with `POST`, with optional OAuth2
193
- client-credentials auth (see [Backlog format](docs/backlog.md)). A *file*
194
- backlog is snapshotted (rotating `backlog.json.bak` files) before every
195
- agent run and on daemon startup, and restored automatically if it is ever
196
- found corrupt — a bad write never loses your tasks.
215
+ another application behind an `http(s)` URL, or in Jira. HTTP backlogs
216
+ exchange the complete task document; Jira issues are read and transitioned
217
+ individually with workflow state and engine metadata stored on the issue (see
218
+ [Backlog format](docs/backlog.md)). A *file* backlog is snapshotted (rotating
219
+ `backlog.json.bak` files) before every agent run and on daemon startup, and
220
+ restored automatically if it is ever found corrupt — a bad write never loses
221
+ your tasks.
197
222
 
198
223
  ## Develop
199
224
 
@@ -67,6 +67,9 @@ configuration, start the daemon.
67
67
  # Backlog format), created on first use:
68
68
  # .forgeo/backlog.json
69
69
 
70
+ # Or: configure `backlog_provider: jira` and set `backlog` to a Jira base URL
71
+ # (see the Jira backlog documentation).
72
+
70
73
  # Or: add tasks from the web console once your forgeo is registered
71
74
  # (first `forgeo start` registers it automatically):
72
75
  forgeo web # dashboard at http://0.0.0.0:8790, or keep it on with `forgeo web -d`
@@ -113,6 +116,27 @@ shared host protect it with `forgeo web --token` (requires
113
116
  `Authorization: Bearer <token>` on every `/api/*` route — see
114
117
  [Web console & HTTP API](docs/web-console-api.md)).
115
118
 
119
+ ### Running the agent in a container
120
+
121
+ By default Forgeo runs the agent directly on the host. To run it inside a
122
+ Docker container instead, set `agent_sandbox: docker` in `forgeo.yaml`:
123
+
124
+ ```yaml
125
+ agent_sandbox: docker
126
+ agent_sandbox_image: your-image
127
+ agent_sandbox_network: none # default; set bridge/host to allow network
128
+ agent_sandbox_mounts: # optional, read-only, e.g. ~/.claude
129
+ - ~/.claude
130
+ ```
131
+
132
+ The image must already contain the agent CLI your `agent_command` uses plus a
133
+ shell (nothing is installed at run time). Forgeo bind-mounts the repository
134
+ into the container at the same path, so the agent's edits land on your
135
+ checkout and are committed as usual; the task is passed through as
136
+ `FORGEO_TASK`. Networking is off by default (`none`) and nothing else is
137
+ visible inside the container unless you list it in `agent_sandbox_mounts`.
138
+ See the [Configuration](docs/configuration.md) docs for details.
139
+
116
140
  ### Multiple repositories (instances)
117
141
 
118
142
  Run several factories at once, one per repository; each config is fully
@@ -132,14 +156,15 @@ and get one aggregate overview with the central dashboard, `forgeo web`.
132
156
  | All CLI commands | [CLI reference](docs/cli-reference.md) |
133
157
  | Web dashboard & HTTP API | [Web console & HTTP API](docs/web-console-api.md) |
134
158
 
135
- Everything is stored in plain files: the backlog, `forgeo.log`, and
159
+ Everything is stored in plain files: the local backlog, `forgeo.log`, and
136
160
  `BLOCKER.md` whenever a decision is pending. The backlog can also live in
137
- another application behind an `http(s)` URL Forgeo reads the whole task
138
- document with `GET` and writes it back with `POST`, with optional OAuth2
139
- client-credentials auth (see [Backlog format](docs/backlog.md)). A *file*
140
- backlog is snapshotted (rotating `backlog.json.bak` files) before every
141
- agent run and on daemon startup, and restored automatically if it is ever
142
- found corrupt — a bad write never loses your tasks.
161
+ another application behind an `http(s)` URL, or in Jira. HTTP backlogs
162
+ exchange the complete task document; Jira issues are read and transitioned
163
+ individually with workflow state and engine metadata stored on the issue (see
164
+ [Backlog format](docs/backlog.md)). A *file* backlog is snapshotted (rotating
165
+ `backlog.json.bak` files) before every agent run and on daemon startup, and
166
+ restored automatically if it is ever found corrupt — a bad write never loses
167
+ your tasks.
143
168
 
144
169
  ## Develop
145
170
 
@@ -6,7 +6,7 @@ hand, living wherever `backlog:` points in [forgeo.yaml](configuration.md) —
6
6
  `forgeo init`. Keep it outside the repository if you can so the agent never
7
7
  touches it. It can also be [served over HTTP](#a-backlog-over-http) by another
8
8
  application, in which case the document below is exactly what that endpoint
9
- exchanges with Forgeo.
9
+ exchanges with Forgeo, or it can be sourced directly from [Jira](#a-jira-backlog).
10
10
 
11
11
  ```json
12
12
  {
@@ -43,6 +43,7 @@ Each entry in `tasks` is a task object:
43
43
  | <span style="white-space: nowrap">`blocker_reason`</span> | list[string] | `[]` | Engine-managed: the agent's explanation (its questions, falling back to captured output) when the task becomes `BLOCKED`. Cleared on reopen; not editable via `PATCH`. |
44
44
  | <span style="white-space: nowrap">`blocked_count`</span> | integer | `0` | Engine-managed: how many times the task has transitioned into `BLOCKED`. Kept as history when the task is reopened, so you can see a task that keeps blocking needs splitting or rewriting rather than a blind retry. Not editable via `PATCH`. |
45
45
  | <span style="white-space: nowrap">`failure_reason`</span> | list[string] | `[]` | Engine-managed: the agent's error when the task becomes `FAILED` (e.g. a timeout message or a non-zero exit code). Shown in the web console's task modal so you can see why a task failed without opening the logs. Cleared when the task leaves the `FAILED` state; not editable via `PATCH`. |
46
+ | <span style="white-space: nowrap">`agent_response`</span> | string / `null` | `null` | Engine-managed: the agent's last stdout/stderr, stripped of its stream prefixes and persisted on the task's status transitions (bounded by `agent_response_lines` when set). Shown in the web console's task modal; not editable via `PATCH`. |
46
47
  | <span style="white-space: nowrap">`retries_left`</span> | integer / `null` | `null` | Per-task override of the automatic-retry budget (`failed_retry_max` in the config): how many times this task may be retried after a failure. `null` falls back to the config; `0` disables retries for this task. Editable via `PATCH`. |
47
48
  | <span style="white-space: nowrap">`retry_count`</span> | integer | `0` | Engine-managed: how many times this task has already been retried. Shown in `runs.jsonl` and the web console; reset when a human reopens a `FAILED` task. Not editable via `PATCH`. |
48
49
  | <span style="white-space: nowrap">`failed_wait_cycles`</span> | integer | `0` | Engine-managed: how many cycles this task has been `FAILED` awaiting a retry (backed off by `failed_retry_wait_cycles`). Reset when the task leaves `FAILED`. Not editable via `PATCH`. |
@@ -286,7 +287,7 @@ favor of an older valid one; when no snapshot exists, the forgeo falls back to
286
287
  an empty store exactly as before. A missing backlog is a no-op — no snapshot
287
288
  is created for a file that does not exist.
288
289
 
289
- This whole section is about a backlog *file*. A backlog URL is owned by the
290
+ This whole section is about a backlog *file*. A remote backlog is owned by the
290
291
  application serving it, which keeps its own history, so Forgeo neither
291
292
  snapshots nor repairs it — see below.
292
293
 
@@ -348,3 +349,78 @@ There is no backlog file for Forgeo's own runtime files to sit beside, so
348
349
  `runs.jsonl` go into `state_dir`, which defaults to the directory holding
349
350
  `forgeo.yaml`. No snapshots are written: the document belongs to the remote
350
351
  application, so rolling it back is that application's job, not Forgeo's.
352
+
353
+ ## A Jira backlog
354
+
355
+ Set `backlog_provider: jira` and point `backlog:` at the Jira base URL:
356
+
357
+ ```yaml
358
+ backlog_provider: jira
359
+ backlog: https://jira.example.com
360
+
361
+ jira:
362
+ jql: 'project = APP AND labels = forgeo'
363
+ project_key: APP
364
+ auth:
365
+ scheme: basic
366
+ username_env: JIRA_USER
367
+ token_env: JIRA_TOKEN
368
+ workflow:
369
+ open_statuses: ["10000", "10001"]
370
+ open_status: "10000"
371
+ running_status: "3"
372
+ completed_status: "10002"
373
+ ```
374
+
375
+ The JQL is the provider's scope. It should include open, running, blocked and
376
+ completed issues so Forgeo can see dependencies and render the dashboard
377
+ correctly. Jira status references can be names or ids; ids are more stable.
378
+
379
+ ### Mapping and lifecycle
380
+
381
+ - Jira issue keys are Forgeo task ids.
382
+ - `summary`, `description`, `created` and `updated` map to the corresponding task fields.
383
+ - `open_statuses` identifies issues eligible for picking.
384
+ - `running_status` is applied before the agent starts, preventing a second worker from claiming the same issue.
385
+ - `completed_status` is applied after a successful commit.
386
+ - `blocked_status` is optional; the `forgeo-blocked` label is always applied when the agent needs human input.
387
+ - `failed_status` is optional; without it, the `forgeo-failed` label represents a failed task while the issue returns to the configured open status.
388
+
389
+ Forgeo stores blocker reasons, failure reasons, retry counters, claim time and
390
+ bounded agent output in the Jira issue property named `forgeo` by default.
391
+ Set `jira.property_key` to change it. Optional Jira custom fields can carry
392
+ `acceptance_criteria`, `dependencies`, `files_to_modify`, per-task agent
393
+ settings, `run_at`, and `retries_left`. If no custom `run_at` field is
394
+ configured, Jira's native `duedate` is used at midnight UTC:
395
+
396
+ ```yaml
397
+ jira:
398
+ fields:
399
+ acceptance_criteria: customfield_10042
400
+ dependencies: customfield_10043
401
+ ```
402
+
403
+ Dependencies may also be inferred from Jira issue links whose link type is
404
+ `blocks`. The issue that is blocked is treated as depending on the issue that
405
+ blocks it.
406
+
407
+ ### Authentication
408
+
409
+ Jira credentials are never stored directly in `forgeo.yaml`:
410
+
411
+ - `basic` uses `username` or `username_env` plus an API token named by `token_env`.
412
+ - `bearer` uses a personal-access token named by `token_env`.
413
+
414
+ The client uses Jira REST API v3 by default, including Jira Cloud's
415
+ `/search/jql` endpoint and cursor pagination via `nextPageToken`. Set
416
+ `jira.api_version: 2` for Jira installations that expose the older offset-based
417
+ search endpoint and v2 comment/description format.
418
+
419
+ ### Runtime behavior
420
+
421
+ The daemon reads Jira with paginated JQL searches. A task is claimed by
422
+ rechecking it and transitioning it to `running_status` before the agent runs.
423
+ If a process dies while holding a claim, a later cycle releases claims older
424
+ than `claim_timeout_seconds` and returns them to the configured open status.
425
+ An unavailable Jira endpoint fails the cycle; it is never treated as an empty
426
+ backlog.
@@ -184,8 +184,9 @@ It validates, reporting **all** problems at once:
184
184
  would refuse as dirty — make an initial commit first
185
185
  (`git add -A && git commit -m "Initial commit"`);
186
186
  - the remote resolves when `remote` is set (`git remote get-url`);
187
- - the backlog parses and every task is valid (a missing backlog is fine: it
188
- is treated as empty on the first cycle);
187
+ - the backlog parses and every task is valid (a missing file backlog is fine:
188
+ it is treated as empty on the first cycle; HTTP and Jira providers are
189
+ contacted read-only and must answer);
189
190
  - the run lock state (`backlog.lock`); a held lock is a warning, since
190
191
  `forgeo start`/`forgeo once` will refuse to run until it is released.
191
192
 
@@ -22,9 +22,11 @@ paths), so `forgeo restart` is still used for those.
22
22
  | <span style="white-space: nowrap">`interval_minutes`</span> | `60` | How often Forgeo runs (≥ 1). |
23
23
  | <span style="white-space: nowrap">`branch`</span> | `main` | The single branch everything is committed to. |
24
24
  | <span style="white-space: nowrap">`remote`</span> | — | Remote to push to (e.g. `origin`); omit to only commit locally. |
25
- | <span style="white-space: nowrap">`backlog`</span> | `backlog.json` | The task backlog: the path of a JSON file (keep it outside the repo if you can), or an `http(s)` URL serving the same document see [a backlog over HTTP](backlog.md#a-backlog-over-http). |
26
- | <span style="white-space: nowrap">`state_dir`</span> | | Directory for Forgeo's runtime files (locks, run history, daemon state). Only meaningful with a backlog URL, where it defaults to the directory of `forgeo.yaml`. |
25
+ | <span style="white-space: nowrap">`backlog`</span> | `backlog.json` | The task backlog: the path of a JSON file, an HTTP endpoint serving the same document, or a Jira base URL when `backlog_provider: jira`. |
26
+ | <span style="white-space: nowrap">`backlog_provider`</span> | `auto` | `auto` infers file/HTTP from `backlog`, or Jira when a `jira` block is present; explicitly choose `file`, `http`, or `jira` when preferred. |
27
+ | <span style="white-space: nowrap">`state_dir`</span> | — | Directory for Forgeo's runtime files (locks, run history, daemon state). Remote backlogs default this to the directory of `forgeo.yaml`. |
27
28
  | <span style="white-space: nowrap">`backlog_auth`</span> | — | OAuth2 client credentials for a backlog URL that requires them (see [below](#backlog_auth)). |
29
+ | <span style="white-space: nowrap">`jira`</span> | — | Jira REST, workflow, authentication, and custom-field settings. Required when `backlog_provider: jira`. |
28
30
  | <span style="white-space: nowrap">`blocker_file`</span> | `BLOCKER.md` | Where `BLOCKER.md` is written. Keep it outside the repo so it is never committed. |
29
31
  | <span style="white-space: nowrap">`agent_command`</span> | — | The coding agent: any shell command (string) or argv list. **Required.** |
30
32
  | <span style="white-space: nowrap">`agent_timeout_seconds`</span> | — | Optional: kill the agent after this many seconds (`null` = never). |
@@ -40,6 +42,7 @@ paths), so `forgeo restart` is still used for those.
40
42
  | <span style="white-space: nowrap">`log_file`</span> | `forgeo.log` | Where the daemon writes its log. |
41
43
  | <span style="white-space: nowrap">`run_history_keep`</span> | `2000` | How many finished runs `runs.jsonl` keeps (oldest trimmed atomically on append). `0` disables retention (file grows forever). |
42
44
  | <span style="white-space: nowrap">`run_output_lines`</span> | `200` | How many agent output lines each run record keeps in `runs.jsonl` (the bounded tail of the agent's stdout/stderr). `0` disables persisting agent output. |
45
+ | <span style="white-space: nowrap">`agent_response_lines`</span> | — (unbounded) | How many agent output lines the task's `agent_response` keeps on a status transition (the bounded tail of the agent's stdout/stderr, shown in the task modal / available to a backlog consumer). Omit = unbounded; `0` disables persisting agent output on the task. |
43
46
  | <span style="white-space: nowrap">`failed_retry_max`</span> | `0` | How many times a `FAILED` task is retried automatically. `0` (default) = a `FAILED` task stays `FAILED` until a human reopens it, exactly as before. A task may override this budget per-task with `retries_left`. |
44
47
  | <span style="white-space: nowrap">`failed_retry_wait_cycles`</span> | `1` | How many cycles a retry-eligible `FAILED` task waits (backoff) before it is moved back to `OPEN`. |
45
48
  | <span style="white-space: nowrap">`no_changes_retry_max`</span> | `0` | How many times a task whose agent exits `0` without producing any code changes is re-run immediately, in the same cycle, before the task is marked `BLOCKED` for human review. `0` (default) = a silent no-change SUCCESS is marked `BLOCKED` on the first attempt. |
@@ -66,6 +69,77 @@ refactor_prompt: >
66
69
  behavior, run the test suite, and apply safe changes.
67
70
  ```
68
71
 
72
+ ## Jira backlog
73
+
74
+ Forgeo can read and update Jira issues directly. Set `backlog_provider: jira`
75
+ and make `backlog` the Jira base URL. Jira issue keys become task ids. The JQL
76
+ should include every lifecycle state that should be visible to Forgeo; do not
77
+ filter it to only `To Do`, or completed and blocked issues will disappear from
78
+ the dashboard and dependency checks.
79
+
80
+ ```yaml
81
+ backlog_provider: jira
82
+ backlog: https://jira.example.com
83
+ state_dir: .forgeo
84
+
85
+ jira:
86
+ jql: 'project = APP AND labels = forgeo'
87
+ project_key: APP # Needed for task creation from the web UI.
88
+ issue_type: Task
89
+ auth:
90
+ scheme: basic # Jira Cloud; use bearer for a Server/DC PAT.
91
+ username_env: JIRA_USER
92
+ token_env: JIRA_TOKEN
93
+ workflow:
94
+ open_statuses: ["10000", "10001"]
95
+ open_status: "10000"
96
+ running_status: "3"
97
+ completed_status: "10002"
98
+ blocked_status: null # Optional Jira workflow transition.
99
+ failed_status: null # Failed is represented by a Forgeo label.
100
+ fields:
101
+ acceptance_criteria: customfield_10042
102
+ dependencies: customfield_10043
103
+ ```
104
+
105
+ Status values may be names, but stable Jira status ids are preferred. Forgeo
106
+ adds `forgeo-running`, `forgeo-blocked`, and `forgeo-failed` labels as needed.
107
+ Engine-managed details (`blocker_reason`, failure details, retry counters and
108
+ the bounded agent response) are stored in a Jira issue property, whose key is
109
+ `forgeo` by default. A Jira workflow does not need a custom `FAILED` status.
110
+
111
+ Before starting the daemon, set the configured environment variables and run:
112
+
113
+ ```bash
114
+ export JIRA_USER='automation@example.com'
115
+ export JIRA_TOKEN='...'
116
+ forgeo validate
117
+ ```
118
+
119
+ Forgeo transitions an issue to `running_status` before invoking the agent and
120
+ releases stale claims after `claim_timeout_seconds` (one day by default). The
121
+ web console can create, edit, reopen, and delete Jira issues when the required
122
+ project and custom-field mappings are configured; Jira remains the source of
123
+ truth for human changes.
124
+
125
+ ### Jira settings
126
+
127
+ | Key | Default | Meaning |
128
+ | --- | --- | --- |
129
+ | `jira.jql` | — | Required JQL scope. Include all lifecycle states that Forgeo must see. |
130
+ | `jira.auth` | — | Required credentials. Use `basic` with a username and API-token environment variable, or `bearer` with a token environment variable. |
131
+ | `jira.project_key` | — | Jira project key used when the web console creates issues. |
132
+ | `jira.issue_type` | `Task` | Jira issue type name used for creation. |
133
+ | `jira.api_version` | `3` | API version. v3 uses Jira Cloud's cursor-based `/search/jql`; v2 uses offset-based search. |
134
+ | `jira.page_size` | `50` | Issues requested per search page, from 1 to 100. |
135
+ | `jira.max_issues` | `1000` | Maximum issues read from one JQL search. |
136
+ | `jira.timeout_seconds` | `30` | Timeout for each Jira REST request. |
137
+ | `jira.claim_timeout_seconds` | `86400` | Age after which an abandoned running claim is released. |
138
+ | `jira.label_prefix` | `forgeo` | Prefix for the running, blocked, and failed labels. |
139
+ | `jira.property_key` | `forgeo` | Jira issue-property key holding Forgeo engine state. |
140
+ | `jira.workflow` | defaults | Status ids or names for open, running, blocked, completed, and failed transitions. |
141
+ | `jira.fields` | — | Optional custom-field ids for task attributes such as acceptance criteria and dependencies. |
142
+
69
143
  ## Key details
70
144
 
71
145
  ### `backlog_auth`
@@ -226,6 +300,18 @@ Set `0` to stop persisting agent output entirely (run records stay small and
226
300
  the History tab shows nothing for them). Old run records written before this
227
301
  field existed simply have no output.
228
302
 
303
+ ### `agent_response_lines`
304
+
305
+ Alongside the per-run record above, Forgeo persists the agent's output on the
306
+ task itself (`agent_response`, shown in the task modal and available to a
307
+ backlog consumer served over HTTP). Unlike `run_output_lines` it is **unbounded
308
+ by default**: the whole stdout/stderr is stored, overwritten on each status
309
+ transition (a transition that carries no output never wipes a previously
310
+ stored response).
311
+
312
+ Set a positive value to keep only the last that many lines; set `0` to stop
313
+ persisting agent output on the task entirely.
314
+
229
315
  ### `failed_retry_max` / `failed_retry_wait_cycles`
230
316
 
231
317
  Some failures are transient — a network blip, a flaky test, a dependency
@@ -372,7 +458,7 @@ Each registered instance is fully independent: every instance owns its own
372
458
  **backlog**, **logs** (`log_file`), **run history** (`runs.jsonl`), **locks**
373
459
  (`backlog.lock` and the per-iteration `backlog.run`), and a
374
460
  **`backlog.state.json`** with its live state. Those runtime files sit next to
375
- the backlog file, or — when the backlog is a URL — in `state_dir`, which
461
+ the backlog file, or — when the backlog is remote — in `state_dir`, which
376
462
  defaults to the directory of that instance's `forgeo.yaml`. Because relative
377
463
  paths resolve against each config file's own directory, two configs in
378
464
  different directories can never share state.
@@ -82,7 +82,13 @@ the file configured as `backlog:` in your `forgeo.yaml` — by default
82
82
  `.forgeo/backlog.json`. Once Forgeo is running you can also add tasks
83
83
  from the [web console](web-console-api.md) — no file editing needed. (If your
84
84
  tasks already live in another application, `backlog:` also accepts an
85
- [HTTP endpoint](backlog.md#a-backlog-over-http) instead of a file.)
85
+ [HTTP endpoint](backlog.md#a-backlog-over-http) or a [Jira source](backlog.md#a-jira-backlog)
86
+ instead of a file.)
87
+
88
+ For Jira, set `backlog_provider: jira`, point `backlog:` at the Jira base URL,
89
+ configure `jira.jql` and the workflow mappings, export the credentials named in
90
+ `jira.auth`, and run `forgeo validate` before starting the daemon. See [Jira
91
+ backlogs](backlog.md#a-jira-backlog) for the complete configuration.
86
92
 
87
93
  ```json
88
94
  {
@@ -36,12 +36,12 @@ Forgeo is deliberately single-purpose:
36
36
  ## Where state lives
37
37
 
38
38
  - `forgeo.yaml` — the config (see [Configuration](configuration.md)).
39
- - `backlog.json` (configurable) — the task backlog, a file or an HTTP
40
- endpoint (see [Backlog format](backlog.md)).
39
+ - `backlog.json` (configurable) — the task backlog, a file, an HTTP endpoint,
40
+ or Jira (see [Backlog format](backlog.md)).
41
41
  - `backlog.json.bak`, `backlog.json.bak.1`, ... — rotating snapshots of a
42
42
  *file* backlog, written before every agent run and on daemon startup so a
43
43
  bad write can always be rolled back (see [Backlog format](backlog.md)). A
44
- URL backlog is owned by the remote application and is never snapshotted.
44
+ remote backlog is owned by its provider and is never snapshotted locally.
45
45
  - `BLOCKER.md` (configurable) — written when a human decision is needed; keep
46
46
  it outside the repo so it is never committed.
47
47
  - `forgeo.log` — rotating daemon log (5 MB × 3), also served over HTTP.
@@ -58,8 +58,8 @@ Forgeo is deliberately single-purpose:
58
58
  token (`forgeo web --token`): when present, every `/api/*` route requires
59
59
  `Authorization: Bearer <token>`; with no file the dashboard stays open.
60
60
 
61
- The runtime files above sit next to the backlog file; with a backlog URL they
62
- go in `state_dir`, which defaults to the directory holding `forgeo.yaml`.
61
+ The runtime files above sit next to the backlog file; with a remote backlog
62
+ they go in `state_dir`, which defaults to the directory holding `forgeo.yaml`.
63
63
 
64
64
  ## Next steps
65
65
 
@@ -6,9 +6,9 @@ just schedules cycles and writes its live state to `daemon.state.json`. The
6
6
  dashboard reads every registered instance's data straight from its files
7
7
  (`backlog.json`, `runs.jsonl`, `forgeo.log`, `BLOCKER.md`,
8
8
  `daemon.state.json`), so it works whether or not each instance's daemon is
9
- running. An instance whose `backlog` is an HTTP endpoint is the exception: its
10
- tasks are fetched from there, and an endpoint that cannot be reached answers
11
- `502` rather than showing an empty board.
9
+ running. HTTP and Jira backlogs are fetched from their providers instead, and
10
+ a remote source that cannot be reached answers `502` rather than showing an
11
+ empty board.
12
12
 
13
13
  ```bash
14
14
  forgeo web # default 0.0.0.0:8790, foreground
@@ -153,10 +153,9 @@ curl http://127.0.0.1:8790/api/instances
153
153
  ```
154
154
 
155
155
  Each row also carries `backlog_error`: `null` normally, and the reason when
156
- that instance's backlog could not be read (only possible for a backlog served
157
- over HTTP). Its counts are zero in that case the row reports a backlog it
158
- could not reach, not an empty one. One unreachable endpoint never fails the
159
- whole listing.
156
+ that instance's remote backlog could not be read. Its counts are zero in that
157
+ case the row reports a backlog it could not reach, not an empty one. One
158
+ unreachable provider never fails the whole listing.
160
159
 
161
160
  ### `GET /api/instances/<name>/tasks`
162
161
 
@@ -177,6 +176,7 @@ curl http://127.0.0.1:8790/api/instances/my-repo/tasks
177
176
  "blocker_reason": [],
178
177
  "blocked_count": 0,
179
178
  "failure_reason": [],
179
+ "agent_response": null,
180
180
  "created_at": "2026-07-31T10:00:00Z",
181
181
  "updated_at": "2026-07-31T10:00:00Z",
182
182
  "run_at": null,
@@ -188,8 +188,8 @@ curl http://127.0.0.1:8790/api/instances/my-repo/tasks
188
188
  ]
189
189
  ```
190
190
 
191
- A backlog served over HTTP is fetched on each request; when the endpoint is
192
- unreachable the response is `502` with the reason in `error`.
191
+ A remote backlog is fetched on each request; when the provider is unreachable
192
+ the response is `502` with the reason in `error`.
193
193
 
194
194
  ### `GET /api/instances/<name>/tasks/{id}`
195
195
 
@@ -258,6 +258,7 @@ curl -X POST http://127.0.0.1:8790/api/instances/my-repo/tasks \
258
258
  "blocker_reason": [],
259
259
  "blocked_count": 0,
260
260
  "failure_reason": [],
261
+ "agent_response": null,
261
262
  "created_at": "2026-08-01T12:00:00Z",
262
263
  "updated_at": "2026-08-01T12:00:00Z",
263
264
  "run_at": "2026-08-20T12:30:00Z",
@@ -300,6 +301,7 @@ curl -X POST http://127.0.0.1:8790/api/instances/my-repo/tasks/TASK-001/reopen
300
301
  "blocker_reason": [],
301
302
  "blocked_count": 1,
302
303
  "failure_reason": [],
304
+ "agent_response": null,
303
305
  "created_at": "2026-07-31T10:00:00Z",
304
306
  "updated_at": "2026-08-01T12:00:00Z",
305
307
  "dependencies": [],
@@ -329,8 +331,9 @@ budget override; a non-negative integer or `null`), and `run_at` (the
329
331
  optional one-shot schedule; an ISO-8601 datetime string or `null` to clear
330
332
  it). The request body is a JSON object; omitted fields are left unchanged
331
333
  and `id`, `status`, `blocker_reason`, `blocked_count`, `failure_reason`,
332
- `retry_count`, `failed_wait_cycles`, and `created_at` are always preserved
333
- (they are engine-managed — `PATCH` rejects them like it rejects `status`).
334
+ `agent_response`, `retry_count`, `failed_wait_cycles`, and `created_at` are
335
+ always preserved (they are engine-managed — `PATCH` rejects them like it
336
+ rejects `status`).
334
337
  `agent_command` may be a string, an array, or `null` (clear the per-task
335
338
  override); `agent_timeout_seconds` may be a positive number or `null`.
336
339
  `updated_at` is bumped to the current time.
@@ -350,6 +353,7 @@ curl -X PATCH http://127.0.0.1:8790/api/instances/my-repo/tasks/TASK-001 \
350
353
  "blocker_reason": [],
351
354
  "blocked_count": 0,
352
355
  "failure_reason": [],
356
+ "agent_response": null,
353
357
  "created_at": "2026-07-31T10:00:00Z",
354
358
  "updated_at": "2026-08-01T12:00:00Z",
355
359
  "dependencies": [],
@@ -364,8 +368,8 @@ Errors:
364
368
 
365
369
  - `400` with `{"error": "..."}` — unparseable or non-object body, an empty
366
370
  body, an unknown field (e.g. `status`, `blocker_reason`, `blocked_count`,
367
- `failure_reason`), or an invalid value (blank `title`, wrong field types, a
368
- non-positive `agent_timeout_seconds`).
371
+ `failure_reason`, `agent_response`), or an invalid value (blank `title`,
372
+ wrong field types, a non-positive `agent_timeout_seconds`).
369
373
  - `404` with `{"error": "not found"}` — the task id does not exist in that
370
374
  instance's backlog.
371
375
  - `404` with `{"error": "unknown instance"}` — the instance is not
@@ -14,7 +14,7 @@ set -eu
14
14
 
15
15
  REPO_OWNER="lucaGazzola"
16
16
  REPO_NAME="forgeo"
17
- DEFAULT_VERSION="0.7.2"
17
+ DEFAULT_VERSION="0.8.0"
18
18
  MIN_PYTHON="3.11"
19
19
  PYPI_PACKAGE="forgeo-cli"
20
20
  PREFIX="${FORGEO_PREFIX:-${HOME:-}/.local}"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "forgeo-cli"
7
- version = "0.7.2"
7
+ version = "0.8.0"
8
8
  description = "A scheduled software forgeo: executes backlog tasks on main, refactors when idle, and writes BLOCKER.md when it needs human input."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,4 +7,4 @@ try:
7
7
 
8
8
  __version__ = _pkg_version("forgeo-cli")
9
9
  except PackageNotFoundError: # standalone binary: no installed package metadata
10
- __version__ = "0.7.2"
10
+ __version__ = "0.8.0"