@defrex/autobuild 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -113
- package/SPEC.md +22 -5
- package/bin/ab-dispatch-kernel.ts +1 -1
- package/docs/configuration.md +34 -7
- package/docs/operator-api.md +17 -0
- package/docs/remote-ticket-protocol.md +5 -0
- package/package.json +3 -3
- package/packages/core/src/cli/binary.ts +3 -3
- package/packages/core/src/cli/dashboard/model.ts +4 -0
- package/packages/core/src/cli/dashboard/render.ts +37 -7
- package/packages/core/src/cli/dispatch-frontend.ts +16 -1
- package/packages/core/src/cli/dispatch-process.ts +25 -7
- package/packages/core/src/cli/dispatch.ts +64 -6
- package/packages/core/src/cli/dispatcher-liveness.ts +91 -0
- package/packages/core/src/cli/help.ts +8 -5
- package/packages/core/src/cli/init.ts +3 -2
- package/packages/core/src/cli/repository-status.ts +45 -5
- package/packages/core/src/cli/watch.ts +159 -14
- package/packages/core/src/config/live.ts +1 -0
- package/packages/core/src/config/schema.ts +9 -2
- package/packages/core/src/events/globs.ts +8 -3
- package/packages/core/src/events/repository.ts +51 -2
- package/packages/core/src/integration/harness.ts +2 -1
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +18 -0
- package/packages/core/src/kernel/dispatch-status.ts +9 -1
- package/packages/core/src/kernel/dispatcher-liveness.ts +70 -0
- package/packages/core/src/kernel/reducer-registry.ts +4 -0
- package/packages/core/src/kernel/ticket-source-health.ts +30 -0
- package/packages/core/src/operator/query.ts +6 -3
- package/packages/core/src/orchestrator/prompt.ts +3 -1
- package/packages/core/src/ports/tickets/contract.ts +25 -0
- package/packages/core/src/ports/tickets/fake.ts +10 -8
- package/packages/core/src/ports/tickets/file.ts +1 -0
- package/packages/core/src/ports/tickets/linear.ts +241 -49
- package/packages/core/src/ports/tickets/remote-protocol.ts +2 -0
- package/packages/core/src/ports/types.ts +17 -0
- package/packages/core/src/processes/dispatcher.ts +277 -73
- package/packages/core/src/processes/repo-view.ts +2 -1
- package/packages/core/src/store/adapter.ts +1 -0
- package/packages/core/src/store/contract.ts +109 -0
- package/packages/core/src/store/digest.ts +14 -2
- package/packages/core/src/store/local/store.ts +18 -5
- package/packages/core/src/store/remote/protocol.ts +1 -0
- package/packages/core/src/store/repo-state-events.ts +49 -59
- package/packages/core/src/store/types.ts +5 -1
- package/skills/guide/SKILL.md +43 -14
- package/skills/operate/SKILL.md +180 -18
package/README.md
CHANGED
|
@@ -2,72 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
# Autobuild
|
|
4
4
|
|
|
5
|
-
**Tickets in,
|
|
5
|
+
**Tickets in, Product out.**
|
|
6
6
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Autobuild runs coding agents through a fixed pipeline that plans, implements,
|
|
10
|
+
reviews, and verifies every ticket before it merges. You groom tickets in and
|
|
11
|
+
review pull requests out; agents do the work, and deterministic code keeps
|
|
12
|
+
them honest.
|
|
12
13
|
|
|
13
14
|

