@defrex/autobuild 0.8.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.
Files changed (251) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +192 -0
  3. package/SPEC.md +2269 -0
  4. package/bin/ab-build-runner.ts +47 -0
  5. package/bin/ab-dev.ts +27 -0
  6. package/bin/ab-dispatch-kernel.ts +92 -0
  7. package/bin/ab-harvest-runner.ts +29 -0
  8. package/bin/ab-init-probe.ts +19 -0
  9. package/bin/ab-process-group-reaper.ts +32 -0
  10. package/bin/ab.ts +6 -0
  11. package/bin/agent/ab +3 -0
  12. package/docs/answering-blocked-builds.md +89 -0
  13. package/docs/architecture.md +726 -0
  14. package/docs/assets/headline-wide.png +0 -0
  15. package/docs/configuration.md +1623 -0
  16. package/docs/hosted-dispatcher-cutover-validation.md +178 -0
  17. package/docs/hosted-dispatcher.md +204 -0
  18. package/docs/mcp.md +119 -0
  19. package/docs/operator-api.md +186 -0
  20. package/docs/remote-store-protocol.md +972 -0
  21. package/docs/remote-ticket-protocol.md +79 -0
  22. package/docs/setup.md +289 -0
  23. package/docs/spec-standard.md +63 -0
  24. package/docs/ticket-dependencies.md +59 -0
  25. package/docs/vercel-sandbox-rollout-probe.md +7 -0
  26. package/package.json +135 -0
  27. package/packages/core/src/cli/args.ts +74 -0
  28. package/packages/core/src/cli/artifact.ts +182 -0
  29. package/packages/core/src/cli/binary.ts +198 -0
  30. package/packages/core/src/cli/build-control.ts +789 -0
  31. package/packages/core/src/cli/build-progress.ts +55 -0
  32. package/packages/core/src/cli/bulk-control.ts +385 -0
  33. package/packages/core/src/cli/context.ts +470 -0
  34. package/packages/core/src/cli/dashboard/actions.ts +59 -0
  35. package/packages/core/src/cli/dashboard/cells.ts +92 -0
  36. package/packages/core/src/cli/dashboard/composer.ts +230 -0
  37. package/packages/core/src/cli/dashboard/detail.ts +100 -0
  38. package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
  39. package/packages/core/src/cli/dashboard/live.ts +152 -0
  40. package/packages/core/src/cli/dashboard/model.ts +1173 -0
  41. package/packages/core/src/cli/dashboard/poll.ts +196 -0
  42. package/packages/core/src/cli/dashboard/render.ts +1568 -0
  43. package/packages/core/src/cli/dashboard/selection.ts +66 -0
  44. package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
  45. package/packages/core/src/cli/dashboard/session-view.ts +506 -0
  46. package/packages/core/src/cli/dashboard/transcript.ts +108 -0
  47. package/packages/core/src/cli/dev.ts +46 -0
  48. package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
  49. package/packages/core/src/cli/dispatch-process.ts +259 -0
  50. package/packages/core/src/cli/dispatch.ts +3692 -0
  51. package/packages/core/src/cli/dotenv.ts +48 -0
  52. package/packages/core/src/cli/env.ts +206 -0
  53. package/packages/core/src/cli/harvest.ts +698 -0
  54. package/packages/core/src/cli/help.ts +713 -0
  55. package/packages/core/src/cli/init-agent.ts +106 -0
  56. package/packages/core/src/cli/init-validation.ts +583 -0
  57. package/packages/core/src/cli/init.ts +756 -0
  58. package/packages/core/src/cli/installation.ts +333 -0
  59. package/packages/core/src/cli/keyboard.ts +261 -0
  60. package/packages/core/src/cli/main.ts +1399 -0
  61. package/packages/core/src/cli/mcp.ts +279 -0
  62. package/packages/core/src/cli/models.ts +65 -0
  63. package/packages/core/src/cli/observe.ts +55 -0
  64. package/packages/core/src/cli/plugin.ts +227 -0
  65. package/packages/core/src/cli/pr-attachments.ts +138 -0
  66. package/packages/core/src/cli/pr-summary.ts +124 -0
  67. package/packages/core/src/cli/publication-settlement.ts +188 -0
  68. package/packages/core/src/cli/repo-state.ts +213 -0
  69. package/packages/core/src/cli/repository-status.ts +80 -0
  70. package/packages/core/src/cli/self-update.ts +443 -0
  71. package/packages/core/src/cli/status.ts +895 -0
  72. package/packages/core/src/cli/store-opening.contract.ts +289 -0
  73. package/packages/core/src/cli/store-opening.ts +167 -0
  74. package/packages/core/src/cli/store-ref.ts +33 -0
  75. package/packages/core/src/cli/terminal-restore.ts +174 -0
  76. package/packages/core/src/cli/terminal.ts +361 -0
  77. package/packages/core/src/cli/terminals.ts +995 -0
  78. package/packages/core/src/cli/testkit.ts +194 -0
  79. package/packages/core/src/cli/ticket.ts +484 -0
  80. package/packages/core/src/cli/upgrade-agent.ts +180 -0
  81. package/packages/core/src/cli/upgrade-commit.ts +468 -0
  82. package/packages/core/src/cli/upgrade-notice.ts +91 -0
  83. package/packages/core/src/cli/upgrade-progress.ts +110 -0
  84. package/packages/core/src/cli/upgrade.ts +888 -0
  85. package/packages/core/src/cli/wait.ts +587 -0
  86. package/packages/core/src/cli/watch.ts +932 -0
  87. package/packages/core/src/config/live.ts +255 -0
  88. package/packages/core/src/config/load.ts +98 -0
  89. package/packages/core/src/config/roles.ts +438 -0
  90. package/packages/core/src/config/schema.ts +774 -0
  91. package/packages/core/src/distribution.ts +24 -0
  92. package/packages/core/src/events/catalog.ts +201 -0
  93. package/packages/core/src/events/envelope.ts +59 -0
  94. package/packages/core/src/events/payloads.ts +618 -0
  95. package/packages/core/src/events/repository.ts +559 -0
  96. package/packages/core/src/events/sessions.ts +180 -0
  97. package/packages/core/src/harvest/schema.ts +147 -0
  98. package/packages/core/src/ids.ts +45 -0
  99. package/packages/core/src/integration/harness.ts +681 -0
  100. package/packages/core/src/kernel/auto-merge.ts +259 -0
  101. package/packages/core/src/kernel/converge.ts +117 -0
  102. package/packages/core/src/kernel/dispatch-settings.ts +60 -0
  103. package/packages/core/src/kernel/dispatch-status.ts +141 -0
  104. package/packages/core/src/kernel/engine.ts +1047 -0
  105. package/packages/core/src/kernel/harvest.ts +823 -0
  106. package/packages/core/src/kernel/origin.ts +69 -0
  107. package/packages/core/src/kernel/phases.ts +196 -0
  108. package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
  109. package/packages/core/src/kernel/pr-attachments.ts +82 -0
  110. package/packages/core/src/kernel/reducer.ts +752 -0
  111. package/packages/core/src/kernel/stall.ts +155 -0
  112. package/packages/core/src/kernel/verify-applicability.ts +52 -0
  113. package/packages/core/src/markdown.ts +67 -0
  114. package/packages/core/src/ontology.ts +288 -0
  115. package/packages/core/src/open-map.ts +203 -0
  116. package/packages/core/src/operator/annotations.ts +187 -0
  117. package/packages/core/src/operator/client.ts +311 -0
  118. package/packages/core/src/operator/control.ts +117 -0
  119. package/packages/core/src/operator/index.ts +62 -0
  120. package/packages/core/src/operator/presentation.ts +22 -0
  121. package/packages/core/src/operator/protocol.ts +98 -0
  122. package/packages/core/src/operator/query.ts +201 -0
  123. package/packages/core/src/operator/registry.ts +1043 -0
  124. package/packages/core/src/operator/requests.ts +138 -0
  125. package/packages/core/src/operator/sandbox.ts +458 -0
  126. package/packages/core/src/operator/server.ts +616 -0
  127. package/packages/core/src/operator/sessions.ts +223 -0
  128. package/packages/core/src/operator/tickets.ts +177 -0
  129. package/packages/core/src/plugin-sdk/index.ts +174 -0
  130. package/packages/core/src/plugins/contract-entry.ts +74 -0
  131. package/packages/core/src/plugins/contract-env.ts +3 -0
  132. package/packages/core/src/plugins/load.ts +259 -0
  133. package/packages/core/src/plugins/manifest.ts +236 -0
  134. package/packages/core/src/plugins/registry.ts +274 -0
  135. package/packages/core/src/plugins/runtimes.ts +75 -0
  136. package/packages/core/src/ports/forge/contract.ts +258 -0
  137. package/packages/core/src/ports/forge/create.ts +74 -0
  138. package/packages/core/src/ports/forge/fake.ts +488 -0
  139. package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
  140. package/packages/core/src/ports/forge/github-transport.ts +352 -0
  141. package/packages/core/src/ports/forge/github.ts +1162 -0
  142. package/packages/core/src/ports/forge/local-git.ts +568 -0
  143. package/packages/core/src/ports/runner/claude.ts +873 -0
  144. package/packages/core/src/ports/runner/cli-stream.ts +90 -0
  145. package/packages/core/src/ports/runner/codex.ts +873 -0
  146. package/packages/core/src/ports/runner/contract.ts +492 -0
  147. package/packages/core/src/ports/runner/fake.ts +213 -0
  148. package/packages/core/src/ports/runner/json-record.ts +2 -0
  149. package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
  150. package/packages/core/src/ports/runner/one-shot.ts +29 -0
  151. package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
  152. package/packages/core/src/ports/runner/pi-bridge.js +6 -0
  153. package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
  154. package/packages/core/src/ports/runner/pi.ts +617 -0
  155. package/packages/core/src/ports/runner/production.ts +74 -0
  156. package/packages/core/src/ports/runner/provider-error.ts +121 -0
  157. package/packages/core/src/ports/runner/routing.ts +234 -0
  158. package/packages/core/src/ports/runner/runtime.ts +214 -0
  159. package/packages/core/src/ports/runner/session-env.ts +34 -0
  160. package/packages/core/src/ports/runner/stream-parts.ts +103 -0
  161. package/packages/core/src/ports/tickets/contract.ts +570 -0
  162. package/packages/core/src/ports/tickets/create.ts +135 -0
  163. package/packages/core/src/ports/tickets/fake.ts +215 -0
  164. package/packages/core/src/ports/tickets/file.ts +559 -0
  165. package/packages/core/src/ports/tickets/linear.ts +811 -0
  166. package/packages/core/src/ports/tickets/operations.ts +161 -0
  167. package/packages/core/src/ports/tickets/remote-index.ts +13 -0
  168. package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
  169. package/packages/core/src/ports/tickets/remote-server.ts +185 -0
  170. package/packages/core/src/ports/tickets/remote.ts +130 -0
  171. package/packages/core/src/ports/tickets/update.ts +51 -0
  172. package/packages/core/src/ports/types.ts +528 -0
  173. package/packages/core/src/ports/workspace/build-execution.ts +123 -0
  174. package/packages/core/src/ports/workspace/contract.ts +136 -0
  175. package/packages/core/src/ports/workspace/create.ts +132 -0
  176. package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
  177. package/packages/core/src/ports/workspace/fake.ts +432 -0
  178. package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
  179. package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
  180. package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
  181. package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
  182. package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
  183. package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
  184. package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
  185. package/packages/core/src/processes/build-child-exit.ts +78 -0
  186. package/packages/core/src/processes/build-child.ts +119 -0
  187. package/packages/core/src/processes/build-execution-state.ts +95 -0
  188. package/packages/core/src/processes/build-parent-watch.ts +35 -0
  189. package/packages/core/src/processes/build-runner.ts +2614 -0
  190. package/packages/core/src/processes/dispatcher.ts +2825 -0
  191. package/packages/core/src/processes/execution-settlement.ts +171 -0
  192. package/packages/core/src/processes/harvest-child.ts +115 -0
  193. package/packages/core/src/processes/harvest-execution-state.ts +121 -0
  194. package/packages/core/src/processes/harvest-runner.ts +1656 -0
  195. package/packages/core/src/processes/harvest.ts +595 -0
  196. package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
  197. package/packages/core/src/processes/phase-failure-budget.ts +52 -0
  198. package/packages/core/src/processes/process-group-reaper.ts +35 -0
  199. package/packages/core/src/processes/process-group.ts +65 -0
  200. package/packages/core/src/processes/publication-loss.ts +62 -0
  201. package/packages/core/src/processes/publication-state.ts +93 -0
  202. package/packages/core/src/processes/sandbox-state.ts +101 -0
  203. package/packages/core/src/registry.ts +24 -0
  204. package/packages/core/src/sessions/reducer.ts +185 -0
  205. package/packages/core/src/skills.ts +7 -0
  206. package/packages/core/src/spec-standard.ts +47 -0
  207. package/packages/core/src/store/adapter.ts +80 -0
  208. package/packages/core/src/store/build-scope.ts +272 -0
  209. package/packages/core/src/store/contract.ts +2581 -0
  210. package/packages/core/src/store/local/blobs.ts +38 -0
  211. package/packages/core/src/store/local/schema.ts +180 -0
  212. package/packages/core/src/store/local/store.ts +1713 -0
  213. package/packages/core/src/store/memory.ts +1138 -0
  214. package/packages/core/src/store/phase-session.ts +425 -0
  215. package/packages/core/src/store/remote/client.ts +780 -0
  216. package/packages/core/src/store/remote/index.ts +12 -0
  217. package/packages/core/src/store/remote/protocol.ts +348 -0
  218. package/packages/core/src/store/remote/server.ts +923 -0
  219. package/packages/core/src/store/remote/token.ts +137 -0
  220. package/packages/core/src/store/remote/version.ts +10 -0
  221. package/packages/core/src/store/retention.ts +91 -0
  222. package/packages/core/src/store/session-handle.ts +265 -0
  223. package/packages/core/src/store/streams/assemble.ts +79 -0
  224. package/packages/core/src/store/streams/lock.ts +42 -0
  225. package/packages/core/src/store/streams/session-writer.ts +306 -0
  226. package/packages/core/src/store/streams/types.ts +171 -0
  227. package/packages/core/src/store/streams/wait.ts +127 -0
  228. package/packages/core/src/store/subscribe.ts +105 -0
  229. package/packages/core/src/store/types.ts +466 -0
  230. package/packages/core/src/testing/fixed.ts +44 -0
  231. package/packages/core/src/testing/store-failures.ts +32 -0
  232. package/packages/core/src/zod-issues.ts +91 -0
  233. package/skills/code-review/SKILL.md +100 -0
  234. package/skills/finalize/SKILL.md +44 -0
  235. package/skills/guide/SKILL.md +1781 -0
  236. package/skills/guide/references/operator-api.md +164 -0
  237. package/skills/guide/references/plugin-authoring.md +354 -0
  238. package/skills/guide/references/remote-store-protocol.md +972 -0
  239. package/skills/guide/references/remote-ticket-protocol.md +79 -0
  240. package/skills/guide/references/setup.md +289 -0
  241. package/skills/guide/references/spec-standard.md +63 -0
  242. package/skills/guide/references/ticket-dependencies.md +59 -0
  243. package/skills/harvest/SKILL.md +66 -0
  244. package/skills/harvest-review/SKILL.md +63 -0
  245. package/skills/implement/SKILL.md +82 -0
  246. package/skills/plan/SKILL.md +123 -0
  247. package/skills/plan-review/SKILL.md +121 -0
  248. package/skills/reconcile/SKILL.md +65 -0
  249. package/skills/spec/SKILL.md +132 -0
  250. package/skills/tickets/SKILL.md +139 -0
  251. package/templates/autobuild.toml +36 -0
