@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,185 @@
1
+ /**
2
+ * The operator-session reducer (SPEC §7.1.1, §15.2): a session's status, open
3
+ * turn, pending approval, wake settings, wake cursors, and turn list are a
4
+ * pure reduction of its event log — never a stored column. The catalog is
5
+ * closed (`events/sessions.ts`), so this function is total over session
6
+ * history; unknown facts cannot exist.
7
+ */
8
+ import type { SessionEvent } from '../events/sessions'
9
+
10
+ export type SessionStatus = 'idle' | 'running' | 'suspended' | 'awaiting-approval' | 'archived'
11
+ export type SessionSuspensionCause = 'budget' | 'approval'
12
+ export type TurnOutcomeState = 'open' | 'suspended' | 'completed' | 'failed'
13
+
14
+ export type SessionTurnTrigger =
15
+ | { kind: 'message'; messageSeq: number }
16
+ | { kind: 'wake'; build: string; seq: number; type: string }
17
+
18
+ export interface SessionTurn {
19
+ turn: string
20
+ stream: string
21
+ startedSeq: number
22
+ trigger: SessionTurnTrigger
23
+ state: TurnOutcomeState
24
+ usage?: { inputTokens: number; outputTokens: number; turns?: number }
25
+ error?: string
26
+ }
27
+
28
+ export interface SessionState {
29
+ status: SessionStatus
30
+ /** Present only when status is `suspended`. */
31
+ suspendedCause?: SessionSuspensionCause
32
+ /** The most recent turn that has started and not completed or failed. */
33
+ openTurn?: { turn: string; stream: string; startedSeq: number; trigger: SessionTurnTrigger }
34
+ /** A requested approval with no matching `approval.answered` yet. */
35
+ pendingApproval?: {
36
+ turn: string
37
+ toolCallId: string
38
+ toolName: string
39
+ input: Record<string, unknown>
40
+ requestedSeq: number
41
+ }
42
+ /** Last `session.wake-set` wins; `[]` before the first (message-only). */
43
+ wakeGlobs: string[]
44
+ /** Per build, the highest build event seq a wake trigger has consumed. */
45
+ wakeCursors: Record<string, number>
46
+ /** Every turn in start order, with its stream id and outcome. */
47
+ turns: SessionTurn[]
48
+ }
49
+
50
+ interface TurnEntry extends SessionTurn {
51
+ /** The most recent suspension cause for this turn, if any. */
52
+ suspensionCause?: SessionSuspensionCause
53
+ }
54
+
55
+ const isTerminal = (state: SessionTurn['state']): boolean =>
56
+ state === 'completed' || state === 'failed'
57
+
58
+ /** Reduce a session's events into its derived state. `session.archived` is
59
+ * terminal: every fact after it is ignored — the archived state is frozen. */
60
+ export function reduceSession(events: SessionEvent[]): SessionState {
61
+ const turns: TurnEntry[] = []
62
+ let archived = false
63
+ let pendingApproval: SessionState['pendingApproval']
64
+ let wakeGlobs: string[] = []
65
+ const wakeCursors: Record<string, number> = {}
66
+
67
+ const open = (): TurnEntry | undefined => turns.findLast((turn) => !isTerminal(turn.state))
68
+
69
+ for (const event of events) {
70
+ if (archived) break
71
+ switch (event.type) {
72
+ case 'session.created':
73
+ case 'message.posted':
74
+ break
75
+ case 'session.wake-set':
76
+ wakeGlobs = [...event.payload.globs]
77
+ break
78
+ case 'turn.started': {
79
+ const entry: TurnEntry = {
80
+ turn: event.payload.turn,
81
+ stream: event.payload.stream,
82
+ startedSeq: event.seq,
83
+ trigger: event.payload.trigger,
84
+ state: 'open',
85
+ }
86
+ turns.push(entry)
87
+ if (event.payload.trigger.kind === 'wake') {
88
+ const build = event.payload.trigger.build
89
+ wakeCursors[build] = Math.max(wakeCursors[build] ?? 0, event.payload.trigger.seq)
90
+ }
91
+ break
92
+ }
93
+ case 'turn.suspended': {
94
+ const entry = turns.findLast((candidate) => candidate.turn === event.payload.turn)
95
+ if (entry && entry.state === 'open') {
96
+ entry.state = 'suspended'
97
+ entry.suspensionCause = event.payload.cause
98
+ }
99
+ break
100
+ }
101
+ case 'turn.resumed': {
102
+ const entry = turns.findLast((candidate) => candidate.turn === event.payload.turn)
103
+ if (entry && entry.state === 'suspended') entry.state = 'open'
104
+ break
105
+ }
106
+ case 'approval.requested':
107
+ pendingApproval = {
108
+ turn: event.payload.turn,
109
+ toolCallId: event.payload.toolCallId,
110
+ toolName: event.payload.toolName,
111
+ input: event.payload.input,
112
+ requestedSeq: event.seq,
113
+ }
114
+ break
115
+ case 'approval.answered': {
116
+ if (
117
+ pendingApproval?.turn === event.payload.turn &&
118
+ pendingApproval.toolCallId === event.payload.toolCallId
119
+ ) {
120
+ pendingApproval = undefined
121
+ // The answer resumes the turn; `turn.resumed` is the runner's
122
+ // acknowledgement of the same fact, so between answer and resume
123
+ // the status is already `running` when a turn is open.
124
+ const entry = turns.findLast((candidate) => candidate.turn === event.payload.turn)
125
+ if (entry && entry.state === 'suspended') entry.state = 'open'
126
+ }
127
+ break
128
+ }
129
+ case 'turn.completed': {
130
+ const entry = turns.findLast((candidate) => candidate.turn === event.payload.turn)
131
+ if (entry && !isTerminal(entry.state)) {
132
+ entry.state = 'completed'
133
+ entry.usage = event.payload.usage
134
+ }
135
+ break
136
+ }
137
+ case 'turn.failed': {
138
+ const entry = turns.findLast((candidate) => candidate.turn === event.payload.turn)
139
+ if (entry && !isTerminal(entry.state)) {
140
+ entry.state = 'failed'
141
+ entry.error = event.payload.error
142
+ }
143
+ break
144
+ }
145
+ case 'session.archived':
146
+ archived = true
147
+ break
148
+ }
149
+ }
150
+
151
+ const openEntry = open()
152
+ // Status precedence: archived > awaiting-approval > suspended > running >
153
+ // idle. An unanswered approval outranks its turn's suspension; the answer
154
+ // that clears it hands status back to the turn.
155
+ const status: SessionStatus = archived
156
+ ? 'archived'
157
+ : pendingApproval !== undefined
158
+ ? 'awaiting-approval'
159
+ : openEntry === undefined
160
+ ? 'idle'
161
+ : openEntry.state === 'suspended'
162
+ ? 'suspended'
163
+ : 'running'
164
+
165
+ return {
166
+ status,
167
+ ...(status === 'suspended' && openEntry?.suspensionCause !== undefined
168
+ ? { suspendedCause: openEntry.suspensionCause }
169
+ : {}),
170
+ ...(openEntry !== undefined
171
+ ? {
172
+ openTurn: {
173
+ turn: openEntry.turn,
174
+ stream: openEntry.stream,
175
+ startedSeq: openEntry.startedSeq,
176
+ trigger: openEntry.trigger,
177
+ },
178
+ }
179
+ : {}),
180
+ ...(pendingApproval !== undefined ? { pendingApproval } : {}),
181
+ wakeGlobs,
182
+ wakeCursors,
183
+ turns: turns.map(({ suspensionCause: _suspensionCause, ...turn }) => turn),
184
+ }
185
+ }
@@ -0,0 +1,7 @@
1
+ /** Repo-installed Autobuild skills share one harness-neutral namespace. */
2
+ export const SKILL_NAMESPACE = 'ab-'
3
+
4
+ /** Add the namespace once, accepting explicit namespaced config values too. */
5
+ export function installedSkillName(name: string): string {
6
+ return name.startsWith(SKILL_NAMESPACE) ? name : `${SKILL_NAMESPACE}${name}`
7
+ }
@@ -0,0 +1,47 @@
1
+ /** The deterministic, mechanically checkable core of docs/spec-standard.md. */
2
+ export interface SpecConformance {
3
+ conforms: boolean
4
+ missing: string[]
5
+ }
6
+
7
+ const LIST_ITEM = /^\s*(?:[-*+]|\d+[.)])\s+\S/
8
+ const HEADING = /^(#{1,6})\s*(.+?)\s*$/
9
+
10
+ function sectionUnder(lines: string[], name: string): string[] | null {
11
+ let start = -1
12
+ let level = 0
13
+ for (let i = 0; i < lines.length; i += 1) {
14
+ const match = lines[i]?.match(HEADING)
15
+ if (match?.[2]?.toLowerCase().startsWith(name)) {
16
+ start = i + 1
17
+ level = match[1]?.length ?? 0
18
+ break
19
+ }
20
+ }
21
+ if (start === -1) return null
22
+ const section: string[] = []
23
+ for (let i = start; i < lines.length; i += 1) {
24
+ const line = lines[i]
25
+ if (line === undefined) break
26
+ const heading = line.match(HEADING)
27
+ if (heading !== null && (heading[1]?.length ?? 0) <= level) break
28
+ section.push(line)
29
+ }
30
+ return section
31
+ }
32
+
33
+ export function specConformance(body: string): SpecConformance {
34
+ const missing: string[] = []
35
+ if (body.trim().length === 0) missing.push('a nonempty spec body')
36
+ const lines = body.split('\n')
37
+ const criteria = sectionUnder(lines, 'acceptance criteria')
38
+ if (criteria === null) {
39
+ missing.push("an '## Acceptance criteria' heading")
40
+ } else if (!criteria.some((line) => LIST_ITEM.test(line))) {
41
+ missing.push("at least one list item under '## Acceptance criteria'")
42
+ }
43
+ if (sectionUnder(lines, 'out of scope') === null) {
44
+ missing.push("an '## Out of scope' heading")
45
+ }
46
+ return { conforms: missing.length === 0, missing }
47
+ }
@@ -0,0 +1,80 @@
1
+ export { validateEventWrite } from '../events/catalog'
2
+ export { humanActor } from '../events/envelope'
3
+ export type { AbEvent, EventEnvelope, EventWrite } from '../events/catalog'
4
+ export type { EventType } from '../events/payloads'
5
+ export { validateRepositoryEventWrite } from '../events/repository'
6
+ export type {
7
+ RepositoryEvent,
8
+ RepositoryEventEnvelope,
9
+ RepositoryEventType,
10
+ RepositoryEventWrite,
11
+ } from '../events/repository'
12
+ export { validateSessionEventWrite } from '../events/sessions'
13
+ export type {
14
+ SessionEvent,
15
+ SessionEventEnvelope,
16
+ SessionEventType,
17
+ SessionEventWrite,
18
+ } from '../events/sessions'
19
+ export { createBuildScopedStore } from './build-scope'
20
+ export { createSessionScopedStore } from './session-handle'
21
+ export {
22
+ DEFAULT_ARTIFACT_RETENTION_MAX_REVISIONS,
23
+ DISPATCHER_RETENTION_BUILD_KINDS,
24
+ DISPATCHER_RETENTION_REPO_KINDS,
25
+ isRetentionManagedKind,
26
+ revisionsToPrune,
27
+ } from './retention'
28
+ export { assembleUIMessageDocument } from './streams/assemble'
29
+ export {
30
+ EVENT_WAIT_POLL_MS,
31
+ readEventsWithWait,
32
+ readStreamWithWait,
33
+ STREAM_WAIT_POLL_MS,
34
+ } from './streams/wait'
35
+ export { createSessionStreamSink } from './streams/session-writer'
36
+ export {
37
+ clampWaitSeconds,
38
+ MAX_STREAM_WAIT_SECONDS,
39
+ serializedBatchSize,
40
+ STREAM_BATCH_MAX_BYTES,
41
+ STREAM_FORMAT,
42
+ StreamBatchTooLargeError,
43
+ StreamClosedError,
44
+ streamArtifactInput,
45
+ validateStreamParts,
46
+ } from './streams/types'
47
+ export type {
48
+ StreamArtifactRef,
49
+ StreamChunk,
50
+ StreamOutcome,
51
+ StreamPart,
52
+ StreamRead,
53
+ StreamRecord,
54
+ StreamScope,
55
+ StreamStatus,
56
+ } from './streams/types'
57
+ export { pollingSubscribe } from './subscribe'
58
+ export { contentHash, systemClock, toBytes, validateExpectedSeq } from './types'
59
+ export type {
60
+ Artifact,
61
+ ArtifactInput,
62
+ ArtifactMeta,
63
+ BlobStore,
64
+ BuildRecord,
65
+ BuildScopedStore,
66
+ BuildStore,
67
+ Clock,
68
+ NewBuildInput,
69
+ NewSessionInput,
70
+ RepositoryArtifact,
71
+ RepositoryArtifactMeta,
72
+ RepositoryRecord,
73
+ SessionArtifact,
74
+ SessionArtifactMeta,
75
+ SessionRecord,
76
+ SessionScopedStore,
77
+ SubscribeOptions,
78
+ Unsubscribe,
79
+ } from './types'
80
+ export { normalizeOperator } from './types'
@@ -0,0 +1,272 @@
1
+ import type { EventType } from '../events/payloads'
2
+ import type { EventWrite } from '../events/catalog'
3
+ import type { RepositoryEventType, RepositoryEventWrite } from '../events/repository'
4
+ import type { AbEvent, EventEnvelope } from '../events/catalog'
5
+ import type { RepositoryEvent, RepositoryEventEnvelope } from '../events/repository'
6
+ import type {
7
+ SessionEvent,
8
+ SessionEventEnvelope,
9
+ SessionEventType,
10
+ SessionEventWrite,
11
+ } from '../events/sessions'
12
+ import type {
13
+ Artifact,
14
+ ArtifactInput,
15
+ ArtifactMeta,
16
+ BuildRecord,
17
+ BuildScopedStore,
18
+ BuildStore,
19
+ NewBuildInput,
20
+ RepositoryArtifact,
21
+ RepositoryArtifactMeta,
22
+ RepositoryRecord,
23
+ SessionArtifactMeta,
24
+ SessionRecord,
25
+ SubscribeOptions,
26
+ Unsubscribe,
27
+ } from './types'
28
+ import type {
29
+ StreamChunk,
30
+ StreamOutcome,
31
+ StreamPart,
32
+ StreamRead,
33
+ StreamRecord,
34
+ StreamScope,
35
+ } from './streams/types'
36
+
37
+ /** Interface-level authority failure. It is deliberately independent of the
38
+ * remote transport's authentication errors: tokens carry this scope over the
39
+ * wire, while every adapter enforces it before delegation. */
40
+ export class BuildScopeError extends Error {
41
+ constructor(
42
+ readonly scope: string,
43
+ readonly operation: string,
44
+ readonly target?: string,
45
+ ) {
46
+ super(
47
+ target === undefined
48
+ ? `build-scoped store for ${JSON.stringify(scope)} forbids ${operation}`
49
+ : `build-scoped store for ${JSON.stringify(scope)} forbids ${operation} targeting ${JSON.stringify(target)}`,
50
+ )
51
+ this.name = 'BuildScopeError'
52
+ }
53
+ }
54
+
55
+ /** Wrap a full store with one-build authority. The wrapper intentionally keeps
56
+ * the complete BuildStore shape so accidental admin, collection, repository,
57
+ * or foreign-build calls fail loudly instead of disappearing behind a type
58
+ * assertion. */
59
+ export function createBuildScopedStore(store: BuildStore, scope: string): BuildScopedStore {
60
+ const own = (operation: string, slug: string): void => {
61
+ if (slug !== scope) throw new BuildScopeError(scope, operation, slug)
62
+ }
63
+ /** Stream scopes ride on the record, not on an argument: create and list
64
+ * name the scope directly; addressed operations resolve the target's scope
65
+ * and reject when it is not exactly this build's. An unknown id delegates
66
+ * so the backing store's `unknown stream` feedback survives. */
67
+ const ownStreamScope = (record: StreamRecord): void => {
68
+ if (record.scope.kind !== 'build' || record.scope.build !== scope) {
69
+ throw new BuildScopeError(scope, 'stream', record.id)
70
+ }
71
+ }
72
+ const ownStream = async (_operation: string, streamId: string): Promise<void> => {
73
+ const record = await store.getStream(streamId)
74
+ if (record) ownStreamScope(record)
75
+ }
76
+ const buildScope = (operation: string, candidate: StreamScope): void => {
77
+ if (candidate.kind !== 'build' || candidate.build !== scope) {
78
+ throw new BuildScopeError(scope, operation, JSON.stringify(candidate))
79
+ }
80
+ }
81
+ return {
82
+ buildScope: scope,
83
+ scopeBuild(slug: string): BuildScopedStore {
84
+ own('scopeBuild', slug)
85
+ return this
86
+ },
87
+ createBuild(_input: NewBuildInput): Promise<BuildRecord> {
88
+ return Promise.reject(new BuildScopeError(scope, 'createBuild'))
89
+ },
90
+ async getBuild(slug: string): Promise<BuildRecord | null> {
91
+ own('getBuild', slug)
92
+ return store.getBuild(slug)
93
+ },
94
+ listBuilds(): Promise<BuildRecord[]> {
95
+ return Promise.reject(new BuildScopeError(scope, 'listBuilds'))
96
+ },
97
+ async append<T extends EventType>(
98
+ slug: string,
99
+ event: EventWrite<T>,
100
+ ): Promise<EventEnvelope<T>> {
101
+ own('append', slug)
102
+ return store.append(slug, event)
103
+ },
104
+ async appendIfCurrent<T extends EventType>(
105
+ slug: string,
106
+ expectedSeq: number,
107
+ event: EventWrite<T>,
108
+ ): Promise<EventEnvelope<T> | null> {
109
+ own('appendIfCurrent', slug)
110
+ return store.appendIfCurrent(slug, expectedSeq, event)
111
+ },
112
+ async appendWithArtifacts<T extends EventType>(
113
+ slug: string,
114
+ artifacts: ArtifactInput[],
115
+ makeEvent: (deposited: ArtifactMeta[]) => EventWrite<T>,
116
+ ): Promise<{ event: EventEnvelope<T>; artifacts: ArtifactMeta[] }> {
117
+ own('appendWithArtifacts', slug)
118
+ return store.appendWithArtifacts(slug, artifacts, makeEvent)
119
+ },
120
+ async getEvents(
121
+ slug: string,
122
+ sinceSeq?: number,
123
+ opts?: { waitSeconds?: number; signal?: AbortSignal },
124
+ ): Promise<AbEvent[]> {
125
+ own('getEvents', slug)
126
+ return store.getEvents(slug, sinceSeq, opts)
127
+ },
128
+ async putArtifact(slug: string, artifact: ArtifactInput): Promise<ArtifactMeta> {
129
+ own('putArtifact', slug)
130
+ return store.putArtifact(slug, artifact)
131
+ },
132
+ async getArtifact(slug: string, kind: string, rev?: number): Promise<Artifact | null> {
133
+ own('getArtifact', slug)
134
+ return store.getArtifact(slug, kind, rev)
135
+ },
136
+ async listArtifacts(slug: string, kind?: string): Promise<ArtifactMeta[]> {
137
+ own('listArtifacts', slug)
138
+ return store.listArtifacts(slug, kind)
139
+ },
140
+ async claimLease(slug: string, holder: string, ttlMs: number): Promise<boolean> {
141
+ own('claimLease', slug)
142
+ return store.claimLease(slug, holder, ttlMs)
143
+ },
144
+ async heartbeat(slug: string, holder: string): Promise<boolean> {
145
+ own('heartbeat', slug)
146
+ return store.heartbeat(slug, holder)
147
+ },
148
+ async releaseLease(slug: string, holder: string): Promise<void> {
149
+ own('releaseLease', slug)
150
+ return store.releaseLease(slug, holder)
151
+ },
152
+ subscribe(
153
+ slug: string,
154
+ opts: SubscribeOptions,
155
+ onEvent: (event: AbEvent) => void,
156
+ ): Unsubscribe {
157
+ own('subscribe', slug)
158
+ return store.subscribe(slug, opts, onEvent)
159
+ },
160
+ ensureRepo(repo: string): Promise<RepositoryRecord> {
161
+ return Promise.reject(new BuildScopeError(scope, 'ensureRepo', repo))
162
+ },
163
+ getRepo(repo: string): Promise<RepositoryRecord | null> {
164
+ return Promise.reject(new BuildScopeError(scope, 'getRepo', repo))
165
+ },
166
+ appendRepo<T extends RepositoryEventType>(
167
+ repo: string,
168
+ _event: RepositoryEventWrite<T>,
169
+ ): Promise<RepositoryEventEnvelope<T>> {
170
+ return Promise.reject(new BuildScopeError(scope, 'appendRepo', repo))
171
+ },
172
+ appendRepoWithArtifacts<T extends RepositoryEventType>(
173
+ repo: string,
174
+ _artifacts: ArtifactInput[],
175
+ _makeEvent: (deposited: RepositoryArtifactMeta[]) => RepositoryEventWrite<T>,
176
+ ): Promise<{ event: RepositoryEventEnvelope<T>; artifacts: RepositoryArtifactMeta[] }> {
177
+ return Promise.reject(new BuildScopeError(scope, 'appendRepoWithArtifacts', repo))
178
+ },
179
+ getRepoEvents(repo: string, _sinceSeq?: number): Promise<RepositoryEvent[]> {
180
+ return Promise.reject(new BuildScopeError(scope, 'getRepoEvents', repo))
181
+ },
182
+ putRepoArtifact(repo: string, _artifact: ArtifactInput): Promise<RepositoryArtifactMeta> {
183
+ return Promise.reject(new BuildScopeError(scope, 'putRepoArtifact', repo))
184
+ },
185
+ getRepoArtifact(
186
+ repo: string,
187
+ _kind: string,
188
+ _rev?: number,
189
+ ): Promise<RepositoryArtifact | null> {
190
+ return Promise.reject(new BuildScopeError(scope, 'getRepoArtifact', repo))
191
+ },
192
+ listRepoArtifacts(repo: string, _kind?: string): Promise<RepositoryArtifactMeta[]> {
193
+ return Promise.reject(new BuildScopeError(scope, 'listRepoArtifacts', repo))
194
+ },
195
+ claimRepoLease(repo: string, _holder: string, _ttlMs: number): Promise<boolean> {
196
+ return Promise.reject(new BuildScopeError(scope, 'claimRepoLease', repo))
197
+ },
198
+ heartbeatRepo(repo: string, _holder: string): Promise<boolean> {
199
+ return Promise.reject(new BuildScopeError(scope, 'heartbeatRepo', repo))
200
+ },
201
+ releaseRepoLease(repo: string, _holder: string): Promise<void> {
202
+ return Promise.reject(new BuildScopeError(scope, 'releaseRepoLease', repo))
203
+ },
204
+ createSession(input): Promise<SessionRecord> {
205
+ return Promise.reject(new BuildScopeError(scope, 'createSession', input.repo))
206
+ },
207
+ getSession(id: string): Promise<SessionRecord | null> {
208
+ return Promise.reject(new BuildScopeError(scope, 'getSession', id))
209
+ },
210
+ listSessions(repo: string): Promise<SessionRecord[]> {
211
+ return Promise.reject(new BuildScopeError(scope, 'listSessions', repo))
212
+ },
213
+ appendSessionEvent<T extends SessionEventType>(
214
+ id: string,
215
+ _event: SessionEventWrite<T>,
216
+ ): Promise<SessionEventEnvelope<T>> {
217
+ return Promise.reject(new BuildScopeError(scope, 'appendSessionEvent', id))
218
+ },
219
+ getSessionEvents(id: string, _sinceSeq?: number): Promise<SessionEvent[]> {
220
+ return Promise.reject(new BuildScopeError(scope, 'getSessionEvents', id))
221
+ },
222
+ appendSessionWithArtifacts<T extends SessionEventType>(
223
+ id: string,
224
+ _artifacts: ArtifactInput[],
225
+ _makeEvent: (deposited: SessionArtifactMeta[]) => SessionEventWrite<T>,
226
+ ): Promise<{ event: SessionEventEnvelope<T>; artifacts: SessionArtifactMeta[] }> {
227
+ return Promise.reject(new BuildScopeError(scope, 'appendSessionWithArtifacts', id))
228
+ },
229
+ putSessionArtifact(id: string, _artifact: ArtifactInput): Promise<SessionArtifactMeta> {
230
+ return Promise.reject(new BuildScopeError(scope, 'putSessionArtifact', id))
231
+ },
232
+ getSessionArtifact(id: string, _kind: string, _rev?: number): Promise<null> {
233
+ return Promise.reject(new BuildScopeError(scope, 'getSessionArtifact', id))
234
+ },
235
+ listSessionArtifacts(id: string, _kind?: string): Promise<SessionArtifactMeta[]> {
236
+ return Promise.reject(new BuildScopeError(scope, 'listSessionArtifacts', id))
237
+ },
238
+ scopeSession(id: string): never {
239
+ throw new BuildScopeError(scope, 'scopeSession', id)
240
+ },
241
+ async createStream(candidate: StreamScope, label: string): Promise<StreamRecord> {
242
+ buildScope('createStream', candidate)
243
+ return store.createStream(candidate, label)
244
+ },
245
+ async appendStreamParts(streamId: string, parts: StreamPart[]): Promise<StreamChunk> {
246
+ await ownStream('appendStreamParts', streamId)
247
+ return store.appendStreamParts(streamId, parts)
248
+ },
249
+ async readStream(
250
+ streamId: string,
251
+ opts?: { since?: number; waitSeconds?: number },
252
+ ): Promise<StreamRead> {
253
+ await ownStream('readStream', streamId)
254
+ return store.readStream(streamId, opts)
255
+ },
256
+ async closeStream(streamId: string, outcome: StreamOutcome): Promise<StreamRecord> {
257
+ await ownStream('closeStream', streamId)
258
+ return store.closeStream(streamId, outcome)
259
+ },
260
+ async getStream(streamId: string): Promise<StreamRecord | null> {
261
+ await ownStream('getStream', streamId)
262
+ return store.getStream(streamId)
263
+ },
264
+ async listStreams(candidate: StreamScope): Promise<StreamRecord[]> {
265
+ buildScope('listStreams', candidate)
266
+ return store.listStreams(candidate)
267
+ },
268
+ close(): Promise<void> {
269
+ return Promise.reject(new BuildScopeError(scope, 'close'))
270
+ },
271
+ }
272
+ }