|
|
15
16
|
|
|
16
|
-
## Groomed ticket in, reviewed PR out
|
|
17
|
-
|
|
18
|
-
Running several coding agents by hand doesn't scale well. Every session wants
|
|
19
|
-
prompts, permissions, and attention, and your focus becomes the ceiling.
|
|
20
|
-
With Autobuild, each build runs the whole loop headlessly and escalates only
|
|
21
|
-
when it is truly blocked. With attention off the critical path, throughput
|
|
22
|
-
is only limited by how fast you can write good tickets.
|
|
23
|
-
|
|
24
|
-
Headless is safe because the pipeline is deterministic code, not model
|
|
25
|
-
judgment. State lives in a typed, append-only event log, phase transitions
|
|
26
|
-
are owned by tested code, and every build leaves a queryable paper trail. Your
|
|
27
|
-
merge gates are never bypassed: a PR lands when your required checks and your
|
|
28
|
-
consent say it lands.
|
|
29
|
-
|
|
30
|
-
The loop also feeds itself. While building, agents record observations —
|
|
31
|
-
latent bugs, worthwhile refactors, follow-ups they noticed but rightly left
|
|
32
|
-
alone. A harvester distills them into proposed tickets and files them for
|
|
33
|
-
triage; approve one and it runs the same loop.
|
|
34
|
-
|
|
35
|
-
Every seam is an adapter: ticket sources (the hosted service with database or
|
|
36
|
-
Linear backend, direct Linear, local files, or a configured plugin), agent runtimes (Claude, Codex, Pi, or a configured plugin), the forge
|
|
37
|
-
(GitHub via `gh`, credential-free local Git, or a configured plugin), workspaces,
|
|
38
|
-
and the build store all sit behind
|
|
39
|
-
narrow interfaces. Trusted Bun plugins declared in `autobuild.toml` can register
|
|
40
|
-
third-party ticket, runtime, workspace, and forge adapters against the versioned
|
|
41
|
-
`@defrex/autobuild/plugin-sdk` surface. Repositories may also opt into the shipped
|
|
42
|
-
`vercel-sandbox` workspace provider: the complete build runs in an isolated
|
|
43
|
-
Vercel VM while the local supervisor retains credentialed branch/PR publication.
|
|
44
|
-
The default remains a local Git worktree. See the [workspace configuration and
|
|
45
|
-
security contract](docs/configuration.md#vercel-sandbox).
|
|
46
|
-
Teams that need one durable store across hosts
|
|
47
|
-
can deploy the optional [hosted service](packages/hosted-store-service/README.md),
|
|
48
|
-
which also serves a GitHub-authenticated browser dashboard, can own ticket
|
|
49
|
-
persistence and external tracker credentials, and can run the dispatcher itself on a schedule (see [the hosted dispatcher](docs/hosted-dispatcher.md)). Repository identity across the
|
|
50
|
-
Store, dispatcher, query commands, and that dashboard is the repository's
|
|
51
|
-
normalized origin URL (e.g. `https://github.com/defrex/autobuild`), never a
|
|
52
|
-
checkout path; pre-origin records are not migrated. The web UI uses a server-managed
|
|
53
|
-
session; automation clients can use the versioned [operator API](docs/operator-api.md)
|
|
54
|
-
for the same controls and projections without exposing service credentials, and
|
|
55
|
-
external MCP agents connect through the [hosted MCP server](docs/mcp.md).
|
|
56
|
-
|
|
57
17
|
## Quickstart
|
|
58
18
|
|
|
59
19
|
You need [Bun](https://bun.sh), `git`, and the local prerequisites for the
|
|
60
|
-
agent runtime you select: Claude Code, Codex CLI, or Pi.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
account or remote.
|
|
64
|
-
Init suggests only runtimes whose executable and/or provider authentication is
|
|
65
|
-
usable on your machine.
|
|
20
|
+
agent runtime you select: Claude Code, Codex CLI, or Pi. The GitHub forge also
|
|
21
|
+
needs an authenticated [`gh` CLI](https://cli.github.com) or an exported
|
|
22
|
+
`GITHUB_TOKEN`/`GH_TOKEN`; the local-git forge needs no account or remote.
|
|
66
23
|
|
|
67
24
|
<!-- release-install:start -->
|
|
68
25
|
|
|
69
26
|
```sh
|
|
70
|
-
bun add -g @defrex/autobuild@0.
|
|
27
|
+
bun add -g @defrex/autobuild@0.11.0
|
|
71
28
|
```
|
|
72
29
|
|
|
73
30
|
<!-- release-install:end -->
|
|
@@ -78,44 +35,36 @@ Then, from the repository you want built:
|
|
|
78
35
|
ab init
|
|
79
36
|
```
|
|
80
37
|
|
|
81
|
-
|
|
82
|
-
repository-aware setup to a coding agent.
|
|
83
|
-
[
|
|
84
|
-
|
|
85
|
-
environment without creating build history:
|
|
86
|
-
|
|
87
|
-
```sh
|
|
88
|
-
ab init --validate
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
See the [configuration reference](docs/configuration.md) for every field. Only
|
|
92
|
-
a clean readiness result should be followed by:
|
|
38
|
+
This vendors the editable `ab-*` skills, writes a stack-neutral config, and
|
|
39
|
+
hands repository-aware setup to a coding agent. The
|
|
40
|
+
[configuration reference](docs/configuration.md) covers every field. Then
|
|
41
|
+
start the dispatcher and its live dashboard:
|
|
93
42
|
|
|
94
43
|
```sh
|
|
95
44
|
ab dispatch
|
|
96
45
|
```
|
|
97
46
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
In your selected coding agent, invoke the ticket-grooming skill:
|
|
47
|
+
Then groom your first ticket in your coding agent:
|
|
101
48
|
|
|
102
49
|
```text
|
|
103
|
-
/ab-spec
|
|
50
|
+
/ab-spec add a retry budget
|
|
104
51
|
```
|
|
105
52
|
|
|
106
|
-
The agent asks the questions needed to produce a
|
|
107
|
-
|
|
108
|
-
largely becomes your input workflow.
|
|
53
|
+
The agent asks the questions needed to produce a buildable ticket and files it
|
|
54
|
+
in your tracker. From then on, that skill is how work goes in.
|
|
109
55
|
|
|
110
56
|
## How it works
|
|
111
57
|
|
|
112
|
-
Every
|
|
58
|
+
### Every ticket runs the pipeline
|
|
113
59
|
|
|
114
60
|
```text
|
|
115
61
|
spec → plan ⇄ plan-review → implement ⇄ code-review → verify:* → finalize
|
|
116
62
|
epilogue: (pr.conflicted → reconcile → verify:*)* → merged or closed
|
|
117
63
|
```
|
|
118
64
|
|
|
65
|
+
Each ticket moves through deterministic phases inside its own build-runner.
|
|
66
|
+
Every phase is a fresh agent session, with artifacts knitting them together.
|
|
67
|
+
|
|
119
68
|
1. **spec** — the dispatcher claims a ready ticket, establishes the final
|
|
120
69
|
spec, and cuts a branch. The spec is the contract for everything after.
|
|
121
70
|
2. **plan ⇄ plan-review** — a planner writes an implementation plan; an
|
|
@@ -125,54 +74,88 @@ spec → plan ⇄ plan-review → implement ⇄ code-review → verify:* → fin
|
|
|
125
74
|
you instead of looping forever.
|
|
126
75
|
4. **verify:\*** — your verification steps, in the order you declare them:
|
|
127
76
|
shell commands judged by exit code, or agent verifiers that return a
|
|
128
|
-
verdict.
|
|
129
|
-
5. **finalize** — the PR opens with an agent-written description and
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
commits selected files locally; the runner extends the open PR branch with
|
|
133
|
-
a regular push. A no-op adds no commit, and a publication failure becomes a
|
|
134
|
-
follow-up observation rather than failing the green build. Agent verifiers
|
|
135
|
-
attach an exact screenshot, trace, or other artifact with
|
|
136
|
-
`ab artifact put <kind> <file> --attach`; the PR always gets a pinned
|
|
137
|
-
retrieval command, and configured public image hosting can also render
|
|
138
|
-
images inline.
|
|
77
|
+
verdict and can attach evidence such as screenshots.
|
|
78
|
+
5. **finalize** — the PR opens with an agent-written description and the
|
|
79
|
+
attached evidence, then any post-PR steps you've configured (changelogs,
|
|
80
|
+
release notes) run.
|
|
139
81
|
6. **epilogue** — the dispatcher watches the open PR. Conflicts route back
|
|
140
82
|
through reconcile and re-verify; the build ends `merged` or `closed`.
|
|
141
83
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
84
|
+
### A dispatcher spawns builds
|
|
85
|
+
|
|
86
|
+
The dispatcher claims ready tickets and starts a build-runner for each.
|
|
87
|
+
Runners are isolated, and all state is logged. Agents never decide what phase
|
|
88
|
+
comes next, and outcomes are never inferred from what a model printed: every
|
|
89
|
+
phase reports through a typed CLI into the event log, and tested code decides
|
|
90
|
+
the transition. That log is the build. Kill the process at any point and the
|
|
91
|
+
dispatcher resumes from durable state.
|
|
149
92
|
|
|
150
93
|
The pipeline grammar is fixed on purpose; `verify:*` and `finalize:*` are the
|
|
151
|
-
extension points, declared per
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
94
|
+
extension points, declared per repository in `autobuild.toml`. For the seams
|
|
95
|
+
and the reasoning behind them, see [`docs/architecture.md`](docs/architecture.md)
|
|
96
|
+
and [`SPEC.md`](SPEC.md).
|
|
97
|
+
|
|
98
|
+
## All local, all remote, or anywhere between
|
|
99
|
+
|
|
100
|
+
Every seam is an adapter you can customize for your project. The build
|
|
101
|
+
pipeline stays the same.
|
|
102
|
+
|
|
103
|
+
| Seam | Decides | Adapters |
|
|
104
|
+
| ---------- | --------------------- | ------------------------------------------ |
|
|
105
|
+
| tickets | where work comes from | local files, Linear, hosted, plugin |
|
|
106
|
+
| dispatcher | what starts builds | local process, scheduled (hosted) |
|
|
107
|
+
| workspace | where a build runs | git worktree, Vercel Sandbox, plugin |
|
|
108
|
+
| runtime | who does the thinking | Claude Code, Codex, Pi, plugin |
|
|
109
|
+
| forge | where code lands | local git, GitHub, plugin |
|
|
110
|
+
| store | where state lives | SQLite, Postgres, any HTTP store |
|
|
111
|
+
| operators | how you watch | terminal, web UI, MCP |
|
|
112
|
+
|
|
113
|
+
Plugins are trusted Bun modules declared in `autobuild.toml` against the
|
|
114
|
+
versioned `@defrex/autobuild/plugin-sdk`. Teams that want one durable store
|
|
115
|
+
across hosts can deploy the optional
|
|
116
|
+
[hosted service](packages/hosted-store-service/README.md), which adds a
|
|
117
|
+
browser dashboard, a [scheduled dispatcher](docs/hosted-dispatcher.md), an
|
|
118
|
+
[operator API](docs/operator-api.md), and an [MCP server](docs/mcp.md).
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Built for throughput, not latency
|
|
122
|
+
|
|
123
|
+
One build may take longer than a chat with an agent. The trade is far more
|
|
124
|
+
changes landing every day, with much greater reliability.
|
|
125
|
+
|
|
126
|
+
- **File it and walk away.** Nothing in the pipeline waits on you. You don't
|
|
127
|
+
approve tool calls, paste errors back, or watch a terminal. A build
|
|
128
|
+
escalates to you only when it is truly blocked.
|
|
129
|
+
- **As many builds as you can groom.** Builds run side by side, each in its
|
|
130
|
+
own workspace, up to the limit you set. Your attention stops being the
|
|
131
|
+
ceiling; how fast you can write good tickets becomes the limit.
|
|
132
|
+
|
|
133
|
+
Headless is safe because the pipeline is deterministic code, not model
|
|
134
|
+
judgment. State lives in a typed, append-only event log, phase transitions
|
|
135
|
+
are owned by tested code, and every build leaves a queryable paper trail. Your
|
|
136
|
+
merge gates are never bypassed: a PR lands when your required checks and your
|
|
137
|
+
consent say it lands.
|
|
138
|
+
|
|
139
|
+
## Let the queue fill itself
|
|
140
|
+
|
|
141
|
+
Once tickets are the interface, anything that can write a ticket can put work
|
|
142
|
+
in front of you.
|
|
143
|
+
|
|
144
|
+
Builds feed the queue themselves. An implementer that spots a latent bug, a
|
|
145
|
+
worthwhile refactor, or a missing follow-up outside its spec records an
|
|
146
|
+
observation (`ab observe`) and moves on: the insight is kept, and scope creep
|
|
147
|
+
stays out of the PR. Once enough pile up, the dispatcher distills them into
|
|
148
|
+
proposed tickets, deduplicated against work already filed, and by default
|
|
149
|
+
files them into triage. You groom them like any ticket you wrote yourself.
|
|
150
|
+
Agents propose; humans dispatch.
|
|
151
|
+
|
|
152
|
+
Support threads, meeting notes, and error telemetry can feed it the same way.
|
|
153
|
+
An agent with the `ab` CLI or the MCP server can file tickets and answer
|
|
154
|
+
routine escalations, leaving you only the real product calls.
|
|
173
155
|
|
|
174
156
|
## Learn more
|
|
175
157
|
|
|
158
|
+
- [autobuild.run](https://www.autobuild.run) — the project site.
|
|
176
159
|
- [`docs/spec-standard.md`](docs/spec-standard.md) — what makes a ticket
|
|
177
160
|
buildable: the standard every dispatched spec must meet.
|
|
178
161
|
- [`docs/configuration.md`](docs/configuration.md) — the complete strict
|
|
@@ -181,7 +164,8 @@ propose; humans dispatch.
|
|
|
181
164
|
configuration, as a worked example of the config surface.
|
|
182
165
|
- [`docs/architecture.md`](docs/architecture.md) — how the design maps to the
|
|
183
166
|
codebase: kernel, ports, processes, and stores.
|
|
184
|
-
- [`packages/postgres-store/README.md`](packages/postgres-store/README.md) —
|
|
167
|
+
- [`packages/postgres-store/README.md`](packages/postgres-store/README.md) —
|
|
168
|
+
serverless PostgreSQL persistence with S3-compatible or Vercel Blob storage.
|
|
185
169
|
- [`docs/remote-store-protocol.md`](docs/remote-store-protocol.md) — the
|
|
186
170
|
complete HTTP server contract and BuildStore conformance instructions.
|
|
187
171
|
- [`SPEC.md`](SPEC.md) — the source of truth for the design and its
|
package/SPEC.md
CHANGED
|
@@ -233,6 +233,19 @@ Small, independently runnable, crash-safe:
|
|
|
233
233
|
- **ingesters** — other outer-loop processes turning signals into proposals (§12).
|
|
234
234
|
- **operator** — UI process(es); see §14.
|
|
235
235
|
|
|
236
|
+
Ticket-source ready scans run immediately when no completed measurement exists,
|
|
237
|
+
then every
|
|
238
|
+
`ticketScanIntervalSeconds` (default 60 seconds, a positive integer). Actual
|
|
239
|
+
tick spacing is the floor when the configured interval is shorter. A
|
|
240
|
+
same-repository `pr.merged`, `build.completed`, or `build.aborted` fact forces
|
|
241
|
+
another scan on the next tick; changed ready criteria also force a scan.
|
|
242
|
+
Housekeeping keeps its ordinary tick cadence. Between scans, queue depth and
|
|
243
|
+
standing diagnostics repeat the last completed measurement, including across
|
|
244
|
+
process restarts. Tickets groomed directly in the tracker are picked up within
|
|
245
|
+
one scan interval plus one tick when capacity permits. Observation while intake
|
|
246
|
+
is off shares the cadence; an observation measurement cannot suppress a fresh
|
|
247
|
+
admission scan when intake is enabled.
|
|
248
|
+
|
|
236
249
|
### 3.4 The event log spine
|
|
237
250
|
|
|
238
251
|
Build processes append typed events to per-build logs; repository-scoped outer
|
|
@@ -501,8 +514,11 @@ stream read's ([docs/remote-store-protocol.md](docs/remote-store-protocol.md),
|
|
|
501
514
|
|
|
502
515
|
#### 7.1.2 The embedded orchestrator (turns)
|
|
503
516
|
|
|
504
|
-
The orchestrator is the agent that files tickets,
|
|
505
|
-
them, and discusses direction
|
|
517
|
+
The orchestrator is a role, not a deployment: the agent that files tickets,
|
|
518
|
+
watches builds, unblocks them, and discusses direction. Any agent can take it
|
|
519
|
+
on — a coding-agent session at the repository root using the `ab` CLI or
|
|
520
|
+
`ab mcp` needs no initialization — and the canonical `ab-operate` skill is its
|
|
521
|
+
manual in either setting. In the hosted service it is played by a stateless
|
|
506
522
|
turn runner over the operator tool registry, not by an agent living in a
|
|
507
523
|
sandbox (parity with MCP; no credential plumbing) and not by an external
|
|
508
524
|
workflow engine (durability comes from the store; the tick pattern already
|
|
@@ -2240,6 +2256,7 @@ ordinary boundary reload resumes.
|
|
|
2240
2256
|
```toml
|
|
2241
2257
|
baseBranch = "main"
|
|
2242
2258
|
capacity = 3 # concurrent builds for this repo
|
|
2259
|
+
ticketScanIntervalSeconds = 60 # positive integer; ready scans, with tick spacing as floor
|
|
2243
2260
|
forge = "github" # builtin: github | local-git; or a plugin name
|
|
2244
2261
|
plugins = ["./plugins/local.ts", "@acme/autobuild-plugin"]
|
|
2245
2262
|
|
|
@@ -2347,7 +2364,7 @@ decide where a build's effective configuration is composed from:
|
|
|
2347
2364
|
|---|---|---|
|
|
2348
2365
|
| Build-owned (pinned) | `[verify]` and `[verify.<step>]`, `[finalize]` and `[finalize.<step>]`, `[commands]`, `[workspace]` (provider and config, including `provisioning` and `runtimeProvisioning`) | The build's own branch: its branch head once it has published commits, otherwise the recorded base commit its workspace was cut from. The exact commit is recorded once per deposit in the effective-config artifact metadata — except in checkout mode when the worktree read cannot be attributed to the branch-head commit: because the worktree file has uncommitted edits, because the clean/dirty comparison could not be performed (non-git workspace or a transient git error), or because the branch head never carried the file. The deposit then records the source `worktree-dirty` and no commit, because no commit claims the worktree bytes (the build still runs them). |
|
|
2349
2366
|
| Deployment-owned (live) | `[roles]` (runtime, model, args, alternates, per-role `sessionBudgetSeconds`) and `[policy]` (budgets, retry bounds, stall and harvest knobs) | The dispatcher's live snapshot; an accepted reload delivers these to active builds. |
|
|
2350
|
-
| Dispatcher-owned | `baseBranch`, `capacity`, `forge`, `plugins`, `pr`, `tickets`, `orchestrator` | The dispatcher's live snapshot; the build runner never interprets them. |
|
|
2367
|
+
| Dispatcher-owned | `baseBranch`, `capacity`, `ticketScanIntervalSeconds`, `forge`, `plugins`, `pr`, `tickets`, `orchestrator` | The dispatcher's live snapshot; the build runner never interprets them. |
|
|
2351
2368
|
|
|
2352
2369
|
A build's pipeline definition is therefore composed from the build's own
|
|
2353
2370
|
branch: in origin mode the dispatcher reads `autobuild.toml` at the build's
|
|
@@ -2423,8 +2440,8 @@ installs into a repo:
|
|
|
2423
2440
|
non-agent-invocable: they are invoked explicitly by the runner or a human,
|
|
2424
2441
|
never auto-triggered by a model pattern-matching a description — a model
|
|
2425
2442
|
must not start a pipeline phase by accident. The model-invocable exceptions
|
|
2426
|
-
(`ab-spec`, `ab-tickets`, `ab-guide`) are exactly the
|
|
2427
|
-
**drive no phase**; membership is decided by that criterion, not taste.
|
|
2443
|
+
(`ab-spec`, `ab-tickets`, `ab-guide`, `ab-operate`) are exactly the four
|
|
2444
|
+
skills that **drive no phase**; membership is decided by that criterion, not taste.
|
|
2428
2445
|
|
|
2429
2446
|
When `.agents/skills` and `.claude/skills` resolve to the same filesystem
|
|
2430
2447
|
entry, that repository-level alias already satisfies harness discovery. Init
|
|
@@ -23,7 +23,7 @@ try {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const stop = new AbortController()
|
|
26
|
-
const requestStop = (): void => stop.abort()
|
|
26
|
+
const requestStop = (signal?: string): void => stop.abort(signal)
|
|
27
27
|
// Keep both handlers installed through the complete graceful drain. The first
|
|
28
28
|
// targeted SIGINT comes from the supervisor; later foreground-group signals
|
|
29
29
|
// must not regain their default disposition inside an open claim tick.
|
package/docs/configuration.md
CHANGED
|
@@ -168,7 +168,7 @@ the branch head never carried the file — the deposit records the source
|
|
|
168
168
|
provenance declines to attribute them to any commit. **Deployment-owned** sections — `[roles]`
|
|
169
169
|
(runtime, model, args, alternates, per-role `sessionBudgetSeconds`) and
|
|
170
170
|
`[policy]` — keep flowing live. **Dispatcher-owned** sections (`baseBranch`,
|
|
171
|
-
`capacity`, `forge`, `plugins`, `pr`, `tickets`, `orchestrator`) configure the
|
|
171
|
+
`capacity`, `ticketScanIntervalSeconds`, `forge`, `plugins`, `pr`, `tickets`, `orchestrator`) configure the
|
|
172
172
|
dispatcher itself and are never interpreted by the build runner.
|
|
173
173
|
|
|
174
174
|
Consequently, an accepted base-branch reload re-deposits only the
|
|
@@ -195,6 +195,7 @@ These fields hot-reload:
|
|
|
195
195
|
| Field or table | Boundary where a new value applies |
|
|
196
196
|
|---|---|
|
|
197
197
|
| `baseBranch` | Next dispatch or base fallback decision; existing `build.created` facts remain immutable. |
|
|
198
|
+
| `ticketScanIntervalSeconds` | Next tick; evaluates elapsed time against the new interval. |
|
|
198
199
|
| `capacity` | Next dispatcher tick. |
|
|
199
200
|
| `[pr]` | Next build creation. |
|
|
200
201
|
| `[commands]` | Next setup, verify check, or finalize check selected for a build whose OWN branch carries it. A base-branch reload does not retarget an in-flight build. |
|
|
@@ -229,10 +230,24 @@ any table header.
|
|
|
229
230
|
| Field | Default | Constraints | Purpose |
|
|
230
231
|
|---|---:|---|---|
|
|
231
232
|
| `baseBranch` | `"main"` | nonempty string | Branch used to cut builds, target PRs, and merge during reconciliation. |
|
|
233
|
+
| `ticketScanIntervalSeconds` | `60` | positive integer seconds | Ticket-source ready scan cadence; tick spacing is the effective minimum. |
|
|
232
234
|
| `capacity` | `1` | positive integer | Maximum concurrent nonterminal builds for this repository. Paused and blocked builds still occupy capacity. |
|
|
233
235
|
| `forge` | `"github"` | nonblank string | Forge adapter name. Shipped values are `github` and `local-git`; configured plugins may register other names. |
|
|
234
236
|
| `plugins` | `[]` | array of unique nonblank module specifiers | Trusted Bun plugin modules loaded by dispatch, `ab ticket`, and scoped phase CLI processes. |
|
|
235
237
|
|
|
238
|
+
Ticket-source ready scans run immediately when no completed measurement exists,
|
|
239
|
+
then every
|
|
240
|
+
`ticketScanIntervalSeconds` (default 60 seconds, a positive integer). Actual
|
|
241
|
+
tick spacing is the floor when the configured interval is shorter. A
|
|
242
|
+
same-repository `pr.merged`, `build.completed`, or `build.aborted` fact forces
|
|
243
|
+
another scan on the next tick; changed ready criteria also force a scan.
|
|
244
|
+
Housekeeping keeps its ordinary tick cadence. Between scans, queue depth and
|
|
245
|
+
standing diagnostics repeat the last completed measurement, including across
|
|
246
|
+
process restarts. Tickets groomed directly in the tracker are picked up within
|
|
247
|
+
one scan interval plus one tick when capacity permits. Observation while intake
|
|
248
|
+
is off shares the cadence; an observation measurement cannot suppress a fresh
|
|
249
|
+
admission scan when intake is enabled.
|
|
250
|
+
|
|
236
251
|
### Plugin modules
|
|
237
252
|
|
|
238
253
|
<!-- config-fragment:plugins -->
|
|
@@ -313,7 +328,12 @@ without adding a runtime Autobuild dependency to the plugin. That entry point
|
|
|
313
328
|
exports the manifest/factory types, port types, fake adapters, and reusable
|
|
314
329
|
TicketSource, AgentRunner, WorkspaceProvider, Forge, BuildStore, and BlobStore
|
|
315
330
|
contract suites. Adapter values may use the backward-compatible bare factory or
|
|
316
|
-
carry a contract fixture descriptor.
|
|
331
|
+
carry a contract fixture descriptor.
|
|
332
|
+
TicketSource adapters must populate the required `Ticket.resolved` boolean on
|
|
333
|
+
listReady, get, and create projections, using the same native lifecycle rules as
|
|
334
|
+
`dependencyStates`. Plugin authors upgrading older adapters must add this field;
|
|
335
|
+
ready-state membership does not imply that a ticket is unresolved. Dispatch
|
|
336
|
+
reuses these facts only within the current tick. Ticket sources may also declare
|
|
317
337
|
`requiredEnv`; the host checks every declared variable for a nonempty value
|
|
318
338
|
before invoking the adapter factory. Plugins using Forge abort-cleanup and
|
|
319
339
|
AgentRunner turn-cancellation capabilities introduced in API 1.2 should require
|
|
@@ -1259,6 +1279,15 @@ triageState = "Backlog"
|
|
|
1259
1279
|
Linear credentials do not belong in this table. Set `LINEAR_API_KEY` in the
|
|
1260
1280
|
environment or local `.env` file described below.
|
|
1261
1281
|
|
|
1282
|
+
[Linear's request budget](https://linear.app/developers/rate-limiting) is shared
|
|
1283
|
+
by every dispatcher and `ab ticket` command using the API key, and by other keys
|
|
1284
|
+
belonging to the same authenticated user. A rate-limited ready scan or dependency
|
|
1285
|
+
read fails the evaluation tick without new intake claims or synthetic
|
|
1286
|
+
dependency-blocked counts. That source instance suppresses requests until the
|
|
1287
|
+
reported reset time; the next call then runs normally. Missing, invalid or expired
|
|
1288
|
+
reset headers use a conservative 60-second cooldown. Cooldowns are local to the
|
|
1289
|
+
instance: processes and restarts do not coordinate their budgets.
|
|
1290
|
+
|
|
1262
1291
|
For a hosted backend, repository policy remains local while credentials remain
|
|
1263
1292
|
at the service:
|
|
1264
1293
|
|
|
@@ -1323,7 +1352,7 @@ restart-classified as a whole.
|
|
|
1323
1352
|
| `model` | — | nonblank string, required when `enabled = true` | Provider-qualified model string for the turn runner, in the same vocabulary as `[roles]` model strings (a leading `vercel-ai-gateway/` prefix is accepted and stripped). Resolved through the deployment's gateway credential. |
|
|
1324
1353
|
| `invocationBudgetSeconds` | `240` | positive integer, clamped to 300 | Wall-clock budget for one turn invocation. A larger value is clamped — not rejected — to the 300-second hosted function limit the runner executes under (`ORCHESTRATOR_ROUTE_LIMIT_SECONDS`, the same number the operator route pins as `maxDuration`). |
|
|
1325
1354
|
| `approvals` | see below | array of `tool` or `tool:qualifier` strings | Registry tool names whose calls suspend a turn until the operator answers. Default: `["builds.control:abort", "builds.control:discard", "builds.answer:revise-spec", "sandbox.publish", "tickets.move:ready"]`. Empty (`[]`) means none — autonomous operation. An entry naming an absent or unknown tool is **inert by design**, never a config error: the list stays stable while the registry grows, and a tool's removal cannot break parsing. A `tool:qualifier` entry applies to the named discriminator value only (for example `tickets.move:ready` approves moving a ticket to `ready`); for a tool without a discriminator, only the bare form matches. |
|
|
1326
|
-
| `wake` | inherits the default | array of event-type globs | The attention events a **new** session wakes for. Kept schema-optional so absent (inherit the default attention set — the build events `ab watch` filters on by default plus the repository-journal events `ab watch
|
|
1355
|
+
| `wake` | inherits the default | array of event-type globs | The attention events a **new** session wakes for. Kept schema-optional so absent (inherit the default attention set — the build events `ab watch` filters on by default plus the repository-journal events `ab watch` follows) and `[]` (never wake; message-only) stay distinct. Each glob must match at least one build or repository-journal event type, validated with the same compiler `ab watch` uses. Existing sessions never retroactively inherit; an explicit `PUT .../wake` always wins. |
|
|
1327
1356
|
| `sandbox` | — | strict subtable; absence reads as the defaults | Sandbox behavior knobs. |
|
|
1328
1357
|
|
|
1329
1358
|
`[orchestrator.sandbox]` fields:
|
|
@@ -1572,10 +1601,8 @@ pre-attempt index and reports the original Git failure; merged worktree files
|
|
|
1572
1601
|
remain in place and the merge's exit status is unchanged. Upgrade never pushes
|
|
1573
1602
|
or rewrites history.
|
|
1574
1603
|
|
|
1575
|
-
Autobuild now installs 12 skills; only `ab-spec`, `ab-tickets`,
|
|
1576
|
-
are model-invocable.
|
|
1577
|
-
manual — also carries `disable-model-invocation: true`; it is read by the
|
|
1578
|
-
hosted turn runner, never invoked by a model in a build session.) The setup reference is an ordinary support file in the
|
|
1604
|
+
Autobuild now installs 12 skills; only `ab-spec`, `ab-tickets`, `ab-guide`,
|
|
1605
|
+
and `ab-operate` are model-invocable. The setup reference is an ordinary support file in the
|
|
1579
1606
|
editable/pristine `ab-guide` tree and participates in the same three-way
|
|
1580
1607
|
upgrade merge as every other vendored file.
|
|
1581
1608
|
|
package/docs/operator-api.md
CHANGED
|
@@ -184,3 +184,20 @@ The API does not expose phase-session commands, runner startup, a push or
|
|
|
184
184
|
long-poll streaming transport, or a generic event-append operation; event
|
|
185
185
|
streaming is composed client-side by `ab watch`, which polls the store's
|
|
186
186
|
event reads.
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
Dispatcher liveness is also visible through `ab repository status --json`:
|
|
190
|
+
`dispatcher.holder` identifies the unexpired repository lease owner, independently
|
|
191
|
+
of `latestRun`. Heartbeat age measures attributable renewals; tick age measures
|
|
192
|
+
durable progress. Missing historical evidence is null. The liveness query uses
|
|
193
|
+
bounded repository facts first, with a full-history fallback when a later
|
|
194
|
+
invocation hides the holder start or the latest stop. Other state reads retain
|
|
195
|
+
their bounded semantics.
|
|
196
|
+
|
|
197
|
+
Repository-following `ab watch` additionally emits a read-only `dispatcher.lost`
|
|
198
|
+
observation when an observed lease expires without a matching stop. JSON carries
|
|
199
|
+
`observation` instead of `event`, with run, observedAt, leaseExpiresAt and available
|
|
200
|
+
identity, plus the usual repo/build/state/cursor fields. It invents no durable
|
|
201
|
+
sequence or actor. Retain the cursor to avoid repeat reports across invocations.
|
|
202
|
+
No lease alone is not evidence of death. SIGINT, SIGHUP and SIGTERM drain dispatch,
|
|
203
|
+
record the first signal in the stop fact, and release its lease before exit.
|
|
@@ -77,3 +77,8 @@ The shared `TicketSource` contract is normative for the database adapter and
|
|
|
77
77
|
the hosted client/server pair. External pass-through is tested with a fake
|
|
78
78
|
source behind HTTP. The existing opt-in live Linear contract remains the proof
|
|
79
79
|
of Linear's GraphQL behavior; the hosted service uses that adapter unchanged.
|
|
80
|
+
|
|
81
|
+
Every ticket returned by list, get, or create requires a boolean `resolved`,
|
|
82
|
+
with the same source-native completion semantics as `DependencyState.resolved`.
|
|
83
|
+
The client preserves it when remapping ticket references. Missing or non-boolean
|
|
84
|
+
values fail wire validation. Client and service still require exact version matches.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defrex/autobuild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Agent-driven software delivery from groomed ticket to merged PR",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -139,8 +139,8 @@
|
|
|
139
139
|
},
|
|
140
140
|
"devDependencies": {
|
|
141
141
|
"@biomejs/biome": "2.5.5",
|
|
142
|
-
"@defrex/autobuild-postgres-store": "0.
|
|
143
|
-
"@defrex/autobuild-vercel-sandbox": "0.
|
|
142
|
+
"@defrex/autobuild-postgres-store": "0.10.1",
|
|
143
|
+
"@defrex/autobuild-vercel-sandbox": "0.10.1",
|
|
144
144
|
"@resvg/resvg-js": "2.6.2",
|
|
145
145
|
"@types/bun": "^1.3.14",
|
|
146
146
|
"@types/node": "^24.0.0",
|
|
@@ -53,7 +53,7 @@ export async function withDispatchTerminalRestore<T>(
|
|
|
53
53
|
terminal: TerminalOut,
|
|
54
54
|
run: () => Promise<T>,
|
|
55
55
|
boundary: TerminalRestoreProcess = process as unknown as TerminalRestoreProcess,
|
|
56
|
-
requestStop: () => void = () => {},
|
|
56
|
+
requestStop: (signal: NodeJS.Signals) => void = () => {},
|
|
57
57
|
): Promise<T> {
|
|
58
58
|
const hook =
|
|
59
59
|
command === 'dispatch'
|
|
@@ -96,7 +96,7 @@ export async function runBinary(
|
|
|
96
96
|
// cleanly at the next tick boundary (§15.6-C: in-flight leases expire and
|
|
97
97
|
// a future dispatch re-attaches).
|
|
98
98
|
const controller = new AbortController()
|
|
99
|
-
const onSigint = (): void => controller.abort()
|
|
99
|
+
const onSigint = (): void => controller.abort('SIGINT')
|
|
100
100
|
const ownsSigint = usesGenericSessionlessSigintHandler(command)
|
|
101
101
|
const terminal = processTerminal(process.stdout)
|
|
102
102
|
const removeSigint = installSessionlessSigintHandler(command, onSigint)
|
|
@@ -119,7 +119,7 @@ export async function runBinary(
|
|
|
119
119
|
...(resolveDashboardRenderer !== undefined ? { resolveDashboardRenderer } : {}),
|
|
120
120
|
}),
|
|
121
121
|
process as unknown as TerminalRestoreProcess,
|
|
122
|
-
|
|
122
|
+
(signal) => controller.abort(signal),
|
|
123
123
|
)
|
|
124
124
|
} finally {
|
|
125
125
|
removeSigint()
|
|
@@ -268,6 +268,8 @@ export interface DashboardModel {
|
|
|
268
268
|
harvestPaused: boolean
|
|
269
269
|
/** Stable row identity, never a row index. */
|
|
270
270
|
selection?: DashboardSelection
|
|
271
|
+
/** Invocation-local standby identity, independent of notices and build rows. */
|
|
272
|
+
standby?: string
|
|
271
273
|
/** Release courtesy, separate from transient and startup warnings so those
|
|
272
274
|
* notices can never overwrite one another. Store-only frontends project it
|
|
273
275
|
* from their correlated dispatcher run. */
|
|
@@ -1090,6 +1092,7 @@ interface DashboardFrameHeader {
|
|
|
1090
1092
|
queued: number
|
|
1091
1093
|
observationCount: number
|
|
1092
1094
|
selection?: DashboardSelection
|
|
1095
|
+
standby?: string
|
|
1093
1096
|
availableUpgrade?: string
|
|
1094
1097
|
warningLines?: readonly string[]
|
|
1095
1098
|
resumeInput?: ResumeInputView
|
|
@@ -1125,6 +1128,7 @@ export function buildDashboardFromProjected(
|
|
|
1125
1128
|
defaultAutoMerge: settings.defaultAutoMerge,
|
|
1126
1129
|
harvestPaused: harvestProjection.harvestPaused,
|
|
1127
1130
|
...(header.selection !== undefined ? { selection: header.selection } : {}),
|
|
1131
|
+
...(header.standby !== undefined ? { standby: header.standby } : {}),
|
|
1128
1132
|
...(header.availableUpgrade !== undefined ? { availableUpgrade: header.availableUpgrade } : {}),
|
|
1129
1133
|
...(header.warningLines !== undefined && header.warningLines.length > 0
|
|
1130
1134
|
? { warningLines: header.warningLines }
|