@@ -0,0 +1,164 @@
1
+ # Operator API v1
2
+
3
+ The hosted service exposes the polling-oriented operator surface at
4
+ `/operator/v1/repos/{repo}`. It is versioned with remote store protocol **2**.
5
+ Every request must send `X-Autobuild-Version`,
6
+ `X-Autobuild-Protocol-Version: 2`, and `Authorization: Bearer …`. Identity is
7
+ checked before authentication or resource lookup.
8
+
9
+ Mint a token offline; the signed user is used as the human actor on every write:
10
+
11
+ ```sh
12
+ AB_STORE_SECRET='…' bun packages/hosted-store-service/src/bin.ts mint operator \
13
+ --user 'Ada Lovelace' --ttl-seconds 3600
14
+ ```
15
+
16
+ Admin, build, repository, and deployment (`{ "operator": true, … }`) tokens
17
+ have no attributed operator authority. Human-operator tokens have no raw
18
+ `/builds`, `/repos`, `/sessions`, or `/tickets` authority. Encode repository,
19
+ build, session, and artifact kind as separate URL path segments.
20
+
21
+ ## Operator sessions
22
+
23
+ Operator sessions are the signed-in operator's durable orchestrator
24
+ conversations: a hosted-only third store resource. A session
25
+ belongs to the operator who created it; other operators of the same repository
26
+ can read it and cannot write to it (`403 auth` on non-owner writes). Every
27
+ write is attributed to the token's signed user — never a client-supplied
28
+ identity — and carries no `via` marker: session events are the delegate's own
29
+ log. Writes of any kind to an archived session are `409 refusal`, and the
30
+ archived status is terminal.
31
+
32
+ | Method and path | Result |
33
+ |---|---|
34
+ | `GET …/sessions` | The repository's session records, newest update first. Readable by any operator of the repository. |
35
+ | `POST …/sessions` | `{"title"?:"…"}` → `201` + the created `SessionRecord`; the signed-in operator owns it. |
36
+ | `GET …/sessions/{sid}` | `{session, state, turns}` where `state` is the reducer's derived state (`status`, `openTurn`, `pendingApproval`, `wakeGlobs`, `wakeCursors`, `turns`) and `turns` the ordered turn list. Readable by any operator of the repository. |
37
+ | `POST …/sessions/{sid}/messages` | `{"text":"…"}` → appends `message.posted`. Owner only. |
38
+ | `PUT …/sessions/{sid}/wake` | `{"globs":["…"]}` (possibly empty) → appends `session.wake-set`. Owner only. |
39
+ | `POST …/sessions/{sid}/approvals` | `{"turn":"…","toolCallId":"…","decision":"approve"\|"deny"}` → `approval.answered`. Owner only; `409 refusal` when the reduced state has no matching pending approval. |
40
+ | `POST …/sessions/{sid}/archive` | Empty body → `session.archived`. Owner only; `409 refusal` when already archived. |
41
+ | `GET …/sessions/{sid}/turns/{turn}/stream?since=N&wait=N` | The turn's stream read: `{chunks,status,outcome?,artifact?}` with `since` strictly greater-than and the same bounded-wait semantics as the stream protocol (whole seconds, clamped at 30). Unknown turn is `404 not-found`. Readable by any operator of the repository. |
42
+
43
+ Session state is a reduction of the session's event log, never a stored
44
+ column; a `GET …/sessions/{sid}` response therefore always reflects the
45
+ session's full history. The API never exposes raw session-event append: the
46
+ turn runner (a later ticket) is the only producer of turn facts.
47
+
48
+ ## Reads
49
+
50
+ | Method and path | Result |
51
+ |---|---|
52
+ | `GET …/builds?scope=active\|queued\|all` | Build summaries, newest update first. `queued` means active plus queued, matching `ab builds --queued`. |
53
+ | `GET …/builds/{slug}` | `{detail, dashboardRow}`; `dashboardRow` remains a `CLEANING` row after `build.aborted` and becomes `null` only when the dashboard stops listing it (after cleanup's `build.completed`). |
54
+ | `GET …/dashboard` | `{generatedAt, model, settingsHeader}`. `model` is the dashboard's rows, steps, elapsed timing inputs, harvest row, queue/capacity and settings projection. |
55
+ | `GET …/status` | Repository intake, pause, and default-auto-merge projection. |
56
+ | `GET …/harvest/status` | Harvest gate, runs, steps, recovery, and attention projection. |
57
+ | `GET …/builds/{slug}/artifacts/{kind}?rev=N` | Raw bytes (`application/octet-stream`) plus content-disposition and `X-Autobuild-Artifact-*` metadata headers. Omit `rev` for latest. |
58
+ | `GET …/tickets?state=S&label=L` | `{states,tickets,diagnostics,criteria,triageState,readyState}`. The two lifecycle names come from effective repository configuration (including provider fallback). With no `state`, `criteria.state` is `triageState`; repeated `label` parameters narrow that state with AND filters. An explicit `state` selects that exact backend workflow state. |
59
+ | `GET …/tickets/{id}` | `{ticket,blockers,build}`. Blockers include native `exists`/`resolved` status; `build` prefers an active matching repository build, otherwise the most recently updated one. |
60
+
61
+ For artifact reads, an absent `rev` selects the latest revision. A supplied value
62
+ must match `[0-9]+` (one or more ASCII base-10 digits) and represent an integer
63
+ from `0` through `Number.MAX_SAFE_INTEGER` (`9007199254740991`). All other
64
+ supplied forms return `400 validation`.
65
+
66
+ Dashboard reads use the latest durable, run-correlated `effectiveConfig`
67
+ repository artifact. Missing, corrupt, or invalid configuration returns a typed
68
+ `409 effective-config-unavailable`; the service never guesses from a checkout.
69
+ Clients poll these reads; streaming is not provided. `triageState`, `readyState`,
70
+ and every entry in `states` are backend state names and must be sent back verbatim
71
+ when used with the move control.
72
+
73
+ ## Controls
74
+
75
+ JSON bodies are strict: unknown fields are rejected.
76
+
77
+ | Method and path | Body |
78
+ |---|---|
79
+ | `POST …/builds/{slug}/control` | `{"action":"pause"\|"cancel-pause"\|"resume"\|"abort"\|"discard"\|"auto-merge-on"\|"auto-merge-off"}` |
80
+ | `POST …/builds/{slug}/answer` | One answer variant described below. |
81
+ | `PUT …/settings/intake` | `{"enabled":boolean}` |
82
+ | `POST …/settings/intake/toggle` | Empty body. |
83
+ | `PUT …/settings/auto-merge-default` | `{"enabled":boolean}` |
84
+ | `POST …/settings/auto-merge-default/toggle` | Empty body. |
85
+ | `POST …/bulk-control` | `{"action":"pause"\|"resume"}`. The hold fact, intake fact, then eligible build events are written in that order. |
86
+ | `POST …/harvest/control` | `{"action":"toggle-gate"}` or `{"action":"run","run":"…"}`. A run action is bound to that concrete projected run. |
87
+ | `POST …/tickets` | `{"title":"…","body":"…","labels"?:[],"state"?:"…","blockedBy"?:[]}`; omitted state preserves the backend default. |
88
+ | `PATCH …/tickets/{id}` | Any nonempty subset of `title`, `body`, and complete-replacement `labels`. Body bytes are not normalized. |
89
+ | `POST …/tickets/{id}/move` | `{"state":"backend state name"}`. |
90
+ | `POST …/tickets/{id}/block` | `{"blockerIds":["…"]}`. |
91
+ | `POST …/tickets/{id}/unblock` | `{"blockerIds":["…"]}`. |
92
+
93
+ Answer variants:
94
+
95
+ ```json
96
+ {"resolution":"guidance","text":"…"}
97
+ {"resolution":"retry"}
98
+ {"resolution":"dismiss","text":"optional audit text"}
99
+ {"resolution":"review-round-ceiling","ceiling":4,"text":"optional guidance"}
100
+ {"resolution":"revise-spec","origin":"body","body":"replacement spec","text":"optional guidance"}
101
+ {"resolution":"revise-spec","origin":"ticket","body":"current amended ticket body","text":"optional guidance"}
102
+ ```
103
+
104
+ The retry object is deliberately bare: adding `text` is a `400 validation`
105
+ rather than silently changing the resolution to guidance. A revision object may
106
+ also carry `ceiling` only to receive the shared `incompatible-answer-options`
107
+ refusal; a ceiling and spec revision cannot be performed together.
108
+
109
+ The hosted service has no ticket-provider credentials. For ticket-origin
110
+ revision, a trusted caller fetches the current ticket body and supplies it; the
111
+ build must still have a recorded ticket. Revision conformance, lazy retry,
112
+ artifact metadata, event ordering, and refusal rules are the same as the CLI.
113
+
114
+ ### Success results
115
+
116
+ Build control returns one of:
117
+
118
+ - `{kind:"command",slug,command,event}` after writing a pause, resume, abort,
119
+ discard, or per-build auto-merge command. `command` is the durable command;
120
+ successful cancel-pause therefore returns `command:"resume"`.
121
+ - `{kind:"answer-required",slug,escalationIds}` when `action:"resume"` targets
122
+ a blocked build. This is a successful prompt transition but **does not append
123
+ an event**; submit one of the answer requests next.
124
+ - `{kind:"answered",slug,count,resolution,resumed,...}` from the answer route.
125
+ `resolution` is `guidance`, `retry`, `dismiss-finding`, or `revise-spec`.
126
+ Depending on the operation, the result also includes `remainingOpen`,
127
+ `reviewRoundCeiling:{loop,value}`, `specRev`, `authorizedEarlier`, or a
128
+ `terminalSignal` describing a terminal status, pending abort, or ended PR.
129
+
130
+ Repository setting writes and toggles return `{enabled,event}`. Harvest gate
131
+ toggle returns `{command:"pause"|"resume",event}`; a concrete-run action
132
+ returns `{action:"resume"|"acknowledge",event}`. Bulk control returns
133
+ `{direction,slugs,paused,intake}`, where `slugs` is the write-ordered set of
134
+ builds that received a command. Every `event` is the complete durable envelope
135
+ including repository/build id, sequence, timestamp, actor, type, and payload.
136
+
137
+ ## Errors
138
+
139
+ JSON errors have `{kind,error,code?,progress?}`. Malformed input is `400
140
+ validation`; missing resources/artifacts are `404 not-found`; missing/invalid
141
+ credentials are `401 auth`; a valid non-operator scope is `403 auth`; version
142
+ skew and unavailable projections are `409 conflict`. A build outside the URL's
143
+ repository is deliberately hidden as `404 not-found` with `unknown build`,
144
+ consistently for detail, control, answer, and artifact routes. Control refusals are `409
145
+ refusal` and preserve the shared `BuildControlError` code and exact reason text.
146
+ A partial bulk failure uses code `bulk-partial` and includes the durable write
147
+ progress and unattempted builds. Unexpected failures are `500 internal`.
148
+
149
+ ## Browser gateway
150
+
151
+ The same deployment serves the web dashboard through `/api/web/repos/{repo}/…`.
152
+ That route is not a second public bearer-token API: it requires a current
153
+ Better Auth HTTP-only cookie, rechecks the deployment email and repository
154
+ allowlists, rejects cross-origin JSON controls, and delegates only the dashboard,
155
+ build, settings, and harvest operator suffixes listed above. It replaces caller-supplied authorization/version
156
+ headers with a server-minted token that expires after 30 seconds and carries
157
+ the normalized signed-in email. Consequently every durable control event has
158
+ the browser user's human actor while no token or signing secret reaches client
159
+ code. Responses are private/no-store; a 401 sends the application back to sign
160
+ in. Browser clients poll the visible dashboard every two seconds; live transcript
161
+ streaming is not provided. Delegated bearer tokens remain server-side.
162
+
163
+ The API does not expose phase-session commands, runner startup, live streaming,
164
+ or a generic event-append operation.
@@ -0,0 +1,354 @@
1
+ # Authoring Autobuild plugins
2
+
3
+ Use this reference when asked to connect Autobuild to another ticket tracker,
4
+ agent runtime, workspace system, or forge. A plugin is a trusted Bun module
5
+ loaded in-process from the consuming repository. **Definition of done for an
6
+ adapter: the unchanged shared contract suite for its port passes.** Interface
7
+ conformance or a successful `ab plugin doctor` alone is not certification.
8
+
9
+ ## 1. Choose the port and preserve its semantics
10
+
11
+ Import public types only from `@defrex/autobuild/plugin-sdk`; package-internal module
12
+ paths are not API.
13
+
14
+ | CLI port | Manifest map | Factory result | Required semantics |
15
+ |---|---|---|---|
16
+ | `ticket-source` | `ticketSources` | `TicketSource` | Initiates a build by listing/getting/claiming tickets and receives comments/transitions/creates/updates. It owns lifecycle names, dependency completion, idempotent creation, stable external creation-key projection, and partial-list diagnostics. It is never consulted mid-build and is not artifact storage. |
17
+ | `agent-runtime` | `agentRuntimes` | `RuntimeRegistration` | Wrap an `AgentRunner` that supports start/continue/end, complete transcripts and usage, per-turn environment refresh and cancellation, typed failures with verbatim messages, the retry-compatible `permanent` bit, and optional `cause: availability | exhaustion | credentials | configuration` routing evidence, plus the distribution-managed `ab` launcher. A cancelled turn must retain an endable handle for transcript deposition. Declare served model prefixes/default; optional tool-free `oneShot` is a capability beside the runner interface. |
18
+ | `workspace-provider` | `workspaceProviders` | `WorkspaceProvider` | Provision an absolute, writable working copy for the requested branch, return durable base evidence, resume an intact branch non-destructively, rematerialize a lost copy, and make release idempotent. Never silently re-cut resumed work from a newer base. |
19
+ | `forge` | `forges` | `Forge` | Implement kernel-owned regular pushes, idempotent PR opening, PR-state projection, gated/ungated auto-merge behavior, head-guarded squash merge, and comments. API 1.2 adds optional idempotent `closePr` and `deleteBranch` abort-cleanup capabilities; `closePr` must preserve a racing merge. Without them abort cleanup remains visibly pending. Agents never receive forge credentials or push. `prAttachments` is an optional image-hosting capability; absence must preserve the supported text-only path. |
20
+
21
+ Plugin API 1.3 adds the optional AgentRunner failure `cause` and the
22
+ `exhaustion-failure` shared-contract scenario. Legacy failures without `cause`
23
+ remain supported: `permanent: false` is eligible for a configured alternate,
24
+ while `permanent: true` stops the chain. A runtime plugin that emits/depends on
25
+ the richer cause should declare an API range containing `^1.3.0`.
26
+
27
+ Plugin API 1.4 adds optional `Ticket.creationKey`. Ticket-source plugins should
28
+ project the stable external create/adoption key from `create`, `get`, and
29
+ `listReady`; Autobuild uses it to correlate ready tickets with durable Harvest
30
+ reservations while blocker recording is unfinished. Legacy tickets may omit the
31
+ field and continue to dispatch normally. A plugin relying on this host gate
32
+ should declare an API range containing `^1.4.0`.
33
+
34
+ Plugin API 1.5 makes remote workspace locations explicit. `path` is the path in
35
+ the workspace environment; set optional `localPath` only when that same working
36
+ copy is directly reachable by the dispatcher. Workspace-owned executors receive
37
+ the durable provider locator as `BuildExecutionStart.workspaceRef`; local PID
38
+ supervision remains host-owned. A workspace plugin relying on these fields
39
+ should declare an API range containing `^1.5.0`.
40
+
41
+ `BuildStore` and `BlobStore` contract types are exported for remote-server
42
+ authors, but BuildStore is **not** an in-process manifest map. Implement the
43
+ colocated language-neutral
44
+ [remote store protocol](remote-store-protocol.md) and run its conformance path
45
+ instead. `TelemetrySource` is a frozen port type with no plugin registration
46
+ surface in this release.
47
+
48
+ All four manifest maps have production selectors. Use `[tickets].source` for a
49
+ ticket source, the root `forge` key for a forge, `[workspace].provider` for a
50
+ workspace provider, and `[roles.*].runtime` for an agent runtime. Omitted forge
51
+ and workspace selectors retain the builtin `github` and `git-worktree` defaults;
52
+ role inheritance retains its normal runtime fallback.
53
+
54
+ ## 2. Create a repository-local module
55
+
56
+ Start in the consuming repository, for example:
57
+
58
+ ```text
59
+ my-repository/
60
+ ├── autobuild.toml
61
+ └── autobuild-plugin.ts
62
+ ```
63
+
64
+ Use erased imports in real adapter production code whenever the SDK symbols are
65
+ only types:
66
+
67
+ ```ts
68
+ import type {
69
+ AutobuildPluginManifest,
70
+ ForgePluginFactory,
71
+ } from '@defrex/autobuild/plugin-sdk'
72
+ import { AcmeForge } from '@acme/forge-client'
73
+
74
+ const createForge: ForgePluginFactory = ({ config, env, repoRoot }) => {
75
+ const token = env.ACME_FORGE_TOKEN
76
+ if (!token) throw new Error('ACME_FORGE_TOKEN is required')
77
+ return new AcmeForge({ token, config, repoRoot })
78
+ }
79
+
80
+ export default {
81
+ name: 'acme-forge',
82
+ apiVersion: '^1.2.0',
83
+ forges: { acme: createForge },
84
+ } satisfies AutobuildPluginManifest
85
+ ```
86
+
87
+ Every module must default-export one strict `AutobuildPluginManifest`:
88
+
89
+ - `name` is a nonblank diagnostic/ownership identity; it need not equal the npm
90
+ package name.
91
+ - `apiVersion` is a semver **range accepted by the plugin**, not the plugin's
92
+ package version. Choose the narrowest range containing the host plugin API
93
+ against which you ran contracts. `ab plugin list` reports the declared range
94
+ and host version; invalid or incompatible ranges fail loading.
95
+ - The only registration keys are `ticketSources`, `agentRuntimes`,
96
+ `workspaceProviders`, and `forges`. Unknown manifest fields fail validation.
97
+ Adapter names are yours to choose: every declared name is registered
98
+ verbatim, including one such as `__proto__` that collides with an inherited
99
+ object property — declare that one computed, `{ ['__proto__']: factory }`,
100
+ since a plain `__proto__:` key in an object literal sets the prototype
101
+ instead of declaring an adapter. The parsed maps have a null prototype, so
102
+ reading an undeclared name answers `undefined` rather than an inherited
103
+ member. A blank or whitespace-only name is rejected: an adapter is selected
104
+ from configuration by name.
105
+ - A registration may be a bare adapter factory, or
106
+ `{ factory, contract: { factory, live? } }`. The first factory returns the
107
+ adapter; the contract factory returns that port suite's fixture factory.
108
+ Ticket-source descriptors may additionally declare a deduplicated
109
+ `requiredEnv` list; the host rejects missing or empty credentials before
110
+ invoking that selected factory.
111
+ - Every adapter and contract factory receives `{ config, env, repoRoot }`:
112
+ adapter-specific read-only `config`, the process `env`, and `repoRoot` as the
113
+ absolute consuming-repository root. Ticket sources receive the existing
114
+ `[tickets]` fields, workspace providers receive `[workspace.config]`, and
115
+ runtime and forge factories currently receive an empty `config` object.
116
+
117
+ Add the module specifier at the TOML root, before any table. Repository-path
118
+ specifiers resolve from the config-bearing root, which is the immutable build
119
+ worktree in scoped phases. npm package specifiers resolve from the consuming
120
+ main checkout's installed dependencies first, then from the Autobuild
121
+ installation the CLI runs from (so `bun add -g @defrex/autobuild-<extension>`
122
+ next to the CLI is enough); a repository copy wins over an installed one, and
123
+ missing packages fail and are never installed automatically:
124
+
125
+ ```toml
126
+ plugins = ["./autobuild-plugin.ts"]
127
+ forge = "acme"
128
+
129
+ [tickets]
130
+ source = "file"
131
+ readyState = "ready"
132
+ ```
133
+
134
+ Each configured specifier string must be unique. An exact repeat fails config
135
+ validation before Bun resolves or evaluates any plugin, identifies the repeated
136
+ value and both list positions, and tells the operator to remove or deduplicate
137
+ the entry. Distinct specifiers remain separate declarations.
138
+
139
+ Select only the adapters the repository uses. Set `forge = "acme"` for the
140
+ example forge above; set `[tickets].source`, `[workspace].provider`, or a
141
+ `[roles.*].runtime` to the matching registration name for the other ports.
142
+ Ticket plugins receive the existing strict `[tickets]` fields—there is no
143
+ untyped plugin-options table. Workspace plugins put adapter-owned settings only
144
+ under the open `[workspace.config]` table.
145
+
146
+ Plugin modules load in declaration order. Resolution/evaluation failure, a
147
+ missing or malformed default export, incompatible API range, or collision
148
+ fails before claims/builds. Builtin and earlier-plugin names cannot be
149
+ shadowed within one port; a collision between distinct plugins identifies the
150
+ adapter and both owners. The same adapter name may exist on different ports.
151
+ Registration is atomic per manifest. Plugins are trusted like configured shell
152
+ commands: Bun evaluates them in-process with repository authority and no
153
+ sandbox.
154
+
155
+ ## 3. Keep secrets out of source and config
156
+
157
+ Never put credentials in `autobuild.toml`, the manifest, committed fixtures, or
158
+ contract output. Read them from the factory's `env` (for example
159
+ `env.ACME_FORGE_TOKEN`) and document the required environment variables. Keep
160
+ local values in ignored environment files or the process secret manager.
161
+ Validate missing credentials when the selected adapter or live fixture is
162
+ created, not at module top level, so registration remains lazy and diagnostics
163
+ can inspect unrelated adapters. Ticket-source descriptors should declare
164
+ `requiredEnv` when the host can enforce the credential names before factory
165
+ invocation; other ports validate their environment in their factories.
166
+
167
+ ## 4. Add the contract harness first
168
+
169
+ A contract-bearing registration has this shape:
170
+
171
+ ```ts
172
+ const manifest = {
173
+ name: 'acme',
174
+ apiVersion: '^1.2.0',
175
+ ticketSources: {
176
+ acme: {
177
+ factory: createAcmeTicketSource,
178
+ contract: { factory: createAcmeTicketContract },
179
+ },
180
+ },
181
+ } satisfies AutobuildPluginManifest
182
+ ```
183
+
184
+ The descriptor's factory returns a `TicketSourceContractFactory`,
185
+ `AgentRunnerContractFactory`, `WorkspaceProviderContractFactory`, or
186
+ `ForgeContractFactory` as appropriate. Use the matching harness types exported
187
+ by `@defrex/autobuild/plugin-sdk`. The host invokes the unchanged shared suite; do not
188
+ copy, wrap, skip, or weaken its assertions.
189
+
190
+ Contract fixtures must isolate resources (UUID-namespaced records, branches,
191
+ PRs, sessions, or workspaces), expose the independent controls/probes required
192
+ by their harness type, clean up in `cleanup` even after assertion failure, and
193
+ make retries/release idempotent where the port requires it. A fixture that
194
+ contacts a provider or mutates external resources must declare
195
+ `contract: { factory, live: true }`. Autobuild refuses to launch it unless the
196
+ operator explicitly sets:
197
+
198
+ ```sh
199
+ AB_RUN_LIVE_PORT_CONTRACTS=1 ab plugin test forge acme
200
+ ```
201
+
202
+ Run the author loop from the consuming repository:
203
+
204
+ ```sh
205
+ ab plugin list
206
+ ab plugin doctor
207
+ ab plugin test ticket-source acme
208
+ ```
209
+
210
+ - `list` shows builtins and configured registrations, provenance, module
211
+ resolution, plugin API compatibility, and contract availability.
212
+ - `doctor` attempts every configured module and exits nonzero if any fail. It
213
+ checks loading/registration, not adapter semantics.
214
+ - `test` launches Bun's unchanged suite for exactly one port/adapter, forwards
215
+ per-test output, and returns its exit status.
216
+
217
+ Repeat after every adapter change. The adapter is done only when that command's
218
+ shared suite is green (with the explicit live opt-in when applicable).
219
+
220
+ ## 5. Zero-network wiring walkthrough
221
+
222
+ This executable scratch example proves module resolution, manifest spelling,
223
+ configuration, and the TicketSource contract bridge without network access. It
224
+ uses Autobuild's reference fake and is **only a wiring exercise**. Replace the
225
+ fake and fixture with the real provider implementation; certification means the
226
+ real adapter's harness passes the same unchanged suite.
227
+
228
+ Create `autobuild-plugin.ts` with the exact block below.
229
+
230
+ <!-- plugin-authoring-walkthrough-module:start -->
231
+ ```ts
232
+ import { FakeTicketSource } from '@defrex/autobuild/plugin-sdk'
233
+ import type {
234
+ AutobuildPluginManifest,
235
+ TicketSourceContractFactory,
236
+ TicketSourcePluginFactory,
237
+ } from '@defrex/autobuild/plugin-sdk'
238
+
239
+ const createSource: TicketSourcePluginFactory = () => new FakeTicketSource()
240
+
241
+ const ticketContract: TicketSourceContractFactory = async () => ({
242
+ source: new FakeTicketSource([], {
243
+ createState: 'Triage',
244
+ doneState: 'Done',
245
+ }),
246
+ states: { ready: 'Ready', claimed: 'Doing', completed: 'Done' },
247
+ editableLabel: 'contract',
248
+ })
249
+
250
+ export default {
251
+ name: 'walkthrough',
252
+ apiVersion: '^1.2.0',
253
+ ticketSources: {
254
+ walkthrough: {
255
+ factory: createSource,
256
+ contract: { factory: () => ticketContract },
257
+ },
258
+ },
259
+ } satisfies AutobuildPluginManifest
260
+ ```
261
+ <!-- plugin-authoring-walkthrough-module:end -->
262
+
263
+ Create `autobuild.toml`:
264
+
265
+ <!-- plugin-authoring-walkthrough-config:start -->
266
+ ```toml
267
+ plugins = ["./autobuild-plugin.ts"]
268
+
269
+ [tickets]
270
+ source = "walkthrough"
271
+ readyState = "ready"
272
+ ```
273
+ <!-- plugin-authoring-walkthrough-config:end -->
274
+
275
+ Then run:
276
+
277
+ ```sh
278
+ ab init
279
+ ab plugin doctor
280
+ ab plugin list
281
+ ab plugin test ticket-source walkthrough
282
+ ```
283
+
284
+ `ab init` preserves the existing config and vendors this guide. Doctor and list
285
+ must show the `walkthrough` plugin/adapter, and the final command must exit zero
286
+ with the TicketSource contract tests.
287
+
288
+ ## 6. Package and publish when reuse warrants it
289
+
290
+ A repository-local module is the shortest path and needs no packaging. For an
291
+ npm plugin, keep one public module and an ordinary test/typecheck setup:
292
+
293
+ ```text
294
+ acme-autobuild-plugin/
295
+ ├── src/index.ts
296
+ ├── package.json
297
+ ├── tsconfig.json
298
+ └── bun.lock
299
+ ```
300
+
301
+ A representative package manifest is:
302
+
303
+ ```json
304
+ {
305
+ "name": "@acme/autobuild-plugin",
306
+ "version": "1.0.0",
307
+ "type": "module",
308
+ "exports": { ".": "./src/index.ts" },
309
+ "files": ["src", "README.md"],
310
+ "scripts": {
311
+ "typecheck": "tsc --noEmit",
312
+ "test": "bun test",
313
+ "prepublishOnly": "bun run typecheck && bun test"
314
+ },
315
+ "devDependencies": { "autobuild": "^2.0.0", "typescript": "^7.0.0" },
316
+ "peerDependencies": { "autobuild": "^2.0.0" },
317
+ "peerDependenciesMeta": { "autobuild": { "optional": true } }
318
+ }
319
+ ```
320
+
321
+ When production code imports only SDK types, those imports erase: Autobuild is
322
+ an authoring/test dev dependency, not a runtime `dependencies` entry. An
323
+ optional peer declaration communicates tested host-package compatibility to
324
+ package consumers without making plugin execution depend on a second Autobuild
325
+ copy. Runtime imports (for example importing a reference fake in the scratch
326
+ walkthrough) are different and require a runtime dependency; do not ship the
327
+ fake walkthrough as a production adapter.
328
+
329
+ Keep the package's own semver independent from the manifest's `apiVersion`
330
+ range. On an incompatible host plugin API, update the adapter and contracts,
331
+ change the range only after they pass against that host, and release the
332
+ package version according to the user-visible compatibility change.
333
+
334
+ Before publishing:
335
+
336
+ ```sh
337
+ bun run typecheck
338
+ bun test
339
+ npm pack --dry-run
340
+ npm pack
341
+ # inspect the tarball, then install that exact .tgz in a scratch consumer
342
+ npm publish --access public
343
+ ```
344
+
345
+ Inspect the dry-run/tarball file list for source, secrets, local fixtures, and
346
+ missing exports. Install the generated tarball in a scratch consuming
347
+ repository, configure `plugins = ["@acme/autobuild-plugin"]`, and rerun
348
+ `ab plugin doctor`, `ab plugin list`, and the contract command before publish.
349
+ Use `--access public` for a public scoped package; omit it when registry/access
350
+ policy says otherwise.
351
+
352
+ Official npm references: [`package.json` fields](https://docs.npmjs.com/cli/v11/configuring-npm/package-json),
353
+ [`npm pack`](https://docs.npmjs.com/cli/v11/commands/npm-pack), and
354
+ [`npm publish`](https://docs.npmjs.com/cli/v11/commands/npm-publish).