@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,713 @@
1
+ export type HelpAudience = 'human' | 'ai'
2
+
3
+ export interface HelpEntry {
4
+ name: string
5
+ audience: HelpAudience
6
+ summary: string
7
+ detail: string
8
+ }
9
+
10
+ function page(lines: string[]): string {
11
+ return lines.join('\n')
12
+ }
13
+
14
+ /**
15
+ * The command catalog is the single source for overview inventory/order and
16
+ * detailed help. Keep command families here rather than deriving them from a
17
+ * parser: several families have mixed session boundaries and behavioral notes
18
+ * that are part of the public CLI contract.
19
+ */
20
+ export const HELP_CATALOG: readonly HelpEntry[] = [
21
+ {
22
+ name: 'help',
23
+ audience: 'human',
24
+ summary: 'Show the command overview or detailed help for one command.',
25
+ detail: page([
26
+ 'Usage:',
27
+ ' ab help',
28
+ ' ab --help',
29
+ ' ab -h',
30
+ ' ab help <command>',
31
+ ' ab <command> --help',
32
+ '',
33
+ 'The first three forms show the command overview. The last two forms show',
34
+ 'the same detailed page for a command. Help is sessionless and requires no',
35
+ 'AB_* environment variables.',
36
+ ]),
37
+ },
38
+ {
39
+ name: 'init',
40
+ audience: 'human',
41
+ summary: 'Install Autobuild and hand repository setup to a coding agent.',
42
+ detail: page([
43
+ 'Usage:',
44
+ ' ab init [target] [--force]',
45
+ ' ab init [target] --validate',
46
+ '',
47
+ 'Deterministically vendor the editable ab-* skills, maintain .gitignore, probe every',
48
+ 'registered agent runtime, and create a stack-neutral autobuild.toml only when absent.',
49
+ 'The target defaults to the current directory. This command runs outside sessions.',
50
+ '',
51
+ 'On an interactive terminal, init selects a usable shipped runtime in a fixed product',
52
+ 'preference order and starts a conversational setup agent. The agent inspects the',
53
+ 'repository, asks for team-specific choices, configures commands, verification, roles,',
54
+ 'and tickets, and leaves one groomed ticket. Its exit status becomes init’s exit status.',
55
+ 'Without a usable runtime or interactive terminal, init completes installation, prints',
56
+ 'every probe result, and emits the identical setup prompt for use in a coding agent.',
57
+ '',
58
+ 'On reruns, init preserves autobuild.toml and asks the setup agent to review and improve it.',
59
+ '--force overwrites edited vendored skills only; it never overwrites autobuild.toml.',
60
+ '',
61
+ 'After the setup agent has committed the repository-specific configuration,',
62
+ '--validate runs a noninteractive readiness check without creating a ticket, build,',
63
+ 'phase, or session. It uses a disposable detached worktree for git-worktree and a',
64
+ 'fresh Vercel Sandbox for vercel-sandbox; the environment is identified and removed.',
65
+ 'Setup, plugins, every selected primary/alternate runtime and model, and read-only',
66
+ 'Store access are checked in that execution context. An absent local database is reported',
67
+ 'without creating .autobuild; an existing one is inspected through a disposable snapshot',
68
+ 'without opening or modifying its repository files. Remote validation never falls back',
69
+ 'locally. --validate cannot be combined with --force.',
70
+ ]),
71
+ },
72
+ {
73
+ name: 'upgrade',
74
+ audience: 'human',
75
+ summary: 'Update Autobuild and merge updated default skills.',
76
+ detail: page([
77
+ 'Usage:',
78
+ ' ab upgrade [target] [--no-self-update | --version <semver>] [--no-commit]',
79
+ '',
80
+ '`ab update` is an accepted alias; `upgrade` remains the canonical name.',
81
+ '',
82
+ 'By default, update the running Bun installation to its latest published',
83
+ 'release — the npm registry for a registry install, the repository’s GitHub',
84
+ 'Release for a github: install — then use the replacement binary and defaults to',
85
+ 'Three-way merge vendored ab-* skills (§16.3). The target defaults to the',
86
+ 'current directory. --version selects an exact release, including an older',
87
+ 'one. --no-self-update skips distribution update and merges against what is',
88
+ 'installed. Source checkouts are never mutated and still merge installed skills.',
89
+ '',
90
+ 'For latest, unknown mechanisms and lookup/install failures warn and continue',
91
+ 'with installed defaults. For --version, those failures stop before skill merge.',
92
+ 'A local Bun install',
93
+ 'updates its owning package.json and bun.lock; a global install updates Bun’s',
94
+ 'global package-manager state. Local customizations remain preferred, and',
95
+ 'unresolved conflicts remain byte-untouched.',
96
+ '',
97
+ 'After a clean merge, upgrade commits only the skill, pristine, discovery-link,',
98
+ 'and target-local package.json/bun.lock changes it owns. Unrelated staged and',
99
+ 'working-tree changes remain untouched. --no-commit leaves all merge output for',
100
+ 'manual review. Content or discovery conflicts, pre-existing dirt in an owned',
101
+ 'path, a non-Git target, an in-progress Git operation, or a replacement handoff',
102
+ 'missing its pre-update baseline suppress the commit with a warning. A staging or',
103
+ 'commit failure restores the pre-attempt index without touching merged files or',
104
+ 'changing upgrade’s merge-derived exit status.',
105
+ 'This command runs outside sessions.',
106
+ ]),
107
+ },
108
+ {
109
+ name: 'dispatch',
110
+ audience: 'human',
111
+ summary: 'Run the repository build dispatcher and dashboard.',
112
+ detail: page([
113
+ 'Usage:',
114
+ ' ab dispatch [--once] [--interval <s>] [--store <ref>] [--plain] [--intake | --no-intake] [--auto-merge | --no-auto-merge]',
115
+ '',
116
+ 'Run the outer loop for this repository: resume current builds, run the',
117
+ 'janitor and lease sweep, and dispatch ready work (§3.3, §12). This command',
118
+ 'runs outside sessions.',
119
+ '',
120
+ 'Options:',
121
+ ' --once Run one pass instead of watching continuously.',
122
+ ' --interval <s> Set the positive polling interval in seconds.',
123
+ ' --store <ref> Override the BuildStore path or URL.',
124
+ ' --plain Force line-oriented output.',
125
+ ' --intake Durably turn new ticket intake on.',
126
+ ' --no-intake Durably turn new ticket intake off.',
127
+ ' --auto-merge Durably enable auto-merge intent for newly claimed builds.',
128
+ ' --no-auto-merge Durably disable that claim-time default.',
129
+ '',
130
+ 'The intake and auto-merge flags durably set repository defaults; omission reuses stored state (fresh repo: intake on, auto-merge off).',
131
+ '--auto-merge seeds durable intent on newly claimed builds only. Opposite forms of the same setting cannot be combined.',
132
+ '',
133
+ 'An interactive terminal gets a fixed global/harvest/build dashboard.',
134
+ 'TTY controls:',
135
+ ' Up/Down Select the global, Harvest, or a build row.',
136
+ ' h Toggle harvesting when the global row is selected.',
137
+ ' i Toggle intake when the global row is selected.',
138
+ ' m Toggle the claim-time default on the global row, or durable',
139
+ ' auto-merge intent on a build.',
140
+ ' p On the global row, pause every pausable build and turn intake',
141
+ ' off. On a build, pause or cancel a pending pause; on Harvest,',
142
+ ' resume or acknowledge the run.',
143
+ ' r On the global row, resume every paused build and turn intake',
144
+ ' back on. On a build, resume a paused or blocked build.',
145
+ ' Ctrl-C Stop dispatch.',
146
+ '',
147
+ 'ab pause --all and ab resume --all are the sessionless equivalents of the',
148
+ 'global row’s p and r; they need no dispatcher and no terminal.',
149
+ '',
150
+ 'For blocked feedback, Enter submits (empty means retry) and Esc cancels.',
151
+ 'The bottom controls list only keys active for the selection. Plain output',
152
+ 'is also selected automatically when stdout is not a TTY.',
153
+ ]),
154
+ },
155
+ {
156
+ name: 'ticket',
157
+ audience: 'human',
158
+ summary: 'Create, groom, inspect, and move source-agnostic tickets.',
159
+ detail: page([
160
+ 'Usage:',
161
+ ' ab ticket create <title> --body <file> [--state <state>] [--labels a,b] [--blocked-by id,id] [--json]',
162
+ ' ab ticket update <id> [--title <title>] [--body <file>] [--labels a,b] [--json]',
163
+ ' ab ticket block <id> <blocker-id[,blocker-id...]> [--json]',
164
+ ' ab ticket unblock <id> <blocker-id[,blocker-id...]> [--json]',
165
+ ' ab ticket list [--state <state>] [--labels a,b] [--json]',
166
+ ' ab ticket show <id> [--json]',
167
+ ' ab ticket move <id> <state> [--json]',
168
+ '',
169
+ 'Operate on the configured [tickets] source (§8.8); all ticket commands run',
170
+ 'outside sessions. create files a ticket and may establish initial blockers.',
171
+ '--state overrides [tickets].createState (or the provider default) for that',
172
+ 'create only; it is passed through unchanged and validated by the source.',
173
+ 'update partially replaces editable fields: omitted fields survive, --labels',
174
+ '"" clears labels, and update never changes state. block and unblock accept',
175
+ 'comma-separated blocker ids and are idempotent. Every id is validated before',
176
+ 'any relationship is written, so an invalid list leaves the ticket unchanged.',
177
+ '',
178
+ 'With no list filters, list uses the same ready criteria as dispatch. show one ticket, including its body/spec.',
179
+ 'Human output is the default. --json emits exactly one bare value: Ticket[]',
180
+ 'for list and the complete resulting Ticket for every other subcommand.',
181
+ '',
182
+ 'Ticket ids are source-local (for example AUT-8 or file-1). For block and',
183
+ 'unblock, the first id is always the ticket being changed. State names and',
184
+ 'unknown-id errors come from the configured source.',
185
+ ]),
186
+ },
187
+ {
188
+ name: 'repository',
189
+ audience: 'human',
190
+ summary: 'Inspect durable repository dispatcher settings.',
191
+ detail: page([
192
+ 'Usage:',
193
+ ' ab repository status [--json] [--store <ref>]',
194
+ '',
195
+ 'Report the durable ticket-intake setting, repository-wide pause, and',
196
+ 'claim-time auto-merge default from the same journal projection used by',
197
+ 'dispatch and the dashboard. A repository with no setting events reports',
198
+ 'the defaults: intake on, repository pause off, and auto-merge default off.',
199
+ '',
200
+ '--json emits one bare object for scripts. --store overrides AB_STORE, which',
201
+ 'overrides the repository-local .autobuild store. This sessionless command',
202
+ 'writes no state and starts no dispatcher, runner, ticket claim, or other work.',
203
+ ]),
204
+ },
205
+ {
206
+ name: 'builds',
207
+ audience: 'human',
208
+ summary: 'List repository builds and their current status.',
209
+ detail: page([
210
+ 'Usage:',
211
+ ' ab builds [--queued] [--all] [--json] [--store <ref>]',
212
+ '',
213
+ "List this repository's builds (§15.5). By default this shows active builds:",
214
+ 'running, paused, blocked. --queued also includes queued builds; --all',
215
+ 'shows every status and subsumes --queued. --json emits the projection as a',
216
+ 'bare JSON value, and --store overrides the BuildStore path or URL.',
217
+ '',
218
+ 'This command is read-only and requires no session identity for operator use.',
219
+ 'Inside a phase, a complete ambient tuple retains its build scope, so the',
220
+ 'repository-wide list is denied; malformed or partial identity is rejected.',
221
+ ]),
222
+ },
223
+ {
224
+ name: 'build',
225
+ audience: 'human',
226
+ summary: 'Inspect detailed state and recent events for one build.',
227
+ detail: page([
228
+ 'Usage:',
229
+ ' ab build status <slug> [--events <n>] [--json] [--store <ref>]',
230
+ '',
231
+ 'Show detailed state for one build: unresolved escalations, open sessions,',
232
+ 'verify progress, PR state, durable-event progress, heartbeat, and lease health.',
233
+ '--events <n> appends the newest positive number of events in chronological',
234
+ 'order. --json emits the complete projection; --store overrides the',
235
+ 'BuildStore path or URL.',
236
+ '',
237
+ 'This command is read-only and requires no session identity for operator use.',
238
+ 'Inside a phase, a complete ambient tuple permits only the ambient build;',
239
+ 'foreign builds and malformed or partial identity are rejected.',
240
+ ]),
241
+ },
242
+ {
243
+ name: 'watch',
244
+ audience: 'human',
245
+ summary: 'Stream build and repository events as they land, with a resumable cursor.',
246
+ detail: page([
247
+ 'Usage:',
248
+ ' ab watch [<slug>...] [--repository] [--event <glob>]... [--since <cursor>] [--timeout <duration>] [--interval <duration>] [--count <n>] [--json] [--store <ref>]',
249
+ '',
250
+ 'Follow the event logs of one, several, or all active builds in this repository —',
251
+ 'optionally the repository journal — and emit one record per matching event as it',
252
+ 'lands. With no slug, every nonterminal build (queued, running, paused, blocked) is',
253
+ 'watched, and a build created while the watch runs joins it without a restart.',
254
+ 'Naming one or more slugs watches exactly those builds; an unknown slug is an error',
255
+ 'before any record is emitted, and the watch ends when every named build reaches a',
256
+ 'terminal status. With no slugs, an empty set of active builds does not end the watch.',
257
+ '',
258
+ 'Options:',
259
+ ' --repository Also follow the repository journal (dispatcher and harvest',
260
+ ' events). Without it, repository events are never emitted.',
261
+ ' --event <glob> Repeatable glob over event type names: * matches any run of',
262
+ ' characters, ? one character (escalation.*, pr.merged, *). Every',
263
+ ' glob must match at least one type in the build catalog — or,',
264
+ ' with --repository, the repository catalog — or the command fails',
265
+ ' before any store read. Any --event replaces the default filter.',
266
+ ' --since <cursor> Resume from a cursor emitted by an earlier record; every matching',
267
+ ' event after that cursor is delivered exactly once, including',
268
+ ' events that landed while no watch was running. A cursor from a',
269
+ ' different repository or store is rejected.',
270
+ ' --timeout <dur> End the watch after this duration (30, 45s, 5m, 1h). Default',
271
+ ' 30 minutes; 0 means unbounded.',
272
+ ' --interval <dur> Poll cadence. Default: 5s for an http(s) store, 1s for a local store.',
273
+ ' --count <n> End the watch after n records.',
274
+ ' --json Newline-delimited JSON: exactly one object per matching event,',
275
+ ' flushed as soon as it is written.',
276
+ ' --store <ref> Override the BuildStore path or URL.',
277
+ '',
278
+ 'Default filter — the attention set. For builds: escalation.raised, phase.failed,',
279
+ 'infrastructure.failed, runner.setup-failed, dispatch.failed, publication.lost,',
280
+ 'finalize.completed, pr.conflicted, pr.merged, pr.closed, build.completed,',
281
+ 'build.aborted. For the repository journal: harvest.escalated, harvest.failed,',
282
+ 'dispatcher.tick-failed, dispatcher.config-rejected, dispatcher.harvest-runner-failed.',
283
+ '',
284
+ 'Record shape (--json): build (slug, or null for a repository event), repo (repository',
285
+ 'id, present only for repository events), event (the complete durable envelope: build,',
286
+ 'seq, ts, actor, type, payload), state (for build events, the reduction after this',
287
+ 'event: status, phase, round, openEscalations with their ids and questions, pr, prState,',
288
+ 'outcome; null for repository events), and cursor. Without --json, each record is one',
289
+ 'human-readable line with no ANSI sequences. On every exit, the last --json line on',
290
+ 'stdout is a bare {"cursor": …} object reflecting the final position, so a caller can',
291
+ 'resume even if no record matched. Cursors are opaque strings; their format is not part',
292
+ 'of the contract.',
293
+ '',
294
+ 'Exit codes: the watch ends with exit 0 when --timeout elapses, when --count records',
295
+ 'have been emitted, or, only when slugs were named, when every named build has reached a',
296
+ 'terminal status. Usage errors, scope violations, unknown slugs, cursor rejection, and a',
297
+ 'store that cannot be opened at start exit 1 with a message on stderr. A store read that',
298
+ 'fails after the watch has started is reported once on stderr and retried at the next',
299
+ 'interval without ending the watch or duplicating or skipping any event.',
300
+ '',
301
+ 'This command is read-only: it appends no event, takes no lease, and creates no build or',
302
+ 'repository record, and requires no session identity for operator use. Inside a phase, a',
303
+ 'complete ambient tuple permits only the ambient build: the no-slug form, a foreign slug,',
304
+ 'and --repository are rejected, as are malformed or partial identity.',
305
+ ]),
306
+ },
307
+ {
308
+ name: 'wait',
309
+ audience: 'human',
310
+ summary: 'Block until a build satisfies a condition, then print one record.',
311
+ detail: page([
312
+ 'Usage:',
313
+ ' ab wait [<slug>...] (--for <condition> | --event <glob>)... [--since <cursor>] [--timeout <duration>] [--interval <duration>] [--json] [--store <ref>]',
314
+ '',
315
+ 'Block until a targeted build satisfies a condition, print exactly one record describing',
316
+ 'the event and the build’s reduced state, and exit with a code that tells the caller',
317
+ 'what happened. It is the blocking sibling of `ab watch` and shares its target',
318
+ 'selection, cursor, record shape, and ambient scoping. At least one --for or --event is',
319
+ 'required. With no slug, every nonterminal build is targeted — including builds created',
320
+ 'while waiting — and an empty or entirely terminal set keeps waiting for a build to',
321
+ 'appear. Naming one or more slugs targets exactly those builds; an unknown slug is an',
322
+ 'error before waiting begins.',
323
+ '',
324
+ 'Conditions (--for, repeatable): blocked, paused, running, done, aborted, terminal (done',
325
+ 'or aborted), pr=open, pr=merged, pr=closed, pr=conflicted, phase=<phase> (a core phase —',
326
+ 'plan, plan-review, implement, code-review, finalize, reconcile — or verify:<step>), and',
327
+ 'attention (any event in the ab watch attention set). An unknown value is rejected before',
328
+ 'any store access. Repeatable --event globs follow the ab watch rules. Multiple --for',
329
+ 'values and --event globs are alternatives: the first one satisfied ends the wait — all',
330
+ '--for values in supply order first (each checked against the state after an event and',
331
+ 'the event’s attention membership), then all --event globs in supply order.',
332
+ '',
333
+ 'A status, PR-lifecycle, or phase condition is satisfied when a targeted build’s reduction',
334
+ 'has that status, PR state, or current phase. If any targeted build already satisfies',
335
+ 'such a condition when the command starts, the command returns immediately — in the',
336
+ 'manner of kubectl wait — with that build’s latest event; --since does not suppress this.',
337
+ 'Otherwise the condition is satisfied by the first later event whose reduction produces',
338
+ 'it. attention and --event conditions are satisfied only by events appended after the',
339
+ 'command started (or, on a cursor-resumed backlog, after --since).',
340
+ '',
341
+ 'Record shape (--json): the ab watch record — build, event (the complete durable',
342
+ 'envelope), state (the reduction after this event: status, phase, round, openEscalations,',
343
+ 'pr, prState, outcome), cursor — plus condition, naming the --for value or --event glob',
344
+ 'that matched. Without --json, one human-readable line with the condition in parentheses',
345
+ 'and no ANSI sequences. Unlike ab watch, no trailing cursor object is emitted: the',
346
+ 'record’s own cursor is the final position, and `ab wait --since <cursor>` resumes.',
347
+ '',
348
+ 'Exit codes: the wait ends with exit 0 when a condition is satisfied and exactly one',
349
+ 'record was printed; exit 1 on usage errors, scope violations, unknown slugs, or a store',
350
+ 'that cannot be opened at start; exit 2 when slugs were named and every named build',
351
+ 'reached a terminal status without satisfying any condition — one record whose condition',
352
+ 'is null, showing how the build ended; exit 3 when --timeout elapsed (default 30 minutes;',
353
+ '0 unbounded) — nothing on stdout except, with --json, a bare {"cursor": …} object for',
354
+ 'the final position, and one line on stderr; exit 4 when interrupted by SIGINT before',
355
+ 'any condition was satisfied, with the same cursor behavior as exit 3.',
356
+ '',
357
+ 'The default poll interval and the --interval override match ab watch: 5s for an http(s)',
358
+ 'store, 1s for a local store. A store read that fails while waiting is reported once on',
359
+ 'stderr, retried at the next interval, and never causes a condition to be missed or',
360
+ 'reported twice.',
361
+ '',
362
+ 'This command is read-only: it appends no event, takes no lease, and creates no build or',
363
+ 'repository record, and requires no session identity for operator use. Inside a phase, a',
364
+ 'complete ambient tuple permits only the ambient build; foreign builds and malformed or',
365
+ 'partial identity are rejected before any store access.',
366
+ ]),
367
+ },
368
+ {
369
+ name: 'mcp',
370
+ audience: 'human',
371
+ summary: 'Serve the operator tool registry as an MCP server over stdio.',
372
+ detail: page([
373
+ 'Usage:',
374
+ ' ab mcp [--store <ref>] [--repo <id>] (§8.2)',
375
+ '',
376
+ 'Serve the agent tool registry as an MCP server on stdio: every tool',
377
+ 'mirrors an operator CLI command or operator API route, with the same',
378
+ 'schemas, annotations, and results. The CLI remains the primary local',
379
+ 'interface for agents that have a shell; this binding exists so the',
380
+ 'registry can be exercised end to end from a local install, for',
381
+ 'dogfooding, and for local agents that cannot run commands.',
382
+ '',
383
+ 'Options:',
384
+ ' --store <ref> Override the BuildStore path or URL.',
385
+ ' --repo <id> Constrain the server: calls naming any other',
386
+ ' repository are refused.',
387
+ '',
388
+ 'Mutating tools attribute their writes to the sessionless operator',
389
+ 'identity (USER/USERNAME, else "dashboard"), exactly like the other',
390
+ 'operator commands. The server fails closed inside a phase: repository-',
391
+ 'wide operator authority cannot be narrowed to the ambient build, so a',
392
+ 'complete or malformed AB_* phase tuple refuses to start the server.',
393
+ 'The server exits when its client disconnects (stdio EOF).',
394
+ ]),
395
+ },
396
+ {
397
+ name: 'pause',
398
+ audience: 'human',
399
+ summary: 'Request that an active build pause, or pause the whole repository.',
400
+ detail: page([
401
+ 'Usage:',
402
+ ' ab pause <slug> [--store <ref>]',
403
+ ' ab pause --all [--store <ref>] [--json]',
404
+ '',
405
+ 'Durably request that an active build pause. --store overrides the BuildStore',
406
+ 'path or URL. This command is sessionless; a phase may not control its own build.',
407
+ '',
408
+ '--all is the repository-wide form and the sessionless equivalent of the',
409
+ 'dashboard’s global-row p: it durably holds every queued build, then',
410
+ 'turns ticket intake off, then requests a pause on every running build that',
411
+ 'does not already have one. While the hold is set no dispatcher tick gives a',
412
+ 'queued build a runner. Pausing, paused, blocked, and finished builds are',
413
+ 'skipped without error, and a pending pause is never cancelled. Output names',
414
+ 'every build that received a request, the hold, and the intake value written;',
415
+ '--json emits that summary as JSON. Re-running it changes nothing further. A',
416
+ 'phase session may not use --all. A failure exits nonzero and reports which of',
417
+ 'the two repository facts landed and which builds were requested.',
418
+ ]),
419
+ },
420
+ {
421
+ name: 'resume',
422
+ audience: 'human',
423
+ summary: 'Request that a paused build resume, or resume the whole repository.',
424
+ detail: page([
425
+ 'Usage:',
426
+ ' ab resume <slug> [--store <ref>]',
427
+ ' ab resume --all [--store <ref>] [--json]',
428
+ '',
429
+ 'Durably request that an active paused build resume. This does not answer open',
430
+ 'escalations; use ab answer for a blocked build. See ab help answer for the complete',
431
+ 'resolution menu. --store overrides the BuildStore path or URL. This command is',
432
+ 'sessionless; a phase may not control',
433
+ 'its own build.',
434
+ '',
435
+ '--all is the repository-wide form and the sessionless equivalent of the',
436
+ 'dashboard’s global-row r: it durably releases the queued-build hold, then',
437
+ 'turns ticket intake back on, then requests a resume on every cleanly paused',
438
+ 'build that does not already have one. A released queued build becomes',
439
+ 'launchable on the next ordinary tick with no further action. Running, resuming,',
440
+ 'blocked, and finished builds are skipped without error, and a build still',
441
+ 'holding an open escalation is left to ab answer. Output names every build that',
442
+ 'received a request, the hold, and the intake value written; --json emits that',
443
+ 'summary as JSON. Re-running it changes nothing further. A phase session may not',
444
+ 'use --all. A failure exits nonzero and reports which of the two repository',
445
+ 'facts landed and which builds were requested.',
446
+ ]),
447
+ },
448
+ {
449
+ name: 'answer',
450
+ audience: 'human',
451
+ summary: 'Answer a build’s open escalations or request a retry.',
452
+ detail: page([
453
+ 'Usage:',
454
+ ' ab answer <slug> [<text>] [--store <ref>]',
455
+ ' ab answer <slug> [<text>] --review-round-ceiling <n> [--store <ref>]',
456
+ ' ab answer <slug> [<text>] --dismiss [--store <ref>]',
457
+ ' ab answer <slug> [<text>] --revise-spec <file> [--store <ref>]',
458
+ ' ab answer <slug> [<text>] --revise-spec-from-ticket [--store <ref>]',
459
+ '',
460
+ 'Nonblank text supplies guidance to the loop producer.',
461
+ 'Omitting text requests a bare retry. --dismiss retires each cited finding chain,',
462
+ 'tells the next reviewer not to re-raise it and settles its stall counter.',
463
+ 'A blocker citing no real review finding remains open and must receive guidance or',
464
+ 'a retry.',
465
+ '',
466
+ '--review-round-ceiling accepts a positive absolute ceiling only while one open',
467
+ 'review-round-limit escalation selects the plan or code loop. The same answer',
468
+ 'durably sets that build/loop/current-spec override and clears the escalation.',
469
+ 'It cannot be used pre-emptively or with either spec-revision form, because a',
470
+ 'revision resets the loop budget itself. The configured maxReviewRounds remains',
471
+ 'the default for every other build and loop; stallRounds is unaffected.',
472
+ '',
473
+ '--revise-spec reads a replacement body from a file; --revise-spec-from-ticket',
474
+ 're-imports the build ticket after it was amended. A conforming replacement becomes',
475
+ 'the next spec revision and restarts from plan while keeping an open PR. Revision',
476
+ 'requires an open escalation. Non-conforming bodies are refused before anything is',
477
+ 'recorded. Retrying an interrupted revision completes the exact body authorized by',
478
+ 'the earlier recorded answer, not a newly supplied body.',
479
+ '',
480
+ 'If paused and every blocker clears, resume is requested last. --store overrides the',
481
+ 'BuildStore path or URL. This command is sessionless; a phase may not answer its',
482
+ 'own build.',
483
+ ]),
484
+ },
485
+ {
486
+ name: 'abort',
487
+ audience: 'human',
488
+ summary: 'Request that a non-terminal build abort and be cleaned up.',
489
+ detail: page([
490
+ 'Usage:',
491
+ ' ab abort <slug> [--store <ref>]',
492
+ '',
493
+ 'Durably request that a queued, running, paused, or blocked build abort. The live',
494
+ 'agent turn is cancelled promptly; dispatcher cleanup closes any unmerged PR,',
495
+ 'removes the workspace and local/remote branches, labels the ticket',
496
+ 'autobuild:aborted, and returns it to triage. Cleanup is asynchronous and retry-safe.',
497
+ '--store overrides the BuildStore path or URL. This command is sessionless; a phase',
498
+ 'may not control its own build.',
499
+ ]),
500
+ },
501
+ {
502
+ name: 'auto-merge',
503
+ audience: 'human',
504
+ summary: 'Enable or cancel native auto-merge for one build.',
505
+ detail: page([
506
+ 'Usage:',
507
+ ' ab auto-merge <slug> <on|off> [--store <ref>]',
508
+ '',
509
+ 'Request or cancel native squash auto-merge for an active build. --store',
510
+ 'overrides the BuildStore path or URL. This command is sessionless; a phase',
511
+ 'may not control its own build.',
512
+ ]),
513
+ },
514
+ {
515
+ name: 'models',
516
+ audience: 'human',
517
+ summary: 'Search Pi’s model catalog for role configuration.',
518
+ detail: page([
519
+ 'Usage:',
520
+ ' ab models [query] [--available]',
521
+ '',
522
+ "List Pi's model catalog, optionally filtered by query, to find a",
523
+ 'provider-qualified model id for autobuild.toml (§9). --available limits',
524
+ 'results to models whose provider credentials are currently available.',
525
+ 'This command runs outside sessions.',
526
+ ]),
527
+ },
528
+ {
529
+ name: 'plugin',
530
+ audience: 'human',
531
+ summary: 'Inspect, diagnose, and contract-test adapters.',
532
+ detail: page([
533
+ 'Usage:',
534
+ ' ab plugin list',
535
+ ' ab plugin doctor',
536
+ ' ab plugin test <ticket-source|agent-runtime|workspace-provider|forge> <adapter>',
537
+ '',
538
+ 'list shows builtin and configured adapters, resolution, API status, and',
539
+ 'contract availability. doctor diagnoses every configured plugin module and',
540
+ "exits nonzero if any fail. test runs the adapter's shared port contract suite;",
541
+ 'live fixtures require AB_RUN_LIVE_PORT_CONTRACTS=1.',
542
+ '',
543
+ 'All plugin commands are sessionless.',
544
+ ]),
545
+ },
546
+ {
547
+ name: 'context',
548
+ audience: 'ai',
549
+ summary: 'Hydrate the current build phase’s input files.',
550
+ detail: page([
551
+ 'Usage:',
552
+ ' ab context [--json]',
553
+ '',
554
+ "Hydrate .ab/ with the current phase's inputs and print the context manifest.",
555
+ '--json prints the manifest as JSON instead of the human summary. This command',
556
+ 'runs inside a build session and uses the runner-provided AB_* phase context.',
557
+ ]),
558
+ },
559
+ {
560
+ name: 'artifact',
561
+ audience: 'ai',
562
+ summary: 'Deposit, fetch, or download versioned build artifacts.',
563
+ detail: page([
564
+ 'Usage:',
565
+ ' ab artifact put <kind> <file> [--attach]',
566
+ ' ab artifact get <kind>[@rev]',
567
+ ' ab artifact download <build> <kind>[@rev] --output <file> [--store <ref>]',
568
+ '',
569
+ 'put deposits a versioned artifact and prints its assigned revision. --attach',
570
+ 'also designates that exact revision for the PR. get fetches an artifact within',
571
+ 'the current build, using the latest revision when @rev is omitted. put and get',
572
+ 'run inside build sessions.',
573
+ '',
574
+ 'download retrieves exact artifact bytes after a build into --output. It is',
575
+ 'read-only and requires no session identity for operator use; --store overrides',
576
+ 'the BuildStore path or URL. Inside a phase, a complete ambient tuple permits',
577
+ 'only the ambient build; foreign builds and malformed or partial identity fail.',
578
+ ]),
579
+ },
580
+ {
581
+ name: 'observe',
582
+ audience: 'ai',
583
+ summary: 'Record a structured non-terminal observation.',
584
+ detail: page([
585
+ 'Usage:',
586
+ ' ab observe --kind <followup|refactor|latent-bug> [--files a,b] [--refs x,y] <summary>',
587
+ '',
588
+ 'Record a structured observation. --files associates repository paths and',
589
+ '--refs associates artifact or source references. This is available in any',
590
+ 'build phase at any time and is not a terminal command.',
591
+ ]),
592
+ },
593
+ {
594
+ name: 'done',
595
+ audience: 'ai',
596
+ summary: 'Complete the current producer phase.',
597
+ detail: page([
598
+ 'Usage:',
599
+ ' ab done [--notes <file>]',
600
+ '',
601
+ 'Complete a producer phase, optionally depositing --notes. This is a terminal',
602
+ 'command: it validates the phase outputs and clean worktree before kernel-side',
603
+ 'plumbing runs. Every phase ends with exactly one terminal command (D5).',
604
+ ]),
605
+ },
606
+ {
607
+ name: 'verdict',
608
+ audience: 'ai',
609
+ summary: 'Complete a review or verification phase with a verdict.',
610
+ detail: page([
611
+ 'Usage:',
612
+ ' ab verdict <approve|revise|escalate|pass|fail|skip> [--findings <json>] [--notes <file>] [--reason <text>] [--report <file>]',
613
+ '',
614
+ 'Complete a review or verify phase. The accepted verdict vocabulary is',
615
+ 'phase-dependent. --findings supplies structured review findings, --notes',
616
+ 'deposits review notes, --reason explains escalation or skip, and --report',
617
+ 'deposits a verification failure report.',
618
+ '',
619
+ 'This is a terminal command. Every phase ends with exactly one terminal',
620
+ 'command (D5).',
621
+ ]),
622
+ },
623
+ {
624
+ name: 'escalate',
625
+ audience: 'ai',
626
+ summary: 'Park the build with a question for a human.',
627
+ detail: page([
628
+ 'Usage:',
629
+ ' ab escalate <question> [--refs a,b]',
630
+ '',
631
+ 'Park the build for human input, optionally attaching comma-separated source',
632
+ 'or artifact references. This is a terminal command. Every phase ends with',
633
+ 'exactly one terminal command (D5).',
634
+ ]),
635
+ },
636
+ {
637
+ name: 'harvest',
638
+ audience: 'ai',
639
+ summary: 'Inspect or drive repository observation-harvest workflows.',
640
+ detail: page([
641
+ 'Usage:',
642
+ ' ab harvest status [--events <n>] [--json] [--store <ref>]',
643
+ ' ab harvest context [--json]',
644
+ ' ab harvest submit <proposals.json>',
645
+ ' ab harvest verdict <approve|revise|escalate> --notes <file> [--findings <json>] [--reason <text>]',
646
+ '',
647
+ 'status shows all unresolved repository harvest workflows and their paper',
648
+ 'trail. --events appends the newest positive number of repository events,',
649
+ '--json emits JSON, and --store overrides the BuildStore path or URL. status',
650
+ 'is read-only and sessionless.',
651
+ '',
652
+ 'context hydrates harvest-session inputs; --json prints its manifest as JSON.',
653
+ 'submit is the synthesize terminal: it validates and deposits proposals from',
654
+ 'the named JSON file. verdict is the harvest-review terminal; --notes is',
655
+ 'required, --findings supplies revision findings, and --reason explains an',
656
+ 'escalation. These three forms require a runner-provided harvest session.',
657
+ ]),
658
+ },
659
+ ]
660
+
661
+ const ENTRY_BY_NAME = new Map(HELP_CATALOG.map((entry) => [entry.name, entry]))
662
+
663
+ export type HelpRequest = { kind: 'overview' } | { kind: 'command'; command: string }
664
+
665
+ /** Recognize only complete help forms. Malformed forms stay on normal routing
666
+ * so the command-specific parser can return usage feedback. */
667
+ export function recognizeHelpRequest(argv: readonly string[]): HelpRequest | undefined {
668
+ if (argv.length === 1 && (argv[0] === 'help' || argv[0] === '--help' || argv[0] === '-h')) {
669
+ return { kind: 'overview' }
670
+ }
671
+ if (argv.length === 2 && argv[1] === '--help') {
672
+ return { kind: 'command', command: argv[0]! }
673
+ }
674
+ if (argv.length === 2 && argv[0] === 'help') {
675
+ return { kind: 'command', command: argv[1]! }
676
+ }
677
+ return undefined
678
+ }
679
+
680
+ export function renderTopLevelHelp(): string {
681
+ const lines = [
682
+ 'ab — agent-driven software delivery from groomed ticket to merged PR',
683
+ '',
684
+ '`ab` is Autobuild’s human/operator CLI and the agent↔store channel (SPEC §8.2).',
685
+ '',
686
+ 'Primary human workflow:',
687
+ ' Run `ab init` once, groom tickets to Ready, then run `ab dispatch`.',
688
+ ' Dispatch drives planning, implementation, review, verification, and merge.',
689
+ '',
690
+ 'For details, run `ab help <command>` or `ab <command> --help`.',
691
+ 'Run `ab --version` to report the installed version and plugin API offline.',
692
+ '',
693
+ ]
694
+
695
+ for (const audience of ['human', 'ai'] as const) {
696
+ lines.push(audience === 'human' ? 'Human-first commands:' : 'AI-first commands:')
697
+ for (const entry of HELP_CATALOG) {
698
+ if (entry.audience === audience) {
699
+ lines.push(` ab ${entry.name.padEnd(12)} ${entry.summary}`)
700
+ }
701
+ }
702
+ if (audience === 'human') lines.push('')
703
+ }
704
+ return lines.join('\n')
705
+ }
706
+
707
+ export function renderCommandHelp(command: string): string {
708
+ const entry = ENTRY_BY_NAME.get(command === 'update' ? 'upgrade' : command)
709
+ if (entry === undefined) {
710
+ throw new Error(`unknown help command "${command}"`)
711
+ }
712
+ return `ab ${entry.name} — ${entry.summary}\n\n${entry.detail}`
713
+ }