@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,48 @@
1
+ /**
2
+ * Minimal local .env loader for the `ab` binary: `KEY=VALUE` lines, `#`
3
+ * comments, optional matching quotes around the value, optional `export `
4
+ * prefix. Values never override variables already present in the environment
5
+ * — the real environment wins, so a checked-in .env cannot shadow runner-set
6
+ * ambient auth (D8). Bun auto-loads .env when running bin/ab.ts directly;
7
+ * this loader makes the behavior explicit and Node-portable.
8
+ */
9
+ import { readFileSync } from 'node:fs'
10
+
11
+ const KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/
12
+
13
+ /** Parse .env text into key/value pairs; malformed lines are skipped. */
14
+ export function parseDotEnv(raw: string): Record<string, string> {
15
+ const result: Record<string, string> = {}
16
+ for (const line of raw.split('\n')) {
17
+ let trimmed = line.trim()
18
+ if (trimmed === '' || trimmed.startsWith('#')) continue
19
+ if (trimmed.startsWith('export ')) trimmed = trimmed.slice('export '.length)
20
+ const eq = trimmed.indexOf('=')
21
+ if (eq === -1) continue
22
+ const key = trimmed.slice(0, eq).trim()
23
+ if (!KEY_PATTERN.test(key)) continue
24
+ let value = trimmed.slice(eq + 1).trim()
25
+ const quote = value[0]
26
+ if ((quote === '"' || quote === "'") && value.length >= 2 && value.endsWith(quote)) {
27
+ value = value.slice(1, -1)
28
+ }
29
+ result[key] = value
30
+ }
31
+ return result
32
+ }
33
+
34
+ /**
35
+ * Load `path` into `env` if the file exists (silently a no-op otherwise).
36
+ * Only keys not already set in `env` are written.
37
+ */
38
+ export function loadDotEnv(path: string, env: Record<string, string | undefined>): void {
39
+ let raw: string
40
+ try {
41
+ raw = readFileSync(path, 'utf8')
42
+ } catch {
43
+ return
44
+ }
45
+ for (const [key, value] of Object.entries(parseDotEnv(raw))) {
46
+ if (env[key] === undefined) env[key] = value
47
+ }
48
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Ambient auth resolution (SPEC §8.1, D8): the runner launches every session
3
+ * with `AB_STORE`, `AB_BUILD`, `AB_PHASE`, `AB_SESSION` (and `AB_TOKEN` for a
4
+ * remote store) set, and the CLI resolves everything from the environment —
5
+ * the skill only ever passes the build slug. Least privilege comes from the
6
+ * runner, not from prompt instructions.
7
+ *
8
+ * Errors here are agent feedback (D6): each names the missing or malformed
9
+ * variable and the format that would be accepted.
10
+ */
11
+ import { CORE_PHASES, isPhase, type Phase } from '../ontology'
12
+
13
+ export interface HarvestCliEnv {
14
+ store: string
15
+ repo: string
16
+ run: string
17
+ phase: 'synthesize' | 'review'
18
+ round: number
19
+ session: string
20
+ token?: string
21
+ }
22
+
23
+ export interface CliEnv {
24
+ /** Store URL or local path (`AB_STORE`). */
25
+ store: string
26
+ /** Build slug (`AB_BUILD`). */
27
+ build: string
28
+ /** Current phase, parsed from `AB_PHASE` (`<phase>[@<round>]`). */
29
+ phase: Phase
30
+ /** Round (loop phases) or attempt (verify/reconcile); defaults to 1. */
31
+ round: number
32
+ /** Session id (`AB_SESSION`). */
33
+ session: string
34
+ /** Scoped token for a remote store (`AB_TOKEN`) — optional locally. */
35
+ token?: string
36
+ }
37
+
38
+ export type AmbientReadSession = CliEnv | HarvestCliEnv
39
+
40
+ const PHASE_FORMAT = "'<phase>[@<round>]' (e.g. 'implement@2', 'verify:e2e@1')"
41
+ const BUILD_AMBIENT_TUPLE = 'AB_STORE/AB_BUILD/AB_PHASE/AB_SESSION'
42
+ const HARVEST_AMBIENT_TUPLE = 'AB_STORE/AB_REPO/AB_HARVEST/AB_PHASE/AB_SESSION'
43
+
44
+ /** Optional ambient identity is present but cannot safely select one scope. */
45
+ export class InvalidAmbientContextError extends Error {
46
+ override readonly name = 'InvalidAmbientContextError'
47
+
48
+ constructor(detail: string) {
49
+ super(
50
+ `invalid ambient context: ${detail}. Expected no agent identity markers, ` +
51
+ `a complete build tuple (${BUILD_AMBIENT_TUPLE}), or a complete Harvest tuple ` +
52
+ `(${HARVEST_AMBIENT_TUPLE})`,
53
+ )
54
+ }
55
+ }
56
+
57
+ /** A required runner-provided session value is absent or empty. Callers that
58
+ * resolve ambient auth directly still receive the variable-specific message;
59
+ * the process wiring uses the type to defer missing-session feedback to the
60
+ * command-aware router guard. */
61
+ export class MissingAmbientContextError extends Error {
62
+ override readonly name = 'MissingAmbientContextError'
63
+
64
+ constructor(
65
+ readonly variable: string,
66
+ expected: string,
67
+ ) {
68
+ super(
69
+ `${variable} is not set — expected ${expected}. ` +
70
+ 'The runner sets ambient auth for every session (D8, SPEC §8.1).',
71
+ )
72
+ }
73
+ }
74
+
75
+ function requireVar(
76
+ env: Record<string, string | undefined>,
77
+ name: string,
78
+ expected: string,
79
+ ): string {
80
+ const value = env[name]
81
+ if (value === undefined || value === '') {
82
+ throw new MissingAmbientContextError(name, expected)
83
+ }
84
+ return value
85
+ }
86
+
87
+ /** `implement@2` → `{ phase: 'implement', round: 2 }`; round defaults to 1. */
88
+ export function parseAbPhase(raw: string): { phase: Phase; round: number } {
89
+ // Split on the LAST '@' so a malformed 'implement@2@3' reports the phase
90
+ // part as unknown rather than silently taking the first segment.
91
+ const at = raw.lastIndexOf('@')
92
+ const phasePart = at === -1 ? raw : raw.slice(0, at)
93
+ const roundPart = at === -1 ? undefined : raw.slice(at + 1)
94
+
95
+ if (!isPhase(phasePart)) {
96
+ throw new Error(
97
+ `AB_PHASE "${raw}" names an unknown phase "${phasePart}" — expected one of ` +
98
+ `${CORE_PHASES.join(', ')}, or 'verify:<step>'; format ${PHASE_FORMAT}`,
99
+ )
100
+ }
101
+
102
+ let round = 1
103
+ if (roundPart !== undefined) {
104
+ if (!/^[1-9]\d*$/.test(roundPart)) {
105
+ throw new Error(
106
+ `AB_PHASE "${raw}" has a malformed round "${roundPart}" — the round must ` +
107
+ `be a positive integer; format ${PHASE_FORMAT}`,
108
+ )
109
+ }
110
+ round = Number(roundPart)
111
+ }
112
+ return { phase: phasePart, round }
113
+ }
114
+
115
+ /**
116
+ * Resolve the CLI's ambient environment (D8). Throws with the exact variable
117
+ * and expected format on the first problem found.
118
+ */
119
+ export function parseHarvestPhase(raw: string): {
120
+ phase: 'synthesize' | 'review'
121
+ round: number
122
+ } {
123
+ const match = /^(synthesize|review)@([1-9]\d*)$/.exec(raw)
124
+ if (!match) {
125
+ throw new Error(
126
+ `AB_PHASE "${raw}" is not a harvest session phase — expected ` +
127
+ `'synthesize@<round>' or 'review@<round>'`,
128
+ )
129
+ }
130
+ return {
131
+ phase: match[1] as 'synthesize' | 'review',
132
+ round: Number(match[2]),
133
+ }
134
+ }
135
+
136
+ export function resolveHarvestCliEnv(env: Record<string, string | undefined>): HarvestCliEnv {
137
+ const store = requireVar(env, 'AB_STORE', 'the store URL or local path')
138
+ const repo = requireVar(env, 'AB_REPO', 'the repository identity')
139
+ const run = requireVar(env, 'AB_HARVEST', 'the harvest run id')
140
+ const rawPhase = requireVar(env, 'AB_PHASE', `'synthesize@<round>' or 'review@<round>'`)
141
+ const session = requireVar(env, 'AB_SESSION', 'the session id')
142
+ const { phase, round } = parseHarvestPhase(rawPhase)
143
+ const token = env.AB_TOKEN
144
+ return {
145
+ store,
146
+ repo,
147
+ run,
148
+ phase,
149
+ round,
150
+ session,
151
+ ...(token !== undefined && token !== '' ? { token } : {}),
152
+ }
153
+ }
154
+
155
+ export function resolveCliEnv(env: Record<string, string | undefined>): CliEnv {
156
+ const store = requireVar(env, 'AB_STORE', 'the store URL or local path')
157
+ const build = requireVar(env, 'AB_BUILD', 'the build slug')
158
+ const rawPhase = requireVar(env, 'AB_PHASE', `the current phase in the format ${PHASE_FORMAT}`)
159
+ const session = requireVar(env, 'AB_SESSION', 'the session id')
160
+ const { phase, round } = parseAbPhase(rawPhase)
161
+ const token = env.AB_TOKEN
162
+ return {
163
+ store,
164
+ build,
165
+ phase,
166
+ round,
167
+ session,
168
+ ...(token !== undefined && token !== '' ? { token } : {}),
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Classify the optional ambient authority used by the finite read-only query
174
+ * commands. AB_STORE and AB_TOKEN remain ordinary operator selection and
175
+ * credentials when no identity marker is present. Once any build/Harvest
176
+ * identity marker appears, however, the context must be complete and
177
+ * unambiguous; a partial agent environment must never fall back to operator
178
+ * authority.
179
+ */
180
+ export function resolveAmbientReadSession(
181
+ env: Record<string, string | undefined>,
182
+ ): AmbientReadSession | undefined {
183
+ const present = (name: string): boolean => env[name] !== undefined
184
+ const hasBuildMarker = present('AB_BUILD')
185
+ const hasHarvestMarker = present('AB_REPO') || present('AB_HARVEST')
186
+ const hasSharedMarker = present('AB_PHASE') || present('AB_SESSION')
187
+
188
+ if (!hasBuildMarker && !hasHarvestMarker && !hasSharedMarker) return undefined
189
+ if (hasBuildMarker && hasHarvestMarker) {
190
+ throw new InvalidAmbientContextError(
191
+ 'build and Harvest identity markers are mixed, so resource authority is ambiguous',
192
+ )
193
+ }
194
+ if (!hasBuildMarker && !hasHarvestMarker) {
195
+ throw new InvalidAmbientContextError(
196
+ 'AB_PHASE or AB_SESSION is present without AB_BUILD or AB_REPO/AB_HARVEST',
197
+ )
198
+ }
199
+
200
+ try {
201
+ return hasBuildMarker ? resolveCliEnv(env) : resolveHarvestCliEnv(env)
202
+ } catch (error) {
203
+ const detail = error instanceof Error ? error.message : String(error)
204
+ throw new InvalidAmbientContextError(detail)
205
+ }
206
+ }